mirror of
https://github.com/4gray/iptvnator.git
synced 2026-10-08 09:01:03 -08:00
refactor(xtream): gate data source by sqlite capability
This commit is contained in:
1 parent
5cf7ab9358
commit
8cfc40f59b
5 files changed
+180
-6
No files matched your search
@@ -68,8 +68,9 @@ Renderer code that needs to branch by runtime should use
|
||||
`RuntimeCapabilitiesService` from `@iptvnator/services` instead of adding new
|
||||
direct `window.electron` or `DataService.getAppEnvironment()` checks. Keep
|
||||
feature decisions expressed as capabilities such as `supportsEpg`,
|
||||
`supportsSqlite`, `supportsDownloads`, or `supportsManagedExternalPlayers` so
|
||||
PWA and Electron behavior stays auditable from one shared boundary.
|
||||
`supportsSqlite`, `supportsXtreamSqliteDataSource`, `supportsDownloads`, or
|
||||
`supportsManagedExternalPlayers` so PWA and Electron behavior stays auditable
|
||||
from one shared boundary.
|
||||
|
||||
## Runtime Limitations
|
||||
|
||||
@@ -109,8 +110,12 @@ certificate authorities, configure Node with `NODE_EXTRA_CA_CERTS`.
|
||||
## PWA Portal User Data
|
||||
|
||||
Xtream favorites and recently viewed items use the browser-side
|
||||
`PwaXtreamDataSource` when Electron DB preload APIs are unavailable. The PWA
|
||||
stores this user activity and sidecar state in localStorage:
|
||||
`PwaXtreamDataSource` when Electron DB preload APIs are unavailable. The
|
||||
`XTREAM_DATA_SOURCE` provider chooses the Electron SQLite-backed source only
|
||||
when `RuntimeCapabilitiesService.supportsXtreamSqliteDataSource` is true; a
|
||||
browser PWA or partial preload bridge must fall back to the PWA source and run
|
||||
the browser cleanup hook on playlist deletion. The PWA stores this user activity
|
||||
and sidecar state in localStorage:
|
||||
|
||||
- `xtream-collection-items`
|
||||
- `xtream-favorites`
|
||||
|
||||
@@ -0,0 +1,91 @@
|
||||
import { TestBed } from '@angular/core/testing';
|
||||
import {
|
||||
PLAYLIST_DELETE_CLEANUP,
|
||||
PlaylistDeleteCleanup,
|
||||
RuntimeCapabilitiesService,
|
||||
} from '@iptvnator/services';
|
||||
import {
|
||||
ElectronXtreamDataSource,
|
||||
PwaXtreamDataSource,
|
||||
provideXtreamDataSource,
|
||||
} from './index';
|
||||
import {
|
||||
IXtreamDataSource,
|
||||
XTREAM_DATA_SOURCE,
|
||||
} from './xtream-data-source.interface';
|
||||
|
||||
describe('provideXtreamDataSource', () => {
|
||||
let electronSource: IXtreamDataSource;
|
||||
let pwaSource: IXtreamDataSource;
|
||||
let runtime: {
|
||||
supportsXtreamSqliteDataSource: boolean;
|
||||
};
|
||||
|
||||
function configure(supportsXtreamSqliteDataSource: boolean): void {
|
||||
electronSource = {
|
||||
deletePlaylist: jest.fn().mockResolvedValue(undefined),
|
||||
} as Partial<IXtreamDataSource> as IXtreamDataSource;
|
||||
pwaSource = {
|
||||
deletePlaylist: jest.fn().mockResolvedValue(undefined),
|
||||
} as Partial<IXtreamDataSource> as IXtreamDataSource;
|
||||
runtime = {
|
||||
supportsXtreamSqliteDataSource,
|
||||
};
|
||||
|
||||
TestBed.configureTestingModule({
|
||||
providers: [
|
||||
...provideXtreamDataSource(),
|
||||
{
|
||||
provide: RuntimeCapabilitiesService,
|
||||
useValue: runtime,
|
||||
},
|
||||
{
|
||||
provide: ElectronXtreamDataSource,
|
||||
useValue: electronSource,
|
||||
},
|
||||
{
|
||||
provide: PwaXtreamDataSource,
|
||||
useValue: pwaSource,
|
||||
},
|
||||
],
|
||||
});
|
||||
}
|
||||
|
||||
afterEach(() => TestBed.resetTestingModule());
|
||||
|
||||
it('uses the Electron data source only when the Xtream SQLite capability is available', () => {
|
||||
configure(true);
|
||||
|
||||
expect(TestBed.inject(XTREAM_DATA_SOURCE)).toBe(electronSource);
|
||||
});
|
||||
|
||||
it('falls back to the PWA data source when the Electron bridge lacks Xtream SQLite methods', () => {
|
||||
configure(false);
|
||||
|
||||
expect(TestBed.inject(XTREAM_DATA_SOURCE)).toBe(pwaSource);
|
||||
});
|
||||
|
||||
it('skips browser sidecar cleanup for SQLite-backed Xtream storage', async () => {
|
||||
configure(true);
|
||||
const [cleanup] = TestBed.inject(
|
||||
PLAYLIST_DELETE_CLEANUP
|
||||
) as PlaylistDeleteCleanup[];
|
||||
|
||||
await cleanup('playlist-1');
|
||||
|
||||
expect(electronSource.deletePlaylist).not.toHaveBeenCalled();
|
||||
expect(pwaSource.deletePlaylist).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('runs browser sidecar cleanup when SQLite-backed Xtream storage is unavailable', async () => {
|
||||
configure(false);
|
||||
const [cleanup] = TestBed.inject(
|
||||
PLAYLIST_DELETE_CLEANUP
|
||||
) as PlaylistDeleteCleanup[];
|
||||
|
||||
await cleanup('playlist-1');
|
||||
|
||||
expect(pwaSource.deletePlaylist).toHaveBeenCalledWith('playlist-1');
|
||||
expect(electronSource.deletePlaylist).not.toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
@@ -23,7 +23,7 @@ export { PwaXtreamDataSource } from './pwa-xtream-data-source';
|
||||
export function xtreamDataSourceFactory(): IXtreamDataSource {
|
||||
const runtime = inject(RuntimeCapabilitiesService);
|
||||
|
||||
if (runtime.isElectron) {
|
||||
if (runtime.supportsXtreamSqliteDataSource) {
|
||||
return inject(ElectronXtreamDataSource);
|
||||
}
|
||||
|
||||
@@ -50,7 +50,7 @@ export function provideXtreamDataSource(): Provider[] {
|
||||
const runtime = inject(RuntimeCapabilitiesService);
|
||||
|
||||
return (playlistId: string) => {
|
||||
if (runtime.isElectron) {
|
||||
if (runtime.supportsXtreamSqliteDataSource) {
|
||||
// Electron playlist deletion must use DatabaseService so
|
||||
// SQLite content and sidecars are cleaned together; do
|
||||
// not invoke this cleanup path through PlaylistsService.
|
||||
|
||||
@@ -22,6 +22,7 @@ describe('RuntimeCapabilitiesService', () => {
|
||||
expect(service.isMacOS).toBe(false);
|
||||
expect(service.supportsEpg).toBe(false);
|
||||
expect(service.supportsSqlite).toBe(false);
|
||||
expect(service.supportsXtreamSqliteDataSource).toBe(false);
|
||||
expect(service.supportsDownloads).toBe(false);
|
||||
expect(service.supportsPortalActivityStorage).toBe(false);
|
||||
expect(service.supportsManagedExternalPlayers).toBe(false);
|
||||
@@ -52,6 +53,29 @@ describe('RuntimeCapabilitiesService', () => {
|
||||
dbClearPlaylistRecentItems: jest.fn(),
|
||||
dbRemoveRecentItemsBatch: jest.fn(),
|
||||
dbGetContentByXtreamId: jest.fn(),
|
||||
dbGetPlaylist: jest.fn(),
|
||||
dbCreatePlaylist: jest.fn(),
|
||||
dbUpdatePlaylist: jest.fn(),
|
||||
dbDeletePlaylist: jest.fn(),
|
||||
dbHasCategories: jest.fn(),
|
||||
dbGetCategories: jest.fn(),
|
||||
dbSaveCategories: jest.fn(),
|
||||
dbGetAllCategories: jest.fn(),
|
||||
dbUpdateCategoryVisibility: jest.fn(),
|
||||
dbHasContent: jest.fn(),
|
||||
dbGetContent: jest.fn(),
|
||||
dbSaveContent: jest.fn(),
|
||||
dbSearchContent: jest.fn(),
|
||||
dbIsFavorite: jest.fn(),
|
||||
dbSavePlaybackPosition: jest.fn(),
|
||||
dbGetPlaybackPosition: jest.fn(),
|
||||
dbGetSeriesPlaybackPositions: jest.fn(),
|
||||
dbGetRecentPlaybackPositions: jest.fn(),
|
||||
dbGetAllPlaybackPositions: jest.fn(),
|
||||
dbClearAllPlaybackPositions: jest.fn(),
|
||||
dbClearPlaybackPosition: jest.fn(),
|
||||
dbDeleteXtreamContent: jest.fn(),
|
||||
dbRestoreXtreamUserData: jest.fn(),
|
||||
downloadsGetList: jest.fn(),
|
||||
prepareEmbeddedMpv: jest.fn(),
|
||||
saveFileDialog: jest.fn(),
|
||||
@@ -70,6 +94,7 @@ describe('RuntimeCapabilitiesService', () => {
|
||||
expect(service.isMacOS).toBe(true);
|
||||
expect(service.supportsEpg).toBe(true);
|
||||
expect(service.supportsSqlite).toBe(true);
|
||||
expect(service.supportsXtreamSqliteDataSource).toBe(true);
|
||||
expect(service.supportsDownloads).toBe(true);
|
||||
expect(service.supportsPortalActivityStorage).toBe(true);
|
||||
expect(service.supportsManagedExternalPlayers).toBe(true);
|
||||
@@ -91,6 +116,7 @@ describe('RuntimeCapabilitiesService', () => {
|
||||
expect(service.isMacOS).toBe(false);
|
||||
expect(service.supportsEpg).toBe(true);
|
||||
expect(service.supportsSqlite).toBe(false);
|
||||
expect(service.supportsXtreamSqliteDataSource).toBe(false);
|
||||
expect(service.supportsDownloads).toBe(false);
|
||||
expect(service.supportsPortalActivityStorage).toBe(false);
|
||||
expect(service.supportsEmbeddedMpv).toBe(false);
|
||||
@@ -110,4 +136,18 @@ describe('RuntimeCapabilitiesService', () => {
|
||||
expect(service.isElectron).toBe(true);
|
||||
expect(service.environment).toBe('electron');
|
||||
});
|
||||
|
||||
it('keeps the Xtream SQLite data source disabled when only generic SQLite methods exist', () => {
|
||||
testWindow.electron = {
|
||||
dbGetAppPlaylists: jest.fn(),
|
||||
dbUpsertAppPlaylist: jest.fn(),
|
||||
dbGetAppState: jest.fn(),
|
||||
dbSetAppState: jest.fn(),
|
||||
};
|
||||
|
||||
const service = new RuntimeCapabilitiesService();
|
||||
|
||||
expect(service.supportsSqlite).toBe(true);
|
||||
expect(service.supportsXtreamSqliteDataSource).toBe(false);
|
||||
});
|
||||
});
|
||||
@@ -44,6 +44,44 @@ export class RuntimeCapabilitiesService {
|
||||
);
|
||||
}
|
||||
|
||||
get supportsXtreamSqliteDataSource(): boolean {
|
||||
return [
|
||||
'dbGetPlaylist',
|
||||
'dbCreatePlaylist',
|
||||
'dbUpdatePlaylist',
|
||||
'dbDeletePlaylist',
|
||||
'dbHasCategories',
|
||||
'dbGetCategories',
|
||||
'dbSaveCategories',
|
||||
'dbGetAllCategories',
|
||||
'dbUpdateCategoryVisibility',
|
||||
'dbHasContent',
|
||||
'dbGetContent',
|
||||
'dbSaveContent',
|
||||
'dbGetAppState',
|
||||
'dbSetAppState',
|
||||
'dbSearchContent',
|
||||
'dbGetFavorites',
|
||||
'dbAddFavorite',
|
||||
'dbRemoveFavorite',
|
||||
'dbIsFavorite',
|
||||
'dbGetRecentItems',
|
||||
'dbAddRecentItem',
|
||||
'dbRemoveRecentItem',
|
||||
'dbClearPlaylistRecentItems',
|
||||
'dbGetContentByXtreamId',
|
||||
'dbSavePlaybackPosition',
|
||||
'dbGetPlaybackPosition',
|
||||
'dbGetSeriesPlaybackPositions',
|
||||
'dbGetRecentPlaybackPositions',
|
||||
'dbGetAllPlaybackPositions',
|
||||
'dbClearAllPlaybackPositions',
|
||||
'dbClearPlaybackPosition',
|
||||
'dbDeleteXtreamContent',
|
||||
'dbRestoreXtreamUserData',
|
||||
].every((methodName) => this.hasElectronMethod(methodName));
|
||||
}
|
||||
|
||||
get supportsDownloads(): boolean {
|
||||
return this.hasElectronMethod('downloadsGetList');
|
||||
}
|
||||
|
||||
Reference in new issue
Block a user