Skip to content

Commit

Permalink
Refactor folder upload handling to include renaming and duplication c…
Browse files Browse the repository at this point in the history
…hecks
  • Loading branch information
larryrider committed Feb 10, 2025
1 parent 1d84786 commit df0877b
Show file tree
Hide file tree
Showing 2 changed files with 138 additions and 9 deletions.
48 changes: 39 additions & 9 deletions src/app/network/UploadFolderManager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,10 @@ import { planThunks } from '../store/slices/plan';
import { uploadItemsParallelThunk } from '../store/slices/storage/storage.thunks/uploadItemsThunk';
import { createFolder } from '../store/slices/storage/folderUtils/createFolder';
import { deleteItemsThunk } from '../store/slices/storage/storage.thunks/deleteItemsThunk';
import { checkFolderDuplicated } from '../store/slices/storage/folderUtils/checkFolderDuplicated';
import { getUniqueFolderName } from '../store/slices/storage/folderUtils/getUniqueFolderName';

interface UploadFolderThunkPayload {
interface UploadFolderPayload {
root: IRoot;
currentFolderId: string;
options?: {
Expand Down Expand Up @@ -43,9 +45,13 @@ interface TaskInfo {
};
}

const generateTaskIdForFolders = (foldersPayload: UploadFolderThunkPayload[]): TaskFolder[] => {
return foldersPayload.map(({ root, currentFolderId, options: payloadOptions }) => {
const generateTaskIdForFolders = async (foldersPayload: UploadFolderPayload[]): Promise<TaskFolder[]> => {
const taskFolders: TaskFolder[] = [];

for (const folder of foldersPayload) {
const { root: originalRoot, currentFolderId, options: payloadOptions } = folder;
const options = { withNotification: true, ...payloadOptions };
const root = await handleFoldersRename(originalRoot, currentFolderId);

const uploadFolderAbortController = new AbortController();

Expand All @@ -55,6 +61,7 @@ const generateTaskIdForFolders = (foldersPayload: UploadFolderThunkPayload[]): T
tasksService.updateTask({
taskId,
merge: {
folderName: root.name,
status: TaskStatus.InProcess,
progress: 0,
},
Expand All @@ -70,8 +77,15 @@ const generateTaskIdForFolders = (foldersPayload: UploadFolderThunkPayload[]): T
});
}

return { root, currentFolderId, options: payloadOptions, taskId, abortController: uploadFolderAbortController };
});
taskFolders.push({
root,
currentFolderId,
options: payloadOptions,
taskId,
abortController: uploadFolderAbortController,
});
}
return taskFolders;
};

const countItemsUnderRoot = (root: IRoot): number => {
Expand All @@ -92,6 +106,22 @@ const countItemsUnderRoot = (root: IRoot): number => {
return count;
};

const handleFoldersRename = async (root: IRoot, currentFolderId: string) => {
const { duplicatedFoldersResponse } = await checkFolderDuplicated([root], currentFolderId);

let finalFilename = root.name;
if (duplicatedFoldersResponse.length > 0) {
finalFilename = await getUniqueFolderName(
root.name,
duplicatedFoldersResponse as DriveFolderData[],
currentFolderId,
);
}

const folder: IRoot = { ...root, name: finalFilename };
return folder;
};

const wait = (ms: number): Promise<void> => {
return new Promise((resolve) => setTimeout(resolve, ms));
};
Expand All @@ -101,7 +131,7 @@ export const uploadFoldersWithManager = ({
selectedWorkspace,
dispatch,
}: {
payload: UploadFolderThunkPayload[];
payload: UploadFolderPayload[];
selectedWorkspace: WorkspaceData | null;
dispatch: ThunkDispatch<RootState, unknown, AnyAction>;
}): Promise<void> => {
Expand All @@ -113,15 +143,15 @@ export class UploadFoldersManager {
private static readonly MAX_CONCURRENT_UPLOADS = 6;
private static readonly MAX_UPLOAD_ATTEMPTS = 2;

private readonly payload: UploadFolderThunkPayload[];
private readonly payload: UploadFolderPayload[];
private readonly selectedWorkspace: WorkspaceData | null;
private readonly dispatch: ThunkDispatch<RootState, unknown, AnyAction>;
private readonly abortController?: AbortController;

private tasksInfo: Record<string, TaskInfo> = {};

constructor(
payload: UploadFolderThunkPayload[],
payload: UploadFolderPayload[],
selectedWorkspace: WorkspaceData | null,
dispatch: ThunkDispatch<RootState, unknown, AnyAction>,
) {
Expand Down Expand Up @@ -309,7 +339,7 @@ export class UploadFoldersManager {
};

public readonly run = async (): Promise<void> => {
const payloadWithTaskId = generateTaskIdForFolders(this.payload);
const payloadWithTaskId = await generateTaskIdForFolders(this.payload);

const memberId = this.selectedWorkspace?.workspaceUser?.memberId;

Expand Down
99 changes: 99 additions & 0 deletions src/app/network/UploadFoldersManager.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@ import errorService from 'app/core/services/error.service';
import AppError from 'app/core/types';
import { DriveFolderData } from 'app/drive/types';
import { createFolder } from 'app/store/slices/storage/folderUtils/createFolder';
import { checkFolderDuplicated } from 'app/store/slices/storage/folderUtils/checkFolderDuplicated';
import { getUniqueFolderName } from 'app/store/slices/storage/folderUtils/getUniqueFolderName';
import tasksService from 'app/tasks/services/tasks.service';
import { beforeEach, describe, expect, it, Mock, vi } from 'vitest';
import { TaskFolder, UploadFoldersManager, uploadFoldersWithManager } from './UploadFolderManager';
Expand Down Expand Up @@ -60,6 +62,14 @@ vi.mock('app/store/slices/storage/folderUtils/createFolder', () => ({
createFolder: vi.fn(),
}));

vi.mock('app/store/slices/storage/folderUtils/checkFolderDuplicated', () => ({
checkFolderDuplicated: vi.fn(),
}));

vi.mock('app/store/slices/storage/folderUtils/getUniqueFolderName', () => ({
getUniqueFolderName: vi.fn(),
}));

describe('checkUploadFolders', () => {
const mockDispatch = vi.fn();

Expand Down Expand Up @@ -93,8 +103,78 @@ describe('checkUploadFolders', () => {

const createFolderSpy = (createFolder as Mock).mockResolvedValueOnce(mockFolder);

(checkFolderDuplicated as Mock).mockResolvedValueOnce({
duplicatedFoldersResponse: [] as DriveFolderData[],
foldersWithDuplicates: [] as DriveFolderData[],
foldersWithoutDuplicates: [mockFolder],
});
vi.spyOn(tasksService, 'create').mockReturnValue(taskId);
vi.spyOn(tasksService, 'updateTask').mockReturnValue();
vi.spyOn(tasksService, 'addListener').mockReturnValue();
vi.spyOn(tasksService, 'removeListener').mockReturnValue();
vi.spyOn(errorService, 'castError').mockResolvedValue(new AppError('error'));

await uploadFoldersWithManager({
payload: [
{
currentFolderId: 'currentFolderId',
root: {
folderId: mockFolder.uuid,
childrenFiles: [],
childrenFolders: [],
name: mockFolder.name,
fullPathEdited: 'path1',
},
options: {
taskId,
},
},
],
selectedWorkspace: null,
dispatch: mockDispatch,
});

expect(createFolderSpy).toHaveBeenCalledOnce();
});

it('should rename folder before upload using an async queue', async () => {
const mockFolder: DriveFolderData = {
id: 0,
uuid: 'uuid',
name: 'Folder1',
bucket: 'bucket',
parentId: 0,
parent_id: 0,
parentUuid: 'parentUuid',
userId: 0,
user_id: 0,
icon: null,
iconId: null,
icon_id: null,
isFolder: true,
color: null,
encrypt_version: null,
plain_name: 'Folder1',
deleted: false,
createdAt: new Date().toISOString(),
updatedAt: new Date().toISOString(),
};
const taskId = 'task-id';

const createFolderSpy = (createFolder as Mock).mockResolvedValueOnce(mockFolder);

(checkFolderDuplicated as Mock).mockResolvedValueOnce({
duplicatedFoldersResponse: [mockFolder] as DriveFolderData[],
foldersWithDuplicates: [mockFolder] as DriveFolderData[],
foldersWithoutDuplicates: [],
});

const renameFolderSpy = (getUniqueFolderName as Mock).mockResolvedValueOnce('renamed-folder1');

vi.spyOn(tasksService, 'create').mockReturnValue(taskId);
vi.spyOn(tasksService, 'updateTask').mockReturnValue();
vi.spyOn(tasksService, 'addListener').mockReturnValue();
vi.spyOn(tasksService, 'removeListener').mockReturnValue();
vi.spyOn(errorService, 'castError').mockResolvedValue(new AppError('error'));

await uploadFoldersWithManager({
Expand All @@ -118,6 +198,7 @@ describe('checkUploadFolders', () => {
});

expect(createFolderSpy).toHaveBeenCalledOnce();
expect(renameFolderSpy).toHaveBeenCalledOnce();
});

it('should upload multiple folders using an async queue', async () => {
Expand Down Expand Up @@ -169,6 +250,14 @@ describe('checkUploadFolders', () => {
.mockResolvedValueOnce(mockParentFolder)
.mockResolvedValueOnce(mockChildFolder);

(checkFolderDuplicated as Mock).mockResolvedValueOnce({
duplicatedFoldersResponse: [] as DriveFolderData[],
foldersWithDuplicates: [] as DriveFolderData[],
foldersWithoutDuplicates: [mockParentFolder],
});

const renameFolderSpy = (getUniqueFolderName as Mock).mockResolvedValueOnce('');

vi.spyOn(tasksService, 'create').mockReturnValue(taskId);
vi.spyOn(tasksService, 'updateTask').mockReturnValue();
vi.spyOn(errorService, 'castError').mockResolvedValue(new AppError('error'));
Expand Down Expand Up @@ -202,6 +291,7 @@ describe('checkUploadFolders', () => {
});

expect(createFolderSpy).toHaveBeenCalledTimes(2);
expect(renameFolderSpy).not.toHaveBeenCalled();
});

it('should abort the upload if abortController is called', async () => {
Expand Down Expand Up @@ -279,6 +369,14 @@ describe('checkUploadFolders', () => {
.mockResolvedValueOnce(mockParentFolder)
.mockResolvedValueOnce(mockChildFolder);

(checkFolderDuplicated as Mock).mockResolvedValueOnce({
duplicatedFoldersResponse: [] as DriveFolderData[],
foldersWithDuplicates: [] as DriveFolderData[],
foldersWithoutDuplicates: [mockParentFolder],
});

const renameFolderSpy = (getUniqueFolderName as Mock).mockResolvedValueOnce('');

manager['tasksInfo'][taskId] = {
progress: {
itemsUploaded: 0,
Expand All @@ -294,5 +392,6 @@ describe('checkUploadFolders', () => {
await expect(uploadPromise).resolves.toBeUndefined();
expect(abortController.signal.aborted).toBe(true);
expect(createFolderSpy).not.toHaveBeenCalled();
expect(renameFolderSpy).not.toHaveBeenCalled();
});
});

0 comments on commit df0877b

Please sign in to comment.