fix: don't delete offline library files from disk

This commit is contained in:
Jonathan Jogenfors
2026-03-24 23:31:16 +01:00
parent 4af9edc20b
commit d21c89c0ed
4 changed files with 85 additions and 12 deletions
+2 -2
View File
@@ -7,8 +7,8 @@ import { DB } from 'src/schema';
export class TrashRepository {
constructor(@InjectKysely() private db: Kysely<DB>) {}
getDeletedIds(): AsyncIterableIterator<{ id: string }> {
return this.db.selectFrom('asset').select(['id']).where('status', '=', AssetStatus.Deleted).stream();
getDeletedIds(): AsyncIterableIterator<{ id: string; isOffline: boolean }> {
return this.db.selectFrom('asset').select(['id', 'isOffline']).where('status', '=', AssetStatus.Deleted).stream();
}
@GenerateSql({ params: [DummyValue.UUID] })
+33 -2
View File
@@ -4,10 +4,19 @@ import { TrashService } from 'src/services/trash.service';
import { authStub } from 'test/fixtures/auth.stub';
import { newTestService, ServiceMocks } from 'test/utils';
async function* makeAssetIdStream(count: number): AsyncIterableIterator<{ id: string }> {
async function* makeAssetIdStream(count: number): AsyncIterableIterator<{ id: string; isOffline: boolean }> {
for (let i = 0; i < count; i++) {
await Promise.resolve();
yield { id: `asset-${i + 1}` };
yield { id: `asset-${i + 1}`, isOffline: false };
}
}
async function* makeDeletedAssetStream(
assets: Array<{ id: string; isOffline: boolean }>,
): AsyncIterableIterator<{ id: string; isOffline: boolean }> {
for (const asset of assets) {
await Promise.resolve();
yield asset;
}
}
@@ -99,5 +108,27 @@ describe(TrashService.name, () => {
},
]);
});
it('should not delete offline assets on disk', async () => {
mocks.trash.getDeletedIds.mockReturnValue(
makeDeletedAssetStream([
{ id: 'asset-1', isOffline: false },
{ id: 'asset-2', isOffline: true },
]),
);
await expect(sut.handleEmptyTrash()).resolves.toEqual(JobStatus.Success);
expect(mocks.job.queueAll).toHaveBeenCalledWith([
{
name: JobName.AssetDelete,
data: { id: 'asset-1', deleteOnDisk: true },
},
{
name: JobName.AssetDelete,
data: { id: 'asset-2', deleteOnDisk: false },
},
]);
});
});
});
+8 -8
View File
@@ -50,9 +50,9 @@ export class TrashService extends BaseService {
const assets = this.trashRepository.getDeletedIds();
let count = 0;
const batch: string[] = [];
for await (const { id } of assets) {
batch.push(id);
const batch: Array<{ id: string; isOffline: boolean }> = [];
for await (const asset of assets) {
batch.push(asset);
if (batch.length === JOBS_ASSET_PAGINATION_SIZE) {
await this.handleBatch(batch);
@@ -70,14 +70,14 @@ export class TrashService extends BaseService {
return JobStatus.Success;
}
private async handleBatch(ids: string[]) {
this.logger.debug(`Queueing ${ids.length} asset(s) for deletion from the trash`);
private async handleBatch(assets: Array<{ id: string; isOffline: boolean }>) {
this.logger.debug(`Queueing ${assets.length} asset(s) for deletion from the trash`);
await this.jobRepository.queueAll(
ids.map((assetId) => ({
assets.map(({ id, isOffline }) => ({
name: JobName.AssetDelete,
data: {
id: assetId,
deleteOnDisk: true,
id,
deleteOnDisk: !isOffline,
},
})),
);