Skip to content

Commit 0712dc0

Browse files
authored
Merge pull request #647 from taglia/pr/webdav-replace-writes
Optimize WebDAV replacement writes
2 parents d810367 + 943b849 commit 0712dc0

3 files changed

Lines changed: 62 additions & 10 deletions

File tree

src/services/drive/drive-file.service.ts

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,32 @@ export class DriveFileService {
3131
return driveFileItem;
3232
};
3333

34+
public replaceFile = async (uuid: string, payload: StorageTypes.FileEntryByUuid): Promise<DriveFileItem> => {
35+
const storageClient = SdkManager.instance.getStorage();
36+
const driveFile = await storageClient.replaceFile(uuid, {
37+
fileId: payload.fileId ?? '',
38+
size: payload.size,
39+
});
40+
41+
const driveFileItem: DriveFileItem = {
42+
itemType: 'file',
43+
name: driveFile.plainName ?? driveFile.name,
44+
uuid: driveFile.uuid,
45+
size: driveFile.size,
46+
bucket: driveFile.bucket,
47+
createdAt: new Date(driveFile.createdAt),
48+
updatedAt: new Date(driveFile.updatedAt),
49+
fileId: driveFile.fileId ?? null,
50+
type: driveFile.type ?? null,
51+
status: driveFile.status as DriveFileItem['status'],
52+
folderUuid: driveFile.folderUuid,
53+
creationTime: new Date(driveFile.creationTime ?? driveFile.createdAt),
54+
modificationTime: new Date(driveFile.modificationTime ?? driveFile.updatedAt),
55+
};
56+
57+
return driveFileItem;
58+
};
59+
3460
private readonly createDriveFileEntry = async (
3561
payload: StorageTypes.FileEntryByUuid,
3662
): Promise<StorageTypes.DriveFileData> => {

src/webdav/handlers/PUT.handler.ts

Lines changed: 24 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -52,12 +52,6 @@ export class PUTRequestHandler implements WebDavMethodHandler {
5252
webdavLogger.info(
5353
`[PUT] File '${resource.name}' already exists in '${resource.path.dir}', it will be replaced...`,
5454
);
55-
try {
56-
await WebDavUtils.deleteOrTrashItem(driveFileItem);
57-
await DriveItemRepository.instance.delete([driveFileItem.uuid]);
58-
} catch {
59-
//noop
60-
}
6155
}
6256

6357
const { user } = await AuthService.instance.getAuthDetails();
@@ -104,15 +98,37 @@ export class PUTRequestHandler implements WebDavMethodHandler {
10498
}
10599

106100
const driveTimer = CLIUtils.timer();
107-
const file = await DriveFileService.instance.createFile({
101+
const filePayload = {
108102
plainName: resource.path.name,
109103
type: fileType,
110104
size: contentLength,
111105
folderUuid: parentDriveFolderItem.uuid,
112106
fileId,
113107
bucket,
114108
encryptVersion: EncryptionVersion.Aes03,
115-
});
109+
};
110+
111+
let file;
112+
if (driveFileItem?.itemType === 'file' && contentLength > 0) {
113+
try {
114+
file = await DriveFileService.instance.replaceFile(driveFileItem.uuid, filePayload);
115+
} catch (error) {
116+
webdavLogger.warn(
117+
`[PUT] File replace failed for '${resource.url}', falling back to delete and create: ${
118+
error instanceof Error ? error.message : String(error)
119+
}`,
120+
);
121+
await WebDavUtils.deleteOrTrashItem(driveFileItem);
122+
await DriveItemRepository.instance.delete([driveFileItem.uuid]);
123+
file = await DriveFileService.instance.createFile(filePayload);
124+
}
125+
} else {
126+
if (driveFileItem?.itemType === 'file') {
127+
await WebDavUtils.deleteOrTrashItem(driveFileItem);
128+
await DriveItemRepository.instance.delete([driveFileItem.uuid]);
129+
}
130+
file = await DriveFileService.instance.createFile(filePayload);
131+
}
116132
timings.driveUpload = driveTimer.stop();
117133

118134
await DriveItemRepository.instance.createOrUpdate([

test/webdav/handlers/PUT.handler.test.ts

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -168,6 +168,7 @@ describe('PUT request handler', () => {
168168
.mockResolvedValue(UserCredentialsFixture);
169169
const uploadStub = vi.spyOn(networkFacade, 'uploadFile').mockResolvedValue('uploaded-file-id');
170170
const createDriveFileStub = vi.spyOn(DriveFileService.instance, 'createFile').mockResolvedValue(fileFixture);
171+
const replaceDriveFileStub = vi.spyOn(DriveFileService.instance, 'replaceFile').mockResolvedValue(fileFixture);
171172

172173
await sut.handle(request, response);
173174
expect(response.status).toHaveBeenCalledWith(204);
@@ -176,7 +177,16 @@ describe('PUT request handler', () => {
176177
expect(getDriveFolderFromResourceStub).toHaveBeenCalledOnce();
177178
expect(getAuthDetailsStub).toHaveBeenCalledOnce();
178179
expect(uploadStub).toHaveBeenCalledOnce();
179-
expect(createDriveFileStub).toHaveBeenCalledOnce();
180-
expect(deleteDriveFileStub).toHaveBeenCalledOnce();
180+
expect(replaceDriveFileStub).toHaveBeenCalledWith(
181+
fileFixture.uuid,
182+
expect.objectContaining({
183+
fileId: 'uploaded-file-id',
184+
folderUuid: folderFixture.uuid,
185+
plainName: requestedFileResource.path.name,
186+
size: 100,
187+
}),
188+
);
189+
expect(createDriveFileStub).not.toHaveBeenCalled();
190+
expect(deleteDriveFileStub).not.toHaveBeenCalled();
181191
});
182192
});

0 commit comments

Comments
 (0)