-
-
Notifications
You must be signed in to change notification settings - Fork 195
feat: add configurable recordings save location #650
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
8a3cd68
18885c9
f130684
02a612d
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,3 @@ | ||
| import fs from "node:fs/promises"; | ||
| import path from "node:path"; | ||
| import { fileURLToPath } from "node:url"; | ||
| import { | ||
|
|
@@ -60,6 +59,7 @@ import { | |
| registerIpcHandlers, | ||
| } from "./ipc/handlers"; | ||
| import { installMainProcessErrorGuards } from "./main-process-errors"; | ||
| import { RecordingsDirManager } from "./recording/recordingsDirManager"; | ||
| import { registerSttIpc, shutdownStt } from "./stt"; | ||
| import { checkLatestRelease } from "./update-checker"; | ||
| import { loadUpdateMode, saveUpdateMode } from "./update-settings"; | ||
|
|
@@ -105,18 +105,44 @@ disableHttpCacheForDevServer(app.commandLine, process.env); | |
|
|
||
| installMainProcessErrorGuards(); | ||
|
|
||
| export const RECORDINGS_DIR = path.join(app.getPath("userData"), "recordings"); | ||
| const recordingsDirManager = new RecordingsDirManager(app.getPath("userData"), () => isRecording); | ||
|
|
||
| export const DEFAULT_RECORDINGS_DIR = recordingsDirManager.defaultDir; | ||
|
|
||
| // Mutable: reassigned by setRecordingsDir() when the user picks a custom | ||
| // location in settings. `handlers.ts` imports this as a live named binding, | ||
| // so every call site there sees the change immediately — no restart needed. | ||
| export let RECORDINGS_DIR = recordingsDirManager.dir; | ||
|
|
||
| async function ensureRecordingsDir() { | ||
| try { | ||
| await fs.mkdir(RECORDINGS_DIR, { recursive: true }); | ||
| await recordingsDirManager.ensureExists(); | ||
| console.log("RECORDINGS_DIR:", RECORDINGS_DIR); | ||
| console.log("User Data Path:", app.getPath("userData")); | ||
| } catch (error) { | ||
| console.error("Failed to create recordings directory:", error); | ||
| } | ||
| } | ||
|
|
||
| /** | ||
| * Switches where recordings are read from and written to, going forward. | ||
| * Pass `null` to reset to the default (userData/recordings). Does not move | ||
| * any existing files — the old location is left untouched. | ||
| * | ||
| * Refuses to run while a recording is active: a capture in progress builds | ||
| * its output path from `RECORDINGS_DIR` up front, so swapping it mid-take | ||
| * would split one session's video and manifest across two directories. See | ||
| * RecordingsDirManager.setDir for the tested guard/ordering logic. | ||
| */ | ||
| export async function setRecordingsDir(customDir: string | null): Promise<string> { | ||
|
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. 📐 Maintainability & Code Quality | 🟠 Major | 🏗️ Heavy lift Add tests next to the Electron source for
🤖 Prompt for AI Agents |
||
| RECORDINGS_DIR = await recordingsDirManager.setDir(customDir); | ||
|
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. 🗄️ Data Integrity & Integration | 🟠 Major | 🏗️ Heavy lift 🔎 Supported by static analysis🏁 Script executed: sed -n '100,155p' electron/main.ts
sed -n '1,125p' electron/recording/recordingsDirManager.ts
sed -n '210,245p' electron/ipc/handlers.ts
sed -n '2470,2530p' electron/ipc/handlers.ts
sed -n '2670,2730p' electron/ipc/handlers.ts
sed -n '2890,2960p' electron/ipc/handlers.ts
rg -n "RECORDINGS_DIR|isRecording|recordingInProgress|start-native|manifest|media-link|mediaLink" electronRepository: getopenscreen/openscreen Length of output: 26085 🏁 Script executed: #!/bin/bash
set -e
printf '%s\n' '--- state and callback definitions ---'
sed -n '600,710p' electron/main.ts
sed -n '1290,1350p' electron/main.ts
printf '%s\n' '--- recording start handlers ---'
sed -n '2360,2495p' electron/ipc/handlers.ts
sed -n '2490,2680p' electron/ipc/handlers.ts
sed -n '2670,3060p' electron/ipc/handlers.ts
printf '%s\n' '--- finalization and cleanup paths ---'
sed -n '3200,3380p' electron/ipc/handlers.ts
sed -n '3480,3670p' electron/ipc/handlers.ts
sed -n '3780,3870p' electron/ipc/handlers.ts
printf '%s\n' '--- session-directory identifiers ---'
rg -n -C 3 "sessionDir|sessionDirectory|recordingDir|outputPath|manifestPath|onRecordingStateChange|stop-native|stop-recording|finaliz|cleanup|writeAlongside|registerMediaLinks|findMediaLinksByFingerprint" electron/ipc/handlers.ts electron/main.ts electron/recordingRepository: getopenscreen/openscreen Length of output: 50381 🏁 Script executed: sed -n '600,710p' electron/main.ts
sed -n '1290,1350p' electron/main.ts
sed -n '2360,2495p' electron/ipc/handlers.ts
sed -n '2490,2680p' electron/ipc/handlers.ts
sed -n '2670,3060p' electron/ipc/handlers.ts
sed -n '3200,3380p' electron/ipc/handlers.ts
sed -n '3480,3670p' electron/ipc/handlers.ts
sed -n '3780,3870p' electron/ipc/handlers.ts
rg -n -C 3 "sessionDir|sessionDirectory|recordingDir|outputPath|manifestPath|onRecordingStateChange|stop-native|stop-recording|finaliz|cleanup|writeAlongside|registerMediaLinks|findMediaLinksByFingerprint" electron/ipc/handlers.ts electron/main.ts electron/recordingRepository: getopenscreen/openscreen Length of output: 50381 🏁 Script executed: #!/bin/bash
set -e
printf '%s\n' '--- directory consumers and session state ---'
rg -n "function (registerRecordingMediaLinks|removeNativeWindowsCaptureOutputs|resolveRecordingOutputPath|storeRecordedSessionFiles)|const (registerRecordingMediaLinks|removeNativeWindowsCaptureOutputs|resolveRecordingOutputPath|storeRecordedSessionFiles)|currentRecordingSession|setCurrentRecordingSessionState|RECORDINGS_DIR" electron/ipc/handlers.ts
printf '%s\n' '--- exact native start/state/stop locations ---'
rg -n '"(prepare-native-linux-recording|start-native-linux-recording|stop-native-linux-recording|start-native-windows-recording|stop-native-windows-recording|start-native-mac-recording|stop-native-mac-recording|set-recording-state|store-recorded-video)"|onRecordingStateChange\((true|false)|outputPath = path.join\(RECORDINGS_DIR|manifestPath: path.join\(\s*$' electron/ipc/handlers.ts
printf '%s\n' '--- path and registry definitions ---'
sed -n '160,250p' electron/ipc/handlers.ts
sed -n '1240,1305p' electron/ipc/handlers.ts
sed -n '2580,2665p' electron/ipc/handlers.ts
sed -n '3050,3185p' electron/ipc/handlers.ts
sed -n '3430,3540p' electron/ipc/handlers.ts
printf '%s\n' '--- mac start completion and stop entry ---'
sed -n '3020,3075p' electron/ipc/handlers.ts
rg -n -C 5 "stop-native-mac-recording|nativeMacCaptureTargetPath|screenVideoPath =|preferredPath|preferredWebcamPath" electron/ipc/handlers.ts
printf '%s\n' '--- manager and main publication ---'
sed -n '35,85p' electron/recording/recordingsDirManager.ts
sed -n '128,142p' electron/main.tsRepository: getopenscreen/openscreen Length of output: 41992 🏁 Script executed: #!/bin/bash
set -e
rg -n "function (registerRecordingMediaLinks|removeNativeWindowsCaptureOutputs|resolveRecordingOutputPath|storeRecordedSessionFiles)|const (registerRecordingMediaLinks|removeNativeWindowsCaptureOutputs|resolveRecordingOutputPath|storeRecordedSessionFiles)|currentRecordingSession|setCurrentRecordingSessionState|RECORDINGS_DIR" electron/ipc/handlers.ts
rg -n '"(prepare-native-linux-recording|start-native-linux-recording|stop-native-linux-recording|start-native-windows-recording|stop-native-windows-recording|start-native-mac-recording|stop-native-mac-recording|set-recording-state|store-recorded-video)"|onRecordingStateChange\((true|false)|outputPath = path.join\(RECORDINGS_DIR|manifestPath: path.join\(\s*$' electron/ipc/handlers.ts
sed -n '160,250p' electron/ipc/handlers.ts
sed -n '1240,1305p' electron/ipc/handlers.ts
sed -n '2580,2665p' electron/ipc/handlers.ts
sed -n '3050,3185p' electron/ipc/handlers.ts
sed -n '3430,3540p' electron/ipc/handlers.ts
sed -n '3020,3075p' electron/ipc/handlers.ts
rg -n -C 5 "stop-native-mac-recording|nativeMacCaptureTargetPath|screenVideoPath =|preferredPath|preferredWebcamPath" electron/ipc/handlers.ts
sed -n '35,85p' electron/recording/recordingsDirManager.ts
sed -n '128,142p' electron/main.tsRepository: getopenscreen/openscreen Length of output: 41779 Serialize directory changes with recording start. The native Linux, Windows, and macOS start handlers snapshot Use one lock for 🤖 Prompt for AI Agents |
||
| return RECORDINGS_DIR; | ||
| } | ||
|
|
||
| export function getRecordingsDirInfo() { | ||
| return recordingsDirManager.getInfo(); | ||
| } | ||
|
|
||
| // The built directory structure | ||
| // | ||
| // ├─┬─┬ dist | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,33 @@ | ||
| import { mkdtemp, rm } from "node:fs/promises"; | ||
| import { tmpdir } from "node:os"; | ||
| import path from "node:path"; | ||
| import { afterEach, beforeEach, describe, expect, it } from "vitest"; | ||
| import { checkDiskSpace } from "./diskSpaceCheck"; | ||
|
|
||
| describe("checkDiskSpace", () => { | ||
| let dir: string; | ||
|
|
||
| beforeEach(async () => { | ||
| dir = await mkdtemp(path.join(tmpdir(), "openscreen-disk-space-")); | ||
| }); | ||
|
|
||
| afterEach(async () => { | ||
| await rm(dir, { recursive: true, force: true }); | ||
| }); | ||
|
|
||
| it("reports the real filesystem as not low, using a near-zero threshold", async () => { | ||
| const status = await checkDiskSpace(dir, 1); | ||
| expect(status.low).toBe(false); | ||
| expect(status.availableBytes).toBeGreaterThan(0); | ||
| }); | ||
|
|
||
| it("reports low when the threshold is set far above any real free space", async () => { | ||
| const status = await checkDiskSpace(dir, Number.MAX_SAFE_INTEGER); | ||
| expect(status.low).toBe(true); | ||
| }); | ||
|
|
||
| it("does not throw and reports not-low for a directory that doesn't exist", async () => { | ||
| const status = await checkDiskSpace(path.join(dir, "does-not-exist")); | ||
| expect(status.low).toBe(false); | ||
| }); | ||
| }); |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,34 @@ | ||
| // Pre-flight free-space check for recording start. Before this, the app had | ||
| // no disk-space awareness anywhere: a recording could run for its full | ||
| // duration and only fail once the user tried to save it, discarding the | ||
| // take. Catching it before capture starts costs one statfs() call and saves | ||
| // a wasted recording. | ||
| import fs from "node:fs/promises"; | ||
|
|
||
| /** Recording output is usually well under this; below it, a take is likely to run out mid-capture. */ | ||
| export const LOW_DISK_SPACE_THRESHOLD_BYTES = 500 * 1024 * 1024; | ||
|
|
||
| export interface DiskSpaceStatus { | ||
| /** Bytes free on the filesystem backing the recordings directory. */ | ||
| availableBytes: number; | ||
| low: boolean; | ||
| } | ||
|
|
||
| /** | ||
| * Checks free space on the filesystem that backs `dir`. Never throws — a | ||
| * platform or filesystem that doesn't support statfs (or a directory that | ||
| * doesn't exist yet) reports as not-low, since a bad check must never block | ||
| * a recording that would otherwise have worked. | ||
| */ | ||
| export async function checkDiskSpace( | ||
| dir: string, | ||
| thresholdBytes: number = LOW_DISK_SPACE_THRESHOLD_BYTES, | ||
| ): Promise<DiskSpaceStatus> { | ||
| try { | ||
| const stats = await fs.statfs(dir); | ||
| const availableBytes = stats.bavail * stats.bsize; | ||
| return { availableBytes, low: availableBytes < thresholdBytes }; | ||
| } catch { | ||
| return { availableBytes: Number.POSITIVE_INFINITY, low: false }; | ||
| } | ||
| } |
Uh oh!
There was an error while loading. Please reload this page.