[gnome-software/wip/temp/ubuntu-xenial-rebased: 106/326] Load the package lists directly into memory, saving a lot of time



commit 369fe646630c1373360454e4f79d32e823a083e5
Author: Robert Ancell <robert ancell canonical com>
Date:   Tue Feb 16 18:24:15 2016 -0800

    Load the package lists directly into memory, saving a lot of time

 src/plugins/gs-plugin-apt.c |  374 ++++++++++++++++++++++++++-----------------
 1 files changed, 224 insertions(+), 150 deletions(-)
---
diff --git a/src/plugins/gs-plugin-apt.c b/src/plugins/gs-plugin-apt.c
index 1ed3eae..98185a9 100644
--- a/src/plugins/gs-plugin-apt.c
+++ b/src/plugins/gs-plugin-apt.c
@@ -24,11 +24,23 @@
 #include <stdlib.h>
 #include <string.h>
 #include <ctype.h>
+
 #include <gs-plugin.h>
+#include <gs-utils.h>
+
+typedef struct {
+       gchar *name;
+       gchar *installed_version;
+       gchar *update_version;
+       gint installed_size;
+} PackageInfo;
+
 
 struct GsPluginPrivate {
-       gboolean cache_loaded;
-       GList *dpkg_cache;
+       gsize            loaded;
+       GHashTable      *package_info;
+       GList           *installed_packages;
+       GList           *updatable_packages;
 };
 
 const gchar *
@@ -37,29 +49,29 @@ gs_plugin_get_name (void)
        return "apt";
 }
 
+static void
+free_package_info (gpointer data)
+{
+       PackageInfo *info = data;
+       g_free (info->name);
+       g_free (info->installed_version);
+       g_free (info->update_version);
+       g_free (info);
+}
+
 void
 gs_plugin_initialize (GsPlugin *plugin)
 {
        plugin->priv = GS_PLUGIN_GET_PRIVATE (GsPluginPrivate);
+       plugin->priv->package_info = g_hash_table_new_full (g_str_hash, g_str_equal, NULL, free_package_info);
 }
 
 void
 gs_plugin_destroy (GsPlugin *plugin)
 {
-}
-
-static GsApp *
-find_app (GList **list, const gchar *source)
-{
-       GList *link;
-
-       for (link = *list; link; link = link->next) {
-               GsApp *app = GS_APP (link->data);
-               if (g_strcmp0 (gs_app_get_source_default (app), source) == 0)
-                       return app;
-       }
-
-       return NULL;
+       g_hash_table_unref (plugin->priv->package_info);
+       g_list_free (plugin->priv->installed_packages);
+       g_list_free (plugin->priv->updatable_packages);
 }
 
 // Ordering of symbols in dpkg ~, 0-9, A-Z, a-z, everything else (ASCII ordering)
@@ -175,102 +187,169 @@ version_newer (const gchar *v0, const gchar *v1)
        return v0 ? compare_dpkg_version (v0, v1) < 0 : TRUE;
 }
 
+typedef gboolean (*PackageFileFunc) (const gchar *name, gsize name_length, const gchar *value, gsize 
value_length, gpointer user_data, GError **error);
+
 static void
-parse_package_info (const gchar *info, GsPluginRefineFlags flags, GList **list, gboolean mark_available)
+skip_to_eol (gchar *data, gsize data_length, gsize *offset)
 {
-       gchar **lines;
-       gint i;
-       GsApp *app = NULL;
-       const gchar *package_prefix = "Package: ";
-       const gchar *status_prefix = "Status: ";
-       const gchar *installed_size_prefix = "Installed-Size: ";
-       const gchar *version_prefix = "Version: ";
-
-       lines = g_strsplit (info, "\n", -1);
-       for (app = NULL, i = 0; lines[i]; i++) {
-               if (g_str_has_prefix (lines[i], package_prefix)) {
-                       app = find_app (list, lines[i] + strlen (package_prefix));
-                       if (app && mark_available && gs_app_get_state (app) == AS_APP_STATE_UNKNOWN)
-                               gs_app_set_state (app, AS_APP_STATE_AVAILABLE);
+       while (*offset < data_length && data[*offset] != '\n')
+               (*offset)++;
+       if (*offset >= data_length)
+               return;
+       (*offset)++;
+}
+
+static gboolean
+parse_package_file_data (gchar *data, gsize data_length, PackageFileFunc callback, gpointer user_data, 
GError **error)
+{
+       gsize offset;
+
+       for (offset = 0; offset < data_length; ) {
+               gsize name_start, name_end, name_length;
+               gsize value_start, value_end, value_length;
+
+               // Entry divided by empty space
+               if (data[offset] == '\n') {
+                       offset++;
+                       continue;
                }
 
-               // Skip other fields until we find an app we know
-               if (app == NULL)
+               // Line continuations start with a space
+               if (data[offset] == ' ') {
+                       skip_to_eol (data, data_length, &offset);
                        continue;
+               }
 
-               if (g_str_has_prefix (lines[i], status_prefix)) {
-                       if (g_str_has_suffix (lines[i] + strlen (status_prefix), " installed") && 
gs_app_get_state (app) == AS_APP_STATE_UNKNOWN)
-                               gs_app_set_state (app, AS_APP_STATE_INSTALLED);
-               } else if (g_str_has_prefix (lines[i], installed_size_prefix) && (flags & 
GS_PLUGIN_REFINE_FLAGS_REQUIRE_SIZE) != 0) {
-                       if (gs_app_get_size (app) == 0)
-                               gs_app_set_size (app, atoi (lines[i] + strlen (installed_size_prefix)) * 
1024);
-               } else if (g_str_has_prefix (lines[i], version_prefix) && (flags & 
GS_PLUGIN_REFINE_FLAGS_REQUIRE_VERSION) != 0) {
-                       const gchar *version = lines[i] + strlen (version_prefix);
-
-                       if (gs_app_get_version (app) == NULL)
-                               gs_app_set_version (app, version);
-                       if ((gs_app_get_state (app) == AS_APP_STATE_INSTALLED ||gs_app_get_state (app) == 
AS_APP_STATE_UPDATABLE_LIVE) && version_newer (gs_app_get_update_version (app), version)) {
-                               if (gs_app_get_state (app) == AS_APP_STATE_INSTALLED)
-                                       gs_app_set_state (app, AS_APP_STATE_UNKNOWN);
-                               gs_app_set_state (app, AS_APP_STATE_UPDATABLE_LIVE);
-                               gs_app_set_update_version (app, version);
-                       }
+               // Find the field in the form "name: value"
+               name_start = offset;
+               name_end = name_start + 1;
+               while (name_end < data_length && !(data[name_end] == ':' || data[name_end] == '\n'))
+                       name_end++;
+               if ((name_end + 1) >= data_length)
+                       return TRUE;
+               if (data[name_end] != ':') {
+                       offset = name_end;
+                       skip_to_eol (data, data_length, &offset);
                }
+               value_start = name_end + 1;
+               while (value_start < data_length && data[value_start] == ' ')
+                       value_start++;
+               value_end = value_start + 1;
+               while (value_end < data_length && data[value_end] != '\n')
+                       value_end++;
+               if (value_end >= data_length)
+                       return TRUE;
+               name_length = name_end - name_start;
+               value_length = value_end - value_start;
+
+               if (!callback (data + name_start, name_length, data + value_start, value_length, user_data, 
error))
+                       return FALSE;
+               offset = value_end + 1;
        }
 
-       g_strfreev (lines);
+       return TRUE;
 }
 
 static gboolean
-refine (GsPlugin *plugin,
-        GList **list,
-        GsPluginRefineFlags flags,
-        GCancellable *cancellable,
-        GError **error)
+parse_package_file (const gchar *filename, PackageFileFunc callback, gpointer user_data, GError **error)
 {
-       GList *link;
-       GPtrArray *dpkg_argv_array, *cache_argv_array;
-       gboolean known_apps = FALSE;
-       g_autofree gchar **dpkg_argv = NULL, **cache_argv = NULL, *dpkg_stdout = NULL, *cache_stdout = NULL, 
*dpkg_stderr = NULL, *cache_stderr = NULL;
-
-       // Get the information from the cache
-       dpkg_argv_array = g_ptr_array_new ();
-       g_ptr_array_add (dpkg_argv_array, (gpointer) "dpkg");
-       g_ptr_array_add (dpkg_argv_array, (gpointer) "--status");
-       cache_argv_array = g_ptr_array_new ();
-       g_ptr_array_add (cache_argv_array, (gpointer) "apt-cache");
-       g_ptr_array_add (cache_argv_array, (gpointer) "show");
-       for (link = *list; link; link = link->next) {
-               GsApp *app = GS_APP (link->data);
-               const gchar *source;
+       g_autoptr(GMappedFile) f = NULL;
 
-               source = gs_app_get_source_default (app);
-               if (source == NULL)
-                       continue;
+       f = g_mapped_file_new (filename, FALSE, NULL);
+       if (f == NULL)
+               return FALSE;
+       return parse_package_file_data (g_mapped_file_get_contents (f), g_mapped_file_get_length (f), 
callback, user_data, error);
+}
 
-               known_apps = TRUE;
-               g_ptr_array_add (dpkg_argv_array, (gpointer) source);
-               g_ptr_array_add (cache_argv_array, (gpointer) source);
-       }
-       g_ptr_array_add (dpkg_argv_array, NULL);
-       dpkg_argv = (gchar **) g_ptr_array_free (dpkg_argv_array, FALSE);
-       g_ptr_array_add (cache_argv_array, NULL);
-       cache_argv = (gchar **) g_ptr_array_free (cache_argv_array, FALSE);
+typedef struct {
+       GsPlugin *plugin;
+       PackageInfo *current_info;
+       gboolean current_installed;
+} FieldData;
 
-       if (!known_apps)
+static gboolean
+field_cb (const gchar *name, gsize name_length, const gchar *value, gsize value_length, gpointer user_data, 
GError **error)
+{
+       FieldData *data = user_data;
+
+       if (strncmp (name, "Package", name_length) == 0) {
+               gchar *id = g_strndup (value, value_length);
+               data->current_info = g_hash_table_lookup (data->plugin->priv->package_info, id);
+               if (data->current_info == NULL) {
+                       data->current_info = g_slice_new0 (PackageInfo);
+                       data->current_installed = FALSE;
+                       data->current_info->name = id;
+                       g_hash_table_insert (data->plugin->priv->package_info, data->current_info->name, 
data->current_info);
+               } else
+                       g_free (id);
                return TRUE;
+       }
 
-       if (!g_spawn_sync (NULL, dpkg_argv, NULL, G_SPAWN_SEARCH_PATH, NULL, NULL, &dpkg_stdout, 
&dpkg_stderr, NULL, error))
-               return FALSE;
-       if (!g_spawn_sync (NULL, cache_argv, NULL, G_SPAWN_SEARCH_PATH, NULL, NULL, &cache_stdout, 
&cache_stderr, NULL, error))
-               return FALSE;
+       if (data->current_info == NULL)
+               return TRUE;
 
-       parse_package_info (dpkg_stdout, flags, list, FALSE);
-       parse_package_info (cache_stdout, flags, list, TRUE);
+       if (strncmp (name, "Status", name_length) == 0) {
+               if (strncmp (value, "install ok installed", value_length) == 0) {
+                       data->current_installed = TRUE;
+                       data->plugin->priv->installed_packages = g_list_append 
(data->plugin->priv->installed_packages, data->current_info);
+               }
+       } else if (strncmp (name, "Installed-Size", name_length) == 0) {
+               data->current_info->installed_size = atoi (value);
+       } else if (strncmp (name, "Version", name_length) == 0) {
+               gchar *version = g_strndup (value, value_length);
+               if (data->current_installed) {
+                       g_free (data->current_info->installed_version);
+                       data->current_info->installed_version = version;
+               } else if (version_newer (data->current_info->installed_version, version) && version_newer 
(data->current_info->update_version, version)) {
+                       if (data->current_info->installed_version && data->current_info->update_version == 
NULL)
+                               data->plugin->priv->updatable_packages = g_list_append 
(data->plugin->priv->updatable_packages, data->current_info);
+                       g_free (data->current_info->update_version);
+                       data->current_info->update_version = version;
+               } else
+                       g_free (version);
+       }
 
        return TRUE;
 }
 
+static gboolean
+load_db (GsPlugin *plugin, GError **error)
+{
+       GPtrArray *lists;
+       GDir *dir;
+       guint i;
+       gboolean result = FALSE;
+
+       // Find the package lists to load
+       lists = g_ptr_array_new ();
+       g_ptr_array_set_free_func (lists, g_free);
+       g_ptr_array_add (lists, g_strdup ("/var/lib/dpkg/status"));
+       dir = g_dir_open ("/var/lib/apt/lists", 0, NULL);
+       while (TRUE) {
+               const gchar *name = g_dir_read_name (dir);
+               if (name == NULL)
+                       break;
+               if (g_str_has_suffix (name, "_Packages"))
+                       g_ptr_array_add (lists, g_build_filename ("/var/lib/apt/lists", name, NULL));
+       }
+       g_dir_close (dir);
+
+       for (i = 0; i < lists->len; i++) {
+               const gchar *list = lists->pdata[i];
+               FieldData data;
+               data.plugin = plugin;
+               data.current_info = NULL;
+               data.current_installed = FALSE;
+               result = parse_package_file (list, field_cb, &data, error);
+               if (!result)
+                       goto done;
+       }
+
+done:
+       g_ptr_array_unref (lists);
+       return result;
+}
+
 gboolean
 gs_plugin_refine (GsPlugin *plugin,
                  GList **list,
@@ -278,52 +357,39 @@ gs_plugin_refine (GsPlugin *plugin,
                  GCancellable *cancellable,
                  GError **error)
 {
-       // NOTE: Had to put into a static function so can be called from inside plugin - not sure why
-       return refine (plugin, list, flags, cancellable, error);
-}
+       GList *link;
 
-static gchar **
-get_installed (GError **error)
-{
-       g_autofree gchar *dpkg_stdout = NULL, *dpkg_stderr = NULL;
-       gint exit_status;
-       gchar *argv[3] = { (gchar *) "dpkg", (gchar *) "--get-selections", NULL }, **lines;
-       int i;
-       GPtrArray *array;
+       /* Load database once */
+       if (g_once_init_enter (&plugin->priv->loaded)) {
+               gboolean ret;
 
-       if (!g_spawn_sync (NULL, argv, NULL, G_SPAWN_SEARCH_PATH, NULL, NULL, &dpkg_stdout, &dpkg_stderr, 
&exit_status, error))
-               return FALSE;
-       if (exit_status != EXIT_SUCCESS) {
-               g_set_error (error,
-                            GS_PLUGIN_ERROR,
-                            GS_PLUGIN_ERROR_FAILED,
-                            "dpkg --get-selections returned status %d", exit_status);
-               return FALSE;
+               ret = load_db (plugin, error);
+               g_once_init_leave (&plugin->priv->loaded, TRUE);
+               if (!ret)
+                       return FALSE;
        }
 
-       array = g_ptr_array_new ();
-       lines = g_strsplit (dpkg_stdout, "\n", -1);
-       for (i = 0; lines[i]; i++) {
-               g_autoptr(GsApp) app = NULL;
-               gchar *status, *c;
+       for (link = *list; link; link = link->next) {
+               GsApp *app = link->data;
+               PackageInfo *info;
 
-               // Line is the form <name>\t<status> - find the status and only use installed packages
-               status = strrchr (lines[i], '\t');
-               if (status == NULL)
-                       continue;
-               status++;
-               if (strcmp (status, "install") != 0)
+               if (gs_app_get_source_default (app) == NULL)
                        continue;
 
-               // Split out name
-               c = strchr (lines[i], '\t');
-               *c = '\0';
-               g_ptr_array_add (array, (gpointer) g_strdup (lines[i]));
+               info = g_hash_table_lookup (plugin->priv->package_info, gs_app_get_source_default (app));
+               if (info != NULL) {
+                       if (gs_app_get_size (app) == 0)
+                               gs_app_set_size (app, info->installed_size * 1024);
+                       if (info->installed_version) {
+                               gs_app_set_version (app, info->installed_version);
+                               if (gs_app_get_state (app) == AS_APP_STATE_UNKNOWN)
+                                       gs_app_set_state (app, AS_APP_STATE_INSTALLED);
+                       } else
+                               gs_app_set_update_version (app, info->update_version);
+               }
        }
-       g_strfreev (lines);
-       g_ptr_array_add (array, NULL);
 
-       return (gchar **) g_ptr_array_free (array, FALSE);
+       return TRUE;
 }
 
 gboolean
@@ -332,24 +398,29 @@ gs_plugin_add_installed (GsPlugin *plugin,
                         GCancellable *cancellable,
                         GError **error)
 {
-       gchar **installed;
-       int i;
+       GList *link;
 
-       installed = get_installed (error);
-       if (installed == NULL)
-               return FALSE;
+       /* Load database once */
+       if (g_once_init_enter (&plugin->priv->loaded)) {
+               gboolean ret;
+
+               ret = load_db (plugin, error);
+               g_once_init_leave (&plugin->priv->loaded, TRUE);
+               if (!ret)
+                       return FALSE;
+       }
 
-       for (i = 0; installed[i] != NULL; i++) {
+       for (link = plugin->priv->installed_packages; link; link = link->next) {
+               PackageInfo *info = link->data;
                g_autoptr(GsApp) app = NULL;
 
-               app = gs_app_new (installed[i]);
+               app = gs_app_new (info->name);
                // FIXME: Since appstream marks all packages as owned by PackageKit and we are replacing 
PackageKit we need to accept those packages
                gs_app_set_management_plugin (app, "PackageKit");
-               gs_app_add_source (app, installed[i]);
+               gs_app_add_source (app, info->name);
                gs_app_set_state (app, AS_APP_STATE_INSTALLED);
                gs_plugin_add_app (list, app);
        }
-       g_strfreev (installed);
 
        return TRUE;
 }
@@ -562,26 +633,29 @@ gs_plugin_add_updates (GsPlugin *plugin,
                        GCancellable *cancellable,
                        GError **error)
 {
-       GList *installed = NULL, *link;
+       GList *link;
 
-       // Get the version of everything installed
-       // FIXME: Checks all the packages we don't have appstream data for (so inefficient)
-       if (!gs_plugin_add_installed (plugin, &installed, NULL, error) ||
-           !refine (plugin, &installed, GS_PLUGIN_REFINE_FLAGS_REQUIRE_VERSION, NULL, error)) {
-               g_list_free_full (installed, g_object_unref);
-               return FALSE;
+       /* Load database once */
+       if (g_once_init_enter (&plugin->priv->loaded)) {
+               gboolean ret;
+
+               ret = load_db (plugin, error);
+               g_once_init_leave (&plugin->priv->loaded, TRUE);
+               if (!ret)
+                       return FALSE;
        }
 
-       for (link = installed; link; link = link->next) {
-               GsApp *app = GS_APP (link->data);
-               const gchar *v0, *v1;
+       for (link = plugin->priv->updatable_packages; link; link = link->next) {
+               PackageInfo *info = link->data;
+               g_autoptr(GsApp) app = NULL;
 
-               v0 = gs_app_get_version (app);
-               v1 = gs_app_get_update_version (app);
-               if (v0 != NULL && v1 != NULL && version_newer (v0, v1))
-                       gs_plugin_add_app (list, app);
+               app = gs_app_new (info->name);
+               // FIXME: Since appstream marks all packages as owned by PackageKit and we are replacing 
PackageKit we need to accept those packages
+               gs_app_set_management_plugin (app, "PackageKit");
+               gs_app_add_source (app, info->name);
+               gs_app_set_state (app, AS_APP_STATE_UPDATABLE_LIVE);
+               gs_plugin_add_app (list, app);
        }
-       g_list_free_full (installed, g_object_unref);
 
        return TRUE;
 }


[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]