Repository navigation
feat(settings): configurable storage locations for recordings, exports, temp, caption models and cache #1213
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,4 +1,14 @@ | ||
| /// <reference types="vite-plugin-electron/electron-env" /> | ||
| import type { | ||
| StorageFolderId, | ||
| StorageFolderMutationResponse, | ||
| StorageFoldersResponse, | ||
| } from "../src/types/storageFolders"; | ||
| import type { | ||
| StorageFolderId, | ||
| StorageFolderMutationResponse, | ||
| StorageFoldersResponse, | ||
| } from "./ipc/types"; | ||
|
Comment on lines
+7
to
+11
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Remove the second storage-type import. This block redeclares all three identifiers imported on Lines 2-6. The supplied Biome diagnostics report each duplicate binding. The supplied dependency inspection also found no matching exports in Keep the shared type definitions as the single source. Remove this duplicate block.
|
||
|
|
||
| declare namespace NodeJS { | ||
| interface ProcessEnv { | ||
|
|
@@ -1275,6 +1285,14 @@ interface Window { | |
| message?: string; | ||
| error?: string; | ||
| }>; | ||
| getStorageFolders: () => Promise<StorageFoldersResponse>; | ||
| chooseStorageFolder: (id: StorageFolderId) => Promise<StorageFolderMutationResponse>; | ||
| openStorageFolder: (id: StorageFolderId) => Promise<{ | ||
| success: boolean; | ||
| error?: string; | ||
| }>; | ||
| resetStorageFolder: (id: StorageFolderId) => Promise<StorageFoldersResponse>; | ||
| resetAllStorageFolders: () => Promise<StorageFoldersResponse>; | ||
| getShortcuts: () => Promise<Record<string, unknown> | null>; | ||
| saveShortcuts: (shortcuts: unknown) => Promise<{ success: boolean; error?: string }>; | ||
| getEditorStartupView: () => "home" | "editor"; | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -5,7 +5,8 @@ import { get as httpsGet } from "node:https"; | |
| import path from "node:path"; | ||
| import { Transform } from "node:stream"; | ||
| import { pipeline } from "node:stream/promises"; | ||
| import { app, type WebContents } from "electron"; | ||
| import { type WebContents } from "electron"; | ||
| import { getCaptionModelsDir } from "../../storageLocations"; | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Load the Parakeet module after the test sets
This can cause two failures:
Load the module after each test sets its root. For example, call
|
||
|
|
||
| export const PARAKEET_MODEL_REVISION = "2bda32ec70b097a55adaa07d9a7173915b43cc78"; | ||
| export const PARAKEET_MODEL_FILES = [ | ||
|
|
@@ -42,7 +43,7 @@ type Progress = { | |
| }; | ||
|
|
||
| function modelDirectory() { | ||
| return path.join(app.getPath("userData"), "captions", "parakeet-tdt-0.6b-v3-int8"); | ||
| return path.join(getCaptionModelsDir(), "parakeet-tdt-0.6b-v3-int8"); | ||
| } | ||
|
|
||
| export function sendParakeetModelDownloadProgress(webContents: WebContents, payload: Progress) { | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -2,7 +2,7 @@ import { randomUUID } from "node:crypto"; | |
| import fs from "node:fs"; | ||
| import fsp from "node:fs/promises"; | ||
| import path from "node:path"; | ||
| import { app } from "electron"; | ||
| import { getTempStorageDir } from "../../storageLocations"; | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Mock
Suggested fix
|
||
|
|
||
| type ExportStreamSession = { | ||
| streamId: string; | ||
|
|
@@ -60,7 +60,7 @@ export async function openExportStream(options?: { extension?: string }): Promis | |
| // Per-session 0700 directory defeats TOCTOU/symlink races on shared | ||
| // tempdirs (e.g. /tmp on Linux): only the current user can enter the dir, | ||
| // so an adversary cannot pre-plant a symlink at the file path. | ||
| const sessionDir = await fsp.mkdtemp(path.join(app.getPath("temp"), SESSION_DIR_PREFIX)); | ||
| const sessionDir = await fsp.mkdtemp(path.join(getTempStorageDir(), SESSION_DIR_PREFIX)); | ||
| try { | ||
| await fsp.chmod(sessionDir, 0o700); | ||
| } catch { | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -35,6 +35,8 @@ import { | |
| } from "../state"; | ||
| import type { ProjectLibraryEntry, RecordingSessionData } from "../types"; | ||
| import { canonicalMediaSources } from "./canonicalMediaSources"; | ||
| import { getConfiguredStorageLocations, getTempStorageDir } from "../../storageLocations"; | ||
| import { getCacheDir } from "../../storageLocations"; | ||
| import { | ||
| getRecordingsDir, | ||
| normalizePath, | ||
|
|
@@ -66,11 +68,14 @@ export function isPathInsideDirectory(candidatePath: string, directoryPath: stri | |
| } | ||
|
|
||
| export function isAllowedLocalReadPath(candidatePath: string) { | ||
| // User-chosen storage folders must stay readable: exports land there, and the | ||
| // editor reopens them from the media server after a restart. | ||
| const allowedPrefixes = [ | ||
| RECORDINGS_DIR, | ||
| USER_DATA_PATH, | ||
| getAssetRootPath(), | ||
| app.getPath("temp"), | ||
| getTempStorageDir(), | ||
| ...getConfiguredStorageLocations(), | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Match canonical file paths against canonical storage roots.
Canonicalize the allowed prefixes as well, and keep the lexical roots so existing paths still match. Suggested fix
|
||
| ]; | ||
| const normalizedCandidatePath = normalizePath(candidatePath); | ||
|
|
||
|
|
@@ -290,7 +295,7 @@ export async function getProjectThumbnailPath(projectPath: string) { | |
|
|
||
| function projectThumbnailStoragePath(key: string) { | ||
| const digest = createHash("sha256").update(key).digest("hex"); | ||
| return path.join(USER_DATA_PATH, "project-thumbnails", `${digest}.png`); | ||
| return path.join(getCacheDir("project-thumbnails"), `${digest}.png`); | ||
| } | ||
|
|
||
| export function getLegacyProjectThumbnailPath(projectPath: string) { | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -4,11 +4,11 @@ import fs from "node:fs/promises"; | |
| import path from "node:path"; | ||
| import { pathToFileURL } from "node:url"; | ||
| import { app, ipcMain } from "electron"; | ||
| import { USER_DATA_PATH } from "../../appPaths"; | ||
| import { getAssetRootPath, isAllowedLocalReadPath } from "../project/manager"; | ||
| import { isTrustedRenderer } from "../../trustedRenderer"; | ||
| import { normalizePath } from "../utils"; | ||
| import { registerDesktopWallpaperHandler } from "./desktopWallpaper"; | ||
| import { getCacheDir } from "../../storageLocations"; | ||
|
|
||
| export function registerAssetHandlers() { | ||
| registerDesktopWallpaperHandler(); | ||
|
|
@@ -30,7 +30,7 @@ export function registerAssetHandlers() { | |
| // Returns the cached thumbnail as raw JPEG bytes for fast grid rendering. | ||
| // Serialized to prevent concurrent nativeImage operations from eating memory. | ||
| const THUMB_SIZE = 96; | ||
| const thumbCacheDir = path.join(USER_DATA_PATH, "wallpaper-thumbs"); | ||
| const thumbCacheDir = getCacheDir("wallpaper-thumbs"); | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Resolve the wallpaper thumbnail cache directory per request. The handler captures Suggested fix
|
||
| let thumbGenerationQueue: Promise<void> = Promise.resolve(); | ||
|
|
||
| ipcMain.handle("generate-wallpaper-thumbnail", async (event, filePath: unknown) => { | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Preserve the global scope of these declarations.
Adding top-level imports makes this declaration file an external module. The existing
interface Window,declare namespace NodeJS, and supporting interfaces then become module-local instead of augmenting global types. Renderer code therefore loses this file's globalWindow.electronAPIdeclaration.Use inline
import("...").Typereferences without top-level imports, or move the ambient declarations intodeclare global. Removing only the duplicate import does not fix this scope change.