mirror of
https://github.com/fluxerapp/fluxer
synced 2026-10-17 13:05:32 +09:00
fix(desktop): stop reading whole voice backgrounds to classify them (#1839)
This commit is contained in:
1 parent
c8ff177f85
commit
7ec155eac1
6 files changed
+57
-27
No files matched your search
@@ -319,7 +319,6 @@ export interface ElectronAPI {
|
|||||||
clearThemeLocalFiles: () => Promise<void>;
|
clearThemeLocalFiles: () => Promise<void>;
|
||||||
importThemeDirectory: () => Promise<Array<ThemeDirectoryCssFile>>;
|
importThemeDirectory: () => Promise<Array<ThemeDirectoryCssFile>>;
|
||||||
cacheVoiceBackgroundMedia: (options: VoiceBackgroundMediaCacheRequest) => Promise<VoiceBackgroundMediaCacheResult>;
|
cacheVoiceBackgroundMedia: (options: VoiceBackgroundMediaCacheRequest) => Promise<VoiceBackgroundMediaCacheResult>;
|
||||||
resolveVoiceBackgroundMedia: (id: string) => Promise<VoiceBackgroundMediaCacheResult | null>;
|
|
||||||
readVoiceBackgroundMedia: (id: string) => Promise<VoiceBackgroundMediaReadResult | null>;
|
readVoiceBackgroundMedia: (id: string) => Promise<VoiceBackgroundMediaReadResult | null>;
|
||||||
deleteVoiceBackgroundMedia: (id: string) => Promise<void>;
|
deleteVoiceBackgroundMedia: (id: string) => Promise<void>;
|
||||||
onUpdaterEvent: (callback: (event: UpdaterEvent) => void) => () => void;
|
onUpdaterEvent: (callback: (event: UpdaterEvent) => void) => () => void;
|
||||||
|
|||||||
Vendored
-1
@@ -377,7 +377,6 @@ export interface ElectronAPI {
|
|||||||
clearThemeLocalFiles?(): Promise<void>;
|
clearThemeLocalFiles?(): Promise<void>;
|
||||||
importThemeDirectory?(): Promise<Array<ThemeDirectoryCssFile>>;
|
importThemeDirectory?(): Promise<Array<ThemeDirectoryCssFile>>;
|
||||||
cacheVoiceBackgroundMedia?(options: VoiceBackgroundMediaCacheRequest): Promise<VoiceBackgroundMediaCacheResult>;
|
cacheVoiceBackgroundMedia?(options: VoiceBackgroundMediaCacheRequest): Promise<VoiceBackgroundMediaCacheResult>;
|
||||||
resolveVoiceBackgroundMedia?(id: string): Promise<VoiceBackgroundMediaCacheResult | null>;
|
|
||||||
readVoiceBackgroundMedia?(id: string): Promise<VoiceBackgroundMediaReadResult | null>;
|
readVoiceBackgroundMedia?(id: string): Promise<VoiceBackgroundMediaReadResult | null>;
|
||||||
deleteVoiceBackgroundMedia?(id: string): Promise<void>;
|
deleteVoiceBackgroundMedia?(id: string): Promise<void>;
|
||||||
getDesktopTroubleshootingSettings?(): Promise<DesktopTroubleshootingSettings>;
|
getDesktopTroubleshootingSettings?(): Promise<DesktopTroubleshootingSettings>;
|
||||||
|
|||||||
@@ -656,7 +656,6 @@ export interface ElectronAPI {
|
|||||||
clearThemeLocalFiles: () => Promise<void>;
|
clearThemeLocalFiles: () => Promise<void>;
|
||||||
importThemeDirectory: () => Promise<Array<ThemeDirectoryCssFile>>;
|
importThemeDirectory: () => Promise<Array<ThemeDirectoryCssFile>>;
|
||||||
cacheVoiceBackgroundMedia: (options: VoiceBackgroundMediaCacheRequest) => Promise<VoiceBackgroundMediaCacheResult>;
|
cacheVoiceBackgroundMedia: (options: VoiceBackgroundMediaCacheRequest) => Promise<VoiceBackgroundMediaCacheResult>;
|
||||||
resolveVoiceBackgroundMedia: (id: string) => Promise<VoiceBackgroundMediaCacheResult | null>;
|
|
||||||
readVoiceBackgroundMedia: (id: string) => Promise<VoiceBackgroundMediaReadResult | null>;
|
readVoiceBackgroundMedia: (id: string) => Promise<VoiceBackgroundMediaReadResult | null>;
|
||||||
deleteVoiceBackgroundMedia: (id: string) => Promise<void>;
|
deleteVoiceBackgroundMedia: (id: string) => Promise<void>;
|
||||||
getDesktopTroubleshootingSettings: () => Promise<DesktopTroubleshootingSettings>;
|
getDesktopTroubleshootingSettings: () => Promise<DesktopTroubleshootingSettings>;
|
||||||
|
|||||||
@@ -87,3 +87,19 @@ describe('ChromiumRuntime Windows capture policy', () => {
|
|||||||
assert.deepEqual([...features], ['ExistingFeature']);
|
assert.deepEqual([...features], ['ExistingFeature']);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe('ChromiumRuntime configured switch allowlist', () => {
|
||||||
|
test('rejects settings-supplied switches that would disable or shrink the HTTP cache', () => {
|
||||||
|
const {appendedSwitches, module} = loadChromiumRuntime('win32');
|
||||||
|
|
||||||
|
module.appendConfiguredChromiumSwitches([
|
||||||
|
'disable-http-cache',
|
||||||
|
'disk-cache-size',
|
||||||
|
'disk-cache-dir',
|
||||||
|
'disable-gpu-shader-disk-cache',
|
||||||
|
'disable_metal',
|
||||||
|
]);
|
||||||
|
|
||||||
|
assert.deepEqual(appendedSwitches, [{name: 'disable_metal', value: undefined}]);
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -15,6 +15,8 @@ const CACHE_DIR_NAME = 'voice-background-media';
|
|||||||
const MAX_BACKGROUND_MEDIA_BYTES = 10 * 1024 * 1024;
|
const MAX_BACKGROUND_MEDIA_BYTES = 10 * 1024 * 1024;
|
||||||
const MAX_BACKGROUND_MEDIA_CACHE_FILES = 16;
|
const MAX_BACKGROUND_MEDIA_CACHE_FILES = 16;
|
||||||
const MAX_BACKGROUND_MEDIA_ID_LENGTH = 128;
|
const MAX_BACKGROUND_MEDIA_ID_LENGTH = 128;
|
||||||
|
const MAX_BACKGROUND_MEDIA_TEMP_AGE_MS = 60 * 60 * 1000;
|
||||||
|
const WEBP_HEADER_BYTES = 1024;
|
||||||
|
|
||||||
const MEDIA_TYPES = new Map<
|
const MEDIA_TYPES = new Map<
|
||||||
string,
|
string,
|
||||||
@@ -82,7 +84,7 @@ function isAnimatedWebp(data: Buffer): boolean {
|
|||||||
if (data.subarray(12, 16).toString('ascii') === 'VP8X') {
|
if (data.subarray(12, 16).toString('ascii') === 'VP8X') {
|
||||||
return (data[20] & 0b0000_0010) !== 0;
|
return (data[20] & 0b0000_0010) !== 0;
|
||||||
}
|
}
|
||||||
return data.includes(Buffer.from('ANMF', 'ascii'));
|
return data.subarray(0, WEBP_HEADER_BYTES).includes(Buffer.from('ANMF', 'ascii'));
|
||||||
}
|
}
|
||||||
|
|
||||||
function classifyMediaKind(
|
function classifyMediaKind(
|
||||||
@@ -95,6 +97,20 @@ function classifyMediaKind(
|
|||||||
return mediaType.mediaKind;
|
return mediaType.mediaKind;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function readMediaHeader(filePath: string, byteLength: number): Promise<Buffer | null> {
|
||||||
|
const handle = await fs.open(filePath, 'r').catch(() => null);
|
||||||
|
if (!handle) return null;
|
||||||
|
try {
|
||||||
|
const header = Buffer.alloc(byteLength);
|
||||||
|
const {bytesRead} = await handle.read(header, 0, byteLength, 0);
|
||||||
|
return header.subarray(0, bytesRead);
|
||||||
|
} catch {
|
||||||
|
return null;
|
||||||
|
} finally {
|
||||||
|
await handle.close().catch(() => undefined);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
async function findCachedVoiceBackgroundMedia(
|
async function findCachedVoiceBackgroundMedia(
|
||||||
id: string,
|
id: string,
|
||||||
): Promise<{path: string; mimeType: string; mediaKind: VoiceBackgroundMediaKind} | null> {
|
): Promise<{path: string; mimeType: string; mediaKind: VoiceBackgroundMediaKind} | null> {
|
||||||
@@ -110,9 +126,9 @@ async function findCachedVoiceBackgroundMedia(
|
|||||||
const filePath = path.join(cacheDir, entry.name);
|
const filePath = path.join(cacheDir, entry.name);
|
||||||
let mediaKind = mediaType.mediaKind;
|
let mediaKind = mediaType.mediaKind;
|
||||||
if (mediaType.extension === 'webp') {
|
if (mediaType.extension === 'webp') {
|
||||||
const data = await fs.readFile(filePath).catch(() => null);
|
const header = await readMediaHeader(filePath, WEBP_HEADER_BYTES);
|
||||||
if (!data) continue;
|
if (!header) continue;
|
||||||
mediaKind = classifyMediaKind(mediaType, data);
|
mediaKind = classifyMediaKind(mediaType, header);
|
||||||
}
|
}
|
||||||
return {
|
return {
|
||||||
path: filePath,
|
path: filePath,
|
||||||
@@ -124,20 +140,29 @@ async function findCachedVoiceBackgroundMedia(
|
|||||||
}
|
}
|
||||||
|
|
||||||
async function trimVoiceBackgroundMediaCache(cacheDir: string): Promise<void> {
|
async function trimVoiceBackgroundMediaCache(cacheDir: string): Promise<void> {
|
||||||
const entries = await fs.readdir(cacheDir, {withFileTypes: true});
|
const entries = await fs.readdir(cacheDir, {withFileTypes: true}).catch(() => []);
|
||||||
const files = await Promise.all(
|
const now = Date.now();
|
||||||
entries
|
const cachedMedia: Array<{filePath: string; mtimeMs: number}> = [];
|
||||||
.filter((entry) => entry.isFile())
|
for (const entry of entries) {
|
||||||
.map(async (entry) => {
|
if (!entry.isFile()) continue;
|
||||||
const filePath = path.join(cacheDir, entry.name);
|
const filePath = path.join(cacheDir, entry.name);
|
||||||
const stats = await fs.stat(filePath);
|
const stats = await fs.stat(filePath).catch(() => null);
|
||||||
return {filePath, mtimeMs: stats.mtimeMs};
|
if (!stats) continue;
|
||||||
}),
|
const extension = path.extname(entry.name).slice(1).toLowerCase();
|
||||||
|
if (!MEDIA_TYPES_BY_EXTENSION.has(extension)) {
|
||||||
|
if (now - stats.mtimeMs > MAX_BACKGROUND_MEDIA_TEMP_AGE_MS) {
|
||||||
|
await fs.rm(filePath, {force: true}).catch(() => undefined);
|
||||||
|
}
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
cachedMedia.push({filePath, mtimeMs: stats.mtimeMs});
|
||||||
|
}
|
||||||
|
if (cachedMedia.length <= MAX_BACKGROUND_MEDIA_CACHE_FILES) return;
|
||||||
|
cachedMedia.sort((a, b) => a.mtimeMs - b.mtimeMs);
|
||||||
|
const deleteCount = cachedMedia.length - MAX_BACKGROUND_MEDIA_CACHE_FILES;
|
||||||
|
await Promise.all(
|
||||||
|
cachedMedia.slice(0, deleteCount).map(({filePath}) => fs.rm(filePath, {force: true}).catch(() => undefined)),
|
||||||
);
|
);
|
||||||
if (files.length <= MAX_BACKGROUND_MEDIA_CACHE_FILES) return;
|
|
||||||
files.sort((a, b) => a.mtimeMs - b.mtimeMs);
|
|
||||||
const deleteCount = files.length - MAX_BACKGROUND_MEDIA_CACHE_FILES;
|
|
||||||
await Promise.all(files.slice(0, deleteCount).map(({filePath}) => fs.rm(filePath, {force: true})));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async function cacheVoiceBackgroundMedia(
|
async function cacheVoiceBackgroundMedia(
|
||||||
@@ -165,12 +190,6 @@ async function cacheVoiceBackgroundMedia(
|
|||||||
return {path: filePath, mediaKind};
|
return {path: filePath, mediaKind};
|
||||||
}
|
}
|
||||||
|
|
||||||
async function resolveVoiceBackgroundMedia(id: string): Promise<VoiceBackgroundMediaCacheResult | null> {
|
|
||||||
const cached = await findCachedVoiceBackgroundMedia(id);
|
|
||||||
if (!cached) return null;
|
|
||||||
return {path: cached.path, mediaKind: cached.mediaKind};
|
|
||||||
}
|
|
||||||
|
|
||||||
async function readVoiceBackgroundMedia(id: string): Promise<VoiceBackgroundMediaReadResult | null> {
|
async function readVoiceBackgroundMedia(id: string): Promise<VoiceBackgroundMediaReadResult | null> {
|
||||||
const cached = await findCachedVoiceBackgroundMedia(id);
|
const cached = await findCachedVoiceBackgroundMedia(id);
|
||||||
if (!cached) return null;
|
if (!cached) return null;
|
||||||
@@ -200,7 +219,6 @@ export function registerVoiceBackgroundMediaCacheHandlers(): void {
|
|||||||
ipcMain.handle('voice-background-media-cache:write', (_event, options: VoiceBackgroundMediaCacheRequest) =>
|
ipcMain.handle('voice-background-media-cache:write', (_event, options: VoiceBackgroundMediaCacheRequest) =>
|
||||||
cacheVoiceBackgroundMedia(options),
|
cacheVoiceBackgroundMedia(options),
|
||||||
);
|
);
|
||||||
ipcMain.handle('voice-background-media-cache:resolve', (_event, id: string) => resolveVoiceBackgroundMedia(id));
|
|
||||||
ipcMain.handle('voice-background-media-cache:read', (_event, id: string) => readVoiceBackgroundMedia(id));
|
ipcMain.handle('voice-background-media-cache:read', (_event, id: string) => readVoiceBackgroundMedia(id));
|
||||||
ipcMain.handle('voice-background-media-cache:delete', (_event, id: string) => deleteVoiceBackgroundMedia(id));
|
ipcMain.handle('voice-background-media-cache:delete', (_event, id: string) => deleteVoiceBackgroundMedia(id));
|
||||||
}
|
}
|
||||||
@@ -357,7 +357,6 @@ const api: ElectronAPI = {
|
|||||||
clearThemeLocalFiles: () => ipcRenderer.invoke('theme-local-files-clear'),
|
clearThemeLocalFiles: () => ipcRenderer.invoke('theme-local-files-clear'),
|
||||||
importThemeDirectory: () => ipcRenderer.invoke('theme-directory-import'),
|
importThemeDirectory: () => ipcRenderer.invoke('theme-directory-import'),
|
||||||
cacheVoiceBackgroundMedia: (options) => ipcRenderer.invoke('voice-background-media-cache:write', options),
|
cacheVoiceBackgroundMedia: (options) => ipcRenderer.invoke('voice-background-media-cache:write', options),
|
||||||
resolveVoiceBackgroundMedia: (id) => ipcRenderer.invoke('voice-background-media-cache:resolve', id),
|
|
||||||
readVoiceBackgroundMedia: (id) => ipcRenderer.invoke('voice-background-media-cache:read', id),
|
readVoiceBackgroundMedia: (id) => ipcRenderer.invoke('voice-background-media-cache:read', id),
|
||||||
deleteVoiceBackgroundMedia: (id) => ipcRenderer.invoke('voice-background-media-cache:delete', id),
|
deleteVoiceBackgroundMedia: (id) => ipcRenderer.invoke('voice-background-media-cache:delete', id),
|
||||||
getDesktopTroubleshootingSettings: (): Promise<DesktopTroubleshootingSettings> =>
|
getDesktopTroubleshootingSettings: (): Promise<DesktopTroubleshootingSettings> =>
|
||||||
|
|||||||
Reference in new issue
Block a user