mirror of
https://github.com/4gray/iptvnator.git
synced 2026-10-11 19:46:16 -08:00
fix(settings): restore the previous locks when an Xtream rollback write fails
When an Xtream lock edit's re-stamp fails and the rollback store write fails too, memory now goes back to the previous locks and a pending rewrite persists them on the next store access before the index is re-stamped, so the failed edit cannot take effect through that re-stamp. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
3b12b8a1c0
commit
41ac30494c
3 files changed
+31
-8
No files matched your search
@@ -399,7 +399,10 @@ by the index alone:
|
||||
reload on is published only once every touched type is stamped — also
|
||||
after a rollback — so a reload cannot read a later type through its old
|
||||
stamps. If the rollback write or its re-stamp fails too, the playlist is
|
||||
marked stale and re-stamped on the next store access. For the whole
|
||||
marked stale and re-stamped on the next store access. A failed rollback
|
||||
WRITE still puts memory back to the previous locks and leaves a pending
|
||||
rewrite: the next store access persists them before it re-stamps, so the
|
||||
failed edit cannot take effect through that re-stamp. For the whole
|
||||
re-stamp window — store changed, stamps not yet landed — the playlist
|
||||
counts as stale, so `readable` is false and a relock inside the window
|
||||
reloads fail-closed instead of through the old stamps.
|
||||
|
||||
@@ -384,9 +384,10 @@ describe('ParentalLockLockStore', () => {
|
||||
expect(store.readable()).toBe(true);
|
||||
});
|
||||
|
||||
it('re-stamps on the next access when even the rollback write failed', async () => {
|
||||
it('restores the previous locks on the next access when even the rollback write failed', async () => {
|
||||
await store.load();
|
||||
await store.ensureReadable();
|
||||
jest.spyOn(console, 'error').mockImplementation(() => undefined);
|
||||
setCategoryLocks.mockClear();
|
||||
setCategoryLocks.mockResolvedValueOnce(false);
|
||||
storage.writeLocks
|
||||
@@ -396,12 +397,22 @@ describe('ParentalLockLockStore', () => {
|
||||
await expect(
|
||||
store.setXtreamLocks('pl-1', 'live', [7, 9])
|
||||
).resolves.toBe(false);
|
||||
expect(store.lockedXtreamIds('pl-1', 'live')).toEqual([7, 9]);
|
||||
// The failed edit does not take effect: memory holds the previous
|
||||
// locks and the store stays fail-closed until they are persisted.
|
||||
expect(store.lockedXtreamIds('pl-1', 'live')).toEqual([7]);
|
||||
expect(store.readable()).toBe(false);
|
||||
|
||||
await expect(store.ensureReadable()).resolves.toBe(true);
|
||||
|
||||
expect(setCategoryLocks).toHaveBeenCalledWith('pl-1', 'live', [7, 9]);
|
||||
expect(storage.writeLocks).toHaveBeenLastCalledWith(
|
||||
expect.objectContaining({
|
||||
'pl-1': expect.objectContaining({
|
||||
xtream: [{ categoryType: 'live', xtreamId: 7 }],
|
||||
}),
|
||||
})
|
||||
);
|
||||
// Re-stamped from the restored locks, not the failed edit.
|
||||
expect(setCategoryLocks).toHaveBeenCalledWith('pl-1', 'live', [7]);
|
||||
expect(store.readable()).toBe(true);
|
||||
});
|
||||
|
||||
|
||||
@@ -79,10 +79,10 @@ export class ParentalLockLockStore {
|
||||
private writeQueue: Promise<unknown> = Promise.resolve();
|
||||
/**
|
||||
* The persisted store differs from the in-memory one and must be
|
||||
* rewritten from it: "Remove all playlists" could not clear it. Retried
|
||||
* on the next store access;
|
||||
* the store is not `readable` meanwhile, since a restart would load the
|
||||
* persisted copy.
|
||||
* rewritten from it: "Remove all playlists" could not clear it, or a
|
||||
* failed edit could not be rolled back. Retried on the next store
|
||||
* access (before the index is re-stamped from memory); the store is not
|
||||
* `readable` meanwhile.
|
||||
*/
|
||||
private readonly pendingRewrite = signal(false);
|
||||
private readonly staleIndex = new ParentalLockStaleIndex();
|
||||
@@ -452,6 +452,15 @@ export class ParentalLockLockStore {
|
||||
const restored = await this.persistPlaylistLocks(playlistId, previous, {
|
||||
publish: false,
|
||||
});
|
||||
if (!restored) {
|
||||
// Memory goes back to the previous locks anyway, and the
|
||||
// persisted copy is rewritten from it on the next access: the
|
||||
// failed edit must not take effect through a later re-stamp.
|
||||
this.locks.set(
|
||||
withPlaylistLocksInStore(this.locks(), playlistId, previous)
|
||||
);
|
||||
this.pendingRewrite.set(true);
|
||||
}
|
||||
const restamped =
|
||||
restored &&
|
||||
(await this.stampXtreamLocks(playlistId, categoryTypes));
|
||||
|
||||
Reference in new issue
Block a user