static void feedlist_update_node_counters (nodePtr node) { node_update_counters (node); /* update with parent propagation */ if (node->needsUpdate) ui_node_update (node->id); if (node->children) node_foreach_child (node, feedlist_update_node_counters); }
void item_read_state_changed (itemPtr item, gboolean newState) { nodePtr node; debug_start_measurement (DEBUG_GUI); /* 1. set values in memory */ item->readStatus = newState; item->updateStatus = FALSE; /* 2. propagate to vfolders */ vfolder_foreach_data (vfolder_merge_item, item); vfolder_foreach (node_update_counters); /* 3. apply to DB */ db_item_state_update (item); /* 4. update item list GUI state */ itemlist_update_item (item); /* 5. updated feed list unread counters */ node = node_from_id (item->nodeId); node_update_counters (node); /* 6. update notification statistics */ feedlist_reset_new_item_count (); /* 7. duplicate state propagation */ if (item->validGuid) { GSList *duplicates, *iter; duplicates = iter = db_item_get_duplicates (item->sourceId); while (iter) { itemPtr duplicate = item_load (GPOINTER_TO_UINT (iter->data)); /* The check on node_from_id() is an evil workaround to handle "lost" items in the DB that have no associated node in the feed list. This should be fixed by having the feed list in the DB too, so we can clean up correctly after crashes. */ if (duplicate && duplicate->id != item->id && node_from_id (duplicate->nodeId)) { item_set_read_state (duplicate, newState); } if (duplicate) item_unload (duplicate); iter = g_slist_next (iter); } g_slist_free (duplicates); } debug_end_measurement (DEBUG_GUI, "set read status"); }
/* This method is used to initialize the node states in the feed list */ static void feedlist_init_node (nodePtr node) { if (node->expanded) ui_node_set_expansion (node, TRUE); if (node->subscription) db_subscription_load (node->subscription); node_update_counters (node); ui_node_update (node->id); /* Necessary to initially set folder unread counters */ node_foreach_child (node, feedlist_init_node); }
static gboolean vfolder_loader_fetch_cb (gpointer user_data, GSList **resultItems) { vfolderPtr vfolder = (vfolderPtr)user_data; itemSetPtr items = g_new0 (struct itemSet, 1); GList *iter; gboolean result; /* 1. Fetch a batch of items */ result = db_itemset_get (items, vfolder->loadOffset, VFOLDER_LOADER_BATCH_SIZE); vfolder->loadOffset += VFOLDER_LOADER_BATCH_SIZE; if (result) { /* 2. Match all items against search folder */ iter = items->ids; while (iter) { gulong id = GPOINTER_TO_UINT (iter->data); itemPtr item = db_item_load (id); if (itemset_check_item (vfolder->itemset, item)) *resultItems = g_slist_append (*resultItems, item); else item_unload (item); iter = g_list_next (iter); } } else { debug1 (DEBUG_CACHE, "search folder '%s' reload complete", vfolder->node->title); vfolder->reloading = FALSE; } itemset_free (items); /* 3. Save items to DB and update UI (except for search results) */ if (vfolder->node) { db_search_folder_add_items (vfolder->node->id, *resultItems); node_update_counters (vfolder->node); ui_node_update (vfolder->node->id); } return result; /* FALSE on last fetch */ }
void feedlist_node_was_updated (nodePtr node, guint newCount) { node_update_counters (node); feedlist_update_new_item_count (newCount); }
guint itemset_merge_items (itemSetPtr itemSet, GList *list, gboolean allowUpdates, gboolean markAsRead) { GList *iter, *droppedItems = NULL, *items = NULL; guint i, max, length, toBeDropped, newCount = 0, flagCount = 0; nodePtr node; debug_start_measurement (DEBUG_UPDATE); debug2 (DEBUG_UPDATE, "old item set %p of (node id=%s):", itemSet, itemSet->nodeId); /* 1. Preparation: determine effective maximum cache size The problem here is that the configured maximum cache size might not always be sufficient. We need to check border use cases in the following. */ length = g_list_length (list); max = itemset_get_max_item_count (itemSet); /* Preload all items for flag counting and later merging comparison */ iter = itemSet->ids; while (iter) { itemPtr item = item_load (GPOINTER_TO_UINT (iter->data)); if (item) { items = g_list_append (items, item); if (item->flagStatus) flagCount++; } iter = g_list_next (iter); } debug1(DEBUG_UPDATE, "current cache size: %d", g_list_length(itemSet->ids)); debug1(DEBUG_UPDATE, "current cache limit: %d", max); debug1(DEBUG_UPDATE, "downloaded feed size: %d", g_list_length(list)); debug1(DEBUG_UPDATE, "flag count: %d", flagCount); /* Case #1: Avoid having too many flagged items. We count the flagged items and check if they are fewer than <cache limit> - <downloaded items> to ensure that all new items fit in a feed cache full of flagged items. This handling MUST NOT be invoked when the number of items is larger then the cache size, otherwise we would never remove any items for large feeds. */ if ((length < max) && (max < length + flagCount)) { max = flagCount + length; debug2 (DEBUG_UPDATE, "too many flagged items -> increasing cache limit to %u (node id=%s)", max, itemSet->nodeId); } /* 2. Avoid cache wrapping (if feed size > cache size) Truncate the new itemset if it is longer than the maximum cache size which could cause items to be dropped and added again on subsequent merges with the same feed content */ if (length > max) { debug2 (DEBUG_UPDATE, "item list too long (%u, max=%u) for merging!", length, max); /* reach max element */ for(i = 0, iter = list; (i < max) && iter; ++i) iter = g_list_next (iter); /* and remove all following elements */ while (iter) { itemPtr item = (itemPtr) iter->data; debug2 (DEBUG_UPDATE, "ignoring item nr %u (%s)...", ++i, item_get_title (item)); item_unload (item); iter = g_list_next (iter); list = g_list_remove (list, item); } } /* 3. Merge received items to existing item set Items are given in top to bottom display order. Adding them in this order would mean to reverse their order in the merged list, so merging needs to be done bottom to top. During this step the item list (items) may exceed the cache limit. */ iter = g_list_last (list); while (iter) { itemPtr item = (itemPtr)iter->data; if (markAsRead) item->readStatus = TRUE; if (itemset_merge_item (itemSet, items, item, length, allowUpdates)) { newCount++; items = g_list_prepend (items, iter->data); } iter = g_list_previous (iter); } g_list_free (list); vfolder_foreach (node_update_counters); node = node_from_id (itemSet->nodeId); if (node && (NODE_SOURCE_TYPE (node)->capabilities & NODE_SOURCE_CAPABILITY_ITEM_STATE_SYNC)) node_update_counters (node); debug1(DEBUG_UPDATE, "added %d new items", newCount); /* 4. Apply cache limit for effective item set size and unload older items as necessary. In this step it is important never to drop flagged items and to drop the oldest items only. */ if (g_list_length (items) > max) toBeDropped = g_list_length (items) - max; else toBeDropped = 0; debug3 (DEBUG_UPDATE, "%u new items, cache limit is %u -> dropping %u items", newCount, max, toBeDropped); items = g_list_sort (items, itemset_sort_by_date); iter = g_list_last (items); while (iter) { itemPtr item = (itemPtr) iter->data; if (toBeDropped > 0 && !item->flagStatus) { debug2 (DEBUG_UPDATE, "dropping item nr %u (%s)....", item->id, item_get_title (item)); droppedItems = g_list_append (droppedItems, item); /* no unloading here, it's done in itemlist_remove_items() */ toBeDropped--; } else { item_unload (item); } iter = g_list_previous (iter); } if (droppedItems) { itemlist_remove_items (itemSet, droppedItems); g_list_free (droppedItems); } /* 5. Sanity check to detect merging bugs */ if (g_list_length (items) > itemset_get_max_item_count (itemSet) + flagCount) debug0 (DEBUG_CACHE, "Fatal: Item merging bug! Resulting item list is too long! Cache limit does not work. This is a severe program bug!"); g_list_free (items); debug_end_measurement (DEBUG_UPDATE, "merge itemset"); return newCount; }
static gboolean ui_dnd_feed_drag_data_received (GtkTreeDragDest *drag_dest, GtkTreePath *dest, GtkSelectionData *selection_data) { GtkTreeIter iter, iter2, parentIter; nodePtr node, oldParent, newParent; gboolean result, valid, added; gint oldPos, pos; result = old_feed_drag_data_received (drag_dest, dest, selection_data); if (result) { if (gtk_tree_model_get_iter (GTK_TREE_MODEL (drag_dest), &iter, dest)) { gtk_tree_model_get (GTK_TREE_MODEL (drag_dest), &iter, FS_PTR, &node, -1); /* If we don't do anything, then because DnD is implemented by removal and re-insertion, and the removed node is selected, the treeview selects the next row after the removal, which is supremely irritating. But setting a selection at this point is pointless, because the treeview will reset it as soon as the DnD callback returns. Instead, we set the cursor, which controls where treeview resets the selection later. */ gtk_tree_view_set_cursor(GTK_TREE_VIEW (liferea_shell_lookup ("feedlist")), dest, NULL, FALSE); /* remove from old parents child list */ oldParent = node->parent; g_assert (oldParent); oldPos = g_slist_index (oldParent->children, node); oldParent->children = g_slist_remove (oldParent->children, node); node_update_counters (oldParent); if (0 == g_slist_length (oldParent->children)) feed_list_node_add_empty_node (feed_list_node_to_iter (oldParent->id)); /* and rebuild new parents child list */ if (gtk_tree_model_iter_parent (GTK_TREE_MODEL (drag_dest), &parentIter, &iter)) { gtk_tree_model_get (GTK_TREE_MODEL (drag_dest), &parentIter, FS_PTR, &newParent, -1); } else { gtk_tree_model_get_iter_first (GTK_TREE_MODEL (drag_dest), &parentIter); newParent = feedlist_get_root (); } /* drop old list... */ debug3 (DEBUG_GUI, "old parent is %s (%d, position=%d)", oldParent->title, g_slist_length (oldParent->children), oldPos); debug2 (DEBUG_GUI, "new parent is %s (%d)", newParent->title, g_slist_length (newParent->children)); g_slist_free (newParent->children); newParent->children = NULL; node->parent = newParent; debug0 (DEBUG_GUI, "new newParent child list:"); /* and rebuild it from the tree model */ if (feedlist_get_root() != newParent) valid = gtk_tree_model_iter_children (GTK_TREE_MODEL (drag_dest), &iter2, &parentIter); else valid = gtk_tree_model_iter_children (GTK_TREE_MODEL (drag_dest), &iter2, NULL); pos = 0; added = FALSE; while (valid) { nodePtr child; gtk_tree_model_get (GTK_TREE_MODEL (drag_dest), &iter2, FS_PTR, &child, -1); if (child) { /* Well this is a bit complicated... If we move a feed inside a folder we need to skip the old insertion point (oldPos). This is easy if the feed is added behind this position. If it is dropped before the flag added is set once the new copy is encountered. The remaining copy is skipped automatically when the flag is set. */ /* check if this is a copy of the dragged node or the original itself */ if ((newParent == oldParent) && !strcmp(node->id, child->id)) { if ((pos == oldPos) || added) { /* it is the original */ debug2 (DEBUG_GUI, " -> %d: skipping old insertion point %s", pos, child->title); } else { /* it is a copy inserted before the original */ added = TRUE; debug2 (DEBUG_GUI, " -> %d: new insertion point of %s", pos, child->title); newParent->children = g_slist_append (newParent->children, child); } } else { /* all other nodes */ debug2 (DEBUG_GUI, " -> %d: adding %s", pos, child->title); newParent->children = g_slist_append (newParent->children, child); } } else { debug0 (DEBUG_GUI, " -> removing empty node"); /* remove possible existing "(empty)" node from newParent */ feed_list_node_remove_empty_node (&parentIter); } valid = gtk_tree_model_iter_next (GTK_TREE_MODEL (drag_dest), &iter2); pos++; } db_node_update (node); node_update_counters (newParent); feedlist_schedule_save (); } } return result; }