ente/src/services/upload/uploadManager.ts

202 lines
6.7 KiB
TypeScript
Raw Normal View History

2021-08-10 12:29:57 +00:00
import { File, getLocalFiles } from '../fileService';
import { Collection, getLocalCollections } from '../collectionService';
2021-08-10 12:29:57 +00:00
import { SetFiles } from 'pages/gallery';
import { ComlinkWorker, getDedicatedCryptoWorker } from 'utils/crypto';
import {
sortFilesIntoCollections,
sortFiles,
removeUnneccessaryFileProps,
} from 'utils/file';
import { logError } from 'utils/sentry';
import localForage from 'utils/storage/localForage';
import {
getMetadataKey,
ParsedMetaDataJSON,
parseMetadataJSON,
} from './metadataService';
2021-08-10 12:29:57 +00:00
import { segregateFiles } from 'utils/upload';
import { ProgressUpdater } from 'components/pages/gallery/Upload';
import uploader from './uploader';
2021-08-11 05:26:40 +00:00
import UIService from './uiService';
import UploadService from './uploadService';
2021-08-10 12:29:57 +00:00
const MAX_CONCURRENT_UPLOADS = 4;
const FILE_UPLOAD_COMPLETED = 100;
export enum FileUploadResults {
FAILED = -1,
SKIPPED = -2,
UNSUPPORTED = -3,
BLOCKED = -4,
UPLOADED = 100,
}
export interface FileWithCollection {
file: globalThis.File;
collectionID: number;
2021-08-10 12:29:57 +00:00
}
export enum UPLOAD_STAGES {
START,
READING_GOOGLE_METADATA_FILES,
UPLOADING,
FINISH,
}
export type MetadataMap = Map<string, ParsedMetaDataJSON>;
2021-08-10 12:29:57 +00:00
class UploadManager {
private cryptoWorkers = new Array<ComlinkWorker>(MAX_CONCURRENT_UPLOADS);
private metadataMap: MetadataMap;
2021-08-10 12:29:57 +00:00
private filesToBeUploaded: FileWithCollection[];
private failedFiles: FileWithCollection[];
private existingFilesCollectionWise: Map<number, File[]>;
private existingFiles: File[];
private setFiles: SetFiles;
private collections: Map<number, Collection>;
2021-08-11 05:24:07 +00:00
public initUploader(progressUpdater: ProgressUpdater, setFiles: SetFiles) {
2021-08-11 05:26:40 +00:00
UIService.init(progressUpdater);
2021-08-10 12:29:57 +00:00
this.setFiles = setFiles;
}
private async init(newCollections?: Collection[]) {
2021-08-11 05:24:07 +00:00
this.filesToBeUploaded = [];
2021-08-10 12:29:57 +00:00
this.failedFiles = [];
this.metadataMap = new Map<string, ParsedMetaDataJSON>();
this.existingFiles = await getLocalFiles();
this.existingFilesCollectionWise = sortFilesIntoCollections(
2021-08-13 03:38:02 +00:00
this.existingFiles
2021-08-10 12:29:57 +00:00
);
const collections = await getLocalCollections();
if (newCollections) {
collections.push(...newCollections);
}
this.collections = new Map(
2021-08-13 03:38:02 +00:00
collections.map((collection) => [collection.id, collection])
);
2021-08-10 12:29:57 +00:00
}
public async queueFilesForUpload(
fileWithCollectionTobeUploaded: FileWithCollection[],
2021-08-13 03:38:02 +00:00
newCreatedCollections?: Collection[]
2021-08-10 12:29:57 +00:00
) {
try {
2021-08-12 08:25:17 +00:00
await this.init(newCreatedCollections);
2021-08-10 12:29:57 +00:00
const { metadataFiles, mediaFiles } = segregateFiles(
2021-08-13 03:38:02 +00:00
fileWithCollectionTobeUploaded
2021-08-10 12:29:57 +00:00
);
if (metadataFiles.length) {
2021-08-11 05:26:40 +00:00
UIService.setUploadStage(
2021-08-13 03:38:02 +00:00
UPLOAD_STAGES.READING_GOOGLE_METADATA_FILES
2021-08-10 12:29:57 +00:00
);
2021-08-11 04:46:06 +00:00
await this.seedMetadataMap(metadataFiles);
2021-08-10 12:29:57 +00:00
}
if (mediaFiles.length) {
2021-08-11 05:26:40 +00:00
UIService.setUploadStage(UPLOAD_STAGES.START);
2021-08-10 12:29:57 +00:00
await this.uploadMediaFiles(mediaFiles);
}
2021-08-11 05:26:40 +00:00
UIService.setUploadStage(UPLOAD_STAGES.FINISH);
UIService.setPercentComplete(FILE_UPLOAD_COMPLETED);
2021-08-10 12:29:57 +00:00
} catch (e) {
logError(e, 'uploading failed with error');
throw e;
} finally {
for (let i = 0; i < MAX_CONCURRENT_UPLOADS; i++) {
this.cryptoWorkers[i]?.worker.terminate();
}
}
}
private async seedMetadataMap(metadataFiles: FileWithCollection[]) {
2021-08-12 15:36:44 +00:00
try {
UIService.reset(metadataFiles.length);
2021-08-10 12:29:57 +00:00
2021-08-12 15:36:44 +00:00
for (const fileWithCollection of metadataFiles) {
const parsedMetaDataJSONWithTitle = await parseMetadataJSON(
2021-08-13 03:38:02 +00:00
fileWithCollection.file
);
2021-08-12 15:36:44 +00:00
if (parsedMetaDataJSONWithTitle) {
const { title, ...parsedMetaDataJSON } =
parsedMetaDataJSONWithTitle;
this.metadataMap.set(
getMetadataKey(fileWithCollection.collectionID, title),
2021-08-13 03:38:02 +00:00
parsedMetaDataJSON
2021-08-12 15:36:44 +00:00
);
UIService.increaseFileUploaded();
}
}
2021-08-12 15:36:44 +00:00
} catch (e) {
logError(e, 'error seeding Metadatamap');
2021-08-10 12:29:57 +00:00
}
}
private async uploadMediaFiles(mediaFiles: FileWithCollection[]) {
this.filesToBeUploaded.push(...mediaFiles);
2021-08-11 05:26:40 +00:00
UIService.reset(mediaFiles.length);
2021-08-10 12:29:57 +00:00
await UploadService.init(mediaFiles.length, this.metadataMap);
2021-08-10 12:29:57 +00:00
2021-08-11 05:26:40 +00:00
UIService.setUploadStage(UPLOAD_STAGES.UPLOADING);
2021-08-10 12:29:57 +00:00
const uploadProcesses = [];
for (
let i = 0;
2021-08-11 05:24:07 +00:00
i < MAX_CONCURRENT_UPLOADS && this.filesToBeUploaded.length > 0;
2021-08-10 12:29:57 +00:00
i++
) {
this.cryptoWorkers[i] = getDedicatedCryptoWorker();
uploadProcesses.push(
this.uploadNextFileInQueue(
await new this.cryptoWorkers[i].comlink(),
2021-08-13 03:38:02 +00:00
new FileReader()
)
2021-08-10 12:29:57 +00:00
);
}
await Promise.all(uploadProcesses);
}
private async uploadNextFileInQueue(worker: any, fileReader: FileReader) {
2021-08-11 04:46:06 +00:00
while (this.filesToBeUploaded.length > 0) {
2021-08-10 12:29:57 +00:00
const fileWithCollection = this.filesToBeUploaded.pop();
2021-08-12 15:36:44 +00:00
const existingFilesInCollection =
this.existingFilesCollectionWise.get(
2021-08-13 03:38:02 +00:00
fileWithCollection.collectionID
2021-08-13 04:47:55 +00:00
) ?? [];
2021-08-12 15:36:44 +00:00
const collection = this.collections.get(
2021-08-13 03:38:02 +00:00
fileWithCollection.collectionID
2021-08-12 15:36:44 +00:00
);
2021-08-10 12:29:57 +00:00
const { fileUploadResult, file } = await uploader(
worker,
fileReader,
2021-08-12 15:36:44 +00:00
existingFilesInCollection,
fileWithCollection.file,
2021-08-13 03:38:02 +00:00
collection
2021-08-10 12:29:57 +00:00
);
if (fileUploadResult === FileUploadResults.UPLOADED) {
this.existingFiles.push(file);
this.existingFiles = sortFiles(this.existingFiles);
await localForage.setItem(
'files',
2021-08-13 03:38:02 +00:00
removeUnneccessaryFileProps(this.existingFiles)
2021-08-10 12:29:57 +00:00
);
this.setFiles(this.existingFiles);
}
2021-08-11 05:55:18 +00:00
if (
fileUploadResult === FileUploadResults.BLOCKED ||
2021-08-12 08:25:17 +00:00
fileUploadResult === FileUploadResults.FAILED
2021-08-11 05:55:18 +00:00
) {
this.failedFiles.push(fileWithCollection);
}
2021-08-10 12:29:57 +00:00
2021-08-11 05:26:40 +00:00
UIService.moveFileToResultList(fileWithCollection.file.name);
2021-08-10 12:29:57 +00:00
}
}
async retryFailedFiles() {
await this.queueFilesForUpload(this.failedFiles);
}
}
export default new UploadManager();