fix(xtream): track and show import progress of playlist

This commit is contained in:
4gray committed 2026-01-12 00:54:55 +01:00
1 parent c173553d78
commit 54f4abb48b
8 files changed
+82 -24

No files matched your search

@@ -28,6 +28,16 @@ contextBridge.exposeInMainWorld('electron', {
) => {
ipcRenderer.on('EPG_PROGRESS_UPDATE', (_event, data) => callback(data));
},
// DB save content progress listener
onDbSaveContentProgress: (callback: (count: number) => void) => {
ipcRenderer.on('DB_SAVE_CONTENT_PROGRESS', (_event, count) =>
callback(count)
);
},
// Remove DB save content progress listener
removeDbSaveContentProgress: () => {
ipcRenderer.removeAllListeners('DB_SAVE_CONTENT_PROGRESS');
},
getAppVersion: () => ipcRenderer.invoke('get-app-version'),
platform: process.platform,
fetchPlaylistByUrl: (url: string, title?: string) =>
@@ -184,8 +184,8 @@ ipcMain.handle(
);
totalInserted += chunk.length;
// Send progress update
event.sender.send('DB_SAVE_CONTENT_PROGRESS', totalInserted);
// Send incremental progress update (chunk size, not cumulative total)
event.sender.send('DB_SAVE_CONTENT_PROGRESS', chunk.length);
}
return { success: true, count: totalInserted };
@@ -184,7 +184,9 @@ export class ElectronXtreamDataSource implements IXtreamDataSource {
async getContent(
playlistId: string,
credentials: XtreamCredentials,
type: StreamType
type: StreamType,
onProgress?: (count: number) => void,
onTotal?: (total: number) => void
): Promise<XtreamContentItem[]> {
// Check if we have cached data
const exists = await this.dbService.hasXtreamContent(playlistId, type);
@@ -198,11 +200,17 @@ export class ElectronXtreamDataSource implements IXtreamDataSource {
const remoteData = await this.apiService.getStreams(credentials, type);
if (remoteData && Array.isArray(remoteData) && remoteData.length > 0) {
// Save to cache
// Report total items to import
if (onTotal) {
onTotal(remoteData.length);
}
// Save to cache with progress tracking
await this.dbService.saveXtreamContent(
playlistId,
remoteData as any[],
type
type,
onProgress
);
}
@@ -167,7 +167,9 @@ export class PwaXtreamDataSource implements IXtreamDataSource {
async getContent(
playlistId: string,
credentials: XtreamCredentials,
type: StreamType
type: StreamType,
onProgress?: (count: number) => void,
onTotal?: (total: number) => void
): Promise<XtreamLiveStream[] | XtreamVodStream[] | XtreamSerieItem[]> {
const cacheKey = `${playlistId}-${type}-content`;
@@ -179,6 +181,14 @@ export class PwaXtreamDataSource implements IXtreamDataSource {
// Fetch from API
const content = await this.apiService.getStreams(credentials, type);
// Report total and progress (PWA doesn't have incremental save, so report all at once)
if (onTotal) {
onTotal(content.length);
}
if (onProgress) {
onProgress(content.length);
}
// Cache in memory
this.contentCache.set(cacheKey, content);
@@ -199,11 +199,15 @@ export interface IXtreamDataSource {
/**
* Get content/streams for a playlist and type
* @param onProgress - Optional callback for import progress (count)
* @param onTotal - Optional callback for total items to import
*/
getContent(
playlistId: string,
credentials: XtreamCredentials,
type: StreamType
type: StreamType,
onProgress?: ProgressCallback,
onTotal?: (total: number) => void
): Promise<XtreamLiveStream[] | XtreamVodStream[] | XtreamSerieItem[] | XtreamContentItem[]>;
/**
@@ -181,7 +181,7 @@ export function withContent() {
},
/**
* Fetch all content/streams in parallel
* Fetch all content/streams in parallel with progress tracking
*/
async fetchAllContent(): Promise<void> {
const ctx = getCredentialsFromStore();
@@ -189,22 +189,42 @@ export function withContent() {
patchState(store, { isLoadingContent: true });
// Track combined progress across all content types
let totalItems = 0;
let importedItems = 0;
const onTotal = (count: number) => {
totalItems += count;
patchState(store, { itemsToImport: totalItems });
};
const onProgress = (count: number) => {
importedItems += count;
patchState(store, { importCount: importedItems });
};
try {
const [live, vod, series] = await Promise.all([
dataSource.getContent(
ctx.playlistId,
ctx.credentials,
'live'
'live',
onProgress,
onTotal
),
dataSource.getContent(
ctx.playlistId,
ctx.credentials,
'movie'
'movie',
onProgress,
onTotal
),
dataSource.getContent(
ctx.playlistId,
ctx.credentials,
'series'
'series',
onProgress,
onTotal
),
]);
@@ -227,13 +247,13 @@ export function withContent() {
const ctx = getCredentialsFromStore();
if (!ctx) return;
patchState(store, { isImporting: true, importCount: 0 });
patchState(store, { isImporting: true, importCount: 0, itemsToImport: 0 });
try {
// Fetch categories first
await this.fetchAllCategories();
// Then fetch content
// Then fetch content (with progress tracking)
await this.fetchAllContent();
// Restore user data if needed
Vendored
+3
View File
@@ -210,6 +210,9 @@ declare global {
error?: string;
}) => void
) => void;
// DB save content progress listener
onDbSaveContentProgress: (callback: (count: number) => void) => void;
removeDbSaveContentProgress: () => void;
dbDeleteAllPlaylists: () => Promise<{ success: boolean }>;
};
process: NodeJS.Process;
@@ -237,19 +237,22 @@ export class DatabaseService {
): Promise<number> {
// Setup progress listener if callback provided
if (onProgress) {
window.addEventListener('message', (event) => {
if (event.data.type === 'DB_SAVE_CONTENT_PROGRESS') {
onProgress(event.data.count);
}
});
window.electron.onDbSaveContentProgress(onProgress);
}
const result = await window.electron.dbSaveContent(
playlistId,
streams,
type
);
return result.count;
try {
const result = await window.electron.dbSaveContent(
playlistId,
streams,
type
);
return result.count;
} finally {
// Clean up the listener
if (onProgress) {
window.electron.removeDbSaveContentProgress();
}
}
}
/**