2021-09-07 15:26:05 +03:00
|
|
|
import * as FileUtilities from "~/common/file-utilities";
|
|
|
|
import * as Logging from "~/common/logging";
|
|
|
|
import * as Actions from "~/common/actions";
|
|
|
|
|
2021-09-15 21:17:33 +03:00
|
|
|
// NOTE(amine): utilities
|
2021-11-22 20:29:13 +03:00
|
|
|
export const getFileKey = ({ lastModified, name, cid }) => cid || `${lastModified}-${name}`;
|
2021-09-15 21:17:33 +03:00
|
|
|
|
2021-09-22 15:30:47 +03:00
|
|
|
const getLinkSize = (url) => new TextEncoder().encode(url).length;
|
|
|
|
|
2021-09-07 15:26:05 +03:00
|
|
|
let UploadStore = {
|
|
|
|
queue: [],
|
|
|
|
failedFilesCache: {},
|
|
|
|
isUploading: false,
|
2021-09-21 17:53:37 +03:00
|
|
|
uploadedFiles: {},
|
2021-09-07 15:26:05 +03:00
|
|
|
};
|
|
|
|
|
2021-09-15 21:17:33 +03:00
|
|
|
let UploadAbort = {
|
|
|
|
currentUploadingFile: null,
|
|
|
|
abort: null,
|
|
|
|
};
|
2021-09-09 14:21:56 +03:00
|
|
|
|
|
|
|
// NOTE(amine): queue utilities
|
2021-09-15 21:17:33 +03:00
|
|
|
const getUploadQueue = () => UploadStore.queue;
|
2021-09-25 04:20:28 +03:00
|
|
|
const pushToUploadQueue = ({ file, slate }) => UploadStore.queue.push({ file, slate });
|
2021-09-15 21:17:33 +03:00
|
|
|
const resetUploadQueue = () => (UploadStore.queue = []);
|
|
|
|
const removeFromUploadQueue = ({ fileKey }) =>
|
2021-09-09 14:21:56 +03:00
|
|
|
(UploadStore.queue = UploadStore.queue.filter(({ file }) => getFileKey(file) !== fileKey));
|
|
|
|
|
|
|
|
// NOTE(amine): failedFilesCache utilities
|
2021-09-25 04:20:28 +03:00
|
|
|
const storeFileInCache = ({ file, slate }) =>
|
|
|
|
(UploadStore.failedFilesCache[getFileKey(file)] = { file, slate });
|
2021-09-15 21:17:33 +03:00
|
|
|
const removeFileFromCache = ({ fileKey }) => delete UploadStore.failedFilesCache[fileKey];
|
2021-09-09 14:21:56 +03:00
|
|
|
const getFileFromCache = ({ fileKey }) => UploadStore.failedFilesCache[fileKey] || {};
|
2021-10-08 19:37:57 +03:00
|
|
|
const getFailedFilesCache = () => UploadStore.failedFilesCache;
|
2021-09-09 14:21:56 +03:00
|
|
|
|
2021-09-15 21:17:33 +03:00
|
|
|
// NOTE(amine): UploadAbort utilities
|
|
|
|
const registerFileUploading = ({ fileKey }) => (UploadAbort.currentUploadingFile = fileKey);
|
|
|
|
const resetAbortUploadState = () => (UploadAbort = { currentUploadingFile: null, abort: null });
|
|
|
|
const abortCurrentFileUpload = () => UploadAbort.abort();
|
|
|
|
const canCurrentFileBeAborted = () => UploadAbort.currentUploadingFile && UploadAbort.abort;
|
|
|
|
const isFileCurrentlyUploading = ({ fileKey }) =>
|
|
|
|
fileKey === UploadAbort.currentUploadingFile && UploadAbort.abort;
|
|
|
|
|
2021-09-07 15:26:05 +03:00
|
|
|
// NOTE(amine): upload factory function
|
|
|
|
export function createUploadProvider({
|
|
|
|
onStart,
|
|
|
|
onFinish,
|
|
|
|
onAddedToQueue,
|
|
|
|
onProgress,
|
|
|
|
onSuccess,
|
|
|
|
onError,
|
2021-09-09 14:21:56 +03:00
|
|
|
onCancel,
|
2021-09-07 15:26:05 +03:00
|
|
|
onDuplicate,
|
|
|
|
}) {
|
|
|
|
const scheduleQueueUpload = async () => {
|
2021-09-15 21:17:33 +03:00
|
|
|
const uploadQueue = getUploadQueue();
|
|
|
|
if (UploadStore.isUploading || uploadQueue.length === 0) return;
|
2021-09-07 15:26:05 +03:00
|
|
|
|
2021-09-25 04:20:28 +03:00
|
|
|
const { file, slate } = getUploadQueue().shift() || {};
|
2021-09-07 15:26:05 +03:00
|
|
|
|
2021-09-15 21:17:33 +03:00
|
|
|
const fileKey = getFileKey(file);
|
2021-09-07 15:26:05 +03:00
|
|
|
|
2021-09-15 21:17:33 +03:00
|
|
|
UploadStore.isUploading = true;
|
|
|
|
registerFileUploading({ fileKey });
|
2021-09-07 15:26:05 +03:00
|
|
|
|
|
|
|
try {
|
2021-09-22 15:30:47 +03:00
|
|
|
if (file.type === "link") {
|
|
|
|
onProgress({ fileKey, loaded: getLinkSize(file.name) });
|
|
|
|
const response = await FileUtilities.uploadLink({
|
|
|
|
url: file.name,
|
|
|
|
slate,
|
|
|
|
uploadAbort: UploadAbort,
|
|
|
|
});
|
|
|
|
|
|
|
|
if (!response?.aborted) {
|
|
|
|
if (!response || response.error) throw new Error(response);
|
|
|
|
|
|
|
|
const isDuplicate = response.data?.duplicate;
|
|
|
|
const fileCid = response.data?.links[0];
|
|
|
|
|
2021-09-21 17:53:37 +03:00
|
|
|
UploadStore.uploadedFiles[fileKey] = true;
|
2021-09-22 15:30:47 +03:00
|
|
|
if (isDuplicate) {
|
|
|
|
if (onDuplicate) onDuplicate({ fileKey, cid: fileCid });
|
|
|
|
} else {
|
|
|
|
if (onSuccess) onSuccess({ fileKey, cid: fileCid });
|
|
|
|
}
|
|
|
|
}
|
2021-11-22 20:29:13 +03:00
|
|
|
// NOTE(amine): if the file being upload has a cid, use savecopy action.
|
|
|
|
} else if (file.cid) {
|
|
|
|
onProgress({ fileKey, loaded: file.size });
|
|
|
|
const response = await FileUtilities.saveCopy({ file, uploadAbort: UploadAbort });
|
|
|
|
|
|
|
|
if (!response?.aborted) {
|
|
|
|
if (!response || response.error) throw new Error(response);
|
|
|
|
|
|
|
|
if (onSuccess) onSuccess({ fileKey });
|
|
|
|
}
|
2021-09-22 15:30:47 +03:00
|
|
|
} else {
|
|
|
|
const response = await FileUtilities.upload({
|
|
|
|
file,
|
|
|
|
uploadAbort: UploadAbort,
|
|
|
|
onProgress: (e) => onProgress({ fileKey, loaded: e.loaded }),
|
|
|
|
});
|
|
|
|
|
|
|
|
if (!response?.aborted) {
|
|
|
|
if (!response || response.error) throw new Error(response);
|
|
|
|
// TODO(amine): merge createFile and upload endpoints
|
|
|
|
let createResponse = await Actions.createFile({ files: [response], slate });
|
|
|
|
if (!createResponse || createResponse.error) throw new Error(response);
|
|
|
|
|
|
|
|
const isDuplicate = createResponse?.data?.skipped > 0;
|
|
|
|
const fileCid = createResponse.data?.cid;
|
2021-09-21 17:53:37 +03:00
|
|
|
UploadStore.uploadedFiles[fileKey] = true;
|
2021-09-22 15:30:47 +03:00
|
|
|
|
|
|
|
if (isDuplicate) {
|
|
|
|
if (onDuplicate) onDuplicate({ fileKey, cid: fileCid });
|
|
|
|
} else {
|
|
|
|
if (onSuccess) onSuccess({ fileKey, cid: fileCid });
|
|
|
|
}
|
2021-09-15 21:17:33 +03:00
|
|
|
}
|
2021-09-07 15:26:05 +03:00
|
|
|
}
|
|
|
|
} catch (e) {
|
2021-09-25 04:20:28 +03:00
|
|
|
storeFileInCache({ file, slate });
|
2021-09-15 21:17:33 +03:00
|
|
|
|
2021-09-07 15:26:05 +03:00
|
|
|
if (onError) onError({ fileKey });
|
|
|
|
Logging.error(e);
|
|
|
|
}
|
|
|
|
|
2021-09-09 14:21:56 +03:00
|
|
|
UploadStore.isUploading = false;
|
2021-09-15 21:17:33 +03:00
|
|
|
resetAbortUploadState();
|
2021-09-09 14:21:56 +03:00
|
|
|
|
2021-09-15 21:17:33 +03:00
|
|
|
const isQueueEmpty = getUploadQueue().length === 0;
|
|
|
|
if (!isQueueEmpty) {
|
2021-09-07 15:26:05 +03:00
|
|
|
scheduleQueueUpload();
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (onFinish) onFinish();
|
|
|
|
};
|
|
|
|
|
2021-11-22 20:29:13 +03:00
|
|
|
const addBlobToUploadQueue = ({ files, slate }) => {
|
2021-09-09 14:21:56 +03:00
|
|
|
if (!files || !files.length) return;
|
2021-09-07 15:26:05 +03:00
|
|
|
|
|
|
|
for (let i = 0; i < files.length; i++) {
|
2021-12-08 20:29:32 +03:00
|
|
|
files[i].isBlob = true;
|
2021-09-15 21:17:33 +03:00
|
|
|
const fileKey = getFileKey(files[i]);
|
|
|
|
const doesQueueIncludeFile = getUploadQueue().some(
|
2021-11-22 20:29:13 +03:00
|
|
|
({ file }) => fileKey === getFileKey(file)
|
2021-09-15 21:17:33 +03:00
|
|
|
);
|
2021-09-21 17:53:37 +03:00
|
|
|
const isUploaded = fileKey in UploadStore.uploadedFiles;
|
2021-09-27 14:29:30 +03:00
|
|
|
const isUploading = UploadAbort.currentUploadingFile === fileKey;
|
2021-09-21 17:53:37 +03:00
|
|
|
// NOTE(amine): skip the file if already uploaded or is in queue
|
2021-09-27 14:29:30 +03:00
|
|
|
if (doesQueueIncludeFile || isUploaded || isUploading) continue;
|
2021-09-15 21:17:33 +03:00
|
|
|
|
|
|
|
// NOTE(amine): if the added file has failed before, remove it from failedFilesCache
|
|
|
|
if (fileKey in UploadStore.failedFilesCache) removeFileFromCache({ fileKey });
|
|
|
|
|
2021-09-09 14:21:56 +03:00
|
|
|
if (onAddedToQueue) onAddedToQueue(files[i]);
|
2021-09-25 04:20:28 +03:00
|
|
|
pushToUploadQueue({ file: files[i], slate });
|
2021-09-15 21:17:33 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
const isQueueEmpty = getUploadQueue().length === 0;
|
|
|
|
if (!UploadStore.isUploading && !isQueueEmpty && onStart) {
|
|
|
|
onStart();
|
2021-09-07 15:26:05 +03:00
|
|
|
scheduleQueueUpload();
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
2021-09-09 14:21:56 +03:00
|
|
|
const retry = ({ fileKey }) => {
|
2021-09-25 04:20:28 +03:00
|
|
|
const { file, slate } = getFileFromCache({ fileKey });
|
2021-09-22 15:30:47 +03:00
|
|
|
if (file.type === "link") {
|
|
|
|
addLinkToUploadQueue({ url: file.name, slate });
|
|
|
|
return;
|
|
|
|
}
|
2021-11-22 20:29:13 +03:00
|
|
|
if (file.cid) {
|
|
|
|
return addFileToUploadQueue(file);
|
|
|
|
}
|
|
|
|
|
|
|
|
addBlobToUploadQueue({ files: [file], slate });
|
2021-09-09 14:21:56 +03:00
|
|
|
};
|
|
|
|
|
2021-10-08 19:37:57 +03:00
|
|
|
const retryAll = () => {
|
|
|
|
const failedFilesCache = getFailedFilesCache();
|
|
|
|
Object.entries(failedFilesCache).forEach(([key]) => {
|
|
|
|
retry({ fileKey: key });
|
|
|
|
});
|
|
|
|
};
|
|
|
|
|
2021-09-09 14:21:56 +03:00
|
|
|
const cancel = ({ fileKey }) => {
|
|
|
|
if (onCancel) onCancel({ fileKeys: [fileKey] });
|
2021-09-15 21:17:33 +03:00
|
|
|
|
|
|
|
if (isFileCurrentlyUploading({ fileKey })) {
|
|
|
|
abortCurrentFileUpload();
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
removeFromUploadQueue({ fileKey });
|
2021-09-09 14:21:56 +03:00
|
|
|
};
|
|
|
|
|
|
|
|
const cancelAll = () => {
|
2021-09-15 21:17:33 +03:00
|
|
|
const fileKeys = getUploadQueue().map(({ file }) => getFileKey(file));
|
|
|
|
if (onCancel) onCancel({ fileKeys: [UploadAbort.currentUploadingFile, ...fileKeys] });
|
|
|
|
|
|
|
|
if (canCurrentFileBeAborted()) abortCurrentFileUpload();
|
|
|
|
resetUploadQueue();
|
2021-09-09 14:21:56 +03:00
|
|
|
};
|
|
|
|
|
2021-09-22 15:30:47 +03:00
|
|
|
const addLinkToUploadQueue = async ({ url, slate }) => {
|
2021-11-22 20:29:13 +03:00
|
|
|
const linkAsBlob = {
|
2021-09-22 15:30:47 +03:00
|
|
|
name: url,
|
2021-12-06 19:36:00 +03:00
|
|
|
filename: url,
|
2021-09-22 15:30:47 +03:00
|
|
|
type: "link",
|
2021-12-08 20:29:32 +03:00
|
|
|
isBlob: true,
|
|
|
|
isLink: true,
|
2021-09-22 15:30:47 +03:00
|
|
|
size: getLinkSize(url),
|
|
|
|
lastModified: "",
|
|
|
|
};
|
2021-11-22 20:29:13 +03:00
|
|
|
const fileKey = getFileKey(linkAsBlob);
|
|
|
|
|
|
|
|
const doesQueueIncludeFile = getUploadQueue().some(({ file }) => fileKey === getFileKey(file));
|
|
|
|
const isUploaded = fileKey in UploadStore.uploadedFiles;
|
|
|
|
const isUploading = UploadAbort.currentUploadingFile === fileKey;
|
|
|
|
// NOTE(amine): skip the file if already uploaded or is in queue
|
|
|
|
if (doesQueueIncludeFile || isUploaded || isUploading) return;
|
|
|
|
|
|
|
|
// NOTE(amine): if the added file has failed before, remove it from failedFilesCache
|
|
|
|
if (fileKey in UploadStore.failedFilesCache) removeFileFromCache({ fileKey });
|
|
|
|
|
|
|
|
if (onAddedToQueue) onAddedToQueue(linkAsBlob);
|
|
|
|
pushToUploadQueue({ file: linkAsBlob, slate });
|
|
|
|
|
|
|
|
const isQueueEmpty = getUploadQueue().length === 0;
|
|
|
|
if (!UploadStore.isUploading && !isQueueEmpty && onStart) {
|
|
|
|
onStart();
|
|
|
|
scheduleQueueUpload();
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
const addFileToUploadQueue = async (file) => {
|
|
|
|
const fileAsBlob = {
|
|
|
|
...file,
|
|
|
|
};
|
|
|
|
|
|
|
|
const fileKey = getFileKey(fileAsBlob);
|
2021-09-22 15:30:47 +03:00
|
|
|
|
2021-11-22 20:29:13 +03:00
|
|
|
const doesQueueIncludeFile = getUploadQueue().some(({ file }) => fileKey === getFileKey(file));
|
2021-09-22 15:30:47 +03:00
|
|
|
const isUploaded = fileKey in UploadStore.uploadedFiles;
|
2021-09-27 14:29:30 +03:00
|
|
|
const isUploading = UploadAbort.currentUploadingFile === fileKey;
|
2021-09-22 15:30:47 +03:00
|
|
|
// NOTE(amine): skip the file if already uploaded or is in queue
|
2021-09-27 14:29:30 +03:00
|
|
|
if (doesQueueIncludeFile || isUploaded || isUploading) return;
|
2021-09-22 15:30:47 +03:00
|
|
|
|
|
|
|
// NOTE(amine): if the added file has failed before, remove it from failedFilesCache
|
|
|
|
if (fileKey in UploadStore.failedFilesCache) removeFileFromCache({ fileKey });
|
|
|
|
|
2021-11-22 20:29:13 +03:00
|
|
|
if (onAddedToQueue) onAddedToQueue(fileAsBlob);
|
|
|
|
pushToUploadQueue({ file: fileAsBlob });
|
2021-09-22 15:30:47 +03:00
|
|
|
|
|
|
|
const isQueueEmpty = getUploadQueue().length === 0;
|
|
|
|
if (!UploadStore.isUploading && !isQueueEmpty && onStart) {
|
|
|
|
onStart();
|
|
|
|
scheduleQueueUpload();
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
2021-10-08 19:37:57 +03:00
|
|
|
const clearUploadCache = () => {
|
|
|
|
UploadStore.failedFilesCache = {};
|
|
|
|
UploadStore.uploadedFiles = {};
|
|
|
|
};
|
|
|
|
|
2021-09-07 15:26:05 +03:00
|
|
|
return {
|
2021-11-22 20:29:13 +03:00
|
|
|
upload: addBlobToUploadQueue,
|
|
|
|
saveCopy: addFileToUploadQueue,
|
2021-09-22 15:30:47 +03:00
|
|
|
uploadLink: addLinkToUploadQueue,
|
2021-09-09 14:21:56 +03:00
|
|
|
retry,
|
2021-10-08 19:37:57 +03:00
|
|
|
retryAll,
|
2021-09-09 14:21:56 +03:00
|
|
|
cancel,
|
|
|
|
cancelAll,
|
2021-10-08 19:37:57 +03:00
|
|
|
clearUploadCache,
|
2021-09-07 15:26:05 +03:00
|
|
|
};
|
|
|
|
}
|