2021-07-07 07:45:55 +00:00
|
|
|
import { ExportStats } from 'components/ExportModal';
|
2021-07-05 10:25:42 +00:00
|
|
|
import { retryPromise, runningInBrowser } from 'utils/common';
|
2021-06-12 17:14:21 +00:00
|
|
|
import { logError } from 'utils/sentry';
|
2021-07-05 13:08:35 +00:00
|
|
|
import { getData, LS_KEYS, setData } from 'utils/storage/localStorage';
|
|
|
|
import { Collection, getLocalCollections } from './collectionService';
|
2021-03-29 05:15:08 +00:00
|
|
|
import downloadManager from './downloadManager';
|
2021-07-05 13:08:35 +00:00
|
|
|
import { File, getLocalFiles } from './fileService';
|
2021-03-29 05:15:08 +00:00
|
|
|
|
2021-03-30 11:37:32 +00:00
|
|
|
enum ExportNotification {
|
|
|
|
START = 'export started',
|
2021-03-31 07:13:01 +00:00
|
|
|
IN_PROGRESS = 'export already in progress',
|
2021-03-30 11:37:32 +00:00
|
|
|
FINISH = 'export finished',
|
2021-07-05 12:46:20 +00:00
|
|
|
FAILED = 'export failed',
|
2021-03-31 08:34:00 +00:00
|
|
|
ABORT = 'export aborted',
|
2021-03-30 11:37:32 +00:00
|
|
|
}
|
2021-03-29 05:15:08 +00:00
|
|
|
class ExportService {
|
2021-07-05 13:08:35 +00:00
|
|
|
ElectronAPIs: any;
|
2021-05-29 06:27:52 +00:00
|
|
|
|
2021-03-31 07:13:01 +00:00
|
|
|
exportInProgress: Promise<void> = null;
|
2021-05-29 06:27:52 +00:00
|
|
|
|
2021-03-31 08:34:00 +00:00
|
|
|
abortExport: boolean = false;
|
2021-07-05 12:46:20 +00:00
|
|
|
failedFiles: File[] = [];
|
2021-07-05 13:08:35 +00:00
|
|
|
constructor() {
|
|
|
|
const main = async () => {
|
|
|
|
this.ElectronAPIs = runningInBrowser() && window['ElectronAPIs'];
|
|
|
|
if (this.ElectronAPIs) {
|
2021-07-07 07:45:55 +00:00
|
|
|
const autoStartExport = getData(LS_KEYS.EXPORT);
|
2021-07-05 13:08:35 +00:00
|
|
|
if (autoStartExport?.status) {
|
2021-07-07 07:45:55 +00:00
|
|
|
this.exportFiles(null);
|
2021-07-05 13:08:35 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
};
|
|
|
|
main();
|
|
|
|
}
|
2021-07-07 07:45:55 +00:00
|
|
|
async selectExportDirectory() {
|
|
|
|
return await this.ElectronAPIs.selectRootDirectory();
|
|
|
|
}
|
|
|
|
cancelExport() {
|
|
|
|
this.abortExport = true;
|
|
|
|
}
|
|
|
|
async exportFiles(updateProgress: (stats: ExportStats) => void) {
|
|
|
|
const files = await getLocalFiles();
|
|
|
|
const collections = await getLocalCollections();
|
2021-03-31 07:13:01 +00:00
|
|
|
if (this.exportInProgress) {
|
|
|
|
this.ElectronAPIs.sendNotification(ExportNotification.IN_PROGRESS);
|
|
|
|
return this.exportInProgress;
|
|
|
|
}
|
2021-07-07 07:45:55 +00:00
|
|
|
this.exportInProgress = this.fileExporter(files, collections, updateProgress);
|
2021-03-31 07:13:01 +00:00
|
|
|
return this.exportInProgress;
|
|
|
|
}
|
2021-05-29 06:27:52 +00:00
|
|
|
|
2021-07-07 07:45:55 +00:00
|
|
|
async fileExporter(files: File[], collections: Collection[], updateProgress: (stats: ExportStats) => void) {
|
2021-03-30 13:00:09 +00:00
|
|
|
try {
|
2021-07-07 07:45:55 +00:00
|
|
|
const dir = getData(LS_KEYS.EXPORT).folder;
|
2021-03-30 13:00:09 +00:00
|
|
|
if (!dir) {
|
|
|
|
// directory selector closed
|
|
|
|
return;
|
2021-03-29 12:45:54 +00:00
|
|
|
}
|
2021-03-30 13:00:09 +00:00
|
|
|
const exportedFiles: Set<string> = await this.ElectronAPIs.getExportedFiles(
|
2021-05-29 06:27:52 +00:00
|
|
|
dir,
|
2021-03-30 13:00:09 +00:00
|
|
|
);
|
2021-05-29 06:27:52 +00:00
|
|
|
this.ElectronAPIs.showOnTray('starting export');
|
2021-07-07 07:45:55 +00:00
|
|
|
this.ElectronAPIs.registerStopExportListener(() => (this.abortExport = true));
|
|
|
|
setData(LS_KEYS.EXPORT, { ...getData(LS_KEYS.EXPORT), status: true });
|
2021-03-31 05:53:21 +00:00
|
|
|
const collectionIDMap = new Map<number, string>();
|
2021-05-29 06:27:52 +00:00
|
|
|
for (const collection of collections) {
|
2021-06-07 05:04:50 +00:00
|
|
|
const collectionFolderPath = `${dir}/${collection.id}_${this.sanitizeName(collection.name)}`;
|
2021-03-31 05:53:21 +00:00
|
|
|
await this.ElectronAPIs.checkExistsAndCreateCollectionDir(
|
2021-05-29 06:27:52 +00:00
|
|
|
collectionFolderPath,
|
2021-03-31 05:53:21 +00:00
|
|
|
);
|
|
|
|
collectionIDMap.set(collection.id, collectionFolderPath);
|
|
|
|
}
|
2021-03-30 13:00:09 +00:00
|
|
|
this.ElectronAPIs.sendNotification(ExportNotification.START);
|
2021-05-29 06:27:52 +00:00
|
|
|
for (const [index, file] of files.entries()) {
|
2021-03-31 08:34:00 +00:00
|
|
|
if (this.abortExport) {
|
|
|
|
break;
|
|
|
|
}
|
2021-04-04 08:34:42 +00:00
|
|
|
const uid = `${file.id}_${this.sanitizeName(
|
2021-05-29 06:27:52 +00:00
|
|
|
file.metadata.title,
|
2021-03-31 05:53:21 +00:00
|
|
|
)}`;
|
2021-05-29 06:27:52 +00:00
|
|
|
const filePath = `${collectionIDMap.get(file.collectionID)}/${uid}`;
|
2021-03-31 05:53:21 +00:00
|
|
|
if (!exportedFiles.has(filePath)) {
|
2021-07-05 12:46:20 +00:00
|
|
|
try {
|
|
|
|
await this.downloadAndSave(file, filePath);
|
|
|
|
this.ElectronAPIs.updateExportRecord(dir, filePath);
|
|
|
|
} catch (e) {
|
|
|
|
this.failedFiles.push(file);
|
|
|
|
logError(e, 'download and save failed for file during export');
|
|
|
|
}
|
2021-03-30 13:00:09 +00:00
|
|
|
}
|
2021-07-05 12:46:20 +00:00
|
|
|
this.ElectronAPIs.showOnTray({
|
|
|
|
export_progress:
|
|
|
|
`exporting file ${index + 1} / ${files.length}`,
|
|
|
|
});
|
2021-07-07 07:45:55 +00:00
|
|
|
updateProgress({ current: index + 1, total: files.length, failed: this.failedFiles.length });
|
2021-03-30 13:00:09 +00:00
|
|
|
}
|
2021-03-31 08:34:00 +00:00
|
|
|
this.ElectronAPIs.sendNotification(
|
2021-05-29 06:27:52 +00:00
|
|
|
this.abortExport ?
|
|
|
|
ExportNotification.ABORT :
|
2021-07-05 12:46:20 +00:00
|
|
|
this.failedFiles.length > 0 ? ExportNotification.FAILED :
|
|
|
|
ExportNotification.FINISH,
|
2021-03-31 08:34:00 +00:00
|
|
|
);
|
2021-07-05 12:46:20 +00:00
|
|
|
if (this.failedFiles.length > 0) {
|
|
|
|
this.ElectronAPIs.registerRetryFailedExportListener(this.fileExporter.bind(this, this.failedFiles, collections));
|
|
|
|
this.ElectronAPIs.showOnTray({
|
|
|
|
retry_export:
|
|
|
|
`export failed - retry export`,
|
|
|
|
});
|
|
|
|
} else {
|
|
|
|
this.ElectronAPIs.showOnTray();
|
2021-07-07 07:45:55 +00:00
|
|
|
setData(LS_KEYS.EXPORT, { ...getData(LS_KEYS.EXPORT), status: false });
|
2021-07-05 12:46:20 +00:00
|
|
|
}
|
2021-03-30 13:00:09 +00:00
|
|
|
} catch (e) {
|
2021-06-12 17:14:21 +00:00
|
|
|
logError(e);
|
2021-03-31 08:34:00 +00:00
|
|
|
} finally {
|
|
|
|
this.exportInProgress = null;
|
|
|
|
this.abortExport = false;
|
2021-07-05 12:46:20 +00:00
|
|
|
this.failedFiles = [];
|
2021-03-29 05:15:08 +00:00
|
|
|
}
|
|
|
|
}
|
2021-03-29 11:29:19 +00:00
|
|
|
|
2021-04-27 09:17:38 +00:00
|
|
|
async downloadAndSave(file: File, path) {
|
2021-07-05 10:25:42 +00:00
|
|
|
const fileStream = await retryPromise(downloadManager.downloadFile(file));
|
2021-03-31 06:45:41 +00:00
|
|
|
this.ElectronAPIs.saveStreamToDisk(path, fileStream);
|
|
|
|
this.ElectronAPIs.saveFileToDisk(
|
|
|
|
`${path}.json`,
|
2021-05-29 06:27:52 +00:00
|
|
|
JSON.stringify(file.metadata, null, 2),
|
2021-03-31 06:45:41 +00:00
|
|
|
);
|
2021-03-29 11:29:19 +00:00
|
|
|
}
|
2021-05-29 06:27:52 +00:00
|
|
|
|
2021-04-04 08:23:00 +00:00
|
|
|
private sanitizeName(name) {
|
2021-03-31 05:53:21 +00:00
|
|
|
return name.replaceAll('/', '_').replaceAll(' ', '_');
|
|
|
|
}
|
2021-03-29 05:15:08 +00:00
|
|
|
}
|
|
|
|
export default new ExportService();
|