mirror of
https://github.com/4gray/iptvnator.git
synced 2026-10-08 09:01:03 -08:00
feat(xtream): preserve user Xtream data during refresh
Save and restore favorited and recently viewed Xtream items when deleting and re-importing playlist content. - Return favoritedXtreamIds and recentlyViewedXtreamIds from deleteXtreamPlaylistContent so callers can capture user data before . - Add restoreXtreamUserData to the database service to allow restoring favorites and recently viewed entries after a refresh. - Query and collect xtreamIds and timestamps for favorites and recently viewed items in the backend before deleting content. - Persist the captured user data to localStorage in the recent playlists component so it can be used to restore state after re-importing the playlist. - Keep existing playlist metadata update flow intact. This prevents loss of user-specific favorites and recent history when refreshing Xtream playlists.
This commit is contained in:
1 parent
67f54a94c7
commit
730200f704
7 files changed
+207
-21
No files matched your search
@@ -80,6 +80,17 @@ contextBridge.exposeInMainWorld('electron', {
|
||||
ipcRenderer.invoke('DB_DELETE_PLAYLIST', playlistId),
|
||||
dbDeleteXtreamContent: (playlistId: string) =>
|
||||
ipcRenderer.invoke('DB_DELETE_XTREAM_CONTENT', playlistId),
|
||||
dbRestoreXtreamUserData: (
|
||||
playlistId: string,
|
||||
favoritedXtreamIds: number[],
|
||||
recentlyViewedXtreamIds: { xtreamId: number; viewedAt: string }[]
|
||||
) =>
|
||||
ipcRenderer.invoke(
|
||||
'DB_RESTORE_XTREAM_USER_DATA',
|
||||
playlistId,
|
||||
favoritedXtreamIds,
|
||||
recentlyViewedXtreamIds
|
||||
),
|
||||
dbHasCategories: (playlistId: string, type: string) =>
|
||||
ipcRenderer.invoke('DB_HAS_CATEGORIES', playlistId, type),
|
||||
dbGetCategories: (playlistId: string, type: string) =>
|
||||
|
||||
@@ -139,39 +139,149 @@ ipcMain.handle(
|
||||
|
||||
const categoryIds = categories.map((c) => c.id);
|
||||
|
||||
console.log(
|
||||
`Deleting playlist content for ${playlistId}: ${categoryIds.length} categories found`
|
||||
);
|
||||
// Before deleting content, save xtreamIds of favorited and recently viewed items
|
||||
// so they can be restored after refresh
|
||||
let favoritedXtreamIds: number[] = [];
|
||||
let recentlyViewedXtreamIds: { xtreamId: number; viewedAt: string }[] = [];
|
||||
|
||||
if (categoryIds.length > 0) {
|
||||
// Get favorites with their xtreamIds
|
||||
const favorites = await db
|
||||
.select({
|
||||
xtreamId: schema.content.xtreamId,
|
||||
})
|
||||
.from(schema.favorites)
|
||||
.innerJoin(schema.content, eq(schema.favorites.contentId, schema.content.id))
|
||||
.where(
|
||||
and(
|
||||
eq(schema.favorites.playlistId, playlistId),
|
||||
inArray(schema.content.categoryId, categoryIds)
|
||||
)
|
||||
);
|
||||
|
||||
favoritedXtreamIds = favorites.map((f) => f.xtreamId);
|
||||
|
||||
// Get recently viewed with their xtreamIds and timestamps
|
||||
const recentlyViewed = await db
|
||||
.select({
|
||||
xtreamId: schema.content.xtreamId,
|
||||
viewedAt: schema.recentlyViewed.viewedAt,
|
||||
})
|
||||
.from(schema.recentlyViewed)
|
||||
.innerJoin(schema.content, eq(schema.recentlyViewed.contentId, schema.content.id))
|
||||
.where(
|
||||
and(
|
||||
eq(schema.recentlyViewed.playlistId, playlistId),
|
||||
inArray(schema.content.categoryId, categoryIds)
|
||||
)
|
||||
);
|
||||
|
||||
recentlyViewedXtreamIds = recentlyViewed.map((rv) => ({
|
||||
xtreamId: rv.xtreamId,
|
||||
viewedAt: rv.viewedAt || new Date().toISOString(),
|
||||
}));
|
||||
|
||||
// Delete all content for these categories
|
||||
// (This will cascade delete favorites and recently viewed)
|
||||
await db
|
||||
.delete(schema.content)
|
||||
.where(inArray(schema.content.categoryId, categoryIds));
|
||||
|
||||
console.log(
|
||||
`Deleted content for ${categoryIds.length} categories`
|
||||
);
|
||||
}
|
||||
|
||||
// Delete all categories for this playlist
|
||||
// (Could rely on cascade delete, but being explicit is clearer)
|
||||
await db
|
||||
.delete(schema.categories)
|
||||
.where(eq(schema.categories.playlistId, playlistId));
|
||||
|
||||
console.log(
|
||||
`Deleted ${categoryIds.length} categories for playlist ${playlistId}`
|
||||
);
|
||||
// Return the saved xtreamIds so favorites and recently viewed can be restored
|
||||
return {
|
||||
success: true,
|
||||
favoritedXtreamIds,
|
||||
recentlyViewedXtreamIds,
|
||||
};
|
||||
} catch (error) {
|
||||
console.error('Error deleting Xtream content:', error);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
// NOTE: Do NOT delete user favorites or recently viewed items here.
|
||||
// When refreshing an Xtream playlist we want to remove only the
|
||||
// imported categories and content so user-specific data (favorites,
|
||||
// recently viewed) is preserved.
|
||||
/**
|
||||
* Restore favorites and recently viewed items after Xtream refresh
|
||||
* Matches content by xtreamId and re-creates favorites/recently viewed entries
|
||||
*/
|
||||
ipcMain.handle(
|
||||
'DB_RESTORE_XTREAM_USER_DATA',
|
||||
async (
|
||||
event,
|
||||
playlistId: string,
|
||||
favoritedXtreamIds: number[],
|
||||
recentlyViewedXtreamIds: { xtreamId: number; viewedAt: string }[]
|
||||
) => {
|
||||
try {
|
||||
const db = await getDatabase();
|
||||
|
||||
// Restore favorites
|
||||
if (favoritedXtreamIds.length > 0) {
|
||||
// Find content IDs for the xtreamIds
|
||||
const content = await db
|
||||
.select({ id: schema.content.id, xtreamId: schema.content.xtreamId })
|
||||
.from(schema.content)
|
||||
.innerJoin(schema.categories, eq(schema.content.categoryId, schema.categories.id))
|
||||
.where(
|
||||
and(
|
||||
eq(schema.categories.playlistId, playlistId),
|
||||
inArray(schema.content.xtreamId, favoritedXtreamIds)
|
||||
)
|
||||
);
|
||||
|
||||
// Re-create favorite entries
|
||||
for (const item of content) {
|
||||
await db.insert(schema.favorites).values({
|
||||
contentId: item.id,
|
||||
playlistId: playlistId,
|
||||
addedAt: new Date().toISOString(),
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// Restore recently viewed
|
||||
if (recentlyViewedXtreamIds.length > 0) {
|
||||
const xtreamIds = recentlyViewedXtreamIds.map((rv) => rv.xtreamId);
|
||||
|
||||
// Find content IDs for the xtreamIds
|
||||
const content = await db
|
||||
.select({ id: schema.content.id, xtreamId: schema.content.xtreamId })
|
||||
.from(schema.content)
|
||||
.innerJoin(schema.categories, eq(schema.content.categoryId, schema.categories.id))
|
||||
.where(
|
||||
and(
|
||||
eq(schema.categories.playlistId, playlistId),
|
||||
inArray(schema.content.xtreamId, xtreamIds)
|
||||
)
|
||||
);
|
||||
|
||||
// Create a map of xtreamId -> contentId
|
||||
const xtreamIdToContentId = new Map(
|
||||
content.map((item) => [item.xtreamId, item.id])
|
||||
);
|
||||
|
||||
// Re-create recently viewed entries with original timestamps
|
||||
for (const item of recentlyViewedXtreamIds) {
|
||||
const contentId = xtreamIdToContentId.get(item.xtreamId);
|
||||
if (contentId) {
|
||||
await db.insert(schema.recentlyViewed).values({
|
||||
contentId: contentId,
|
||||
playlistId: playlistId,
|
||||
viewedAt: item.viewedAt,
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return { success: true };
|
||||
} catch (error) {
|
||||
console.error('Error deleting Xtream content:', error);
|
||||
console.error('Error restoring Xtream user data:', error);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -620,6 +620,29 @@ export const XtreamStore = signalStore(
|
||||
),
|
||||
]);
|
||||
console.log('Content initialization completed');
|
||||
|
||||
// Check if we need to restore favorites and recently viewed after refresh
|
||||
const playlistId = route.snapshot.params.id;
|
||||
if (playlistId) {
|
||||
const restoreKey = `xtream-restore-${playlistId}`;
|
||||
const restoreData = localStorage.getItem(restoreKey);
|
||||
if (restoreData) {
|
||||
try {
|
||||
const {
|
||||
favoritedXtreamIds,
|
||||
recentlyViewedXtreamIds,
|
||||
} = JSON.parse(restoreData);
|
||||
await dbService.restoreXtreamUserData(
|
||||
playlistId,
|
||||
favoritedXtreamIds,
|
||||
recentlyViewedXtreamIds
|
||||
);
|
||||
localStorage.removeItem(restoreKey);
|
||||
} catch (err) {
|
||||
console.error('Error restoring user data:', err);
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
console.error(
|
||||
'Error during content initialization:',
|
||||
|
||||
Vendored
+9
@@ -81,6 +81,15 @@ declare global {
|
||||
) => Promise<{ success: boolean }>;
|
||||
dbDeleteXtreamContent: (
|
||||
playlistId: string
|
||||
) => Promise<{
|
||||
success: boolean;
|
||||
favoritedXtreamIds: number[];
|
||||
recentlyViewedXtreamIds: { xtreamId: number; viewedAt: string }[];
|
||||
}>;
|
||||
dbRestoreXtreamUserData: (
|
||||
playlistId: string,
|
||||
favoritedXtreamIds: number[],
|
||||
recentlyViewedXtreamIds: { xtreamId: number; viewedAt: string }[]
|
||||
) => Promise<{ success: boolean }>;
|
||||
dbHasCategories: (
|
||||
playlistId: string,
|
||||
|
||||
@@ -67,9 +67,29 @@ export class DatabaseService {
|
||||
/**
|
||||
* Delete all content and categories for an Xtream playlist (for refresh)
|
||||
* Keeps the playlist entry but removes all imported data
|
||||
* Returns saved favorites and recently viewed xtreamIds for restoration
|
||||
*/
|
||||
async deleteXtreamPlaylistContent(playlistId: string): Promise<void> {
|
||||
await window.electron.dbDeleteXtreamContent(playlistId);
|
||||
async deleteXtreamPlaylistContent(playlistId: string): Promise<{
|
||||
success: boolean;
|
||||
favoritedXtreamIds: number[];
|
||||
recentlyViewedXtreamIds: { xtreamId: number; viewedAt: string }[];
|
||||
}> {
|
||||
return await window.electron.dbDeleteXtreamContent(playlistId);
|
||||
}
|
||||
|
||||
/**
|
||||
* Restore favorites and recently viewed items after Xtream refresh
|
||||
*/
|
||||
async restoreXtreamUserData(
|
||||
playlistId: string,
|
||||
favoritedXtreamIds: number[],
|
||||
recentlyViewedXtreamIds: { xtreamId: number; viewedAt: string }[]
|
||||
): Promise<void> {
|
||||
await window.electron.dbRestoreXtreamUserData(
|
||||
playlistId,
|
||||
favoritedXtreamIds,
|
||||
recentlyViewedXtreamIds
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -7,6 +7,7 @@
|
||||
"inlineSources": true,
|
||||
"types": []
|
||||
},
|
||||
"files": ["../../global.d.ts"],
|
||||
"exclude": [
|
||||
"src/**/*.spec.ts",
|
||||
"src/test-setup.ts",
|
||||
|
||||
@@ -223,9 +223,11 @@ export class RecentPlaylistsComponent {
|
||||
onConfirm: async () => {
|
||||
try {
|
||||
// Delete all content and categories for this playlist
|
||||
await this.databaseService.deleteXtreamPlaylistContent(
|
||||
item._id
|
||||
);
|
||||
// This returns the xtreamIds of favorites and recently viewed items
|
||||
const { favoritedXtreamIds, recentlyViewedXtreamIds } =
|
||||
await this.databaseService.deleteXtreamPlaylistContent(
|
||||
item._id
|
||||
);
|
||||
|
||||
const updateDate = Date.now();
|
||||
|
||||
@@ -242,6 +244,16 @@ export class RecentPlaylistsComponent {
|
||||
})
|
||||
);
|
||||
|
||||
// Store the user data in localStorage to restore after re-import
|
||||
const restoreKey = `xtream-restore-${item._id}`;
|
||||
localStorage.setItem(
|
||||
restoreKey,
|
||||
JSON.stringify({
|
||||
favoritedXtreamIds,
|
||||
recentlyViewedXtreamIds,
|
||||
})
|
||||
);
|
||||
|
||||
this.snackBar.open(
|
||||
this.translate.instant(
|
||||
'HOME.PLAYLISTS.REFRESH_XTREAM_DIALOG.STARTED'
|
||||
|
||||
Reference in new issue
Block a user