mirror of
https://github.com/immich-app/immich.git
synced 2024-12-28 14:41:59 +00:00
fix(web): prevent duplicate time bucket loads (#8091)
This commit is contained in:
parent
ec9a6bca14
commit
048d437b0b
3 changed files with 53 additions and 20 deletions
|
@ -1,4 +1,5 @@
|
|||
import { sdkMock } from '$lib/__mocks__/sdk.mock';
|
||||
import { AbortError } from '$lib/utils';
|
||||
import { TimeBucketSize, type AssetResponseDto } from '@immich/sdk';
|
||||
import { assetFactory } from '@test-data/factories/asset-factory';
|
||||
import { AssetStore, BucketPosition } from './assets.store';
|
||||
|
@ -62,7 +63,15 @@ describe('AssetStore', () => {
|
|||
{ count: 1, timeBucket: '2024-01-03T00:00:00.000Z' },
|
||||
{ count: 3, timeBucket: '2024-01-01T00:00:00.000Z' },
|
||||
]);
|
||||
sdkMock.getTimeBucket.mockImplementation(({ timeBucket }) => Promise.resolve(bucketAssets[timeBucket]));
|
||||
sdkMock.getTimeBucket.mockImplementation(async ({ timeBucket }, { signal } = {}) => {
|
||||
// Allow request to be aborted
|
||||
await new Promise((resolve) => setTimeout(resolve, 0));
|
||||
if (signal?.aborted) {
|
||||
throw new AbortError();
|
||||
}
|
||||
|
||||
return bucketAssets[timeBucket];
|
||||
});
|
||||
await assetStore.init({ width: 0, height: 0 });
|
||||
});
|
||||
|
||||
|
@ -87,17 +96,39 @@ describe('AssetStore', () => {
|
|||
});
|
||||
|
||||
it('cancels bucket loading', async () => {
|
||||
const abortSpy = vi.spyOn(AbortController.prototype, 'abort');
|
||||
const loadPromise = assetStore.loadBucket('2024-01-01T00:00:00.000Z', BucketPosition.Unknown);
|
||||
|
||||
const bucket = assetStore.getBucketByDate('2024-01-01T00:00:00.000Z');
|
||||
expect(bucket).not.toBeNull();
|
||||
const loadPromise = assetStore.loadBucket(bucket!.bucketDate, BucketPosition.Unknown);
|
||||
|
||||
const abortSpy = vi.spyOn(bucket!.cancelToken!, 'abort');
|
||||
assetStore.cancelBucket(bucket!);
|
||||
expect(abortSpy).toBeCalledTimes(1);
|
||||
|
||||
await loadPromise;
|
||||
expect(assetStore.getBucketByDate('2024-01-01T00:00:00.000Z')?.assets.length).toEqual(0);
|
||||
});
|
||||
|
||||
it('prevents loading buckets multiple times', async () => {
|
||||
await Promise.all([
|
||||
assetStore.loadBucket('2024-01-01T00:00:00.000Z', BucketPosition.Unknown),
|
||||
assetStore.loadBucket('2024-01-01T00:00:00.000Z', BucketPosition.Unknown),
|
||||
]);
|
||||
expect(sdkMock.getTimeBucket).toBeCalledTimes(1);
|
||||
|
||||
await assetStore.loadBucket('2024-01-01T00:00:00.000Z', BucketPosition.Unknown);
|
||||
expect(sdkMock.getTimeBucket).toBeCalledTimes(1);
|
||||
});
|
||||
|
||||
it('allows loading a canceled bucket', async () => {
|
||||
const bucket = assetStore.getBucketByDate('2024-01-01T00:00:00.000Z');
|
||||
const loadPromise = assetStore.loadBucket(bucket!.bucketDate, BucketPosition.Unknown);
|
||||
|
||||
assetStore.cancelBucket(bucket!);
|
||||
await loadPromise;
|
||||
expect(bucket?.assets.length).toEqual(0);
|
||||
|
||||
await assetStore.loadBucket(bucket!.bucketDate, BucketPosition.Unknown);
|
||||
expect(bucket!.assets.length).toEqual(3);
|
||||
});
|
||||
});
|
||||
|
||||
describe('addAssets', () => {
|
||||
|
|
|
@ -229,21 +229,21 @@ export class AssetStore {
|
|||
}
|
||||
|
||||
async loadBucket(bucketDate: string, position: BucketPosition): Promise<void> {
|
||||
const bucket = this.getBucketByDate(bucketDate);
|
||||
if (!bucket) {
|
||||
return;
|
||||
}
|
||||
|
||||
bucket.position = position;
|
||||
|
||||
if (bucket.cancelToken || bucket.assets.length > 0) {
|
||||
this.emit(false);
|
||||
return;
|
||||
}
|
||||
|
||||
bucket.cancelToken = new AbortController();
|
||||
|
||||
try {
|
||||
const bucket = this.getBucketByDate(bucketDate);
|
||||
if (!bucket) {
|
||||
return;
|
||||
}
|
||||
|
||||
bucket.position = position;
|
||||
|
||||
if (bucket.assets.length > 0) {
|
||||
this.emit(false);
|
||||
return;
|
||||
}
|
||||
|
||||
bucket.cancelToken = new AbortController();
|
||||
|
||||
const assets = await getTimeBucket(
|
||||
{
|
||||
...this.options,
|
||||
|
@ -278,6 +278,8 @@ export class AssetStore {
|
|||
this.emit(true);
|
||||
} catch (error) {
|
||||
handleError(error, 'Failed to load assets');
|
||||
} finally {
|
||||
bucket.cancelToken = null;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -27,7 +27,7 @@ interface UploadRequestOptions {
|
|||
onUploadProgress?: (event: ProgressEvent<XMLHttpRequestEventTarget>) => void;
|
||||
}
|
||||
|
||||
class AbortError extends Error {
|
||||
export class AbortError extends Error {
|
||||
name = 'AbortError';
|
||||
}
|
||||
|
||||
|
|
Loading…
Reference in a new issue