mirror of
https://github.com/4gray/iptvnator.git
synced 2026-10-12 03:56:15 -08:00
fix(services): drop the migration receipt memo for M3U favorites
Skipping the receipt read let the dashboard ask the database for M3U favorite channels before a just-toggled favorite was written: the write waits in the per-playlist queue and the cross-context lock, and the dashboard does not reload when the store's favorites are unchanged. The extra round trip had been masking that race, and the Windows E2E run hit it (epg.e2e.ts "dashboard live rails find a programme that only another playlist's XMLTV carries"). The memo was off the first card's path, so it bought nothing measurable for J1; restore the per-call read. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
9599ed481c
commit
e82d46c051
4 files changed
+9
-68
No files matched your search
@@ -3,4 +3,4 @@ type: perf
|
||||
area: workspace
|
||||
---
|
||||
|
||||
On launch the workspace reads the source list once instead of twice before its first source card (journey J1, `renderer.ipcCallsToFirstCard`), and M3U favorites stop re-checking the finished playlist migration for every playlist.
|
||||
On launch the workspace reads the source list once instead of twice before its first source card (journey J1, `renderer.ipcCallsToFirstCard`).
|
||||
@@ -44,7 +44,6 @@ describe('PlaylistsService inventory reads', () => {
|
||||
runtime: { supportsSqlite: true },
|
||||
electronMigrationPromise: null,
|
||||
pendingMetas: null,
|
||||
migrated: false,
|
||||
playlistWriteQueues: new Map(),
|
||||
});
|
||||
const settle = async (index: number, playlists: Playlist[]) => {
|
||||
@@ -102,50 +101,4 @@ describe('PlaylistsService inventory reads', () => {
|
||||
await expect(freshRead).resolves.toEqual([added]);
|
||||
expect(electron.dbGetAppPlaylistMetas).toHaveBeenCalledTimes(2);
|
||||
});
|
||||
|
||||
it('reads the SQLite migration receipt once for M3U favorites', async () => {
|
||||
const { electron, service, settle } = setup();
|
||||
|
||||
const inventory = firstValueFrom(service.getAllPlaylists());
|
||||
await settle(0, []);
|
||||
await inventory;
|
||||
const receiptReads = electron.dbGetAppState.mock.calls.length;
|
||||
await firstValueFrom(service.getM3uFavoriteChannels('one'));
|
||||
await firstValueFrom(service.getM3uFavoriteChannels('two'));
|
||||
|
||||
expect(electron.dbGetAppState).toHaveBeenCalledTimes(receiptReads);
|
||||
expect(
|
||||
electron.dbGetAppPlaylistFavoriteChannels
|
||||
).toHaveBeenCalledTimes(2);
|
||||
});
|
||||
|
||||
it.each([
|
||||
['an empty legacy store', [], { success: true, count: 0 }, true],
|
||||
['a committed migration', [source('legacy')], { success: true, count: 1 }, true],
|
||||
['an uncommitted migration', [source('legacy')], { success: false, count: 0 }, false],
|
||||
])(
|
||||
'trusts the receipt written by %s in this session',
|
||||
async (_case, legacy, result, confirmed) => {
|
||||
const { electron, service, settle } = setup();
|
||||
electron.dbGetAppState.mockImplementation(async (key: string) =>
|
||||
key === 'm3u-playlists-indexeddb-to-sqlite-v1' ? null : '1'
|
||||
);
|
||||
Object.assign(electron, {
|
||||
dbMigrateAppPlaylists: jest.fn(async () => result),
|
||||
});
|
||||
Object.assign(service, {
|
||||
dbService: { getAll: jest.fn(() => of(legacy)) },
|
||||
});
|
||||
|
||||
const inventory = firstValueFrom(service.getAllPlaylists());
|
||||
await settle(0, []);
|
||||
await inventory;
|
||||
const receiptReads = electron.dbGetAppState.mock.calls.length;
|
||||
await firstValueFrom(service.getM3uFavoriteChannels('one'));
|
||||
|
||||
expect(electron.dbGetAppState).toHaveBeenCalledTimes(
|
||||
receiptReads + (confirmed ? 0 : 1)
|
||||
);
|
||||
}
|
||||
);
|
||||
});
|
||||
@@ -61,7 +61,6 @@ describe('PlaylistsService', () => {
|
||||
electronMigrationPromise: null,
|
||||
indexedDbMigrationPromise: null,
|
||||
pendingMetas: null,
|
||||
migrated: false,
|
||||
playlistWriteQueues: new Map(),
|
||||
playlistDeleteCleanups: [],
|
||||
});
|
||||
|
||||
@@ -78,9 +78,7 @@ type PlaylistStorageElectronApi = {
|
||||
playlist: Playlist,
|
||||
operationId?: string
|
||||
) => Promise<unknown>;
|
||||
dbMigrateAppPlaylists: (
|
||||
playlists: Playlist[]
|
||||
) => Promise<{ success: boolean; count: number }>;
|
||||
dbMigrateAppPlaylists: (playlists: Playlist[]) => Promise<unknown>;
|
||||
dbRecoverLegacyPlaylists?: () => Promise<void>;
|
||||
dbUpsertAppPlaylists: (playlists: Playlist[]) => Promise<unknown>;
|
||||
};
|
||||
@@ -122,8 +120,6 @@ export class PlaylistsService {
|
||||
// XMLTV source reconciliation); both share one worker round trip. Only a
|
||||
// pending read is shared, and every SQLite write detaches it.
|
||||
private pendingMetas: Promise<Playlist[]> | null = null;
|
||||
// The IndexedDB → SQLite receipt is written once and never cleared.
|
||||
private migrated = false;
|
||||
private indexedDbMigrationPromise: Promise<void> | null = null;
|
||||
private readonly playlistWriteQueues = new Map<string, Promise<unknown>>();
|
||||
|
||||
@@ -203,21 +199,17 @@ export class PlaylistsService {
|
||||
if (
|
||||
(await electron.dbGetAppState(SQLITE_PLAYLIST_MIGRATION_FLAG)) ===
|
||||
'1'
|
||||
) {
|
||||
this.migrated = true;
|
||||
)
|
||||
return;
|
||||
}
|
||||
const playlists = await firstValueFrom(
|
||||
this.dbService.getAll<Playlist>(DbStores.Playlists)
|
||||
);
|
||||
if (playlists.length) {
|
||||
// The worker commits rows and the receipt atomically. Keep the
|
||||
// original IndexedDB as a recovery source, even after success.
|
||||
const result = await electron.dbMigrateAppPlaylists(playlists);
|
||||
this.migrated = result?.success === true;
|
||||
await electron.dbMigrateAppPlaylists(playlists);
|
||||
} else {
|
||||
await electron.dbSetAppState(SQLITE_PLAYLIST_MIGRATION_FLAG, '1');
|
||||
this.migrated = true;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1099,14 +1091,11 @@ export class PlaylistsService {
|
||||
|
||||
return from(
|
||||
(async () => {
|
||||
if (!this.migrated) {
|
||||
const alreadyMigrated = await electron.dbGetAppState(
|
||||
SQLITE_PLAYLIST_MIGRATION_FLAG
|
||||
);
|
||||
if (alreadyMigrated !== '1') {
|
||||
return null;
|
||||
}
|
||||
this.migrated = true;
|
||||
const alreadyMigrated = await electron.dbGetAppState(
|
||||
SQLITE_PLAYLIST_MIGRATION_FLAG
|
||||
);
|
||||
if (alreadyMigrated !== '1') {
|
||||
return null;
|
||||
}
|
||||
|
||||
return getFavoriteChannels(playlistId);
|
||||
|
||||
Reference in new issue
Block a user