jm-tech-power-official/Flash Download
A desktop download manager that opens up to 32 connections against one file and can send each of them out a different proxy route.
Public
Uncategorized
Clone
jm-tech-power-official/flash-download.jmproj
src/main/main.js
'use strict';
const { app, BrowserWindow, ipcMain, dialog, shell, clipboard, Menu, safeStorage } = require('electron');
const path = require('path');
const { Manager } = require('./engine/manager');
const { SecretStore, electronCodec } = require('./engine/secrets');
const { probe, classify } = require('./engine/net');
const isMac = process.platform === 'darwin';
let win = null;
let manager = null;
let clipboardTimer = null;
let lastClipboard = '';
let pendingUrl = null;
// Only one copy may own the queue and the state file. A second launch — from a
// browser handoff, the launcher, or `flash-download <url>` — hands its argument
// to the running window instead of starting a rival downloader.
// (`return` is legal at the top level of a CommonJS module.)
if (!app.requestSingleInstanceLock()) {
app.quit();
return;
}
const urlFrom = (argv) => (argv || []).find((a) => /^https?:\/\/\S+$/i.test(a)) || null;
/** Offer a link to the window, or hold it until the window can receive it. */
function offerUrl(url) {
if (!url) return;
if (win && !win.isDestroyed() && !win.webContents.isLoading()) {
win.webContents.send('open-url', url);
if (win.isMinimized()) win.restore();
win.focus();
} else {
pendingUrl = url;
}
}
app.on('second-instance', (_event, argv) => {
if (win && !win.isDestroyed()) {
if (win.isMinimized()) win.restore();
win.show();
win.focus();
}
offerUrl(urlFrom(argv));
});
// macOS hands links over this way rather than on argv.
app.on('open-url', (event, url) => { event.preventDefault(); offerUrl(url); });
function createWindow() {
win = new BrowserWindow({
width: 1180,
height: 760,
minWidth: 720,
minHeight: 480,
show: false,
backgroundColor: '#0C121C',
// One custom 38px titlebar on every platform; only the control cluster differs.
frame: false,
titleBarStyle: isMac ? 'hiddenInset' : 'hidden',
trafficLightPosition: isMac ? { x: 14, y: 11 } : undefined,
webPreferences: {
preload: path.join(__dirname, '..', 'preload', 'preload.js'),
contextIsolation: true,
nodeIntegration: false,
sandbox: true,
spellcheck: false,
},
});
win.loadFile(path.join(__dirname, '..', 'renderer', 'index.html'));
win.once('ready-to-show', () => win.show());
win.on('closed', () => { win = null; });
win.webContents.on('did-finish-load', () => {
const url = pendingUrl;
pendingUrl = null;
if (url) win.webContents.send('open-url', url);
});
// There is no menu bar to carry these, so the window handles them itself.
win.webContents.on('before-input-event', (event, input) => {
if (input.type !== 'keyDown') return;
const mod = isMac ? input.meta : input.control;
if (!mod || input.alt) return;
const key = (input.key || '').toLowerCase();
if (key === 'q') { event.preventDefault(); app.quit(); }
else if (key === 'w') { event.preventDefault(); win.close(); }
});
const push = () => {
if (!win || win.isDestroyed()) return;
win.webContents.send('win:state', { maximized: win.isMaximized(), platform: process.platform });
};
win.on('maximize', push);
win.on('unmaximize', push);
}
function send(channel, payload) {
if (win && !win.isDestroyed()) win.webContents.send(channel, payload);
}
/** Watch the clipboard for links and offer them in the capture bar — never auto-start. */
function watchClipboard() {
clearInterval(clipboardTimer);
clipboardTimer = setInterval(() => {
if (!manager || !manager.settings.captureClipboard) return;
const text = (clipboard.readText() || '').trim();
if (!text || text === lastClipboard || text.length > 2048) return;
lastClipboard = text;
if (!/^https?:\/\/\S+$/i.test(text)) return;
if (manager.downloads.some((d) => d.originUrl === text)) return;
send('clipboard:url', text);
}, 1000);
if (clipboardTimer.unref) clipboardTimer.unref();
}
app.whenReady().then(async () => {
// safeStorage is only valid after the app is ready. Its key is held by the OS
// (DPAPI / Keychain / libsecret); the encrypted blobs live beside state.json.
const userData = app.getPath('userData');
const secrets = new SecretStore({
filePath: path.join(userData, 'credentials.enc'),
codec: electronCodec(safeStorage),
});
manager = new Manager({
stateDir: userData,
defaultDownloadDir: app.getPath('downloads'),
secrets,
});
await manager.load();
manager.on('change', (state) => send('state', state));
manager.on('notice', (notice) => send('notice', notice));
createWindow();
watchClipboard();
offerUrl(urlFrom(process.argv));
// A pool with unknown latency is not much of a pool — measure it once the
// window is up, rather than making the user press Test all every launch.
setTimeout(() => manager.testAllRoutes().catch(() => {}), 1200);
if (isMac) Menu.setApplicationMenu(Menu.buildFromTemplate([{ role: 'appMenu' }, { role: 'editMenu' }, { role: 'windowMenu' }]));
else Menu.setApplicationMenu(null);
app.on('activate', () => { if (BrowserWindow.getAllWindows().length === 0) createWindow(); });
});
app.on('window-all-closed', async () => {
if (isMac) return;
if (manager && !manager.closed) {
manager.closed = true;
await manager.shutdown();
}
app.quit();
});
app.on('before-quit', async (event) => {
if (!manager || manager.closed) return;
event.preventDefault();
manager.closed = true;
await manager.shutdown();
app.quit();
});
// ---- IPC --------------------------------------------------------------------
const ok = (value) => ({ ok: true, value });
const fail = (err) => ({ ok: false, error: classify(err).message });
const handle = (channel, fn) => ipcMain.handle(channel, async (_event, ...args) => {
try { return ok(await fn(...args)); } catch (err) { return fail(err); }
});
handle('state:get', () => manager.snapshot());
handle('url:probe', async (url) => {
const route = manager.routes.find((r) => r.id === manager.settings.routeMode) || null;
const info = await probe(url, { route: route && route.type !== 'direct' ? route : null });
return info;
});
handle('download:add', (opts) => manager.add(opts));
handle('download:pause', (id) => manager.pause(id));
handle('download:resume', (id) => manager.resume(id));
handle('download:retry', (id) => manager.retry(id));
handle('download:rebalance', (id) => manager.rebalance(id));
handle('download:cancel', (id) => manager.cancel(id));
handle('download:remove', (id, deleteFile) => manager.remove(id, { deleteFile }));
handle('download:clearFinished', () => manager.clearFinished());
handle('download:reveal', (id) => {
const dl = manager.find(id);
if (dl && dl.filePath) shell.showItemInFolder(dl.filePath);
});
handle('download:open', async (id) => {
const dl = manager.find(id);
if (dl && dl.filePath && dl.state === 'done') return shell.openPath(dl.filePath);
});
handle('route:add', (route) => manager.addRoute(route));
handle('route:update', (id, patch) => manager.updateRoute(id, patch));
handle('route:remove', (id) => manager.removeRoute(id));
handle('route:test', (id) => manager.testRoute(id));
handle('route:testAll', () => manager.testAllRoutes());
handle('settings:update', (patch) => manager.updateSettings(patch));
handle('dialog:chooseFolder', async (current) => {
const result = await dialog.showOpenDialog(win, {
properties: ['openDirectory', 'createDirectory'],
defaultPath: current || manager.settings.downloadDir,
});
return result.canceled ? null : result.filePaths[0];
});
handle('win:minimize', () => win && win.minimize());
handle('win:maximize', () => {
if (!win) return;
win.isMaximized() ? win.unmaximize() : win.maximize();
});
handle('win:close', () => win && win.close());
handle('win:info', () => ({ platform: process.platform, maximized: win ? win.isMaximized() : false }));