aboutsummaryrefslogtreecommitdiff
path: root/app.js
diff options
context:
space:
mode:
authorMinteck <contact@minteck.org>2022-11-28 17:43:50 +0100
committerMinteck <contact@minteck.org>2022-11-28 17:43:50 +0100
commitd302af8487f74af091a5032118c83c2dac08b275 (patch)
tree022852bf7792ad729e1aacf7a25c6c815a1c9a93 /app.js
downloadtempdisk-mane.tar.gz
tempdisk-mane.tar.bz2
tempdisk-mane.zip
Initial commitHEADmane
Diffstat (limited to 'app.js')
-rw-r--r--app.js533
1 files changed, 533 insertions, 0 deletions
diff --git a/app.js b/app.js
new file mode 100644
index 0000000..9fd2eeb
--- /dev/null
+++ b/app.js
@@ -0,0 +1,533 @@
+const { app, Tray, Menu, dialog, Notification, powerMonitor, BrowserWindow } = require('electron')
+const version = require('./package.json').version;
+const child_process = require('child_process');
+const fs = require('fs');
+
+setInterval(() => {
+ if (global.openWindow !== null) {
+ global.openWindow.webContents.send("config", JSON.stringify(config))
+ global.openWindow.webContents.send("watchers", JSON.stringify({
+ fileWatchersPurgeable,
+ fileWatchers,
+ fileWatchersFailed: global.trackingFailures
+ }))
+ }
+}, 1000);
+
+global.openWindow = null;
+function createWindow(page, width, height) {
+ global.openWindow = new BrowserWindow({
+ id: "window",
+ alwaysOnTop: true,
+ backgroundColor: "#333333",
+ skipTaskbar: true,
+ autoHideMenuBar: true,
+ enableLargerThanScreen: true,
+ width: width ?? 800,
+ height: height ?? 600,
+ resizable: false,
+ maximizable: false,
+ webPreferences: {
+ nodeIntegration: true,
+ contextIsolation: false
+ }
+ });
+
+ openWindow.loadFile(page);
+ openWindow.setMenuBarVisibility(false);
+ if (global.linux) openWindow.setMenu(null);
+ openWindow.on('close', () => {
+ global.openWindow = null;
+ });
+}
+
+global.forceDebug = process.argv.includes("--verbose");
+global.linux = require('os').platform() === "linux";
+let configPath = app.getPath("userData") + "/config.json";
+
+if (!fs.existsSync(configPath)) {
+ fs.writeFileSync(configPath, JSON.stringify({
+ disk: linux ? "/dev/vda3" : "/dev/disk3s8",
+ directory: linux ? require('os').homedir() + "/.tempdisk" : "/Volumes/Earth pony",
+ timeout: 600,
+ lastAccessTime: null,
+ lastLockTime: null
+ }));
+}
+
+global.config = config = JSON.parse(fs.readFileSync(configPath).toString());
+global.lastKnownConfig = fs.readFileSync(configPath).toString();
+
+try {
+ if (!child_process.execSync("df").toString().trim().includes(global.config.directory)) {
+ fs.rmdirSync(global.config.directory);
+ }
+} catch (e) {}
+
+setInterval(() => {
+ config.lastLockTime = global.lockDate;
+ config.lastAccessTime = global.lastAccess;
+
+ if (JSON.stringify(config) !== lastKnownConfig) {
+ fs.writeFileSync(configPath, JSON.stringify(config));
+ global.lastKnownConfig = JSON.stringify(config);
+ }
+}, 5000);
+
+global.trayIcon = null;
+function updateTrayIcon(icon) {
+ if (icon !== trayIcon) {
+ try {
+ tray.setImage(icon);
+ trayIcon = icon;
+ } catch (e) {
+ console.error(e);
+ }
+ }
+}
+
+process.chdir(__dirname);
+
+powerMonitor.on('lock-screen', () => {
+ if (diskStatus === 3) {
+ global.commands.lock();
+ global.notifications.lock("lock");
+ }
+})
+
+global.debugMenu = global.forceDebug;
+global.directory = global.config.directory;
+global.disk = global.config.disk
+global.time = global.config.timeout;
+global.oldDiskStatus = null;
+global.processes = [];
+
+global.commands = {
+ lock: () => {
+ if (linux) {
+ try { child_process.execSync("sudo umount " + require('os').userInfo().homedir + "/.tempdisk"); } catch (e) {}
+ try { child_process.execSync("rmdir " + require('os').userInfo().homedir + "/.tempdisk"); } catch (e) {}
+ child_process.execSync("sudo cryptsetup luksClose /dev/mapper/tempdisk");
+ } else {
+ child_process.execSync("diskutil unmount force " + disk);
+ }
+ },
+ unlock: () => {
+ if (linux) {
+ let password = child_process.execSync("zenity --password --width 600 --title \"Enter a password to unlock the disk\" --ok-label \"Unlock\" --cancel-label \"Cancel\"").toString().trim();
+
+ child_process.execSync("echo \"" + Buffer.from(password).toString("base64") + "\" | base64 -d | sudo cryptsetup luksOpen " + disk + " tempdisk");
+ child_process.execSync("mkdir " + require('os').userInfo().homedir + "/.tempdisk");
+ child_process.execSync("sudo mount /dev/mapper/tempdisk " + require('os').userInfo().homedir + "/.tempdisk");
+ } else {
+ child_process.execSync("automator ./decryptDisk.workflow");
+ }
+ }
+}
+
+setInterval(async () => {
+ if (diskStatus === 3) {
+ try {
+ let execFile = require('util').promisify(child_process.execFile);
+
+ let parts = (await execFile("lsof", [ directory ])).stdout.trim().split("\n").map(i => i.replace(/ +/g, " ").split(" ")[1]);
+ parts.shift();
+ let fullParts = [];
+
+ for (let part of parts) {
+ fullParts.push({
+ pid: part,
+ name: (await execFile("ps", [ "-p", part, "-o", "comm=" ])).stdout.trim().replace(/(.*)\/(.*).app\/(.*)/i, "$2")
+ });
+ }
+
+ global.processes = fullParts;
+ } catch (e) {
+ global.processes = [];
+ }
+ }
+}, 1000);
+
+global.TempDisk = {
+ UNLOADED: 0,
+ LOADING: 1,
+ LOCKED: 2,
+ UNLOCKED: 3
+}
+
+global.notifications = {
+ unlock: () => {
+ let notification = new Notification({
+ title: "Disk is unlocked",
+ silent: true,
+ body: "The disk has been unlocked and is now usable.",
+ actions: [
+ {
+ type: "button",
+ text: "Open in Finder"
+ }
+ ]
+ });
+ notification.show();
+ notification.on('action', () => {
+ global.actions.open();
+ })
+ },
+ lock: (reason) => {
+ let notification = new Notification({
+ title: "Disk is locked",
+ silent: true,
+ body: "The disk has been locked" + (reason === "inactivity" ? " because of inactivity" : (reason === "lock" ? " because the session was locked" : "")) + ".",
+ actions: [
+ {
+ type: "button",
+ text: "Unlock"
+ }
+ ]
+ });
+ notification.show();
+ notification.on('action', () => {
+ global.actions.unlockDisk();
+ })
+ }
+}
+
+global.diskName = linux ? disk : child_process.execFileSync("diskutil", [ "info", disk ]).toString().trim().split("\n").map(i => i.trim()).filter(i => i.startsWith("Volume Name: "))[0].replace(/ +/g, " ").split(": ")[1]
+
+function secondsToHMS(input) {
+ if (input > 3600) {
+ let hours = Math.floor(input / 3600)
+ let minutes = Math.floor((input - hours * 60) / 60);
+ let seconds = input - minutes * 60;
+
+ if (minutes < 10) {
+ minutes = "0" + minutes;
+ }
+
+ if (seconds < 10) {
+ seconds = "0" + seconds;
+ }
+
+ if (isNaN(seconds) || isNaN(minutes) || isNaN(hours)) return secondsToHMS(global.time);
+
+ return hours + ":" + minutes + ":" + seconds;
+ } else {
+ let minutes = Math.floor(input / 60);
+ let seconds = input - minutes * 60;
+
+ if (seconds < 10) {
+ seconds = "0" + seconds;
+ }
+
+ if (isNaN(seconds) || isNaN(minutes)) return secondsToHMS(global.time);
+
+ return minutes + ":" + seconds;
+ }
+}
+
+function timeAgo(time) {
+ if (!isNaN(parseInt(time))) {
+ time = new Date(time).getTime();
+ }
+
+ let periods = ["second", "minute", "hour", "day", "week", "month", "year", "age"];
+
+ let lengths = ["60", "60", "24", "7", "4.35", "12", "100"];
+
+ let now = new Date().getTime();
+
+ let difference = Math.round((now - time) / 1000);
+ let tense;
+ let period;
+
+ if (difference <= 10 && difference >= 0) {
+ tray.setTitle("now");
+ return "now";
+ } else if (difference > 0) {
+ tense = "ago";
+ } else {
+ tense = "later";
+ }
+
+ let j;
+
+ for (j = 0; difference >= lengths[j] && j < lengths.length - 1; j++) {
+ difference /= lengths[j];
+ }
+
+ difference = Math.round(difference);
+
+ period = periods[j];
+ if (period === "second") {
+ tray.setTitle(`${difference} ${period}${difference > 1 ? "s" : ""} ${tense}`);
+ }
+
+ return `${difference} ${period}${difference > 1 ? "s" : ""} ${tense}`;
+}
+
+function secondsToMinutes(seconds) {
+ if (seconds < 60) {
+ return "less than a minute";
+ } else if (seconds < 120) {
+ return "a minute";
+ } else {
+ return Math.round(seconds / 60) + " minutes";
+ }
+}
+
+global.actions = {
+ tracking: () => {
+ createWindow("tracking.html", 400);
+ },
+ devtools: () => {
+ if (global.openWindow) {
+ openWindow.toggleDevTools();
+ }
+ },
+ lockDisk: () => {
+ oldDiskStatus = diskStatus;
+ diskStatus = TempDisk.LOADING;
+ updateTrayIcon('./icon/loading/16x16Template@2x.png');
+
+ let add = "";
+ if (global.processes.length > 0) {
+ if (global.processes.length > 1) {
+ add = "\n\nThe following apps are currently using the disk:\n- " + global.processes.map(i => i.name).join("\n- ") + "\nLocking the disk may cause errors in some or all of these apps."
+ } else {
+ add = "\n\n" + global.processes[0].name + " is currently using the disk, locking the disk may cause errors in this app.";
+ }
+ }
+
+ dialog.showMessageBox({
+ message: "Are you sure you want to lock the disk?",
+ detail: "Locking the disk manually will make it unreadable for other apps until you unlock it again." + add,
+ buttons: [
+ "Lock",
+ "Cancel"
+ ],
+ defaultId: 0,
+ type: "warning"
+ }).then((result) => {
+ if (result.response === 0) {
+ global.commands.lock();
+ global.notifications.lock();
+ } else {
+ diskStatus = oldDiskStatus;
+ }
+ })
+ },
+ reveal: () => {
+ child_process.execFile("open", [ "-R", directory ]);
+ },
+ open: () => {
+ if (linux) {
+ child_process.execFile("xdg-open", [ directory ]);
+ } else {
+ child_process.execFile("open", [ directory ]);
+ }
+ },
+ config: () => {
+ if (linux) {
+ child_process.execFile("xdg-open", [ configPath ]);
+ } else {
+ child_process.execFile("open", [ configPath ]);
+ }
+ },
+ unlockDisk: () => {
+ oldDiskStatus = diskStatus;
+ diskStatus = TempDisk.LOADING;
+ updateTrayIcon('./icon/loading/16x16Template@2x.png');
+ fs.writeFileSync("./decryptDisk.workflow/Contents/document.wflow", fs.readFileSync("./decryptDisk.workflow/Contents/document.pre.wflow").toString().replace("%VOLUME%", disk).replace("%NAME%", diskName));
+
+ try {
+ global.commands.unlock();
+ diskStatus = oldDiskStatus;
+ global.lastAccess = new Date();
+ } catch (e) {
+ dialog.showMessageBox({
+ message: "Failed to unlock disk",
+ detail: "An error occurred while unlocking this disk, maybe your password is incorrect? Please try again later.",
+ buttons: [
+ "Close"
+ ],
+ type: "error"
+ })
+
+ console.error(e);
+ fs.writeFileSync(require('os').homedir() + "/.DiskUnlockFailure.txt", e.stack);
+ diskStatus = oldDiskStatus;
+ }
+ },
+ quit: () => {
+ oldDiskStatus = diskStatus;
+ diskStatus = TempDisk.LOADING;
+ updateTrayIcon('./icon/loading/16x16Template@2x.png');
+
+ dialog.showMessageBox({
+ message: "Are you sure you want to quit TempDisk?",
+ detail: "If you quit TempDisk, your disk will be unprotected as it won't be unmounted automatically when unused anymore. This may not be what you want.",
+ buttons: [
+ "Quit",
+ "Cancel"
+ ],
+ defaultId: 0,
+ type: "warning",
+ checkboxLabel: "Lock the disk before quitting",
+ checkboxChecked: oldDiskStatus === 3
+ }).then((result) => {
+ if (result.response === 0) {
+ if (result.checkboxChecked && oldDiskStatus === 3) {
+ global.commands.lock();
+ }
+
+ app.exit();
+ } else {
+ diskStatus = oldDiskStatus;
+ }
+ })
+ }
+}
+
+global.diskStatus = TempDisk.UNLOADED;
+global.lastKnownMenu = null;
+global.updateMenu = () => {
+ switch (diskStatus) {
+ case 0:
+ updateTrayIcon('./icon/unknown/16x16Template@2x.png');
+ tray.setToolTip("TempDisk");
+ tray.setTitle("");
+ if (global.openWindow !== null) {
+ global.openWindow.destroy();
+ global.openWindow = null;
+ }
+ break;
+
+ case 1:
+ updateTrayIcon('./icon/loading/16x16Template@2x.png');
+ tray.setToolTip("TempDisk\nLoading");
+ tray.setTitle("");
+ break;
+
+ case 2:
+ updateTrayIcon('./icon/locked/16x16Template@2x.png');
+ tray.setToolTip("TempDisk\nLocked");
+ tray.setTitle("");
+ if (global.openWindow !== null) {
+ global.openWindow.destroy();
+ global.openWindow = null;
+ }
+ break;
+
+ case 3:
+ updateTrayIcon('./icon/unlocked/16x16Template@2x.png');
+ tray.setToolTip("TempDisk\nUnlocked");
+
+ if (processes.length > 0) {
+ tray.setTitle(processes.length + " app" + (processes.length > 1 ? "s" : ""), { fontType: "monospacedDigit" });
+ } else {
+ tray.setTitle(secondsToHMS(global.left), { fontType: "monospacedDigit" });
+ }
+
+ break;
+ }
+
+ let template = [
+ { label: 'TempDisk ' + version + (debugMenu ? " (" + require('os').platform() +")" : ""), icon: "./icon/app/16x16@2x.png", type: 'normal', enabled: false },
+ { type: 'separator' },
+ { label: diskStatus === 0 ? 'No information available' : diskStatus === 1 ? 'Loading...' : diskStatus === 2 ? 'Disk is locked' : 'Disk is unlocked', type: 'normal', enabled: false },
+ ];
+
+ if (diskStatus === 3) {
+ if (global.left) {
+ if (global.processes.length > 0) {
+ template.push({ label: 'Not locking automatically', type: 'normal', enabled: false })
+ } else {
+ template.push({ label: 'Locking disk in ' + secondsToMinutes(global.left), type: 'normal', enabled: false })
+ }
+ } else {
+ template.push({ label: '-', type: 'normal', enabled: false })
+ }
+
+ if (global.processes.length > 0) {
+ template.push({ type: 'separator' });
+ template.push({ label: 'App' + (global.processes.length > 1 ? 's' : '') + ' using the disk:', enabled: false });
+
+ for (let proc of processes) {
+ template.push({ label: ' ' + proc.name + (debugMenu ? ' [' + proc.pid + ']' : ''), enabled: false });
+ }
+ }
+ }
+
+ if (diskStatus === 2) {
+ if (global.lockDate) {
+ template.push({ label: 'Locked ' + timeAgo(global.lockDate.getTime()), type: 'normal', enabled: false })
+ } else {
+ template.push({ label: '-', type: 'normal', enabled: false })
+ }
+ }
+
+ template.push(...[
+ { type: 'separator' },
+ { id: 'open', label: linux ? 'Open in file manager' : 'Open disk in Finder', type: 'normal', enabled: diskStatus === 3, accelerator: linux ? "" : "CommandOrControl+O", click: global.actions.open }
+ ]);
+
+ if (!linux) {
+ template.push({ id: 'reveal', label: 'Reveal disk in Finder', type: 'normal', enabled: diskStatus === 3, accelerator: linux ? "" : "CommandOrControl+Shift+O", click: global.actions.reveal });
+ }
+
+ template.push(...[
+ { id: 'lock', label: 'Lock disk immediately', type: 'normal', enabled: diskStatus === 3, accelerator: linux ? "" : "CommandOrControl+Shift+L", click: global.actions.lockDisk },
+ { id: 'unlock', label: 'Unlock disk', type: 'normal', enabled: diskStatus === 2, accelerator: linux ? "" : "CommandOrControl+L", click: global.actions.unlockDisk },
+ { type: 'separator' },
+ { id: 'tracking', label: 'File usage tracking', type: 'normal', enabled: diskStatus === 3 && global.openWindow === null, accelerator: linux ? "" : "CommandOrControl+T", click: global.actions.tracking },
+ { type: 'separator' },
+ { id: 'quit', label: 'Quit', type: 'normal', enabled: true, accelerator: linux ? "" : "CommandOrControl+Q", click: global.actions.quit }
+ ]);
+
+ if (global.debugMenu) {
+ template.push({ type: 'separator' })
+ template.push({ label: 'Debugging', type: 'normal', enabled: false })
+ template.push({ label: ' Disk: ' + disk, type: 'normal', enabled: false })
+ template.push({ label: ' Mount point: ' + directory, type: 'normal', enabled: false })
+ template.push({ label: ' Inactivity time: ' + time, type: 'normal', enabled: false })
+ template.push({ label: ' State: ' + diskStatus, type: 'normal', enabled: false })
+ template.push({ label: ' Electron: ' + process.versions.electron, type: 'normal', enabled: false })
+ template.push({ label: ' Node: ' + process.versions.node, type: 'normal', enabled: false })
+ template.push({ label: ' Chrome: ' + process.versions.chrome, type: 'normal', enabled: false })
+ template.push({ label: ' File watchers: ' + Object.keys(global.fileWatchers ?? {}).length + " (" + (global.fileWatchersPurgeable ?? []).length + " purgeable, " + (global.trackingFailures ?? []).length + " failed)", type: 'normal', enabled: false })
+ template.push({ label: ' Edit config file', type: 'normal', enabled: true, click: global.actions.config })
+ template.push({ label: ' Open Chrome dev tools', type: 'normal', enabled: diskStatus === 3 && global.openWindow !== null, click: global.actions.devtools })
+ }
+
+ if (JSON.stringify(global.lastKnownMenu) !== JSON.stringify(template)) {
+ const contextMenu = Menu.buildFromTemplate(template);
+ tray.setContextMenu(contextMenu);
+ if (global.linux) Menu.setApplicationMenu(contextMenu);
+ global.lastKnownMenu = template;
+ }
+}
+
+app.whenReady().then(() => {
+ console.log(__dirname);
+
+ if (!linux) {
+ app.dock.hide();
+ }
+
+ global.tray = new Tray('./icon/unknown/16x16Template@2x.png')
+ updateMenu();
+ tray.setToolTip("TempDisk");
+
+ tray.on('click', (event) => {
+ global.debugMenu = event.altKey;
+ if (global.forceDebug) global.debugMenu = true;
+
+ updateMenu();
+ })
+
+ require('./index');
+})
+
+app.on('window-all-closed', () => {
+ return false;
+}); \ No newline at end of file