Implemented cascading update save logic

This commit is contained in:
Trevor Mears
2026-01-03 21:46:07 -08:00
parent 1193e07017
commit 374a87b2fd
2 changed files with 58 additions and 23 deletions
+24 -13
View File
@@ -967,23 +967,22 @@ class SourceManager {
const itemsToHide = []; const itemsToHide = [];
// Only collect items that have CHANGED from their original state // Only collect items that have CHANGED from their original state
this.treeData.groups.forEach(group => { // Track group changes for redundancy check
// First, handle the GROUP itself (category visibility) const changedGroups = new Map(); // categoryId -> isHidden
// Determine the correct item type for the group based on content type
let groupItemType = 'group'; // default for live channels // First pass: Identify all changed groups
if (this.treeData.type === 'movies') { this.treeData.groups.forEach(group => {
groupItemType = 'vod_category'; let groupItemType = 'group';
} else if (this.treeData.type === 'series') { if (this.treeData.type === 'movies') groupItemType = 'vod_category';
groupItemType = 'series_category'; else if (this.treeData.type === 'series') groupItemType = 'series_category';
}
// Only process groups that have a real categoryId (not wrapper groups like 'all_categories')
if (group.categoryId) { if (group.categoryId) {
const groupKey = `${groupItemType}:${group.categoryId}`; const groupKey = `${groupItemType}:${group.categoryId}`;
const isGroupNowHidden = this.hiddenSet.has(groupKey); const isGroupNowHidden = this.hiddenSet.has(groupKey);
const wasGroupHidden = this.originalHiddenSet.has(groupKey); const wasGroupHidden = this.originalHiddenSet.has(groupKey);
if (isGroupNowHidden !== wasGroupHidden) { if (isGroupNowHidden !== wasGroupHidden) {
changedGroups.set(group.categoryId, isGroupNowHidden);
if (isGroupNowHidden) { if (isGroupNowHidden) {
itemsToHide.push({ sourceId, itemType: groupItemType, itemId: String(group.categoryId) }); itemsToHide.push({ sourceId, itemType: groupItemType, itemId: String(group.categoryId) });
} else { } else {
@@ -991,8 +990,13 @@ class SourceManager {
} }
} }
} }
});
// Second pass: Process items, skipping if redundant with group change
this.treeData.groups.forEach(group => {
const groupIsChanging = changedGroups.has(group.categoryId);
const groupNewState = changedGroups.get(group.categoryId); // true = hiding, false = showing
// Then handle the ITEMS within the group
group.items.forEach(item => { group.items.forEach(item => {
const key = `${item.type}:${item.id}`; const key = `${item.type}:${item.id}`;
const isNowHidden = this.hiddenSet.has(key); const isNowHidden = this.hiddenSet.has(key);
@@ -1000,6 +1004,13 @@ class SourceManager {
// Only send if state changed // Only send if state changed
if (isNowHidden !== wasHidden) { if (isNowHidden !== wasHidden) {
// Check for redundancy:
// If group is changing to the SAME state as the item, skip the item
// The backend cascade will handle it.
if (groupIsChanging && groupNewState === isNowHidden) {
return;
}
if (isNowHidden) { if (isNowHidden) {
itemsToHide.push({ sourceId, itemType: item.type, itemId: String(item.id) }); itemsToHide.push({ sourceId, itemType: item.type, itemId: String(item.id) });
} else { } else {
@@ -1032,8 +1043,8 @@ class SourceManager {
} }
} }
// Batch large operations to avoid timeouts (1000 items per batch) // Batch large operations to avoid timeouts (5000 items per batch)
const BATCH_SIZE = 1000; const BATCH_SIZE = 5000;
const processBatches = async (items, apiFn, label) => { const processBatches = async (items, apiFn, label) => {
for (let i = 0; i < items.length; i += BATCH_SIZE) { for (let i = 0; i < items.length; i += BATCH_SIZE) {
+34 -10
View File
@@ -149,15 +149,27 @@ router.post('/hide/bulk', async (req, res) => {
if (!Array.isArray(items)) return res.status(400).json({ error: 'items array required' }); if (!Array.isArray(items)) return res.status(400).json({ error: 'items array required' });
const db = getDb(); const db = getDb();
// Prepare statements once
const hideCat = db.prepare('UPDATE categories SET is_hidden = 1 WHERE source_id = ? AND type = ? AND category_id = ?');
const hideItem = db.prepare('UPDATE playlist_items SET is_hidden = 1 WHERE source_id = ? AND type = ? AND item_id = ?');
// Cascading statements (hide all children of a category)
const hideCatChildren = db.prepare('UPDATE playlist_items SET is_hidden = 1 WHERE source_id = ? AND type = ? AND category_id = ?');
const runBulk = db.transaction((list) => { const runBulk = db.transaction((list) => {
for (const item of list) { for (const item of list) {
const mapping = mapItemType(item.itemType); const mapping = mapItemType(item.itemType);
if (mapping) { if (mapping) {
const idCol = mapping.table === 'categories' ? 'category_id' : 'item_id'; if (mapping.table === 'categories') {
db.prepare(` // Hide the category
UPDATE ${mapping.table} SET is_hidden = 1 hideCat.run(item.sourceId, mapping.type, item.itemId);
WHERE source_id = ? AND type = ? AND ${idCol} = ? // Cascade to children
`).run(item.sourceId, mapping.type, item.itemId); hideCatChildren.run(item.sourceId, mapping.type, item.itemId);
} else {
// Hide individual item
hideItem.run(item.sourceId, mapping.type, item.itemId);
}
} }
} }
}); });
@@ -180,15 +192,27 @@ router.post('/show/bulk', async (req, res) => {
if (!Array.isArray(items)) return res.status(400).json({ error: 'items array required' }); if (!Array.isArray(items)) return res.status(400).json({ error: 'items array required' });
const db = getDb(); const db = getDb();
// Prepare statements once
const showCat = db.prepare('UPDATE categories SET is_hidden = 0 WHERE source_id = ? AND type = ? AND category_id = ?');
const showItem = db.prepare('UPDATE playlist_items SET is_hidden = 0 WHERE source_id = ? AND type = ? AND item_id = ?');
// Cascading statements (show all children of a category)
const showCatChildren = db.prepare('UPDATE playlist_items SET is_hidden = 0 WHERE source_id = ? AND type = ? AND category_id = ?');
const runBulk = db.transaction((list) => { const runBulk = db.transaction((list) => {
for (const item of list) { for (const item of list) {
const mapping = mapItemType(item.itemType); const mapping = mapItemType(item.itemType);
if (mapping) { if (mapping) {
const idCol = mapping.table === 'categories' ? 'category_id' : 'item_id'; if (mapping.table === 'categories') {
db.prepare(` // Show the category
UPDATE ${mapping.table} SET is_hidden = 0 showCat.run(item.sourceId, mapping.type, item.itemId);
WHERE source_id = ? AND type = ? AND ${idCol} = ? // Cascade to children
`).run(item.sourceId, mapping.type, item.itemId); showCatChildren.run(item.sourceId, mapping.type, item.itemId);
} else {
// Show individual item
showItem.run(item.sourceId, mapping.type, item.itemId);
}
} }
} }
}); });