Skip to content

Commit cff5d28

Browse files
authored
Merge pull request #124 from beNative/codex/add-settings-for-update-options
Add settings controls for update checks
2 parents 50925fa + 55fa78b commit cff5d28

7 files changed

Lines changed: 190 additions & 5 deletions

File tree

components/SettingsView.tsx

Lines changed: 64 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1474,13 +1474,76 @@ requests"
14741474
const GeneralSettingsSection: React.FC<Pick<SectionProps, 'settings' | 'setCurrentSettings'>> = ({ settings, setCurrentSettings }) => {
14751475
const isOfflineRendererAvailable = typeof window !== 'undefined' && !!window.electronAPI?.renderPlantUML;
14761476
const offlineRendererMessage = 'Offline rendering requires the desktop application with a local Java runtime.';
1477+
const { addLog } = useLogger();
1478+
const [isManualCheckRunning, setIsManualCheckRunning] = useState(false);
1479+
const [manualCheckStatus, setManualCheckStatus] = useState<'idle' | 'success' | 'error'>('idle');
1480+
const [manualCheckMessage, setManualCheckMessage] = useState<string | null>(null);
1481+
const canManuallyCheckForUpdates = typeof window !== 'undefined' && !!window.electronAPI?.updaterCheckForUpdates;
1482+
1483+
const handleManualUpdateCheck = useCallback(async () => {
1484+
if (!window.electronAPI?.updaterCheckForUpdates) {
1485+
setManualCheckStatus('error');
1486+
setManualCheckMessage('Manual update checks are only available in the desktop application.');
1487+
return;
1488+
}
1489+
1490+
setIsManualCheckRunning(true);
1491+
setManualCheckStatus('idle');
1492+
setManualCheckMessage(null);
1493+
addLog('INFO', 'User action: Manual update check triggered.');
1494+
1495+
try {
1496+
const result = await window.electronAPI.updaterCheckForUpdates();
1497+
if (result?.success) {
1498+
if (result.updateAvailable) {
1499+
const label = result.version ?? result.releaseName ?? 'latest';
1500+
setManualCheckStatus('success');
1501+
setManualCheckMessage(`Update ${label} found. Downloading will begin automatically.`);
1502+
} else {
1503+
setManualCheckStatus('success');
1504+
setManualCheckMessage('You are running the latest version.');
1505+
}
1506+
} else {
1507+
setManualCheckStatus('error');
1508+
setManualCheckMessage(result?.error ?? 'Failed to check for updates.');
1509+
}
1510+
} catch (error) {
1511+
const message = error instanceof Error ? error.message : 'Failed to check for updates.';
1512+
setManualCheckStatus('error');
1513+
setManualCheckMessage(message);
1514+
} finally {
1515+
setIsManualCheckRunning(false);
1516+
}
1517+
}, [addLog]);
1518+
1519+
const effectiveManualCheckStatus = canManuallyCheckForUpdates ? manualCheckStatus : 'error';
1520+
const manualCheckMessageClass = effectiveManualCheckStatus === 'error'
1521+
? 'text-error'
1522+
: effectiveManualCheckStatus === 'success'
1523+
? 'text-success'
1524+
: 'text-text-secondary';
14771525
return (
14781526
<section className="pt-2 pb-6">
14791527
<h2 className="text-lg font-semibold text-text-main mb-4">General</h2>
14801528
<div className="space-y-6">
1481-
<SettingRow htmlFor="allowPrerelease" label="Receive Pre-releases" description="Get notified about new beta versions and test features early.">
1529+
<SettingRow htmlFor="allowPrerelease" label="Pre-release Updates" description="Allow DocForge to download and install beta releases when available.">
14821530
<ToggleSwitch id="allowPrerelease" checked={settings.allowPrerelease} onChange={(val) => setCurrentSettings(s => ({...s, allowPrerelease: val}))} />
14831531
</SettingRow>
1532+
<SettingRow htmlFor="autoCheckForUpdates" label="Automatic Update Checks" description="Check for new releases whenever DocForge starts.">
1533+
<ToggleSwitch id="autoCheckForUpdates" checked={settings.autoCheckForUpdates} onChange={(val) => setCurrentSettings(s => ({...s, autoCheckForUpdates: val}))} />
1534+
</SettingRow>
1535+
<SettingRow label="Check for Updates" description="Run an update check immediately.">
1536+
<div className="flex flex-col items-start md:items-end gap-2 w-full">
1537+
<Button variant="secondary" onClick={handleManualUpdateCheck} isLoading={isManualCheckRunning} disabled={isManualCheckRunning || !canManuallyCheckForUpdates}>
1538+
{isManualCheckRunning ? 'Checking…' : 'Check for Updates'}
1539+
</Button>
1540+
{(canManuallyCheckForUpdates ? manualCheckMessage : true) && (
1541+
<p className={`text-xs text-left md:text-right ${manualCheckMessageClass}`}>
1542+
{canManuallyCheckForUpdates ? manualCheckMessage : 'Manual update checks are only available in the desktop application.'}
1543+
</p>
1544+
)}
1545+
</div>
1546+
</SettingRow>
14841547
<SettingRow htmlFor="autoSaveLogs" label="Auto-save Logs" description="Automatically save all logs to a daily file on your computer for debugging.">
14851548
<ToggleSwitch id="autoSaveLogs" checked={settings.autoSaveLogs} onChange={(val) => setCurrentSettings(s => ({...s, autoSaveLogs: val}))} />
14861549
</SettingRow>

constants.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ export const DEFAULT_SETTINGS: Settings = {
2525
iconSet: 'heroicons',
2626
autoSaveLogs: false,
2727
allowPrerelease: false,
28+
autoCheckForUpdates: true,
2829
plantumlRendererMode: 'remote',
2930
uiScale: 100,
3031
documentTreeIndent: 16,

electron/database.ts

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -472,7 +472,24 @@ export const databaseService = {
472472
throw e;
473473
}
474474
},
475-
475+
476+
getSetting(key: string): unknown {
477+
try {
478+
const row = db.prepare('SELECT value FROM settings WHERE key = ?').get(key) as { value: string } | undefined;
479+
if (!row) {
480+
return undefined;
481+
}
482+
try {
483+
return JSON.parse(row.value);
484+
} catch {
485+
return row.value;
486+
}
487+
} catch (error) {
488+
console.error('DB Get Setting Error:', key, error);
489+
throw error;
490+
}
491+
},
492+
476493
run(sql: string, params: any[] = []): Database.RunResult {
477494
try {
478495
return db.prepare(sql).run(...(params || []));

electron/main.ts

Lines changed: 79 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,38 @@ log.catchErrors({
4747
console.log(`Log file will be written to: ${log.transports.file.getFile().path}`);
4848

4949
let mainWindow: BrowserWindow | null;
50+
let autoCheckEnabled = true;
51+
let pendingAutoUpdateCheck: NodeJS.Timeout | null = null;
52+
53+
const cancelScheduledAutoUpdateCheck = () => {
54+
if (pendingAutoUpdateCheck) {
55+
clearTimeout(pendingAutoUpdateCheck);
56+
pendingAutoUpdateCheck = null;
57+
}
58+
};
59+
60+
const scheduleAutoUpdateCheck = (delayMs = 3000) => {
61+
if (!autoCheckEnabled) {
62+
console.log('Automatic update checks are disabled; skipping schedule.');
63+
return;
64+
}
65+
66+
cancelScheduledAutoUpdateCheck();
67+
68+
pendingAutoUpdateCheck = setTimeout(async () => {
69+
pendingAutoUpdateCheck = null;
70+
if (!autoCheckEnabled) {
71+
console.log('Automatic update checks disabled before execution; skipping update check.');
72+
return;
73+
}
74+
75+
try {
76+
await autoUpdater.checkForUpdatesAndNotify();
77+
} catch (error) {
78+
console.error('Automatic update check failed:', error);
79+
}
80+
}, delayMs);
81+
};
5082

5183
const broadcastPythonEvent = (channel: string, payload: any) => {
5284
const targets = BrowserWindow.getAllWindows();
@@ -186,10 +218,25 @@ app.on('ready', () => {
186218
// The renderer process will detect the failure when it tries to communicate
187219
// via IPC and will display the fatal error screen. We still create the window.
188220
}
189-
221+
190222
createWindow();
191-
// Check for updates after window is created
192-
setTimeout(() => autoUpdater.checkForUpdatesAndNotify(), 3000);
223+
224+
try {
225+
const storedPreference = databaseService.getSetting('autoCheckForUpdates');
226+
if (typeof storedPreference === 'boolean') {
227+
autoCheckEnabled = storedPreference;
228+
} else if (typeof storedPreference !== 'undefined') {
229+
autoCheckEnabled = Boolean(storedPreference);
230+
}
231+
} catch (error) {
232+
console.error('Failed to read auto-update preference from settings:', error);
233+
}
234+
235+
if (autoCheckEnabled) {
236+
scheduleAutoUpdateCheck();
237+
} else {
238+
console.log('Automatic update checks are disabled via settings.');
239+
}
193240
});
194241

195242
app.on('window-all-closed', () => {
@@ -426,9 +473,38 @@ ipcMain.handle('app:get-log-path', () => log.transports.file.getFile().path);
426473
ipcMain.on('updater:set-allow-prerelease', (_, allow: boolean) => {
427474
autoUpdater.allowPrerelease = allow;
428475
});
476+
ipcMain.on('updater:set-auto-check-enabled', (_, enabled: boolean) => {
477+
autoCheckEnabled = enabled;
478+
if (enabled) {
479+
scheduleAutoUpdateCheck();
480+
} else {
481+
cancelScheduledAutoUpdateCheck();
482+
}
483+
});
429484
ipcMain.on('updater:quit-and-install', () => {
430485
autoUpdater.quitAndInstall();
431486
});
487+
ipcMain.handle('updater:check-now', async () => {
488+
try {
489+
const result = await autoUpdater.checkForUpdates();
490+
const updateInfo = result?.updateInfo;
491+
const version = updateInfo?.version ?? null;
492+
const releaseName = updateInfo?.releaseName ?? null;
493+
const currentVersion = app.getVersion();
494+
const updateAvailable = Boolean(version && version !== currentVersion);
495+
496+
return {
497+
success: true,
498+
updateAvailable,
499+
version,
500+
releaseName,
501+
};
502+
} catch (error) {
503+
const message = error instanceof Error ? error.message : String(error);
504+
console.error('Manual update check failed:', message);
505+
return { success: false, error: message };
506+
}
507+
});
432508

433509

434510
// Window Controls

electron/preload.ts

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,14 @@ type UpdateDownloadProgress = {
1313
bytesPerSecond: number;
1414
};
1515

16+
type ManualUpdateCheckResult = {
17+
success: boolean;
18+
updateAvailable?: boolean;
19+
version?: string | null;
20+
releaseName?: string | null;
21+
error?: string;
22+
};
23+
1624
contextBridge.exposeInMainWorld('electronAPI', {
1725
// --- Database ---
1826
dbQuery: (sql: string, params?: any[]) => ipcRenderer.invoke('db:query', sql, params),
@@ -44,6 +52,8 @@ contextBridge.exposeInMainWorld('electronAPI', {
4452
getLogPath: () => ipcRenderer.invoke('app:get-log-path'),
4553
renderPlantUML: (diagram: string, format: 'svg' = 'svg') => ipcRenderer.invoke('plantuml:render-svg', diagram, format),
4654
updaterSetAllowPrerelease: (allow: boolean) => ipcRenderer.send('updater:set-allow-prerelease', allow),
55+
updaterSetAutoCheckEnabled: (enabled: boolean) => ipcRenderer.send('updater:set-auto-check-enabled', enabled),
56+
updaterCheckForUpdates: () => ipcRenderer.invoke('updater:check-now') as Promise<ManualUpdateCheckResult>,
4757
onUpdateAvailable: (callback: (info: UpdateAvailableInfo) => void) => {
4858
const handler = (_: IpcRendererEvent, info: UpdateAvailableInfo) => callback(info);
4959
ipcRenderer.on('update:available', handler);

hooks/useSettings.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,13 @@ export const useSettings = () => {
6666
}
6767
}, [settings.allowPrerelease, loaded, addLog]);
6868

69+
useEffect(() => {
70+
if (loaded && isElectron && window.electronAPI?.updaterSetAutoCheckEnabled) {
71+
addLog('DEBUG', `Notifying main process: autoCheckForUpdates is ${settings.autoCheckForUpdates}`);
72+
window.electronAPI.updaterSetAutoCheckEnabled(settings.autoCheckForUpdates);
73+
}
74+
}, [settings.autoCheckForUpdates, loaded, addLog]);
75+
6976
const saveSettings = useCallback(async (newSettings: Settings) => {
7077
setSettings(newSettings);
7178
await repository.saveAllSettings(newSettings);

types.ts

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,8 @@ declare global {
4040
format?: 'svg'
4141
) => Promise<{ success: boolean; svg?: string; error?: string; details?: string }>;
4242
updaterSetAllowPrerelease: (allow: boolean) => void;
43+
updaterSetAutoCheckEnabled?: (enabled: boolean) => void;
44+
updaterCheckForUpdates?: () => Promise<ManualUpdateCheckResult>;
4345
onUpdateAvailable?: (callback: (info: UpdateAvailableInfo) => void) => () => void;
4446
onUpdateDownloadProgress?: (callback: (progress: UpdateDownloadProgress) => void) => () => void;
4547
onUpdateDownloaded: (callback: (info: string | UpdateAvailableInfo) => void) => () => void;
@@ -94,6 +96,14 @@ export interface UpdateDownloadProgress {
9496
bytesPerSecond: number;
9597
}
9698

99+
export interface ManualUpdateCheckResult {
100+
success: boolean;
101+
updateAvailable?: boolean;
102+
version?: string | null;
103+
releaseName?: string | null;
104+
error?: string;
105+
}
106+
97107
export type NodeType = 'folder' | 'document';
98108
export type DocType = 'prompt' | 'source_code' | 'pdf' | 'image';
99109
export type ViewMode = 'edit' | 'preview' | 'split-vertical' | 'split-horizontal';
@@ -304,6 +314,7 @@ export interface Settings {
304314
iconSet: 'heroicons' | 'lucide' | 'feather' | 'tabler' | 'material';
305315
autoSaveLogs: boolean;
306316
allowPrerelease: boolean;
317+
autoCheckForUpdates: boolean;
307318
plantumlRendererMode: 'remote' | 'offline';
308319
uiScale: number;
309320
documentTreeIndent: number;

0 commit comments

Comments
 (0)