static const gchar * htmlview_get_item_direction(itemPtr item) { if (item_get_title (item)) return (common_get_text_direction (item_get_title (item))); if (item_get_description (item)) return (common_get_text_direction (item_get_description (item))); /* what can we do? */ return ("ltr"); }
void social_add_bookmark (const itemPtr item) { gchar *link = item_make_link (item); gchar *url = social_get_bookmark_url (link, item_get_title (item)); (void)browser_tabs_add_new (url, social_get_bookmark_site(), TRUE); g_free (link); g_free (url); }
void htmlview_remove_item (itemPtr item) { htmlChunkPtr chunk; debug1 (DEBUG_HTML, "HTML view: removing \"%s\"", item_get_title (item)); chunk = g_hash_table_lookup (htmlView_priv.chunkHash, GUINT_TO_POINTER (item->id)); if (chunk) { g_hash_table_remove (htmlView_priv.chunkHash, GUINT_TO_POINTER (item->id)); htmlView_priv.orderedChunks = g_slist_remove (htmlView_priv.orderedChunks, chunk); htmlview_chunk_free (chunk); } }
void htmlview_add_item (itemPtr item) { htmlChunkPtr chunk; debug1 (DEBUG_HTML, "HTML view: adding \"%s\"", item_get_title (item)); chunk = g_new0 (struct htmlChunk, 1); chunk->id = item->id; g_hash_table_insert (htmlView_priv.chunkHash, GUINT_TO_POINTER (item->id), chunk); htmlView_priv.orderedChunks = g_slist_insert_sorted (htmlView_priv.orderedChunks, chunk, htmlview_chunk_sort); if (!item_get_description (item) || (0 == strlen (item_get_description (item)))) htmlView_priv.missingContent++; }
static void notif_libnotify_callback_show_details (NotifyNotification *n, gchar *action, gpointer user_data) { nodePtr node_p; GList *list_p; itemPtr item_p; gchar *labelText_p; gchar *labelText_now_p = NULL; gchar *labelText_prev_p; gchar *labelHeadline_p; const gchar *labelURL_p; gint item_count = 0; g_assert (action != NULL); g_assert (strcmp(action, "show_details") == 0); node_p = node_from_id (user_data); if (node_p) { itemSetPtr itemSet = node_get_itemset (node_p); labelText_now_p = g_strdup (""); /* Gather the feed's headlines */ list_p = itemSet->ids; while (list_p) { item_p = item_load (GPOINTER_TO_UINT (list_p->data)); if (item_p->popupStatus && !item_p->readStatus) { item_p->popupStatus = FALSE; item_count += 1; labelHeadline_p = g_strdup (item_get_title (item_p)); if (labelHeadline_p == NULL ) { labelHeadline_p = g_strdup_printf (_("This news entry has no headline")); } labelURL_p = item_get_base_url (item_p); if (labelURL_p) { labelText_p = g_strdup_printf ("%s <a href='%s'>%s</a>\n", labelHeadline_p, labelURL_p, _("Visit")); } else { labelText_p = g_strdup_printf ("%s\n", labelHeadline_p); } labelText_prev_p = labelText_now_p; labelText_now_p = g_strconcat(labelText_now_p, labelText_p, NULL); g_free(labelHeadline_p); g_free(labelText_p); g_free(labelText_prev_p); } item_unload (item_p); list_p = g_list_next (list_p); } itemset_free (itemSet); if (item_count == 0) { g_free (labelText_now_p); return; } } else { ui_show_error_box(_("This feed does not exist anymore!")); } notify_notification_close (n, NULL); if (node_p) { // notify_notification_update ( n, node_get_title(node_p), labelText_now_p, NULL); // notify_notification_clear_actions(n); n = notify_notification_new (node_get_title (node_p), labelText_now_p, NULL, NULL); notify_notification_set_icon_from_pixbuf (n, node_get_icon (node_p)); notify_notification_set_category (n, "feed"); notify_notification_set_timeout (n, NOTIFY_EXPIRES_NEVER); if (supports_actions) { notify_notification_add_action (n, "open", _("Open feed"), (NotifyActionCallback)notif_libnotify_callback_open, node_p->id, NULL); notify_notification_add_action (n, "mark_read", _("Mark all as read"), (NotifyActionCallback)notif_libnotify_callback_mark_read, node_p->id, NULL); } notify_notification_attach_to_status_icon (n, ui_tray_get_status_icon ()); if (!notify_notification_show (n, NULL)) { g_warning ("libnotify.c - failed to update notification via libnotify\n"); } g_free (labelText_now_p); } }
/** * itemset_generic_merge_check: (skip) * @items: existing items * @newItem: new item to merge * @maxChecks: maximum number of item checks * @allowUpdates: TRUE if item content update is to be * allowed for existing items * @allowStateChanges: TRUE if item state shall be * overwritten by source * * Generic merge logic suitable for feeds * * Returns: TRUE if merging instead of updating is necessary) */ static gboolean itemset_generic_merge_check (GList *items, itemPtr newItem, gint maxChecks, gboolean allowUpdates, gboolean allowStateChanges) { GList *oldItemIdIter = items; itemPtr oldItem = NULL; gboolean found, equal = FALSE; guint reason = 0; /* determine if we should add it... */ debug3 (DEBUG_CACHE, "check new item for merging: \"%s\", %i, %i", item_get_title (newItem), allowUpdates, allowStateChanges); /* compare to every existing item in this feed */ found = FALSE; while (oldItemIdIter) { oldItem = (itemPtr)(oldItemIdIter->data); /* try to compare the two items */ /* trivial case: one item has id the other doesn't -> they can't be equal */ if (((item_get_id (oldItem) == NULL) && (item_get_id (newItem) != NULL)) || ((item_get_id (oldItem) != NULL) && (item_get_id (newItem) == NULL))) { /* cannot be equal (different ids) so compare to next old item */ oldItemIdIter = g_list_next (oldItemIdIter); continue; } /* just for the case there are no ids: compare titles and HTML descriptions */ equal = TRUE; if (((item_get_title (oldItem) != NULL) && (item_get_title (newItem) != NULL)) && (0 != strcmp (item_get_title (oldItem), item_get_title (newItem)))) { equal = FALSE; reason |= 1; } if (((item_get_description (oldItem) != NULL) && (item_get_description (newItem) != NULL)) && (0 != strcmp (item_get_description(oldItem), item_get_description (newItem)))) { equal = FALSE; reason |= 2; } /* best case: they both have ids (position important: id check is useless without knowing if the items are different!) */ if (item_get_id (oldItem)) { if (0 == strcmp (item_get_id (oldItem), item_get_id (newItem))) { found = TRUE; if (allowStateChanges) { /* found corresponding item, check if they are REALLY equal (eg, read status may have changed) */ if(oldItem->readStatus != newItem->readStatus) { equal = FALSE; reason |= 4; } if(oldItem->flagStatus != newItem->flagStatus) { equal = FALSE; reason |= 8; } } break; } else { /* different ids, but the content might be still equal (e.g. empty) so we need to explicitly unset the equal flag !!! */ equal = FALSE; reason |= 16; } } if (equal) { found = TRUE; break; } oldItemIdIter = g_list_next (oldItemIdIter); } if (!found) { debug0 (DEBUG_CACHE, "-> item is to be added"); } else { /* if the item was found but has other contents -> update contents */ if (!equal) { if (allowUpdates) { /* no item_set_new_status() - we don't treat changed items as new items! */ item_set_title (oldItem, item_get_title (newItem)); /* don't use item_set_description as it does some unwanted length handling and we want to enforce the new description */ g_free (oldItem->description); oldItem->description = newItem->description; newItem->description = NULL; oldItem->time = newItem->time; oldItem->updateStatus = TRUE; // FIXME: this does not remove metadata from DB metadata_list_free (oldItem->metadata); oldItem->metadata = newItem->metadata; newItem->metadata = NULL; /* Only update item state for feed sources where it is necessary which means online accounts we sync against, but not normal online feeds where items have no read status. */ if (allowStateChanges) { /* To avoid notification spam from external sources: never set read items to unread again! */ if ((!oldItem->readStatus) && (newItem->readStatus)) oldItem->readStatus = newItem->readStatus; oldItem->flagStatus = newItem->flagStatus; } db_item_update (oldItem); debug1 (DEBUG_CACHE, "-> item already existing and was updated, reason %x", reason); } else { debug0 (DEBUG_CACHE, "-> item updates not merged because of parser errors"); } } else { debug0 (DEBUG_CACHE, "-> item already exists"); } } return !found; }
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 itemset_merge_item (itemSetPtr itemSet, GList *items, itemPtr item, gint maxChecks, gboolean allowUpdates) { gboolean allowStateChanges = FALSE; gboolean merge; nodePtr node; debug2 (DEBUG_UPDATE, "trying to merge \"%s\" to node id \"%s\"", item_get_title (item), itemSet->nodeId); g_assert (itemSet->nodeId); node = node_from_id (itemSet->nodeId); if (node) allowStateChanges = NODE_SOURCE_TYPE (node)->capabilities & NODE_SOURCE_CAPABILITY_ITEM_STATE_SYNC; /* first try to merge with existing item */ merge = itemset_generic_merge_check (items, item, maxChecks, allowUpdates, allowStateChanges); /* if it is a new item add it to the item set */ if (merge) { g_assert (!item->nodeId); g_assert (!item->id); item->nodeId = g_strdup (itemSet->nodeId); if (!item->parentNodeId) item->parentNodeId = g_strdup (itemSet->nodeId); /* step 1: write item to DB */ db_item_update (item); /* step 2: add to itemset */ itemSet->ids = g_list_prepend (itemSet->ids, GUINT_TO_POINTER (item->id)); /* step 3: trigger async enrichment of item description */ if (node && IS_FEED (node) && ((feedPtr)node->data)->html5Extract) feed_enrich_item (node->subscription, item); debug3 (DEBUG_UPDATE, "-> added \"%s\" (id=%d) to item set %p...", item_get_title (item), item->id, itemSet); /* step 4: duplicate detection, mark read if it is a duplicate */ if (item->validGuid) { GSList *iter, *duplicates; duplicates = iter = db_item_get_duplicates (item->sourceId); while (iter) { debug1 (DEBUG_UPDATE, "-> duplicate guid exists: #%lu", GPOINTER_TO_UINT (iter->data)); iter = g_slist_next (iter); } if (g_slist_length (duplicates) > 1) { item->readStatus = TRUE; /* no unread counting... */ item->popupStatus = FALSE; /* no notification... */ } g_slist_free (duplicates); } /* step 5: Check item for new enclosures to download */ if (node && (((feedPtr)node->data)->encAutoDownload)) { GSList *iter = metadata_list_get_values (item->metadata, "enclosure"); while (iter) { enclosurePtr enc = enclosure_from_string (iter->data); debug1 (DEBUG_UPDATE, "download enclosure (%s)", (gchar *)iter->data); enclosure_download (NULL, enc->url, FALSE /* non interactive */); iter = g_slist_next (iter); enclosure_free (enc); } } } else { debug2 (DEBUG_UPDATE, "-> not adding \"%s\" to node id \"%s\"...", item_get_title (item), itemSet->nodeId); item_unload (item); } return merge; }
void htmlview_update (LifereaHtmlView *htmlview, itemViewMode mode) { GSList *iter; GString *output; itemPtr item = NULL; gchar *baseURL = NULL; gboolean summaryMode; /* determine base URL */ switch (mode) { case ITEMVIEW_SINGLE_ITEM: item = itemlist_get_selected (); if(item) { baseURL = (gchar *)node_get_base_url (node_from_id (item->nodeId)); item_unload (item); } break; default: if (htmlView_priv.node) baseURL = (gchar *) node_get_base_url (htmlView_priv.node); break; } if (baseURL) baseURL = g_markup_escape_text (baseURL, -1); output = g_string_new (NULL); htmlview_start_output (output, baseURL, TRUE, TRUE); /* HTML view updating means checking which items need to be updated, render them and then concatenate everything from cache and output it */ switch (mode) { case ITEMVIEW_SINGLE_ITEM: item = itemlist_get_selected (); if (item) { gchar *html = htmlview_render_item (item, mode, FALSE); if (html) { g_string_append (output, html); g_free (html); } item_unload (item); } break; case ITEMVIEW_ALL_ITEMS: /* Output optimization for feeds without item content. This is not done for folders, because we only support all items in summary mode or all in detailed mode. With folder item sets displaying everything in summary because of only a single feed without item descriptions would make no sense. */ summaryMode = (NULL != htmlView_priv.node) && !IS_FOLDER (htmlView_priv.node) && !IS_VFOLDER (htmlView_priv.node) && (htmlView_priv.missingContent > 3); /* concatenate all items */ iter = htmlView_priv.orderedChunks; while (iter) { /* try to retrieve item HTML chunk from cache */ htmlChunkPtr chunk = (htmlChunkPtr)iter->data; /* if not found: render new item now and add to cache */ if (!chunk->html) { item = item_load (chunk->id); if (item) { debug1 (DEBUG_HTML, "rendering item to HTML view: >>>%s<<<", item_get_title (item)); chunk->html = htmlview_render_item (item, mode, summaryMode); item_unload (item); } } if (chunk->html) g_string_append (output, chunk->html); iter = g_slist_next (iter); } break; case ITEMVIEW_NODE_INFO: { gchar *html; if (htmlView_priv.node) { html = node_render (htmlView_priv.node); if (html) { g_string_append (output, html); g_free (html); } } } break; default: g_warning ("HTML view: invalid viewing mode!!!"); break; } htmlview_finish_output (output); debug1 (DEBUG_HTML, "writing %d bytes to HTML view", strlen (output->str)); liferea_htmlview_write (htmlview, output->str, baseURL); g_string_free (output, TRUE); g_free (baseURL); }
void htmlview_select_item (itemPtr item) { debug1(DEBUG_HTML, "HTML view: selecting \"%s\"", item?item_get_title(item):"none"); /* nothing to do... */ }