From f28955800a934071012cf9e1735c29feb483e7f6 Mon Sep 17 00:00:00 2001 From: Aviv <51673860+aviv926@users.noreply.github.com> Date: Thu, 9 Apr 2026 03:35:30 +0300 Subject: [PATCH 1/2] fix(asset): improve file deletion handling in asset deletion process --- server/src/services/asset.service.ts | 25 ++++++++++++++++++++++++- 1 file changed, 24 insertions(+), 1 deletion(-) diff --git a/server/src/services/asset.service.ts b/server/src/services/asset.service.ts index 1e5d23a98d8f6..1af8d4080656e 100644 --- a/server/src/services/asset.service.ts +++ b/server/src/services/asset.service.ts @@ -329,6 +329,11 @@ export class AssetService extends BaseService { return JobStatus.Failed; } + const fileDeletionStatus = await this.deleteAssetFiles(id, this.getFilesForDeletion(asset, deleteOnDisk)); + if (fileDeletionStatus === JobStatus.Failed) { + return JobStatus.Failed; + } + // replace the parent of the stack children with a new asset if (asset.stack?.primaryAssetId === id) { // this only includes timeline visible assets and excludes the primary asset @@ -362,6 +367,13 @@ export class AssetService extends BaseService { } } + return JobStatus.Success; + } + + private getFilesForDeletion( + asset: { files: AssetFile[]; originalPath: string; isOffline: boolean }, + deleteOnDisk: boolean, + ): string[] { const assetFiles = getAssetFiles(asset.files ?? []); const files = [ assetFiles.thumbnailFile?.path, @@ -377,7 +389,18 @@ export class AssetService extends BaseService { files.push(assetFiles.sidecarFile?.path, asset.originalPath); } - await this.jobRepository.queue({ name: JobName.FileDelete, data: { files: files.filter(Boolean) } }); + return files.filter((file): file is string => file !== undefined); + } + + private async deleteAssetFiles(assetId: string, files: string[]): Promise { + for (const file of files) { + try { + await this.storageRepository.unlink(file); + } catch (error) { + this.logger.warn(`Unable to remove asset file for asset ${assetId}: ${file}`, error); + return JobStatus.Failed; + } + } return JobStatus.Success; } From 3b96de09c99d007f78af3d79e3f7395f47f35b63 Mon Sep 17 00:00:00 2001 From: Aviv <51673860+aviv926@users.noreply.github.com> Date: Thu, 9 Apr 2026 04:14:32 +0300 Subject: [PATCH 2/2] update tests --- server/src/services/asset.service.spec.ts | 20 ++++++-------------- 1 file changed, 6 insertions(+), 14 deletions(-) diff --git a/server/src/services/asset.service.spec.ts b/server/src/services/asset.service.spec.ts index 718ec00f1d11a..9795f1fa97bb6 100755 --- a/server/src/services/asset.service.spec.ts +++ b/server/src/services/asset.service.spec.ts @@ -575,16 +575,9 @@ describe(AssetService.name, () => { await sut.handleAssetDeletion({ id: asset.id, deleteOnDisk: true }); - expect(mocks.job.queue.mock.calls).toEqual([ - [ - { - name: JobName.FileDelete, - data: { - files: [...asset.files.map(({ path }) => path), asset.originalPath], - }, - }, - ], - ]); + expect(mocks.storage.unlink.mock.calls).toEqual( + [...asset.files.map(({ path }) => path), asset.originalPath].map((file) => [file]), + ); expect(mocks.asset.remove).toHaveBeenCalledWith(getForAssetDeletion(asset)); }); @@ -613,8 +606,8 @@ describe(AssetService.name, () => { expect(mocks.job.queue.mock.calls).toEqual([ [{ name: JobName.AssetDelete, data: { id: motionAsset.id, deleteOnDisk: true } }], - [{ name: JobName.FileDelete, data: { files: [asset.originalPath] } }], ]); + expect(mocks.storage.unlink).toHaveBeenCalledWith(asset.originalPath); }); it('should not delete a live motion part if it is being used by another asset', async () => { @@ -624,9 +617,8 @@ describe(AssetService.name, () => { await sut.handleAssetDeletion({ id: asset.id, deleteOnDisk: true }); - expect(mocks.job.queue.mock.calls).toEqual([ - [{ name: JobName.FileDelete, data: { files: [`/data/library/IMG_${asset.id}.jpg`] } }], - ]); + expect(mocks.job.queue.mock.calls).toEqual([]); + expect(mocks.storage.unlink).toHaveBeenCalledWith(`/data/library/IMG_${asset.id}.jpg`); }); it('should update usage', async () => {