Implemented cascading update save logic
This commit is contained in:
@@ -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
@@ -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);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|||||||
Reference in New Issue
Block a user