1 import { c } from 'ttag';
3 import { TransferCancel } from '../../../components/TransferManager/transfer';
4 import useQueuedFunction from '../../../hooks/util/useQueuedFunction';
5 import { isErrorDueToNameConflict } from '../../../utils/isErrorDueToNameConflict';
6 import { useLinkActions, useLinksActions } from '../../_links';
7 import type { UploadFolderControls } from '../interface';
8 import { TransferConflictStrategy } from '../interface';
9 import type { ConflictStrategyHandler } from './interface';
10 import useUploadHelper from './useUploadHelper';
12 type LogCallback = (message: string) => void;
20 export default function useUploadFolder() {
21 const queuedFunction = useQueuedFunction();
22 const { createFolder } = useLinkActions();
23 const { deleteChildrenLinks } = useLinksActions();
24 const { findAvailableName, getLinkByName } = useUploadHelper();
26 const createEmptyFolder = async (
27 abortSignal: AbortSignal,
31 modificationTime?: Date
32 ): Promise<Folder> => {
33 const folderId = await createFolder(abortSignal, shareId, parentId, folderName, modificationTime);
41 const getFolder = async (
42 abortSignal: AbortSignal,
46 ): Promise<Folder> => {
47 const link = await getLinkByName(abortSignal, shareId, parentId, folderName);
49 throw Error(c('Error').t`The original folder not found`);
52 throw Error(c('Error').t`File cannot be merged with folder`);
54 checkSignal(abortSignal, folderName);
56 folderId: link.linkId,
62 const replaceDraft = async (
63 abortSignal: AbortSignal,
68 modificationTime?: Date
70 await deleteChildrenLinks(abortSignal, shareId, parentId, [linkId]);
71 return createEmptyFolder(abortSignal, shareId, parentId, folderName, modificationTime);
74 const handleNameConflict = async (
75 abortSignal: AbortSignal,
81 getFolderConflictStrategy,
87 modificationTime: Date | undefined;
88 getFolderConflictStrategy: ConflictStrategyHandler;
91 { filename, draftLinkId }: { filename: string; draftLinkId?: string }
94 const link = await getLinkByName(abortSignal, shareId, parentId, folderName);
95 const originalIsFolder = link ? !link.isFile : false;
97 checkSignal(abortSignal, folderName);
98 const conflictStrategy = await getFolderConflictStrategy(abortSignal, !!draftLinkId, originalIsFolder);
99 log(`Conflict resolved with: ${conflictStrategy}`);
100 if (conflictStrategy === TransferConflictStrategy.Rename) {
101 log(`Creating new folder`);
102 return createEmptyFolder(abortSignal, shareId, parentId, filename, modificationTime);
104 if (conflictStrategy === TransferConflictStrategy.Replace) {
106 log(`Replacing draft`);
107 return replaceDraft(abortSignal, shareId, parentId, draftLinkId, folderName, modificationTime);
109 log(`Merging folders`);
110 return getFolder(abortSignal, shareId, parentId, folderName);
112 if (conflictStrategy === TransferConflictStrategy.Skip) {
113 throw new TransferCancel({ message: c('Info').t`Transfer skipped for folder "${folderName}"` });
115 throw new Error(`Unknown conflict strategy: ${conflictStrategy}`);
118 const prepareFolderOptimistically = (
119 abortSignal: AbortSignal,
123 modificationTime: Date | undefined,
124 getFolderConflictStrategy: ConflictStrategyHandler,
126 ): Promise<Folder> => {
127 const lowercaseName = folderName.toLowerCase();
129 return queuedFunction(`upload_empty_folder:${lowercaseName}`, async () => {
130 log(`Creating new folder`);
131 return createEmptyFolder(abortSignal, shareId, parentId, folderName, modificationTime).catch(
133 if (isErrorDueToNameConflict(err)) {
138 } = await findAvailableName(abortSignal, {
140 parentLinkId: parentId,
141 filename: folderName,
143 checkSignal(abortSignal, folderName);
144 // Automatically replace file - previous draft was uploaded
145 // by the same client.
146 if (draftLinkId && clientUid) {
147 log(`Automatically replacing draft`);
148 return replaceDraft(abortSignal, shareId, parentId, draftLinkId, newName, modificationTime);
151 return handleNameConflict(
158 getFolderConflictStrategy,
173 const initFolderUpload = (
177 modificationTime: Date | undefined,
178 getFolderConflictStrategy: ConflictStrategyHandler,
180 ): UploadFolderControls => {
181 const abortController = new AbortController();
184 return prepareFolderOptimistically(
185 abortController.signal,
190 getFolderConflictStrategy,
195 abortController.abort();
205 function checkSignal(abortSignal: AbortSignal, name: string) {
206 if (abortSignal.aborted) {
207 throw new TransferCancel({ message: c('Info').t`Transfer canceled for folder "${name}"` });