Skip to content

Commit 5932143

Browse files
committed
Add in-app uninstaller in Settings (Danger Zone)
- New 'Danger Zone' section at the bottom of Settings page showing: - Size breakdown: ML venv, app config/settings, generated content - Checkbox: "Keep generated content" (checked by default) - Red 'Uninstall AutoNote' button - main.js: getDirSizeMB/getDirSizeBytes helpers + rmRecursive - uninstall:sizes IPC → returns venv/data/content sizes in MB - uninstall:run IPC → deletes venv, DATA_DIR, optionally OUTPUT_DIR - preload.js: exposes getUninstallSizes() and runUninstall(keepContent) - After deletion the app closes and shows platform-specific guidance: Windows → "Go to Apps → AutoNote → Uninstall" Linux → "Delete the AutoNote AppImage file"
1 parent 275ff4e commit 5932143

4 files changed

Lines changed: 154 additions & 1 deletion

File tree

electron/main.js

Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -561,6 +561,41 @@ async function runInstaller(basePython, sendLog, sendDone) {
561561
sendDone({ success: true });
562562
}
563563

564+
// ── Uninstall helpers ─────────────────────────────────────────────────────────
565+
function getDirSizeMB(dirPath, excludeDirs = []) {
566+
let bytes = 0;
567+
try {
568+
const entries = fs.readdirSync(dirPath, { withFileTypes: true });
569+
for (const e of entries) {
570+
if (excludeDirs.includes(e.name)) continue;
571+
const full = path.join(dirPath, e.name);
572+
if (e.isDirectory()) {
573+
bytes += getDirSizeBytes(full);
574+
} else {
575+
try { bytes += fs.statSync(full).size; } catch {}
576+
}
577+
}
578+
} catch {}
579+
return Math.round(bytes / (1024 * 1024));
580+
}
581+
582+
function getDirSizeBytes(dirPath) {
583+
let bytes = 0;
584+
try {
585+
const entries = fs.readdirSync(dirPath, { withFileTypes: true });
586+
for (const e of entries) {
587+
const full = path.join(dirPath, e.name);
588+
if (e.isDirectory()) bytes += getDirSizeBytes(full);
589+
else try { bytes += fs.statSync(full).size; } catch {}
590+
}
591+
} catch {}
592+
return bytes;
593+
}
594+
595+
function rmRecursive(dirPath) {
596+
fs.rmSync(dirPath, { recursive: true, force: true });
597+
}
598+
564599
// ── IPC handlers ──────────────────────────────────────────────────────────────
565600
function registerIpc() {
566601
ipcMain.handle('config:get', () => loadConfig());
@@ -606,6 +641,41 @@ function registerIpc() {
606641
ipcMain.handle('path:outputDir', () => getOutputDir());
607642
ipcMain.handle('path:dataDir', () => DATA_DIR);
608643

644+
// ── Uninstaller ──────────────────────────────────────────────────────────
645+
ipcMain.handle('uninstall:sizes', async () => {
646+
const venvDir = path.join(DATA_DIR, 'venv');
647+
const outputDir = getOutputDir();
648+
return {
649+
venv: getDirSizeMB(venvDir),
650+
data: getDirSizeMB(DATA_DIR, ['venv']), // config/scripts/manifest, excl venv
651+
content: outputDir !== DATA_DIR ? getDirSizeMB(outputDir) : null,
652+
outputDir,
653+
};
654+
});
655+
656+
ipcMain.handle('uninstall:run', async (_, { keepContent }) => {
657+
try {
658+
// 1. Delete ML venv
659+
const venvDir = path.join(DATA_DIR, 'venv');
660+
if (fs.existsSync(venvDir)) rmRecursive(venvDir);
661+
662+
// 2. Optionally delete generated content (OUTPUT_DIR if separate from DATA_DIR)
663+
if (!keepContent) {
664+
const outputDir = getOutputDir();
665+
if (outputDir && outputDir !== DATA_DIR && fs.existsSync(outputDir)) {
666+
rmRecursive(outputDir);
667+
}
668+
}
669+
670+
// 3. Delete the rest of DATA_DIR (config, scripts, manifest, api keys…)
671+
if (fs.existsSync(DATA_DIR)) rmRecursive(DATA_DIR);
672+
673+
return { ok: true };
674+
} catch (err) {
675+
return { ok: false, error: err.message };
676+
}
677+
});
678+
609679
ipcMain.handle('dialog:openDir', async () => {
610680
const result = await dialog.showOpenDialog(mainWindow, {
611681
properties: ['openDirectory'],

electron/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "auto-note",
3-
"version": "0.8.7",
3+
"version": "0.8.8",
44
"description": "AutoNote — lecture notes generator from Canvas recordings",
55
"main": "main.js",
66
"scripts": {

electron/preload.js

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,4 +52,8 @@ contextBridge.exposeInMainWorld('api', {
5252
openDirDialog: () => ipcRenderer.invoke('dialog:openDir'),
5353
getOutputDir: () => ipcRenderer.invoke('path:outputDir'),
5454
getDataDir: () => ipcRenderer.invoke('path:dataDir'),
55+
56+
// ── Uninstaller ───────────────────────────────────────────────────────────
57+
getUninstallSizes: () => ipcRenderer.invoke('uninstall:sizes'),
58+
runUninstall: (keepContent) => ipcRenderer.invoke('uninstall:run', { keepContent }),
5559
});

electron/renderer/app.js

Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -678,6 +678,29 @@ function buildSettings() {
678678
<button class="btn-secondary" id="settings-refresh-btn">${I.refresh} Refresh Courses</button>
679679
<span id="settings-refresh-status" style="font-size:11px;color:var(--c-white-60)"></span>
680680
</div>
681+
${buildUninstallSection()}
682+
`;
683+
}
684+
685+
function buildUninstallSection() {
686+
return `
687+
<div style="margin-top:24px;padding:16px;border:1px solid var(--c-red,#e53935);border-radius:8px;background:rgba(229,57,53,0.06)">
688+
<div style="font-weight:600;font-size:14px;color:var(--c-red,#e53935);margin-bottom:6px">Danger Zone — Uninstall AutoNote</div>
689+
<div style="font-size:12px;color:var(--c-white-60);margin-bottom:12px">
690+
Permanently removes the ML environment and all app settings.
691+
The app binary itself must be deleted separately after uninstalling.
692+
</div>
693+
<div id="uninstall-sizes" style="font-size:12px;color:var(--c-white-60);margin-bottom:12px;line-height:1.8">
694+
Calculating sizes…
695+
</div>
696+
<label style="display:flex;align-items:center;gap:8px;font-size:13px;margin-bottom:12px;cursor:pointer">
697+
<input type="checkbox" id="uninstall-keep-content" checked>
698+
Keep generated content (notes, captions, alignment, videos, materials)
699+
</label>
700+
<button id="btn-uninstall" style="background:var(--c-red,#e53935);color:#fff;border:none;padding:8px 18px;border-radius:6px;font-size:13px;cursor:pointer;font-weight:600">
701+
Uninstall AutoNote
702+
</button>
703+
</div>
681704
`;
682705
}
683706

@@ -1216,6 +1239,62 @@ async function attachPageHandlers() {
12161239
refreshCourses(true);
12171240
});
12181241

1242+
// Uninstall sizes
1243+
(async () => {
1244+
const el = document.getElementById('uninstall-sizes');
1245+
if (!el) return;
1246+
try {
1247+
const s = await window.api.getUninstallSizes();
1248+
const fmt = mb => mb >= 1024 ? `${(mb/1024).toFixed(1)} GB` : `${mb} MB`;
1249+
let html = `<b>Will be deleted:</b><br>`;
1250+
html += `&nbsp;&nbsp;• ML Environment: <b>${fmt(s.venv)}</b><br>`;
1251+
html += `&nbsp;&nbsp;• App settings &amp; config: <b>${fmt(s.data)}</b><br>`;
1252+
if (s.content !== null) {
1253+
html += `<br><b>Generated content</b> (kept unless unchecked):<br>`;
1254+
html += `&nbsp;&nbsp;• Output directory (${s.outputDir}): <b>${fmt(s.content)}</b>`;
1255+
} else {
1256+
html += `<br><span style="color:var(--c-white-45)">Generated content is inside the app data dir and will be removed with it.</span>`;
1257+
}
1258+
el.innerHTML = html;
1259+
} catch { el.textContent = 'Could not calculate sizes.'; }
1260+
})();
1261+
1262+
// Uninstall button
1263+
document.getElementById('btn-uninstall')?.addEventListener('click', async () => {
1264+
const keepContent = document.getElementById('uninstall-keep-content')?.checked ?? true;
1265+
const contentLine = keepContent
1266+
? 'Generated notes, captions, and videos will be kept.'
1267+
: 'Generated notes, captions, and videos will also be DELETED.';
1268+
const confirmed = confirm(
1269+
`Are you sure you want to uninstall AutoNote?\n\n` +
1270+
`This will delete:\n` +
1271+
` • The ML environment (~/.auto_note/venv)\n` +
1272+
` • All app settings and credentials\n\n` +
1273+
`${contentLine}\n\n` +
1274+
`After uninstalling, please delete the AutoNote app binary/installer manually.`
1275+
);
1276+
if (!confirmed) return;
1277+
1278+
const btn = document.getElementById('btn-uninstall');
1279+
btn.disabled = true;
1280+
btn.textContent = 'Uninstalling…';
1281+
1282+
const result = await window.api.runUninstall(keepContent);
1283+
if (result.ok) {
1284+
alert(
1285+
'AutoNote data has been removed.\n\n' +
1286+
(process.platform === 'win32'
1287+
? 'To finish uninstalling, go to Windows Settings → Apps → AutoNote → Uninstall.'
1288+
: 'To finish uninstalling, delete the AutoNote AppImage file.')
1289+
);
1290+
window.close();
1291+
} else {
1292+
btn.disabled = false;
1293+
btn.textContent = 'Uninstall AutoNote';
1294+
alert(`Uninstall failed: ${result.error}`);
1295+
}
1296+
});
1297+
12191298
// Venv install / reinstall
12201299
const startInstall = async () => {
12211300
const logEl = document.getElementById('env-log');

0 commit comments

Comments
 (0)