Ejemplo n.º 1
0
void
db_item_state_update (itemPtr item)
{
	sqlite3_stmt	*stmt;
	
	if (!item->id) {
		db_item_update (item);
		return;
	}

	db_item_search_folders_update (item);

	debug_start_measurement (DEBUG_DB);
	
	stmt = db_get_statement ("itemStateUpdateStmt");
	sqlite3_bind_int (stmt, 1, item->readStatus?1:0);
	sqlite3_bind_int (stmt, 2, item->flagStatus?1:0);
	sqlite3_bind_int (stmt, 3, item->updateStatus?1:0);
	sqlite3_bind_int (stmt, 4, item->id);

	if (sqlite3_step (stmt) != SQLITE_DONE) 
		g_warning ("item state update failed (%s)", sqlite3_errmsg (db));
	
	sqlite3_finalize (stmt);

	debug_end_measurement (DEBUG_DB, "item state update");

}
Ejemplo n.º 2
0
void
db_subscription_update (subscriptionPtr subscription)
{
	sqlite3_stmt	*stmt;
	gint		res;
	
	debug1 (DEBUG_DB, "updating subscription info %s", subscription->node->id);
	debug_start_measurement (DEBUG_DB);
	
	stmt = db_get_statement ("subscriptionUpdateStmt");
	sqlite3_bind_text (stmt, 1, subscription->node->id, -1, SQLITE_TRANSIENT);
	sqlite3_bind_text (stmt, 2, subscription->source, -1, SQLITE_TRANSIENT);
	sqlite3_bind_text (stmt, 3, subscription->origSource, -1, SQLITE_TRANSIENT);	
	sqlite3_bind_text (stmt, 4, subscription->filtercmd, -1, SQLITE_TRANSIENT);
	sqlite3_bind_int  (stmt, 5, subscription->updateInterval);
	sqlite3_bind_int  (stmt, 6, subscription->defaultInterval);
	sqlite3_bind_int  (stmt, 7, subscription->discontinued?1:0);
	sqlite3_bind_int  (stmt, 8, (subscription->updateError ||
	                             subscription->httpError ||
				     subscription->filterError)?1:0);
	
	res = sqlite3_step (stmt);
	if (SQLITE_DONE != res)
		g_warning ("Could not update subscription info for node id %s in DB (error code %d)!", subscription->node->id, res);
	
	sqlite3_finalize (stmt);

	db_subscription_metadata_update (subscription);
		
	debug_end_measurement (DEBUG_DB, "subscription update");
}
Ejemplo n.º 3
0
GSList *
db_item_get_duplicate_nodes (const gchar *guid)
{
	GSList		*duplicates = NULL;
	sqlite3_stmt	*stmt;
	gint		res;

	debug_start_measurement (DEBUG_DB);

	stmt = db_get_statement ("duplicateNodesFindStmt");
	res = sqlite3_bind_text (stmt, 1, guid, -1, SQLITE_TRANSIENT);
	if (SQLITE_OK != res)
		g_error ("db_item_get_duplicates: sqlite bind failed (error code %d)!", res);

	while (sqlite3_step (stmt) == SQLITE_ROW) 
	{
		gchar *id = g_strdup( sqlite3_column_text (stmt, 0));
		duplicates = g_slist_append (duplicates, id);
	}

	sqlite3_finalize (stmt);

	debug_end_measurement (DEBUG_DB, "searching for duplicates");

	return duplicates;
}
Ejemplo n.º 4
0
itemPtr
db_item_load (gulong id) 
{
	sqlite3_stmt	*stmt;
	itemPtr 	item = NULL;

	debug1 (DEBUG_DB, "loading item %lu", id);
	debug_start_measurement (DEBUG_DB);
	
	stmt = db_get_statement ("itemLoadStmt");
	sqlite3_bind_int (stmt, 1, id);

	if (sqlite3_step (stmt) == SQLITE_ROW) {
		item = db_load_item_from_columns (stmt);
		sqlite3_step (stmt);
	} else {
		debug1 (DEBUG_DB, "Could not load item with id %lu!", id);
	}
	
	sqlite3_finalize (stmt);

	debug_end_measurement (DEBUG_DB, "item load");

	return item;
}
Ejemplo n.º 5
0
void
db_node_update (nodePtr node)
{
	sqlite3_stmt	*stmt;
	gint		res;
	
	debug1 (DEBUG_DB, "updating node info %s", node->id);
	debug_start_measurement (DEBUG_DB);
	
	stmt = db_get_statement ("nodeUpdateStmt");
	sqlite3_bind_text (stmt, 1, node->id, -1, SQLITE_TRANSIENT);
	sqlite3_bind_text (stmt, 2, node->parent->id, -1, SQLITE_TRANSIENT);
	sqlite3_bind_text (stmt, 3, node->title, -1, SQLITE_TRANSIENT);	
	sqlite3_bind_text (stmt, 4, node_type_to_str (node), -1, SQLITE_TRANSIENT);
	sqlite3_bind_int  (stmt, 5, node->expanded?1:0);
	sqlite3_bind_int  (stmt, 6, node->viewMode);
	sqlite3_bind_int  (stmt, 7, node->sortColumn);
	sqlite3_bind_int  (stmt, 8, node->sortReversed?1:0);
	
	res = sqlite3_step (stmt);
	if (SQLITE_DONE != res)
		g_warning ("Could not update node info %s in DB (error code %d)!", node->id, res);

	sqlite3_finalize (stmt);
		
	debug_end_measurement (DEBUG_DB, "node update");
}
Ejemplo n.º 6
0
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");
}
Ejemplo n.º 7
0
void
db_item_update (itemPtr item) 
{
	sqlite3_stmt	*stmt;
	gint		res;
	
	debug2 (DEBUG_DB, "update of item \"%s\" (id=%lu)", item->title, item->id);
	debug_start_measurement (DEBUG_DB);
	
	db_begin_transaction ();

	if (!item->id) {
		db_item_set_id (item);

		debug1(DEBUG_DB, "insert into table \"items\": \"%s\"", item->title);	
	}

	/* Update the item... */
	stmt = db_get_statement ("itemUpdateStmt");
	sqlite3_bind_text (stmt, 1,  item->title, -1, SQLITE_TRANSIENT);
	sqlite3_bind_int  (stmt, 2,  item->readStatus?1:0);
	sqlite3_bind_int  (stmt, 3,  item->updateStatus?1:0);
	sqlite3_bind_int  (stmt, 4,  item->popupStatus?1:0);
	sqlite3_bind_int  (stmt, 5,  item->flagStatus?1:0);
	sqlite3_bind_text (stmt, 6,  item->source, -1, SQLITE_TRANSIENT);
	sqlite3_bind_text (stmt, 7,  item->sourceId, -1, SQLITE_TRANSIENT);
	sqlite3_bind_int  (stmt, 8,  item->validGuid?1:0);
	sqlite3_bind_text (stmt, 9,  item->description, -1, SQLITE_TRANSIENT);
	sqlite3_bind_int  (stmt, 10, item->time);
	sqlite3_bind_text (stmt, 11, item->commentFeedId, -1, SQLITE_TRANSIENT);
	sqlite3_bind_int  (stmt, 12, item->isComment?1:0);
	sqlite3_bind_int  (stmt, 13, item->id);
	sqlite3_bind_int  (stmt, 14, item->parentItemId);
	sqlite3_bind_text (stmt, 15, item->nodeId, -1, SQLITE_TRANSIENT);
	sqlite3_bind_text (stmt, 16, item->parentNodeId, -1, SQLITE_TRANSIENT);

	res = sqlite3_step (stmt);

	if (SQLITE_DONE != res) 
		g_warning ("item update failed (error code=%d, %s)", res, sqlite3_errmsg (db));

	sqlite3_finalize (stmt);

	db_item_metadata_update (item);
	db_item_search_folders_update (item);

	db_end_transaction ();

	debug_end_measurement (DEBUG_DB, "item update");
}
Ejemplo n.º 8
0
/**
 * To be called whenever an itemset was updated. If it is the
 * displayed itemset it will be merged against the item view.
 */
void
itemlist_merge_itemset (itemSetPtr itemSet) 
{
	debug_enter ("itemlist_merge_itemset");
	
	if (itemlist_itemset_is_valid (itemSet)) {
		debug_start_measurement (DEBUG_GUI);
		itemset_foreach (itemSet, itemlist_merge_item);
		itemview_update ();
		debug_end_measurement (DEBUG_GUI, "itemlist merge");
	}

	debug_exit ("itemlist_merge_itemset");
}
Ejemplo n.º 9
0
/**
 * In difference to all the other item state handling methods
 * item_state_set_all_read does not immediately apply the
 * changes to the GUI because it is usually called recursively
 * and would be to slow. Instead the node structure flag for
 * recounting is set. By calling feedlist_update() afterwards
 * those recounts are executed and applied to the GUI.
 */
void
itemset_mark_read (nodePtr node)
{
    itemSetPtr	itemSet;

    if (!node->unreadCount)
        return;

    itemSet = node_get_itemset (node);
    GList *iter = itemSet->ids;
    while (iter) {
        gulong id = GPOINTER_TO_UINT (iter->data);
        itemPtr item = item_load (id);
        if (item) {
            if (!item->readStatus) {
                nodePtr node;

                node = node_from_id (item->nodeId);
                if (node) {
                    item_state_set_recount_flag (node);
                    node_source_item_mark_read (node, item, TRUE);
                } else {
                    g_warning ("itemset_mark_read() on lost item (id=%lu, node id=%s)!", item->id, item->nodeId);
                }

                debug_start_measurement (DEBUG_GUI);

                GSList *duplicates = db_item_get_duplicate_nodes (item->sourceId);
                GSList *duplicate = duplicates;
                while (duplicate) {
                    gchar *nodeId = (gchar *)duplicate->data;
                    nodePtr affectedNode = node_from_id (nodeId);
                    if (affectedNode)
                        item_state_set_recount_flag (affectedNode);
                    g_free (nodeId);
                    duplicate = g_slist_next (duplicate);
                }
                g_slist_free(duplicates);

                debug_end_measurement (DEBUG_GUI, "mark read of duplicates");
            }
            item_unload (item);
        }
        iter = g_list_next (iter);
    }
}
Ejemplo n.º 10
0
/**
 * To be called whenever an itemset was updated. If it is the
 * displayed itemset it will be merged against the item list
 * tree view.
 */
void
itemlist_merge_itemset (itemSetPtr itemSet) 
{
	gint	folder_display_mode;

	debug_enter ("itemlist_merge_itemset");
	
	debug_start_measurement (DEBUG_GUI);
	
	/* No node check when loading search results directly */
	if (!itemlist_priv.isSearchResult) {
		nodePtr node = node_from_id (itemSet->nodeId);

		if (!itemlist_priv.currentNode)
			return; /* Nothing to do if nothing is displayed */
		
		if (!IS_VFOLDER (itemlist_priv.currentNode) &&
		    (itemlist_priv.currentNode != node) && 
		    !node_is_ancestor (itemlist_priv.currentNode, node))
			return; /* Nothing to do if the item set does not belong to this node, or this is a search folder */

		conf_get_int_value (FOLDER_DISPLAY_MODE, &folder_display_mode);
		if (IS_FOLDER (itemlist_priv.currentNode) && !folder_display_mode)
			return; /* Bail out if it is a folder without the recursive display preference set */
			
		debug1 (DEBUG_GUI, "reloading item list with node \"%s\"", node_get_title (node));
	} else {
		/* If we are loading a search result we must never merge 
		   anything besides the search items. In fact if we already
		   have items we just return. */
		if (itemlist_priv.searchResultComplete)
			return;
			
		itemlist_priv.searchResultComplete = TRUE;
	}

	/* merge items into item view */
	itemset_foreach (itemSet, itemlist_merge_item);
	
	itemview_update ();
	
	debug_end_measurement (DEBUG_GUI, "itemlist merge");

	debug_exit ("itemlist_merge_itemset");
}
Ejemplo n.º 11
0
static void
db_vacuum (void)
{
	sqlite3_stmt	*stmt;
	gint		res, page_count, freelist_count;

	/* Determine fragmentation ratio using 

		PRAGMA page_count
		PRAGMA freelist_count

	   as suggested by adriatic in this blog post
	   http://jeff.ecchi.ca/blog/2011/12/24/investigating-lifereas-startup-performance/#comment-19989	
	   and perform VACUUM only when needed.
	 */

	db_prepare_stmt (&stmt, "PRAGMA page_count");
	sqlite3_reset (stmt);
	res = sqlite3_step (stmt);
	if (SQLITE_ROW != res) 
		g_error ("Could not determine page count (error code %d)!", res);
	page_count = sqlite3_column_int (stmt, 0);
	sqlite3_finalize (stmt);

	db_prepare_stmt (&stmt, "PRAGMA freelist_count");
	sqlite3_reset (stmt);
	res = sqlite3_step (stmt);
	if (SQLITE_ROW != res) 
		g_error ("Could not determine free list count (error code %d)!", res);
	freelist_count = sqlite3_column_int (stmt, 0);
	sqlite3_finalize (stmt);

	float fragmentation = (100 * (float)freelist_count/page_count);
	if (fragmentation > VACUUM_ON_FRAGMENTATION_RATIO) {
		debug2 (DEBUG_DB, "Performing VACUUM as freelist count/page count ratio %2.2f > %d", 
		                  fragmentation, VACUUM_ON_FRAGMENTATION_RATIO);
		debug_start_measurement (DEBUG_DB);
		db_exec ("VACUUM;");
		debug_end_measurement (DEBUG_DB, "VACUUM");
	} else {
		debug2 (DEBUG_DB, "No VACUUM as freelist count/page count ratio %2.2f <= %d", 
		                  fragmentation, VACUUM_ON_FRAGMENTATION_RATIO);
	}
}
Ejemplo n.º 12
0
void
db_subscription_remove (const gchar *id)
{
	sqlite3_stmt	*stmt;
	gint		res;

	debug1 (DEBUG_DB, "removing subscription %s", id);
	debug_start_measurement (DEBUG_DB);
	
	stmt = db_get_statement ("subscriptionRemoveStmt");
	sqlite3_bind_text (stmt, 1, id, -1, SQLITE_TRANSIENT);

	res = sqlite3_step (stmt);
	if (SQLITE_DONE != res)
		g_warning ("Could not remove subscription %s from DB (error code %d)!", id, res);

	sqlite3_finalize (stmt);

	debug_end_measurement (DEBUG_DB, "subscription remove");
}
Ejemplo n.º 13
0
/**
 * In difference to all the other item state handling methods
 * item_state_set_all_read does not immediately apply the 
 * changes to the GUI because it is usually called recursively
 * and would be to slow. Instead the node structure flag for
 * recounting is set. By calling feedlist_update() afterwards
 * those recounts are executed and applied to the GUI.
 */
void
itemset_mark_read (nodePtr node)
{
	itemSetPtr	itemSet;

	itemSet = node_get_itemset (node);
	GList *iter = itemSet->ids;
	while (iter) {
		gulong id = GPOINTER_TO_UINT (iter->data);
		itemPtr item = item_load (id);
		if (item) {
			if (!item->readStatus) {
				nodePtr node = node_from_id (item->nodeId);
				if (node) {
					item_state_set_recount_flag (node);
					node_source_item_mark_read (node, item, TRUE);
				}

				debug_start_measurement (DEBUG_GUI);

				GSList *duplicates = db_item_get_duplicate_nodes (item->sourceId);
				GSList *duplicate = duplicates;
				while (duplicate) {
					gchar *nodeId = (gchar *)duplicate->data;
					nodePtr affectedNode = node_from_id (nodeId);
					if (affectedNode)
						item_state_set_recount_flag (affectedNode);
					g_free (nodeId);
					duplicate = g_slist_next (duplicate);
				}
				g_slist_free(duplicates);

				debug_end_measurement (DEBUG_GUI, "mark read of duplicates");
			}
			item_unload (item);
		}
		iter = g_list_next (iter);
	}

	// FIXME: why not call itemset_free (itemSet); here? Crashes!
}
Ejemplo n.º 14
0
guint 
db_search_folder_get_item_count (const gchar *id) 
{
	sqlite3_stmt	*stmt;
	gint		res;
	guint		count = 0;
	
	debug_start_measurement (DEBUG_DB);
	
	stmt = db_get_statement ("searchFolderCountStmt");
	sqlite3_bind_text (stmt, 1, id, -1, SQLITE_TRANSIENT);
	res = sqlite3_step (stmt);
	
	if (SQLITE_ROW == res)
		count = sqlite3_column_int (stmt, 0);
	else
		g_warning("item read counting failed (error code=%d, %s)", res, sqlite3_errmsg (db));
		
	sqlite3_finalize (stmt);

	debug_end_measurement (DEBUG_DB, "counting unread items");

	return count;
}
Ejemplo n.º 15
0
Archivo: main.c Proyecto: tiwoc/liferea
int
main (int argc, char *argv[])
{
	GtkApplication	*app;
	GError		*error = NULL;
	GOptionContext	*context;
	GOptionGroup	*debug;
	gulong		debug_flags = 0;
	LifereaDBus	*dbus = NULL;
	gchar		*feedUri = NULL;
	gint 		status;

	GOptionEntry entries[] = {
		{ "mainwindow-state", 'w', 0, G_OPTION_ARG_STRING, &initialStateOption, N_("Start Liferea with its main window in STATE. STATE may be `shown', `iconified', or `hidden'"), N_("STATE") },
		{ "version", 'v', G_OPTION_FLAG_NO_ARG, G_OPTION_ARG_CALLBACK, show_version, N_("Show version information and exit"), NULL },
		{ "add-feed", 'a', 0, G_OPTION_ARG_STRING, &feedUri, N_("Add a new subscription"), N_("uri") },
		{ NULL, 0, 0, 0, NULL, NULL, NULL }
	};

	GOptionEntry debug_entries[] = {
		{ "debug-all", 0, G_OPTION_FLAG_NO_ARG, G_OPTION_ARG_CALLBACK, debug_entries_parse_callback, N_("Print debugging messages of all types"), NULL },
		{ "debug-cache", 0, G_OPTION_FLAG_NO_ARG, G_OPTION_ARG_CALLBACK, debug_entries_parse_callback, N_("Print debugging messages for the cache handling"), NULL },
		{ "debug-conf", 0, G_OPTION_FLAG_NO_ARG, G_OPTION_ARG_CALLBACK, debug_entries_parse_callback, N_("Print debugging messages for the configuration handling"), NULL },
		{ "debug-db", 0, G_OPTION_FLAG_NO_ARG, G_OPTION_ARG_CALLBACK, debug_entries_parse_callback, N_("Print debugging messages of the database handling"), NULL },
		{ "debug-gui", 0, G_OPTION_FLAG_NO_ARG, G_OPTION_ARG_CALLBACK, debug_entries_parse_callback, N_("Print debugging messages of all GUI functions"), NULL },
		{ "debug-html", 0, G_OPTION_FLAG_NO_ARG, G_OPTION_ARG_CALLBACK, debug_entries_parse_callback, N_("Enables HTML rendering debugging. Each time Liferea renders HTML output it will also dump the generated HTML into ~/.cache/liferea/output.xhtml"), NULL },
		{ "debug-net", 0, G_OPTION_FLAG_NO_ARG, G_OPTION_ARG_CALLBACK, debug_entries_parse_callback, N_("Print debugging messages of all network activity"), NULL },
		{ "debug-parsing", 0, G_OPTION_FLAG_NO_ARG, G_OPTION_ARG_CALLBACK, debug_entries_parse_callback, N_("Print debugging messages of all parsing functions"), NULL },
		{ "debug-performance", 0, G_OPTION_FLAG_NO_ARG, G_OPTION_ARG_CALLBACK, debug_entries_parse_callback, N_("Print debugging messages when a function takes too long to process"), NULL },
		{ "debug-trace", 0, G_OPTION_FLAG_NO_ARG, G_OPTION_ARG_CALLBACK, debug_entries_parse_callback, N_("Print debugging messages when entering/leaving functions"), NULL },
		{ "debug-update", 0, G_OPTION_FLAG_NO_ARG, G_OPTION_ARG_CALLBACK, debug_entries_parse_callback, N_("Print debugging messages of the feed update processing"), NULL },
		{ "debug-vfolder", 0, G_OPTION_FLAG_NO_ARG, G_OPTION_ARG_CALLBACK, debug_entries_parse_callback, N_("Print debugging messages of the search folder matching"), NULL },
		{ "debug-verbose", 0, G_OPTION_FLAG_NO_ARG, G_OPTION_ARG_CALLBACK, debug_entries_parse_callback, N_("Print verbose debugging messages"), NULL },
		{ NULL, 0, 0, 0, NULL, NULL, NULL }
	};

#ifdef ENABLE_NLS
	bindtextdomain (GETTEXT_PACKAGE, PACKAGE_LOCALE_DIR);
	bind_textdomain_codeset (GETTEXT_PACKAGE, "UTF-8");
	textdomain (GETTEXT_PACKAGE);
#endif

	debug = g_option_group_new ("debug",
				    _("Print debugging messages for the given topic"),
				    _("Print debugging messages for the given topic"),
				    &debug_flags,
				    NULL);
	g_option_group_set_translation_domain(debug, GETTEXT_PACKAGE);
	g_option_group_add_entries (debug, debug_entries);

	context = g_option_context_new (NULL);
	g_option_context_set_summary (context, N_("Liferea, the Linux Feed Reader"));
	g_option_context_set_description (context, N_("For more information, please visit http://lzone.de/liferea/"));
	g_option_context_add_main_entries (context, entries, GETTEXT_PACKAGE);
	g_option_context_set_translation_domain(context, GETTEXT_PACKAGE);
	g_option_context_add_group (context, debug);
	g_option_context_add_group (context, gtk_get_option_group (FALSE));
	g_option_context_add_group (context, g_irepository_get_option_group ());

	g_option_context_parse (context, &argc, &argv, &error);
	g_option_context_free (context);
	if (error) {
		g_print ("Error parsing options: %s\n", error->message);
	}

	set_debug_level (debug_flags);

	/* Configuration necessary for network options, so it
	   has to be initialized before update_init() */
	conf_init ();

	/* We need to do the network initialization here to allow
	   network-manager to be setup before gtk_init() */
	update_init ();

	gtk_init (&argc, &argv);

	/* Single instance checks, also note that we pass or only RPC (add-feed)
	   as activate signal payload as it is simply an URI string. */
	app = gtk_application_new ("net.sourceforge.liferea", G_APPLICATION_HANDLES_OPEN);
	g_signal_connect (app, "activate", G_CALLBACK (on_app_activate), feedUri);
	g_signal_connect (app, "open", G_CALLBACK (on_feed_add), NULL);

	g_set_prgname ("liferea");
	g_set_application_name (_("Liferea"));
	gtk_window_set_default_icon_name ("liferea");	/* GTK theme support */

	debug_start_measurement (DEBUG_DB);

	/* order is important! */
	db_init ();			/* initialize sqlite */
	xml_init ();			/* initialize libxml2 */
#ifdef HAVE_LIBNOTIFY
	notification_plugin_register (&libnotify_plugin);
#endif
	social_init ();			/* initialize social bookmarking */

	dbus = liferea_dbus_new ();

	signal (SIGTERM, signal_handler);
	signal (SIGINT, signal_handler);
	signal (SIGHUP, signal_handler);

	/* Note: we explicitely do not use the gdk_thread_*
	   locking in Liferea because it freezes the program
	   when running Flash applets */

	runState = STATE_STARTING;
	
	debug_end_measurement (DEBUG_DB, "startup");

	status = g_application_run (G_APPLICATION (app), 0, NULL);

	/* Trigger RPCs if we are not primary instance (currently only feed-add) */
	if (feedUri && g_application_get_is_remote (G_APPLICATION (app))) {
		GFile *uris[2];

		uris[0] = g_file_new_for_uri (feedUri);
		uris[1] = NULL;
		g_application_open (G_APPLICATION (app), uris, 1, "feed-add");
		g_object_unref (uris[0]);
	}
	
	g_object_unref (G_OBJECT (dbus));
	g_object_unref (app);

	return status;
}
Ejemplo n.º 16
0
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;
}
Ejemplo n.º 17
0
int
main (int argc, char *argv[])
{
	UniqueApp	*app;
	UniqueMessageData	*msg;
	GError		*error = NULL;
	GOptionContext	*context;
	GOptionGroup	*debug;
	gulong		debug_flags = 0;
	LifereaDBus	*dbus = NULL;
	const gchar	*initial_state = "shown";
	gchar		*feed = NULL;
	int		initialState;
	gboolean	show_tray_icon, start_in_tray;

#ifdef USE_SM
	gchar *opt_session_arg = NULL;
#endif

	GOptionEntry entries[] = {
		{ "mainwindow-state", 'w', 0, G_OPTION_ARG_STRING, &initial_state, N_("Start Liferea with its main window in STATE. STATE may be `shown', `iconified', or `hidden'"), N_("STATE") },
#ifdef USE_SM
		{ "session", 0, G_OPTION_FLAG_HIDDEN, G_OPTION_ARG_STRING, &opt_session_arg, NULL, NULL },
#endif
		{ "version", 'v', G_OPTION_FLAG_NO_ARG, G_OPTION_ARG_CALLBACK, show_version, N_("Show version information and exit"), NULL },
		{ "add-feed", 'a', 0, G_OPTION_ARG_STRING, &feed, N_("Add a new subscription"), N_("uri") },
		{ NULL }
	};

	GOptionEntry debug_entries[] = {
		{ "debug-all", 0, G_OPTION_FLAG_NO_ARG, G_OPTION_ARG_CALLBACK, debug_entries_parse_callback, N_("Print debugging messages of all types"), NULL },
		{ "debug-cache", 0, G_OPTION_FLAG_NO_ARG, G_OPTION_ARG_CALLBACK, debug_entries_parse_callback, N_("Print debugging messages for the cache handling"), NULL },
		{ "debug-conf", 0, G_OPTION_FLAG_NO_ARG, G_OPTION_ARG_CALLBACK, debug_entries_parse_callback, N_("Print debugging messages for the configuration handling"), NULL },
		{ "debug-db", 0, G_OPTION_FLAG_NO_ARG, G_OPTION_ARG_CALLBACK, debug_entries_parse_callback, N_("Print debugging messages of the database handling"), NULL },
		{ "debug-gui", 0, G_OPTION_FLAG_NO_ARG, G_OPTION_ARG_CALLBACK, debug_entries_parse_callback, N_("Print debugging messages of all GUI functions"), NULL },
		{ "debug-html", 0, G_OPTION_FLAG_NO_ARG, G_OPTION_ARG_CALLBACK, debug_entries_parse_callback, N_("Enables HTML rendering debugging. Each time Liferea renders HTML output it will also dump the generated HTML into ~/.liferea_1.6/output.xhtml"), NULL },
		{ "debug-net", 0, G_OPTION_FLAG_NO_ARG, G_OPTION_ARG_CALLBACK, debug_entries_parse_callback, N_("Print debugging messages of all network activity"), NULL },
		{ "debug-parsing", 0, G_OPTION_FLAG_NO_ARG, G_OPTION_ARG_CALLBACK, debug_entries_parse_callback, N_("Print debugging messages of all parsing functions"), NULL },
		{ "debug-performance", 0, G_OPTION_FLAG_NO_ARG, G_OPTION_ARG_CALLBACK, debug_entries_parse_callback, N_("Print debugging messages when a function takes too long to process"), NULL },
		{ "debug-trace", 0, G_OPTION_FLAG_NO_ARG, G_OPTION_ARG_CALLBACK, debug_entries_parse_callback, N_("Print debugging messages when entering/leaving functions"), NULL },
		{ "debug-update", 0, G_OPTION_FLAG_NO_ARG, G_OPTION_ARG_CALLBACK, debug_entries_parse_callback, N_("Print debugging messages of the feed update processing"), NULL },
		{ "debug-vfolder", 0, G_OPTION_FLAG_NO_ARG, G_OPTION_ARG_CALLBACK, debug_entries_parse_callback, N_("Print debugging messages of the search folder matching"), NULL },
		{ "debug-verbose", 0, G_OPTION_FLAG_NO_ARG, G_OPTION_ARG_CALLBACK, debug_entries_parse_callback, N_("Print verbose debugging messages"), NULL },
		{ NULL }
	};

	if (!g_thread_supported ()) g_thread_init (NULL);

#ifdef ENABLE_NLS
	bindtextdomain (GETTEXT_PACKAGE, PACKAGE_LOCALE_DIR);
	bind_textdomain_codeset (GETTEXT_PACKAGE, "UTF-8");
	textdomain (GETTEXT_PACKAGE);
#endif

	debug = g_option_group_new ("debug",
				    _("Print debugging messages for the given topic"),
				    _("Print debugging messages for the given topic"),
				    &debug_flags,
				    NULL);
	g_option_group_set_translation_domain(debug, GETTEXT_PACKAGE);
	g_option_group_add_entries (debug, debug_entries);

	context = g_option_context_new (NULL);
	g_option_context_set_summary (context, N_("Liferea, the Linux Feed Reader"));
	g_option_context_set_description (context, N_("For more information, please visit http://liferea.sourceforge.net/"));
	g_option_context_add_main_entries (context, entries, GETTEXT_PACKAGE);
	g_option_context_set_translation_domain(context, GETTEXT_PACKAGE);
	g_option_context_add_group (context, debug);
	g_option_context_add_group (context, gtk_get_option_group (FALSE));

	g_option_context_parse (context, &argc, &argv, &error);
	g_option_context_free (context);
	if (error) {
		g_print ("Error parsing options: %s\n", error->message);
	}

	set_debug_level (debug_flags);

	/* Configuration necessary for network options, so it
	   has to be initialized before update_init() */
	conf_init ();

#ifdef USE_DBUS
	dbus_g_thread_init ();
#endif

	/* We need to do the network initialization here to allow
	   network-manager to be setup before gtk_init() */
	update_init ();

	gtk_init (&argc, &argv);

	/* Single instance checks */
	app = unique_app_new_with_commands ("net.sourceforge.liferea", NULL,
					    "add_feed", COMMAND_ADD_FEED,
					    NULL);
	if (unique_app_is_running (app)) {
		g_print ("Liferea is already running\n");
		unique_app_send_message (app, UNIQUE_ACTIVATE, NULL);
		if (feed) {
			msg = unique_message_data_new ();
			unique_message_data_set_text (msg, feed, -1);
			unique_app_send_message (app, COMMAND_ADD_FEED, msg);
		}
		return 1;
	} else {
		g_signal_connect (app, "message-received", G_CALLBACK (message_received_cb), NULL);
	}

	/* GTK theme support */
	g_set_application_name (_("Liferea"));
	gtk_window_set_default_icon_name ("liferea");

	debug_start_measurement (DEBUG_DB);

	/* order is important! */
	db_init ();			/* initialize sqlite */
	xml_init ();			/* initialize libxml2 */
#ifdef HAVE_LIBNOTIFY
	notification_plugin_register (&libnotify_plugin);
#endif
	social_init ();			/* initialize social bookmarking */
#ifdef USE_DBUS	
	dbus = liferea_dbus_new ();	
#else
	debug0 (DEBUG_GUI, "Compiled without DBUS support.");
#endif

#ifdef USE_AVAHI
	if (conf_get_bool_value (SYNC_AVAHI_ENABLED)) {
		LifereaAvahiPublisher	*avahiPublisher = NULL;

		debug0 (DEBUG_CACHE, "Registering with AVAHI");
		avahiPublisher = liferea_avahi_publisher_new ();
		liferea_avahi_publisher_publish (avahiPublisher, conf_get_str_value (SYNC_AVAHI_SERVICE_NAME), 23632);
	} else {
		debug0 (DEBUG_CACHE, "Avahi support available, but disabled by preferences.");
	}
#else
	debug0 (DEBUG_CACHE, "Compiled without AVAHI support");
#endif

	/* how to start liferea, command line takes precedence over preferences */
	conf_get_bool_value (SHOW_TRAY_ICON, &show_tray_icon);
	conf_get_bool_value (START_IN_TRAY, &start_in_tray);
	if (g_str_equal(initial_state, "iconified")) {
		initialState = MAINWINDOW_ICONIFIED;
	} else if (g_str_equal(initial_state, "hidden") ||
	    (show_tray_icon && start_in_tray)) {
		initialState = MAINWINDOW_HIDDEN;
	} else {
		initialState = MAINWINDOW_SHOWN;
	}

	liferea_shell_create (initialState);
	g_set_prgname ("liferea");
	
#ifdef USE_SM
	/* This must be after feedlist reading because some session
	   managers will tell Liferea to exit if Liferea does not
	   respond to SM requests within a minute or two. This starts
	   the main loop soon after opening the SM connection. */
	session_init (BIN_DIR G_DIR_SEPARATOR_S "liferea", opt_session_arg);
	session_set_cmd (NULL, initialState);
#endif
	signal (SIGTERM, signal_handler);
	signal (SIGINT, signal_handler);
	signal (SIGHUP, signal_handler);

#ifndef G_OS_WIN32
	signal (SIGBUS, fatal_signal_handler);
	signal (SIGSEGV, fatal_signal_handler);
#endif

	/* Note: we explicitely do not use the gdk_thread_*
	   locking in Liferea because it freezes the program
	   when running Flash applets in gtkmozembed */

	runState = STATE_STARTING;
	
	debug_end_measurement (DEBUG_DB, "startup");

	if (feed)
		feedlist_add_subscription (feed, NULL, NULL, 0);

	gtk_main ();
	
	g_object_unref (G_OBJECT (dbus));
	return 0;
}
Ejemplo n.º 18
0
/* opening or creation of database */
void
db_init (void)
{
	gint		res;
		
	debug_enter ("db_init");

	db_open ();

	/* create info table/check versioning info */				   
	debug1 (DEBUG_DB, "current DB schema version: %d", db_get_schema_version ());

	if (-1 == db_get_schema_version ()) {
		/* no schema version available -> first installation without tables... */
		db_set_schema_version (SCHEMA_TARGET_VERSION);
		/* nothing exists yet, tables will be created below */
	}

	if (SCHEMA_TARGET_VERSION < db_get_schema_version ())
		g_error ("Fatal: The cache database was created by a newer version of Liferea than this one!");

	if (SCHEMA_TARGET_VERSION > db_get_schema_version ()) {		
		/* do table migration */
		if (db_get_schema_version () < 5)
			g_error ("This version of Liferea doesn't support migrating from such an old DB file!");

		if (db_get_schema_version () == 5 || db_get_schema_version () == 6) {
			debug0 (DEBUG_DB, "dropping triggers in preparation of database migration");
			db_exec ("BEGIN; "
			         "DROP TRIGGER item_removal; "
				 "DROP TRIGGER item_insert; "
				 "END;");
		}

		if (db_get_schema_version () == 5) {
				/* 1.4.9 -> 1.4.10 adding parent_item_id to itemset relation */
			debug0 (DEBUG_DB, "migrating from schema version 5 to 6 (this drops all comments)");
			db_exec ("BEGIN; "
			         "DELETE FROM itemsets WHERE comment = 1; "
				 "DELETE FROM items WHERE comment = 1; "
			         "CREATE TEMPORARY TABLE itemsets_backup(item_id,node_id,read,comment); "
				 "INSERT INTO itemsets_backup SELECT item_id,node_id,read,comment FROM itemsets; "
				 "DROP TABLE itemsets; "
				 "CREATE TABLE itemsets ("
		        	 "   item_id		INTEGER,"
				 "   parent_item_id     INTEGER,"
		        	 "   node_id		TEXT,"
		        	 "   read		INTEGER,"
				 "   comment            INTEGER,"
		        	 "   PRIMARY KEY (item_id, node_id)"
		        	 "); "
				 "INSERT INTO itemsets SELECT item_id,0,node_id,read,comment FROM itemsets_backup; "
				 "DROP TABLE itemsets_backup; "
				 "REPLACE INTO info (name, value) VALUES ('schemaVersion',6); "
				 "END;");
		}

		if (db_get_schema_version () == 6) {
			/* 1.4.15 -> 1.4.16 adding parent_node_id to itemset relation */
			debug0 (DEBUG_DB, "migrating from schema version 6 to 7 (this drops all comments)");
			db_exec ("BEGIN; "
			         "DELETE FROM itemsets WHERE comment = 1; "
				 "DELETE FROM items WHERE comment = 1; "
			         "CREATE TEMPORARY TABLE itemsets_backup(item_id,node_id,read,comment); "
				 "INSERT INTO itemsets_backup SELECT item_id,node_id,read,comment FROM itemsets; "
				 "DROP TABLE itemsets; "
				 "CREATE TABLE itemsets ("
		        	 "   item_id		INTEGER,"
				 "   parent_item_id     INTEGER,"
		        	 "   node_id		TEXT,"
				 "   parent_node_id     TEXT,"
		        	 "   read		INTEGER,"
				 "   comment            INTEGER,"
		        	 "   PRIMARY KEY (item_id, node_id)"
		        	 "); "
				 "INSERT INTO itemsets SELECT item_id,0,node_id,node_id,read,comment FROM itemsets_backup; "
				 "DROP TABLE itemsets_backup; "
				 "REPLACE INTO info (name, value) VALUES ('schemaVersion',7); "
				 "END;");
		}
		
		if (db_get_schema_version () == 7) {
			/* 1.7.1 -> 1.7.2 dropping the itemsets and attention_stats relation */
			db_exec ("BEGIN; "
			         "CREATE TEMPORARY TABLE items_backup("
			         "   item_id, "
			         "   title, "
			         "   read, "
			         "   updated, "
			         "   popup, "
			         "   marked, "
			         "   source, "
			         "   source_id, "
			         "   valid_guid, "
			         "   description, "
			         "   date, "
			         "   comment_feed_id, "
			         "   comment); "
			         "INSERT into items_backup SELECT ROWID, title, read, updated, popup, marked, source, source_id, valid_guid, description, date, comment_feed_id, comment FROM items; "
			         "DROP TABLE items; "
		                 "CREATE TABLE items ("
		        	 "   item_id		INTEGER,"
				 "   parent_item_id     INTEGER,"
		        	 "   node_id		TEXT,"
				 "   parent_node_id     TEXT,"
		        	 "   title		TEXT,"
		        	 "   read		INTEGER,"
		        	 "   updated		INTEGER,"
		        	 "   popup		INTEGER,"
		        	 "   marked		INTEGER,"
		        	 "   source		TEXT,"
		        	 "   source_id		TEXT,"
		        	 "   valid_guid		INTEGER,"
		        	 "   description	TEXT,"
		        	 "   date		INTEGER,"
		        	 "   comment_feed_id	INTEGER,"
				 "   comment            INTEGER,"
				 "   PRIMARY KEY (item_id)"
		        	 ");"
			         "INSERT INTO items SELECT itemsets.item_id, parent_item_id, node_id, parent_node_id, title, itemsets.read, updated, popup, marked, source, source_id, valid_guid, description, date, comment_feed_id, itemsets.comment FROM items_backup JOIN itemsets ON itemsets.item_id = items_backup.item_id; "
			         "DROP TABLE items_backup; "
			         "DROP TABLE itemsets; "
			         "REPLACE INTO info (name, value) VALUES ('schemaVersion',8); "
			         "END;" );

			db_exec ("DROP TABLE attention_stats");	/* this is unconditional, no checks and backups needed */
		}

		if (db_get_schema_version () == 8) {
			gchar *sql;
			sqlite3_stmt *stmt;
			
			/* 1.7.3 -> 1.7.4 change search folder handling */
			db_exec ("BEGIN; "
			         "DROP TABLE view_state; "
			         "DROP TABLE update_state; "
				 "CREATE TABLE search_folder_items ("
				 "   node_id            STRING,"
	         		 "   item_id		INTEGER,"
				 "   PRIMARY KEY (node_id, item_id)"
				 ");"
			         "REPLACE INTO info (name, value) VALUES ('schemaVersion',9); "
			         "END;" );
			         
			debug0 (DEBUG_DB, "Removing all views.");
			sql = sqlite3_mprintf("SELECT name FROM sqlite_master WHERE type='view';");
			res = sqlite3_prepare_v2 (db, sql, -1, &stmt, NULL);
			sqlite3_free (sql);
			if (SQLITE_OK != res) {
				debug1 (DEBUG_DB, "Could not determine views (error=%d)", res);
			} else {
				sqlite3_reset (stmt);

					while (sqlite3_step (stmt) == SQLITE_ROW) {
						const gchar *viewName = sqlite3_column_text (stmt, 0) + strlen("view_");
						gchar *copySql = g_strdup_printf("INSERT INTO search_folder_items (node_id, item_id) SELECT '%s',item_id FROM view_%s;", viewName, viewName);
						
						db_exec (copySql);
						db_view_remove (viewName);
						
						g_free (copySql);
					}
			
				sqlite3_finalize (stmt);
			}
		}

		if (db_get_schema_version () == 9) {
			/* A parent node id to search folder relation to allow cleanups */
			db_exec ("BEGIN; "
			         "DROP TABLE search_folder_items; "
				 "CREATE TABLE search_folder_items ("
				 "   node_id            STRING,"
				 "   parent_node_id     STRING,"
	         		 "   item_id		INTEGER,"
				 "   PRIMARY KEY (node_id, item_id)"
				 ");"
			         "REPLACE INTO info (name, value) VALUES ('schemaVersion',10); "
			         "END;" );

			searchFolderRebuild = TRUE;
		}
	}

	if (SCHEMA_TARGET_VERSION != db_get_schema_version ())
		g_error ("Fatal: DB schema version not up-to-date! Running with --debug-db could give some hints about the problem!");
	
	/* Vacuuming... */

	db_vacuum ();
	
	/* Schema creation */
		
	debug_start_measurement (DEBUG_DB);
	db_begin_transaction ();

	/* 1. Create tables if they do not exist yet */
	db_exec ("CREATE TABLE items ("
        	 "   item_id		INTEGER,"
		 "   parent_item_id     INTEGER,"
        	 "   node_id		TEXT," /* FIXME: migrate node ids to real integers */
		 "   parent_node_id     TEXT," /* FIXME: migrate node ids to real integers */
        	 "   title		TEXT,"
        	 "   read		INTEGER,"
        	 "   updated		INTEGER,"
        	 "   popup		INTEGER,"
        	 "   marked		INTEGER,"
        	 "   source		TEXT,"
        	 "   source_id		TEXT,"
        	 "   valid_guid		INTEGER,"
        	 "   description	TEXT,"
        	 "   date		INTEGER,"
        	 "   comment_feed_id	TEXT,"
		 "   comment            INTEGER,"
		 "   PRIMARY KEY (item_id)"
        	 ");");

	db_exec ("CREATE INDEX items_idx ON items (source_id);");
	db_exec ("CREATE INDEX items_idx2 ON items (comment_feed_id);");
	db_exec ("CREATE INDEX items_idx3 ON items (node_id);");
	db_exec ("CREATE INDEX items_idx4 ON items (item_id);");
	db_exec ("CREATE INDEX items_idx5 ON items (parent_item_id);");
	db_exec ("CREATE INDEX items_idx6 ON items (parent_node_id);");
		
	db_exec ("CREATE TABLE metadata ("
        	 "   item_id		INTEGER,"
        	 "   nr              	INTEGER,"
        	 "   key             	TEXT,"
        	 "   value           	TEXT,"
        	 "   PRIMARY KEY (item_id, nr)"
        	 ");");

	db_exec ("CREATE INDEX metadata_idx ON metadata (item_id);");
		
	db_exec ("CREATE TABLE subscription ("
        	 "   node_id            STRING,"
		 "   source             STRING,"
		 "   orig_source        STRING,"
		 "   filter_cmd         STRING,"
		 "   update_interval	INTEGER,"
		 "   default_interval   INTEGER,"
		 "   discontinued       INTEGER,"
		 "   available          INTEGER,"
        	 "   PRIMARY KEY (node_id)"
		 ");");

	db_exec ("CREATE TABLE subscription_metadata ("
        	 "   node_id            STRING,"
		 "   nr                 INTEGER,"
		 "   key                TEXT,"
		 "   value              TEXT,"
		 "   PRIMARY KEY (node_id, nr)"
		 ");");

	db_exec ("CREATE INDEX subscription_metadata_idx ON subscription_metadata (node_id);");

	db_exec ("CREATE TABLE node ("
        	 "   node_id		STRING,"
        	 "   parent_id		STRING,"
        	 "   title		STRING,"
		 "   type		INTEGER,"
		 "   expanded           INTEGER,"
		 "   view_mode		INTEGER,"
		 "   sort_column	INTEGER,"
		 "   sort_reversed	INTEGER,"
		 "   PRIMARY KEY (node_id)"
        	 ");");

	db_exec ("CREATE TABLE search_folder_items ("
	         "   node_id            STRING,"
	         "   parent_node_id     STRING,"
	         "   item_id		INTEGER,"
		 "   PRIMARY KEY (node_id, item_id)"
		 ");");

	db_end_transaction ();
	debug_end_measurement (DEBUG_DB, "table setup");
		
	/* 2. Removing old triggers */
	db_exec ("DROP TRIGGER item_insert;");
	db_exec ("DROP TRIGGER item_update;");
	db_exec ("DROP TRIGGER item_removal;");
	db_exec ("DROP TRIGGER subscription_removal;");
		
	/* 3. Cleanup of DB */

	/* Note: do not check on subscriptions here, as non-subscription node
	   types (e.g. news bin) do contain items too. */
	debug0 (DEBUG_DB, "Checking for items without a feed list node...\n");
	db_exec ("DELETE FROM items WHERE comment = 0 AND node_id NOT IN "
        	 "(SELECT node_id FROM node);");
        	 
        debug0 (DEBUG_DB, "Checking for comments without parent item...\n");
	db_exec ("BEGIN; "
	         "   CREATE TEMP TABLE tmp_id ( id );"
	         "   INSERT INTO tmp_id SELECT item_id FROM items WHERE comment = 1 AND parent_item_id NOT IN (SELECT item_id FROM items WHERE comment = 0);"
	         /* limit to 1000 items as it is very slow */
	         "   DELETE FROM items WHERE item_id IN (SELECT id FROM tmp_id LIMIT 1000);"
	         "   DROP TABLE tmp_id;"
		 "END;");
        
	debug0 (DEBUG_DB, "Checking for search folder items without a feed list node...\n");
	db_exec ("DELETE FROM search_folder_items WHERE parent_node_id NOT IN "
        	 "(SELECT node_id FROM node);");

	debug0 (DEBUG_DB, "Checking for search folder items without a search folder...\n");
	db_exec ("DELETE FROM search_folder_items WHERE node_id NOT IN "
        	 "(SELECT node_id FROM node);");

	debug0 (DEBUG_DB, "Checking for search folder with comments...\n");
	db_exec ("DELETE FROM search_folder_items WHERE comment = 1;");
			  
	debug0 (DEBUG_DB, "DB cleanup finished. Continuing startup.");
		
	/* 4. Creating triggers (after cleanup so it is not slowed down by triggers) */

	/* This trigger does explicitely not remove comments! */
	db_exec ("CREATE TRIGGER item_removal DELETE ON items "
        	 "BEGIN "
		 "   DELETE FROM metadata WHERE item_id = old.item_id; "
		 "   DELETE FROM search_folder_items WHERE item_id = old.item_id; "
        	 "END;");
		
	db_exec ("CREATE TRIGGER subscription_removal DELETE ON subscription "
        	 "BEGIN "
		 "   DELETE FROM node WHERE node_id = old.node_id; "
		 "   DELETE FROM subscription_metadata WHERE node_id = old.node_id; "
		 "   DELETE FROM search_folder_items WHERE parent_node_id = old.node_id; "
        	 "END;");

	/* Note: view counting triggers are set up in the view preparation code (see db_view_create()) */		
	/* prepare statements */
	
	db_new_statement ("itemsetLoadStmt",
	                  "SELECT item_id FROM items WHERE node_id = ?");

	db_new_statement ("itemsetLoadOffsetStmt",
			  "SELECT item_id FROM items WHERE comment = 0 LIMIT ? OFFSET ?");
		       
	db_new_statement ("itemsetReadCountStmt",
	                  "SELECT COUNT(item_id) FROM items "
		          "WHERE read = 0 AND node_id = ?");
	       
	db_new_statement ("itemsetItemCountStmt",
	                  "SELECT COUNT(item_id) FROM items "
		          "WHERE node_id = ?");
		       
	db_new_statement ("itemsetRemoveStmt",
	                  "DELETE FROM items WHERE item_id = ? OR (comment = 1 AND parent_item_id = ?)");
			
	db_new_statement ("itemsetRemoveAllStmt",
	                  "DELETE FROM items WHERE node_id = ? OR (comment = 1 AND parent_node_id = ?)");

	db_new_statement ("itemsetMarkAllPopupStmt",
	                  "UPDATE items SET popup = 0 WHERE node_id = ?");

	db_new_statement ("itemLoadStmt",
	                  "SELECT "
	                  "title,"
	                  "read,"
	                  "updated,"
	                  "popup,"
	                  "marked,"
	                  "source,"
	                  "source_id,"
	                  "valid_guid,"
	                  "description,"
	                  "date,"
		          "comment_feed_id,"
		          "comment,"
		          "item_id,"
			  "parent_item_id, "
		          "node_id, "
			  "parent_node_id "
	                  " FROM items WHERE item_id = ?");      
	
	db_new_statement ("itemUpdateStmt",
	                  "REPLACE INTO items ("
	                  "title,"
	                  "read,"
	                  "updated,"
	                  "popup,"
	                  "marked,"
	                  "source,"
	                  "source_id,"
	                  "valid_guid,"
	                  "description,"
	                  "date,"
		          "comment_feed_id,"
		          "comment,"
	                  "item_id,"
	                  "parent_item_id,"
	                  "node_id,"
	                  "parent_node_id"
	                  ") values (?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)");
			
	db_new_statement ("itemStateUpdateStmt",
			  "UPDATE items SET read=?, marked=?, updated=? "
			  "WHERE item_id=?");

	db_new_statement ("duplicatesFindStmt",
	                  "SELECT item_id FROM items WHERE source_id = ?");
			 
	db_new_statement ("duplicateNodesFindStmt",
	                  "SELECT node_id FROM items WHERE item_id IN "
			  "(SELECT item_id FROM items WHERE source_id = ?)");
		       
	db_new_statement ("duplicatesMarkReadStmt",
 	                  "UPDATE items SET read = 1, updated = 0 WHERE source_id = ?");
						
	db_new_statement ("metadataLoadStmt",
	                  "SELECT key,value,nr FROM metadata WHERE item_id = ? ORDER BY nr");
			
	db_new_statement ("metadataUpdateStmt",
	                  "REPLACE INTO metadata (item_id,nr,key,value) VALUES (?,?,?,?)");
			
	db_new_statement ("subscriptionUpdateStmt",
	                  "REPLACE INTO subscription ("
			  "node_id,"
			  "source,"
			  "orig_source,"
			  "filter_cmd,"
			  "update_interval,"
			  "default_interval,"
			  "discontinued,"
			  "available"
			  ") VALUES (?,?,?,?,?,?,?,?)");
			 
	db_new_statement ("subscriptionRemoveStmt",
	                  "DELETE FROM subscription WHERE node_id = ?");
			 
	db_new_statement ("subscriptionLoadStmt",
	                  "SELECT "
			  "node_id,"
			  "source,"
			  "orig_source,"
			  "filter_cmd,"
			  "update_interval,"
			  "default_interval,"
			  "discontinued,"
			  "available "
			  "FROM subscription");
	
	db_new_statement ("subscriptionMetadataLoadStmt",
	                  "SELECT key,value,nr FROM subscription_metadata WHERE node_id = ? ORDER BY nr");
			
	db_new_statement ("subscriptionMetadataUpdateStmt",
	                  "REPLACE INTO subscription_metadata (node_id,nr,key,value) VALUES (?,?,?,?)");
	
	db_new_statement ("nodeUpdateStmt",
	                  "REPLACE INTO node (node_id,parent_id,title,type,expanded,view_mode,sort_column,sort_reversed) VALUES (?,?,?,?,?,?,?,?)");
	                  
	db_new_statement ("itemUpdateSearchFoldersStmt",
	                  "REPLACE INTO search_folder_items (node_id, parent_node_id, item_id) VALUES (?,?,?)");

	db_new_statement ("itemRemoveFromSearchFolderStmt",
	                  "DELETE FROM search_folder_items WHERE node_id =? AND item_id = ?;");
	                  
	db_new_statement ("searchFolderLoadStmt",
	                  "SELECT item_id FROM search_folder_items WHERE node_id = ?;");

	db_new_statement ("searchFolderCountStmt",
	                  "SELECT count(item_id) FROM search_folder_items WHERE node_id = ?;");

	db_new_statement ("nodeIdListStmt",
	                  "SELECT node_id FROM node;");

	db_new_statement ("nodeRemoveStmt",
	                  "DELETE FROM node WHERE node_id = ?;");
			  
	g_assert (sqlite3_get_autocommit (db));
	
	debug_exit ("db_init");
}
Ejemplo n.º 19
0
static void
inoreader_feed_subscription_process_update_result (subscriptionPtr subscription, const struct updateResult* const result, updateFlags flags)
{
	
	debug_start_measurement (DEBUG_UPDATE);

	if (result->data) { 
		updateResultPtr resultCopy;

		/* FIXME: The following is a very dirty hack to edit the feed's
		   XML before processing it */
		resultCopy = update_result_new () ;
		resultCopy->source = g_strdup (result->source); 
		resultCopy->httpstatus = result->httpstatus;
		resultCopy->contentType = g_strdup (result->contentType);
		g_free (resultCopy->updateState);
		resultCopy->updateState = update_state_copy (result->updateState);
		
		/* update the XML by removing 'read', 'reading-list' etc. as labels. */
		xmlDocPtr doc = xml_parse (result->data, result->size, NULL);
		xmlXPathContextPtr xpathCtxt = xmlXPathNewContext (doc) ;
		xmlXPathRegisterNs (xpathCtxt, "atom", "http://www.w3.org/2005/Atom");
		inoreader_source_xpath_foreach_match ("/atom:feed/atom:entry/atom:category[@scheme='http://www.inoreader.com/reader/']", xpathCtxt, inoreader_source_xml_unlink_node, NULL);
		xmlXPathFreeContext (xpathCtxt);
		
		/* good now we have removed the read and unread labels. */
		
		xmlChar    *newXml; 
		int        newXmlSize ;
		
		xmlDocDumpMemory (doc, &newXml, &newXmlSize);
		
		resultCopy->data = g_strndup ((gchar*) newXml, newXmlSize);
		resultCopy->size = newXmlSize;
		
		xmlFree (newXml);
		xmlFreeDoc (doc);
		
		feed_get_subscription_type ()->process_update_result (subscription, resultCopy, flags);
		update_result_free (resultCopy);
	} else { 
		feed_get_subscription_type ()->process_update_result (subscription, result, flags);
		return ; 
	}

	xmlDocPtr doc = xml_parse (result->data, result->size, NULL);
	if (doc) {		
		xmlNodePtr root = xmlDocGetRootElement (doc);
		xmlNodePtr entry = root->children ; 
		GHashTable *cache = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, NULL);

		while (entry) { 
			if (!g_str_equal (entry->name, "entry")) {
				entry = entry->next;
				continue; /* not an entry */
			}
			
			inoreader_source_item_retrieve_status (entry, subscription, cache);
			entry = entry->next;
		}
		
		g_hash_table_unref (cache);
		xmlFreeDoc (doc);
	} else { 
		debug0 (DEBUG_UPDATE, "google_feed_subscription_process_update_result(): Couldn't parse XML!");
		g_warning ("google_feed_subscription_process_update_result(): Couldn't parse XML!");
	}
	
	debug_end_measurement (DEBUG_UPDATE, "time taken to update statuses");
}