wipx
This commit is contained in:
parent
61de0c9c9c
commit
2c62f983a8
|
@ -21,12 +21,12 @@ import {
|
||||||
savePublicCollectionUploaderName,
|
savePublicCollectionUploaderName,
|
||||||
} from "services/publicCollectionService";
|
} from "services/publicCollectionService";
|
||||||
import type {
|
import type {
|
||||||
FileWithCollection,
|
|
||||||
InProgressUpload,
|
InProgressUpload,
|
||||||
SegregatedFinishedUploads,
|
SegregatedFinishedUploads,
|
||||||
UploadCounter,
|
UploadCounter,
|
||||||
UploadFileNames,
|
UploadFileNames,
|
||||||
UploadItem,
|
UploadItem,
|
||||||
|
UploadItemWithCollection,
|
||||||
} from "services/upload/uploadManager";
|
} from "services/upload/uploadManager";
|
||||||
import uploadManager from "services/upload/uploadManager";
|
import uploadManager from "services/upload/uploadManager";
|
||||||
import watcher from "services/watch";
|
import watcher from "services/watch";
|
||||||
|
@ -86,6 +86,7 @@ interface Props {
|
||||||
}
|
}
|
||||||
|
|
||||||
export default function Uploader({
|
export default function Uploader({
|
||||||
|
isFirstUpload,
|
||||||
dragAndDropFiles,
|
dragAndDropFiles,
|
||||||
openFileSelector,
|
openFileSelector,
|
||||||
fileSelectorFiles,
|
fileSelectorFiles,
|
||||||
|
@ -162,12 +163,14 @@ export default function Uploader({
|
||||||
* {@link desktopFiles}, {@link desktopFilePaths} and
|
* {@link desktopFiles}, {@link desktopFilePaths} and
|
||||||
* {@link desktopZipEntries}.
|
* {@link desktopZipEntries}.
|
||||||
*
|
*
|
||||||
|
* Augment each {@link UploadItem} with its "path" (relative path or name in
|
||||||
|
* the case of {@link webFiles}, absolute path in the case of
|
||||||
|
* {@link desktopFiles}, {@link desktopFilePaths}, and the path within the
|
||||||
|
* zip file for {@link desktopZipEntries}).
|
||||||
|
*
|
||||||
* See the documentation of {@link UploadItem} for more details.
|
* See the documentation of {@link UploadItem} for more details.
|
||||||
*/
|
*/
|
||||||
const uploadItems = useRef<UploadItem[]>([]);
|
const uploadItemsAndPaths = useRef<[UploadItem, string][]>([]);
|
||||||
|
|
||||||
// TODO(MR): temp, doesn't have zips
|
|
||||||
const fileOrPathsToUpload = useRef<(File | string)[]>([]);
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* If true, then the next upload we'll be processing was initiated by our
|
* If true, then the next upload we'll be processing was initiated by our
|
||||||
|
@ -301,15 +304,15 @@ export default function Uploader({
|
||||||
|
|
||||||
// Trigger an upload when any of the dependencies change.
|
// Trigger an upload when any of the dependencies change.
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const itemAndPaths = [
|
const allItemAndPaths = [
|
||||||
/* TODO(MR): ElectronFile | use webkitRelativePath || name here */
|
/* TODO(MR): ElectronFile | use webkitRelativePath || name here */
|
||||||
webFiles.map((f) => [f, f["path"]]),
|
webFiles.map((f) => [f, f["path"] ?? f.name]),
|
||||||
desktopFiles.map((fp) => [fp, fp.path]),
|
desktopFiles.map((fp) => [fp, fp.path]),
|
||||||
desktopFilePaths.map((p) => [p, p]),
|
desktopFilePaths.map((p) => [p, p]),
|
||||||
desktopZipEntries.map((ze) => [ze, ze[1]]),
|
desktopZipEntries.map((ze) => [ze, ze[1]]),
|
||||||
].flat();
|
].flat() as [UploadItem, string][];
|
||||||
|
|
||||||
if (itemAndPaths.length == 0) return;
|
if (allItemAndPaths.length == 0) return;
|
||||||
|
|
||||||
if (uploadManager.isUploadRunning()) {
|
if (uploadManager.isUploadRunning()) {
|
||||||
if (watcher.isUploadRunning()) {
|
if (watcher.isUploadRunning()) {
|
||||||
|
@ -333,42 +336,93 @@ export default function Uploader({
|
||||||
setDesktopZipEntries([]);
|
setDesktopZipEntries([]);
|
||||||
|
|
||||||
// Remove hidden files (files whose names begins with a ".").
|
// Remove hidden files (files whose names begins with a ".").
|
||||||
const prunedItemAndPaths = itemAndPaths.filter(
|
const prunedItemAndPaths = allItemAndPaths.filter(
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||||
([_, p]) => !basename(p).startsWith("."),
|
([_, p]) => !basename(p).startsWith("."),
|
||||||
);
|
);
|
||||||
|
|
||||||
uploadItems.current = prunedItemAndPaths.map(([i]) => i);
|
uploadItemsAndPaths.current = prunedItemAndPaths;
|
||||||
fileOrPathsToUpload.current = uploadItems.current
|
if (uploadItemsAndPaths.current.length === 0) {
|
||||||
.map((i) => {
|
|
||||||
if (typeof i == "string" || i instanceof File) return i;
|
|
||||||
if (Array.isArray(i)) return undefined;
|
|
||||||
return i.file;
|
|
||||||
})
|
|
||||||
.filter((x) => x);
|
|
||||||
uploadItems.current = [];
|
|
||||||
if (fileOrPathsToUpload.current.length === 0) {
|
|
||||||
props.setLoading(false);
|
props.setLoading(false);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const importSuggestion = getImportSuggestion(
|
const importSuggestion = getImportSuggestion(
|
||||||
pickedUploadType.current,
|
pickedUploadType.current,
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||||
prunedItemAndPaths.map(([_, p]) => p),
|
prunedItemAndPaths.map(([_, p]) => p),
|
||||||
);
|
);
|
||||||
setImportSuggestion(importSuggestion);
|
setImportSuggestion(importSuggestion);
|
||||||
|
|
||||||
log.debug(() => "Uploader invoked:");
|
log.debug(() => "Uploader invoked:");
|
||||||
log.debug(() => fileOrPathsToUpload.current);
|
log.debug(() => uploadItemsAndPaths.current);
|
||||||
log.debug(() => importSuggestion);
|
log.debug(() => importSuggestion);
|
||||||
|
|
||||||
handleCollectionCreationAndUpload(
|
const _pickedUploadType = pickedUploadType.current;
|
||||||
importSuggestion,
|
|
||||||
props.isFirstUpload,
|
|
||||||
pickedUploadType.current,
|
|
||||||
publicCollectionGalleryContext.accessedThroughSharedURL,
|
|
||||||
);
|
|
||||||
pickedUploadType.current = null;
|
pickedUploadType.current = null;
|
||||||
props.setLoading(false);
|
props.setLoading(false);
|
||||||
|
|
||||||
|
(async () => {
|
||||||
|
if (publicCollectionGalleryContext.accessedThroughSharedURL) {
|
||||||
|
const uploaderName = await getPublicCollectionUploaderName(
|
||||||
|
getPublicCollectionUID(
|
||||||
|
publicCollectionGalleryContext.token,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
uploaderNameRef.current = uploaderName;
|
||||||
|
showUserNameInputDialog();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isPendingDesktopUpload.current) {
|
||||||
|
isPendingDesktopUpload.current = false;
|
||||||
|
if (pendingDesktopUploadCollectionName.current) {
|
||||||
|
uploadFilesToNewCollections(
|
||||||
|
"root",
|
||||||
|
pendingDesktopUploadCollectionName.current,
|
||||||
|
);
|
||||||
|
pendingDesktopUploadCollectionName.current = null;
|
||||||
|
} else {
|
||||||
|
uploadFilesToNewCollections("parent");
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (electron && _pickedUploadType === PICKED_UPLOAD_TYPE.ZIPS) {
|
||||||
|
uploadFilesToNewCollections("parent");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isFirstUpload && !importSuggestion.rootFolderName) {
|
||||||
|
importSuggestion.rootFolderName = FIRST_ALBUM_NAME;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isDragAndDrop.current) {
|
||||||
|
isDragAndDrop.current = false;
|
||||||
|
if (
|
||||||
|
props.activeCollection &&
|
||||||
|
props.activeCollection.owner.id === galleryContext.user?.id
|
||||||
|
) {
|
||||||
|
uploadFilesToExistingCollection(props.activeCollection);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let showNextModal = () => {};
|
||||||
|
if (importSuggestion.hasNestedFolders) {
|
||||||
|
showNextModal = () => setChoiceModalView(true);
|
||||||
|
} else {
|
||||||
|
showNextModal = () =>
|
||||||
|
showCollectionCreateModal(importSuggestion.rootFolderName);
|
||||||
|
}
|
||||||
|
|
||||||
|
props.setCollectionSelectorAttributes({
|
||||||
|
callback: uploadFilesToExistingCollection,
|
||||||
|
onCancel: handleCollectionSelectorCancel,
|
||||||
|
showNextModal,
|
||||||
|
intent: CollectionSelectorIntent.upload,
|
||||||
|
});
|
||||||
|
})();
|
||||||
}, [webFiles, desktopFiles, desktopFilePaths, desktopZipEntries]);
|
}, [webFiles, desktopFiles, desktopFilePaths, desktopZipEntries]);
|
||||||
|
|
||||||
const preCollectionCreationAction = async () => {
|
const preCollectionCreationAction = async () => {
|
||||||
|
@ -382,100 +436,78 @@ export default function Uploader({
|
||||||
collection: Collection,
|
collection: Collection,
|
||||||
uploaderName?: string,
|
uploaderName?: string,
|
||||||
) => {
|
) => {
|
||||||
try {
|
await preCollectionCreationAction();
|
||||||
log.info(
|
const uploadItemsWithCollection = uploadItemsAndPaths.current.map(
|
||||||
`Uploading files existing collection id ${collection.id} (${collection.name})`,
|
([uploadItem], index) => ({
|
||||||
);
|
uploadItem,
|
||||||
await preCollectionCreationAction();
|
localID: index,
|
||||||
const filesWithCollectionToUpload = fileOrPathsToUpload.current.map(
|
collectionID: collection.id,
|
||||||
(fileOrPath, index) => ({
|
}),
|
||||||
fileOrPath,
|
);
|
||||||
localID: index,
|
await waitInQueueAndUploadFiles(
|
||||||
collectionID: collection.id,
|
uploadItemsWithCollection,
|
||||||
}),
|
[collection],
|
||||||
);
|
uploaderName,
|
||||||
await waitInQueueAndUploadFiles(
|
);
|
||||||
filesWithCollectionToUpload,
|
uploadItemsAndPaths.current = null;
|
||||||
[collection],
|
|
||||||
uploaderName,
|
|
||||||
);
|
|
||||||
} catch (e) {
|
|
||||||
log.error("Failed to upload files to existing collection", e);
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const uploadFilesToNewCollections = async (
|
const uploadFilesToNewCollections = async (
|
||||||
mapping: CollectionMapping,
|
mapping: CollectionMapping,
|
||||||
collectionName?: string,
|
collectionName?: string,
|
||||||
) => {
|
) => {
|
||||||
try {
|
await preCollectionCreationAction();
|
||||||
log.info(
|
let uploadItemsWithCollection: UploadItemWithCollection[] = [];
|
||||||
`Uploading files to collection using ${mapping} mapping (${collectionName ?? "<NA>"})`,
|
const collections: Collection[] = [];
|
||||||
|
let collectionNameToUploadItems = new Map<string, UploadItem[]>();
|
||||||
|
if (mapping == "root") {
|
||||||
|
collectionNameToUploadItems.set(
|
||||||
|
collectionName,
|
||||||
|
uploadItemsAndPaths.current.map(([i]) => i),
|
||||||
);
|
);
|
||||||
await preCollectionCreationAction();
|
} else {
|
||||||
let filesWithCollectionToUpload: FileWithCollection[] = [];
|
collectionNameToUploadItems = groupFilesBasedOnParentFolder(
|
||||||
const collections: Collection[] = [];
|
uploadItemsAndPaths.current,
|
||||||
let collectionNameToFileOrPaths = new Map<
|
|
||||||
string,
|
|
||||||
(File | string)[]
|
|
||||||
>();
|
|
||||||
if (mapping == "root") {
|
|
||||||
collectionNameToFileOrPaths.set(
|
|
||||||
collectionName,
|
|
||||||
fileOrPathsToUpload.current,
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
collectionNameToFileOrPaths = groupFilesBasedOnParentFolder(
|
|
||||||
fileOrPathsToUpload.current,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
try {
|
|
||||||
const existingCollections = await getLatestCollections();
|
|
||||||
let index = 0;
|
|
||||||
for (const [
|
|
||||||
collectionName,
|
|
||||||
fileOrPaths,
|
|
||||||
] of collectionNameToFileOrPaths) {
|
|
||||||
const collection = await getOrCreateAlbum(
|
|
||||||
collectionName,
|
|
||||||
existingCollections,
|
|
||||||
);
|
|
||||||
collections.push(collection);
|
|
||||||
props.setCollections([
|
|
||||||
...existingCollections,
|
|
||||||
...collections,
|
|
||||||
]);
|
|
||||||
filesWithCollectionToUpload = [
|
|
||||||
...filesWithCollectionToUpload,
|
|
||||||
...fileOrPaths.map((fileOrPath) => ({
|
|
||||||
localID: index++,
|
|
||||||
collectionID: collection.id,
|
|
||||||
fileOrPath,
|
|
||||||
})),
|
|
||||||
];
|
|
||||||
}
|
|
||||||
} catch (e) {
|
|
||||||
closeUploadProgress();
|
|
||||||
log.error("Failed to create album", e);
|
|
||||||
appContext.setDialogMessage({
|
|
||||||
title: t("ERROR"),
|
|
||||||
close: { variant: "critical" },
|
|
||||||
content: t("CREATE_ALBUM_FAILED"),
|
|
||||||
});
|
|
||||||
throw e;
|
|
||||||
}
|
|
||||||
await waitInQueueAndUploadFiles(
|
|
||||||
filesWithCollectionToUpload,
|
|
||||||
collections,
|
|
||||||
);
|
);
|
||||||
fileOrPathsToUpload.current = null;
|
|
||||||
} catch (e) {
|
|
||||||
log.error("Failed to upload files to new collections", e);
|
|
||||||
}
|
}
|
||||||
|
try {
|
||||||
|
const existingCollections = await getLatestCollections();
|
||||||
|
let index = 0;
|
||||||
|
for (const [
|
||||||
|
collectionName,
|
||||||
|
fileOrPaths,
|
||||||
|
] of collectionNameToUploadItems) {
|
||||||
|
const collection = await getOrCreateAlbum(
|
||||||
|
collectionName,
|
||||||
|
existingCollections,
|
||||||
|
);
|
||||||
|
collections.push(collection);
|
||||||
|
props.setCollections([...existingCollections, ...collections]);
|
||||||
|
uploadItemsWithCollection = [
|
||||||
|
...uploadItemsWithCollection,
|
||||||
|
...fileOrPaths.map((fileOrPath) => ({
|
||||||
|
localID: index++,
|
||||||
|
collectionID: collection.id,
|
||||||
|
fileOrPath,
|
||||||
|
})),
|
||||||
|
];
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
closeUploadProgress();
|
||||||
|
log.error("Failed to create album", e);
|
||||||
|
appContext.setDialogMessage({
|
||||||
|
title: t("ERROR"),
|
||||||
|
close: { variant: "critical" },
|
||||||
|
content: t("CREATE_ALBUM_FAILED"),
|
||||||
|
});
|
||||||
|
throw e;
|
||||||
|
}
|
||||||
|
await waitInQueueAndUploadFiles(uploadItemsWithCollection, collections);
|
||||||
|
uploadItemsAndPaths.current = null;
|
||||||
};
|
};
|
||||||
|
|
||||||
const waitInQueueAndUploadFiles = async (
|
const waitInQueueAndUploadFiles = async (
|
||||||
filesWithCollectionToUploadIn: FileWithCollection[],
|
uploadItemsWithCollection: UploadItemWithCollection[],
|
||||||
collections: Collection[],
|
collections: Collection[],
|
||||||
uploaderName?: string,
|
uploaderName?: string,
|
||||||
) => {
|
) => {
|
||||||
|
@ -484,7 +516,7 @@ export default function Uploader({
|
||||||
currentPromise,
|
currentPromise,
|
||||||
async () =>
|
async () =>
|
||||||
await uploadFiles(
|
await uploadFiles(
|
||||||
filesWithCollectionToUploadIn,
|
uploadItemsWithCollection,
|
||||||
collections,
|
collections,
|
||||||
uploaderName,
|
uploaderName,
|
||||||
),
|
),
|
||||||
|
@ -505,7 +537,7 @@ export default function Uploader({
|
||||||
}
|
}
|
||||||
|
|
||||||
const uploadFiles = async (
|
const uploadFiles = async (
|
||||||
filesWithCollectionToUploadIn: FileWithCollection[],
|
uploadItemsWithCollection: UploadItemWithCollection[],
|
||||||
collections: Collection[],
|
collections: Collection[],
|
||||||
uploaderName?: string,
|
uploaderName?: string,
|
||||||
) => {
|
) => {
|
||||||
|
@ -519,11 +551,13 @@ export default function Uploader({
|
||||||
setPendingUploads(
|
setPendingUploads(
|
||||||
electron,
|
electron,
|
||||||
collections,
|
collections,
|
||||||
filesWithCollectionToUploadIn,
|
uploadItemsWithCollection
|
||||||
|
.map(({ uploadItem }) => uploadItem)
|
||||||
|
.filter((x) => x),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
const wereFilesProcessed = await uploadManager.uploadFiles(
|
const wereFilesProcessed = await uploadManager.uploadFiles(
|
||||||
filesWithCollectionToUploadIn,
|
uploadItemsWithCollection,
|
||||||
collections,
|
collections,
|
||||||
uploaderName,
|
uploaderName,
|
||||||
);
|
);
|
||||||
|
@ -531,11 +565,12 @@ export default function Uploader({
|
||||||
if (isElectron()) {
|
if (isElectron()) {
|
||||||
if (watcher.isUploadRunning()) {
|
if (watcher.isUploadRunning()) {
|
||||||
await watcher.allFileUploadsDone(
|
await watcher.allFileUploadsDone(
|
||||||
filesWithCollectionToUploadIn,
|
uploadItemsWithCollection,
|
||||||
collections,
|
collections,
|
||||||
);
|
);
|
||||||
} else if (watcher.isSyncPaused()) {
|
} else if (watcher.isSyncPaused()) {
|
||||||
// resume the service after user upload is done
|
// Resume folder watch after the user upload that
|
||||||
|
// interrupted it is done.
|
||||||
watcher.resumePausedSync();
|
watcher.resumePausedSync();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -610,78 +645,6 @@ export default function Uploader({
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleCollectionCreationAndUpload = async (
|
|
||||||
importSuggestion: ImportSuggestion,
|
|
||||||
isFirstUpload: boolean,
|
|
||||||
pickedUploadType: PICKED_UPLOAD_TYPE,
|
|
||||||
accessedThroughSharedURL?: boolean,
|
|
||||||
) => {
|
|
||||||
try {
|
|
||||||
if (accessedThroughSharedURL) {
|
|
||||||
const uploaderName = await getPublicCollectionUploaderName(
|
|
||||||
getPublicCollectionUID(
|
|
||||||
publicCollectionGalleryContext.token,
|
|
||||||
),
|
|
||||||
);
|
|
||||||
uploaderNameRef.current = uploaderName;
|
|
||||||
showUserNameInputDialog();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (isPendingDesktopUpload.current) {
|
|
||||||
isPendingDesktopUpload.current = false;
|
|
||||||
if (pendingDesktopUploadCollectionName.current) {
|
|
||||||
uploadFilesToNewCollections(
|
|
||||||
"root",
|
|
||||||
pendingDesktopUploadCollectionName.current,
|
|
||||||
);
|
|
||||||
pendingDesktopUploadCollectionName.current = null;
|
|
||||||
} else {
|
|
||||||
uploadFilesToNewCollections("parent");
|
|
||||||
}
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (isElectron() && pickedUploadType === PICKED_UPLOAD_TYPE.ZIPS) {
|
|
||||||
uploadFilesToNewCollections("parent");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (isFirstUpload && !importSuggestion.rootFolderName) {
|
|
||||||
importSuggestion.rootFolderName = FIRST_ALBUM_NAME;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (isDragAndDrop.current) {
|
|
||||||
isDragAndDrop.current = false;
|
|
||||||
if (
|
|
||||||
props.activeCollection &&
|
|
||||||
props.activeCollection.owner.id === galleryContext.user?.id
|
|
||||||
) {
|
|
||||||
uploadFilesToExistingCollection(props.activeCollection);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
let showNextModal = () => {};
|
|
||||||
if (importSuggestion.hasNestedFolders) {
|
|
||||||
showNextModal = () => setChoiceModalView(true);
|
|
||||||
} else {
|
|
||||||
showNextModal = () =>
|
|
||||||
showCollectionCreateModal(importSuggestion.rootFolderName);
|
|
||||||
}
|
|
||||||
|
|
||||||
props.setCollectionSelectorAttributes({
|
|
||||||
callback: uploadFilesToExistingCollection,
|
|
||||||
onCancel: handleCollectionSelectorCancel,
|
|
||||||
showNextModal,
|
|
||||||
intent: CollectionSelectorIntent.upload,
|
|
||||||
});
|
|
||||||
} catch (e) {
|
|
||||||
// TODO(MR): Why?
|
|
||||||
log.warn("Ignoring error in handleCollectionCreationAndUpload", e);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const cancelUploads = () => {
|
const cancelUploads = () => {
|
||||||
uploadManager.cancelRunningUpload();
|
uploadManager.cancelRunningUpload();
|
||||||
};
|
};
|
||||||
|
@ -784,7 +747,7 @@ export default function Uploader({
|
||||||
open={userNameInputDialogView}
|
open={userNameInputDialogView}
|
||||||
onClose={handleUserNameInputDialogClose}
|
onClose={handleUserNameInputDialogClose}
|
||||||
onNameSubmit={handlePublicUpload}
|
onNameSubmit={handlePublicUpload}
|
||||||
toUploadFilesCount={fileOrPathsToUpload.current?.length}
|
toUploadFilesCount={uploadItemsAndPaths.current?.length}
|
||||||
uploaderName={uploaderNameRef.current}
|
uploaderName={uploaderNameRef.current}
|
||||||
/>
|
/>
|
||||||
</>
|
</>
|
||||||
|
@ -884,16 +847,12 @@ function getImportSuggestion(
|
||||||
// [a => [j],
|
// [a => [j],
|
||||||
// b => [e,f,g],
|
// b => [e,f,g],
|
||||||
// c => [h, i]]
|
// c => [h, i]]
|
||||||
const groupFilesBasedOnParentFolder = (fileOrPaths: (File | string)[]) => {
|
const groupFilesBasedOnParentFolder = (
|
||||||
const result = new Map<string, (File | string)[]>();
|
uploadItemsAndPaths: [UploadItem, string][],
|
||||||
for (const fileOrPath of fileOrPaths) {
|
) => {
|
||||||
const filePath =
|
const result = new Map<string, UploadItem[]>();
|
||||||
/* TODO(MR): ElectronFile */
|
for (const [uploadItem, pathOrName] of uploadItemsAndPaths) {
|
||||||
typeof fileOrPath == "string"
|
let folderPath = pathOrName.substring(0, pathOrName.lastIndexOf("/"));
|
||||||
? fileOrPath
|
|
||||||
: (fileOrPath["path"] as string);
|
|
||||||
|
|
||||||
let folderPath = filePath.substring(0, filePath.lastIndexOf("/"));
|
|
||||||
// If the parent folder of a file is "metadata"
|
// If the parent folder of a file is "metadata"
|
||||||
// we consider it to be part of the parent folder
|
// we consider it to be part of the parent folder
|
||||||
// For Eg,For FileList -> [a/x.png, a/metadata/x.png.json]
|
// For Eg,For FileList -> [a/x.png, a/metadata/x.png.json]
|
||||||
|
@ -907,7 +866,7 @@ const groupFilesBasedOnParentFolder = (fileOrPaths: (File | string)[]) => {
|
||||||
);
|
);
|
||||||
if (!folderName) throw Error("Unexpected empty folder name");
|
if (!folderName) throw Error("Unexpected empty folder name");
|
||||||
if (!result.has(folderName)) result.set(folderName, []);
|
if (!result.has(folderName)) result.set(folderName, []);
|
||||||
result.get(folderName).push(fileOrPath);
|
result.get(folderName).push(uploadItem);
|
||||||
}
|
}
|
||||||
return result;
|
return result;
|
||||||
};
|
};
|
||||||
|
|
|
@ -109,17 +109,17 @@ const maxConcurrentUploads = 4;
|
||||||
*/
|
*/
|
||||||
export type UploadItem = File | FileAndPath | string | ZipEntry;
|
export type UploadItem = File | FileAndPath | string | ZipEntry;
|
||||||
|
|
||||||
export interface FileWithCollection {
|
export interface UploadItemWithCollection {
|
||||||
localID: number;
|
localID: number;
|
||||||
collectionID: number;
|
collectionID: number;
|
||||||
isLivePhoto?: boolean;
|
isLivePhoto?: boolean;
|
||||||
fileOrPath?: File | string;
|
uploadItem?: UploadItem;
|
||||||
livePhotoAssets?: LivePhotoAssets;
|
livePhotoAssets?: LivePhotoAssets;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface LivePhotoAssets {
|
export interface LivePhotoAssets {
|
||||||
image: File | string;
|
image: UploadItem;
|
||||||
video: File | string;
|
video: UploadItem;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface PublicUploadProps {
|
export interface PublicUploadProps {
|
||||||
|
@ -419,7 +419,7 @@ class UploadManager {
|
||||||
* @returns `true` if at least one file was processed
|
* @returns `true` if at least one file was processed
|
||||||
*/
|
*/
|
||||||
public async uploadFiles(
|
public async uploadFiles(
|
||||||
filesWithCollectionToUploadIn: FileWithCollection[],
|
filesWithCollectionToUploadIn: UploadItemWithCollection[],
|
||||||
collections: Collection[],
|
collections: Collection[],
|
||||||
uploaderName?: string,
|
uploaderName?: string,
|
||||||
) {
|
) {
|
||||||
|
@ -735,8 +735,8 @@ export default new UploadManager();
|
||||||
* As files progress through stages, they get more and more bits tacked on to
|
* As files progress through stages, they get more and more bits tacked on to
|
||||||
* them. These types document the journey.
|
* them. These types document the journey.
|
||||||
*
|
*
|
||||||
* - The input is {@link FileWithCollection}. This can either be a new
|
* - The input is {@link UploadItemWithCollection}. This can either be a new
|
||||||
* {@link FileWithCollection}, in which case it'll only have a
|
* {@link UploadItemWithCollection}, in which case it'll only have a
|
||||||
* {@link localID}, {@link collectionID} and a {@link fileOrPath}. Or it could
|
* {@link localID}, {@link collectionID} and a {@link fileOrPath}. Or it could
|
||||||
* be a retry, in which case it'll not have a {@link fileOrPath} but instead
|
* be a retry, in which case it'll not have a {@link fileOrPath} but instead
|
||||||
* will have data from a previous stage (concretely, it'll just be a
|
* will have data from a previous stage (concretely, it'll just be a
|
||||||
|
@ -772,9 +772,9 @@ type FileWithCollectionIDAndName = {
|
||||||
};
|
};
|
||||||
|
|
||||||
const makeFileWithCollectionIDAndName = (
|
const makeFileWithCollectionIDAndName = (
|
||||||
f: FileWithCollection,
|
f: UploadItemWithCollection,
|
||||||
): FileWithCollectionIDAndName => {
|
): FileWithCollectionIDAndName => {
|
||||||
const fileOrPath = f.fileOrPath;
|
const fileOrPath = f.uploadItem;
|
||||||
/* TODO(MR): ElectronFile */
|
/* TODO(MR): ElectronFile */
|
||||||
if (!(fileOrPath instanceof File || typeof fileOrPath == "string"))
|
if (!(fileOrPath instanceof File || typeof fileOrPath == "string"))
|
||||||
throw new Error(`Unexpected file ${f}`);
|
throw new Error(`Unexpected file ${f}`);
|
||||||
|
|
|
@ -15,7 +15,7 @@ import { ensureString } from "@/utils/ensure";
|
||||||
import { UPLOAD_RESULT } from "constants/upload";
|
import { UPLOAD_RESULT } from "constants/upload";
|
||||||
import debounce from "debounce";
|
import debounce from "debounce";
|
||||||
import uploadManager, {
|
import uploadManager, {
|
||||||
type FileWithCollection,
|
type UploadItemWithCollection,
|
||||||
} from "services/upload/uploadManager";
|
} from "services/upload/uploadManager";
|
||||||
import { Collection } from "types/collection";
|
import { Collection } from "types/collection";
|
||||||
import { EncryptedEnteFile } from "types/file";
|
import { EncryptedEnteFile } from "types/file";
|
||||||
|
@ -317,16 +317,17 @@ class FolderWatcher {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Callback invoked by the uploader whenever a file we requested to
|
* Callback invoked by the uploader whenever a item we requested to
|
||||||
* {@link upload} gets uploaded.
|
* {@link upload} gets uploaded.
|
||||||
*/
|
*/
|
||||||
async onFileUpload(
|
async onFileUpload(
|
||||||
fileUploadResult: UPLOAD_RESULT,
|
fileUploadResult: UPLOAD_RESULT,
|
||||||
fileWithCollection: FileWithCollection,
|
item: UploadItemWithCollection,
|
||||||
file: EncryptedEnteFile,
|
file: EncryptedEnteFile,
|
||||||
) {
|
) {
|
||||||
// The files we get here will have fileWithCollection.file as a string,
|
// Re the usage of ensureString: For desktop watch, the only possibility
|
||||||
// not as a File or a ElectronFile
|
// for a UploadItem is for it to be a string (the absolute path to a
|
||||||
|
// file on disk).
|
||||||
if (
|
if (
|
||||||
[
|
[
|
||||||
UPLOAD_RESULT.ADDED_SYMLINK,
|
UPLOAD_RESULT.ADDED_SYMLINK,
|
||||||
|
@ -335,18 +336,18 @@ class FolderWatcher {
|
||||||
UPLOAD_RESULT.ALREADY_UPLOADED,
|
UPLOAD_RESULT.ALREADY_UPLOADED,
|
||||||
].includes(fileUploadResult)
|
].includes(fileUploadResult)
|
||||||
) {
|
) {
|
||||||
if (fileWithCollection.isLivePhoto) {
|
if (item.isLivePhoto) {
|
||||||
this.uploadedFileForPath.set(
|
this.uploadedFileForPath.set(
|
||||||
ensureString(fileWithCollection.livePhotoAssets.image),
|
ensureString(item.livePhotoAssets.image),
|
||||||
file,
|
file,
|
||||||
);
|
);
|
||||||
this.uploadedFileForPath.set(
|
this.uploadedFileForPath.set(
|
||||||
ensureString(fileWithCollection.livePhotoAssets.video),
|
ensureString(item.livePhotoAssets.video),
|
||||||
file,
|
file,
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
this.uploadedFileForPath.set(
|
this.uploadedFileForPath.set(
|
||||||
ensureString(fileWithCollection.fileOrPath),
|
ensureString(item.uploadItem),
|
||||||
file,
|
file,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -355,17 +356,15 @@ class FolderWatcher {
|
||||||
fileUploadResult,
|
fileUploadResult,
|
||||||
)
|
)
|
||||||
) {
|
) {
|
||||||
if (fileWithCollection.isLivePhoto) {
|
if (item.isLivePhoto) {
|
||||||
this.unUploadableFilePaths.add(
|
this.unUploadableFilePaths.add(
|
||||||
ensureString(fileWithCollection.livePhotoAssets.image),
|
ensureString(item.livePhotoAssets.image),
|
||||||
);
|
);
|
||||||
this.unUploadableFilePaths.add(
|
this.unUploadableFilePaths.add(
|
||||||
ensureString(fileWithCollection.livePhotoAssets.video),
|
ensureString(item.livePhotoAssets.video),
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
this.unUploadableFilePaths.add(
|
this.unUploadableFilePaths.add(ensureString(item.uploadItem));
|
||||||
ensureString(fileWithCollection.fileOrPath),
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -375,7 +374,7 @@ class FolderWatcher {
|
||||||
* {@link upload} get uploaded.
|
* {@link upload} get uploaded.
|
||||||
*/
|
*/
|
||||||
async allFileUploadsDone(
|
async allFileUploadsDone(
|
||||||
filesWithCollection: FileWithCollection[],
|
uploadItemsWithCollection: UploadItemWithCollection[],
|
||||||
collections: Collection[],
|
collections: Collection[],
|
||||||
) {
|
) {
|
||||||
const electron = ensureElectron();
|
const electron = ensureElectron();
|
||||||
|
@ -384,14 +383,15 @@ class FolderWatcher {
|
||||||
log.debug(() =>
|
log.debug(() =>
|
||||||
JSON.stringify({
|
JSON.stringify({
|
||||||
f: "watch/allFileUploadsDone",
|
f: "watch/allFileUploadsDone",
|
||||||
filesWithCollection,
|
uploadItemsWithCollection,
|
||||||
collections,
|
collections,
|
||||||
watch,
|
watch,
|
||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
|
|
||||||
const { syncedFiles, ignoredFiles } =
|
const { syncedFiles, ignoredFiles } = this.deduceSyncedAndIgnored(
|
||||||
this.deduceSyncedAndIgnored(filesWithCollection);
|
uploadItemsWithCollection,
|
||||||
|
);
|
||||||
|
|
||||||
if (syncedFiles.length > 0)
|
if (syncedFiles.length > 0)
|
||||||
await electron.watch.updateSyncedFiles(
|
await electron.watch.updateSyncedFiles(
|
||||||
|
@ -411,7 +411,9 @@ class FolderWatcher {
|
||||||
this.debouncedRunNextEvent();
|
this.debouncedRunNextEvent();
|
||||||
}
|
}
|
||||||
|
|
||||||
private deduceSyncedAndIgnored(filesWithCollection: FileWithCollection[]) {
|
private deduceSyncedAndIgnored(
|
||||||
|
uploadItemsWithCollection: UploadItemWithCollection[],
|
||||||
|
) {
|
||||||
const syncedFiles: FolderWatch["syncedFiles"] = [];
|
const syncedFiles: FolderWatch["syncedFiles"] = [];
|
||||||
const ignoredFiles: FolderWatch["ignoredFiles"] = [];
|
const ignoredFiles: FolderWatch["ignoredFiles"] = [];
|
||||||
|
|
||||||
|
@ -430,14 +432,13 @@ class FolderWatcher {
|
||||||
this.unUploadableFilePaths.delete(path);
|
this.unUploadableFilePaths.delete(path);
|
||||||
};
|
};
|
||||||
|
|
||||||
for (const fileWithCollection of filesWithCollection) {
|
for (const item of uploadItemsWithCollection) {
|
||||||
if (fileWithCollection.isLivePhoto) {
|
// Re the usage of ensureString: For desktop watch, the only
|
||||||
const imagePath = ensureString(
|
// possibility for a UploadItem is for it to be a string (the
|
||||||
fileWithCollection.livePhotoAssets.image,
|
// absolute path to a file on disk).
|
||||||
);
|
if (item.isLivePhoto) {
|
||||||
const videoPath = ensureString(
|
const imagePath = ensureString(item.livePhotoAssets.image);
|
||||||
fileWithCollection.livePhotoAssets.video,
|
const videoPath = ensureString(item.livePhotoAssets.video);
|
||||||
);
|
|
||||||
|
|
||||||
const imageFile = this.uploadedFileForPath.get(imagePath);
|
const imageFile = this.uploadedFileForPath.get(imagePath);
|
||||||
const videoFile = this.uploadedFileForPath.get(videoPath);
|
const videoFile = this.uploadedFileForPath.get(videoPath);
|
||||||
|
@ -453,7 +454,7 @@ class FolderWatcher {
|
||||||
markIgnored(videoPath);
|
markIgnored(videoPath);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
const path = ensureString(fileWithCollection.fileOrPath);
|
const path = ensureString(item.uploadItem);
|
||||||
const file = this.uploadedFileForPath.get(path);
|
const file = this.uploadedFileForPath.get(path);
|
||||||
if (file) {
|
if (file) {
|
||||||
markSynced(file, path);
|
markSynced(file, path);
|
||||||
|
|
Loading…
Reference in a new issue