mirror of
https://github.com/4gray/iptvnator.git
synced 2026-10-15 21:56:18 -08:00
fix(downloads): await authoritative list refresh
This commit is contained in:
1 parent
0ef3006b61
commit
ff9a422b82
5 files changed
+78
-12
No files matched your search
@@ -2,6 +2,8 @@ import { signal } from '@angular/core';
|
||||
|
||||
export class DownloadListLoadState {
|
||||
private requestId = 0;
|
||||
private settledRequestId = 0;
|
||||
private readonly settlementWaiters = new Map<number, () => void>();
|
||||
private readonly loading = signal(false);
|
||||
private readonly loaded = signal(false);
|
||||
private readonly authoritative = signal(false);
|
||||
@@ -29,7 +31,29 @@ export class DownloadListLoadState {
|
||||
this.loaded.set(true);
|
||||
}
|
||||
|
||||
finish(): void {
|
||||
async finishOrJoinLatest(requestId: number): Promise<void> {
|
||||
if (!this.isLatest(requestId)) {
|
||||
await this.waitForSettlementAtOrAfter(requestId);
|
||||
return;
|
||||
}
|
||||
|
||||
this.loading.set(false);
|
||||
this.settledRequestId = requestId;
|
||||
for (const [minimumRequestId, resolve] of this.settlementWaiters) {
|
||||
if (minimumRequestId <= requestId) {
|
||||
this.settlementWaiters.delete(minimumRequestId);
|
||||
resolve();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private waitForSettlementAtOrAfter(requestId: number): Promise<void> {
|
||||
if (this.settledRequestId >= requestId) {
|
||||
return Promise.resolve();
|
||||
}
|
||||
|
||||
return new Promise((resolve) => {
|
||||
this.settlementWaiters.set(requestId, resolve);
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -561,6 +561,40 @@ describe('DownloadsService', () => {
|
||||
expect(electron.downloadsGetList).toHaveBeenNthCalledWith(2);
|
||||
});
|
||||
|
||||
it('keeps a superseded load pending until a newer request commits its snapshot', async () => {
|
||||
const staleItem = createDownload(1, 'playlist-old');
|
||||
const latestItem = createDownload(2, 'playlist-new');
|
||||
const first = createDeferred<DownloadItem[]>();
|
||||
const second = createDeferred<DownloadItem[]>();
|
||||
const electron = {
|
||||
downloadsGetList: jest
|
||||
.fn()
|
||||
.mockReturnValueOnce(first.promise)
|
||||
.mockReturnValueOnce(second.promise),
|
||||
};
|
||||
testWindow.electron = electron;
|
||||
const service = createService();
|
||||
let supersededLoadSettled = false;
|
||||
|
||||
const firstRequest = service.loadDownloads().finally(() => {
|
||||
supersededLoadSettled = true;
|
||||
});
|
||||
const secondRequest = service.loadDownloads();
|
||||
|
||||
first.resolve([staleItem]);
|
||||
await first.promise;
|
||||
await Promise.resolve();
|
||||
|
||||
expect(supersededLoadSettled).toBe(false);
|
||||
expect(service.downloads()).toEqual([]);
|
||||
|
||||
second.resolve([latestItem]);
|
||||
await Promise.all([firstRequest, secondRequest]);
|
||||
|
||||
expect(service.downloads()).toEqual([latestItem]);
|
||||
expect(supersededLoadSettled).toBe(true);
|
||||
});
|
||||
|
||||
it('reports paused content and resumes it by content identity', async () => {
|
||||
const pausedItem = {
|
||||
...createDownload(7),
|
||||
|
||||
@@ -88,7 +88,8 @@ export class DownloadsService implements OnDestroy {
|
||||
}
|
||||
|
||||
/**
|
||||
* Load downloads from the backend
|
||||
* Load downloads from the backend. A superseded caller waits until its
|
||||
* request or a newer one settles as the current snapshot.
|
||||
*/
|
||||
async loadDownloads(): Promise<void> {
|
||||
if (!this.isAvailable()) return;
|
||||
@@ -107,9 +108,7 @@ export class DownloadsService implements OnDestroy {
|
||||
this.downloadListLoadState.markFailed();
|
||||
}
|
||||
} finally {
|
||||
if (this.downloadListLoadState.isLatest(requestId)) {
|
||||
this.downloadListLoadState.finish();
|
||||
}
|
||||
await this.downloadListLoadState.finishOrJoinLatest(requestId);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in new issue
Block a user