[gnome-software] Handle offline installation sensibly



commit b4104f910c556b589e58c24c605b5211011f534b
Author: Matthias Clasen <mclasen redhat com>
Date:   Fri Oct 18 22:24:42 2013 -0400

    Handle offline installation sensibly
    
    This commit adds a new app state, GS_APP_STATE_QUEUED. Apps
    go in this state when you try to install them while offline.
    This is done in the plugin loader, who puts such apps on its
    list of pending apps. In the UI, queued apps show a 'Pending'
    label and a cancel button. When network becomes available, the
    plugin loader installs queued apps. The list of queued apps is
    written out to ~/.local/share/gnome-software/queued-installs,
    and reloaded from there when gnome-software is started.
    
    https://bugzilla.gnome.org/show_bug.cgi?id=710114

 src/app-widget.ui        |   12 +++
 src/gnome-software.ui    |    8 ++
 src/gs-app-tile.c        |    6 ++
 src/gs-app-widget.c      |   17 ++++-
 src/gs-app.c             |   16 ++++
 src/gs-app.h             |    1 +
 src/gs-application.c     |    1 +
 src/gs-plugin-loader.c   |  178 ++++++++++++++++++++++++++++++++++++++++++++++
 src/gs-plugin-loader.h   |    2 +
 src/gs-shell-details.c   |   30 +++++++-
 src/gs-shell-installed.c |   36 +++++++--
 src/gs-shell.c           |    2 +
 12 files changed, 296 insertions(+), 13 deletions(-)
---
diff --git a/src/app-widget.ui b/src/app-widget.ui
index e22a433..c7ee9d8 100644
--- a/src/app-widget.ui
+++ b/src/app-widget.ui
@@ -105,6 +105,18 @@
                 <property name="fill">False</property>
               </packing>
             </child>
+            <child>
+              <object class="GtkLabel" id="label">
+                <property name="margin_left">6</property>
+                <property name="margin_right">6</property>
+                <property name="halign">end</property>
+              </object>
+              <packing>
+                <property name="pack_type">end</property>
+                <property name="expand">False</property>
+                <property name="fill">False</property>
+              </packing>
+            </child>
           </object>
           <packing>
             <property name="pack_type">end</property>
diff --git a/src/gnome-software.ui b/src/gnome-software.ui
index 6c74145..086a18f 100644
--- a/src/gnome-software.ui
+++ b/src/gnome-software.ui
@@ -133,6 +133,14 @@
               </packing>
             </child>
             <child>
+              <object class="GtkLabel" id="header_label">
+                <property name="can_focus">False</property>
+              </object>
+              <packing>
+                <property name="pack_type">end</property>
+              </packing>
+            </child>
+            <child>
               <object class="GtkButton" id="button_update_all">
                 <property name="label" translatable="yes">Restart &amp; Install</property>
                 <property name="can_focus">True</property>
diff --git a/src/gs-app-tile.c b/src/gs-app-tile.c
index 60461aa..a87101d 100644
--- a/src/gs-app-tile.c
+++ b/src/gs-app-tile.c
@@ -92,6 +92,7 @@ app_state_changed (GsApp *app, GParamSpec *pspec, GsAppTile *tile)
                 * application available */
                gtk_label_set_label (GTK_LABEL (label), _("Updates"));
                break;
+        case GS_APP_STATE_QUEUED:
         case GS_APP_STATE_AVAILABLE:
         default:
                installed = FALSE;
@@ -122,6 +123,9 @@ gs_app_tile_set_app (GsAppTile *tile, GsApp *app)
        gtk_image_clear (GTK_IMAGE (priv->image));
        gtk_image_set_pixel_size (GTK_IMAGE (priv->image), 64);
 
+       if (priv->app)
+               g_signal_handlers_disconnect_by_func (priv->app, app_state_changed, tile);
+
        g_clear_object (&priv->app);
        if (!app)
                return;
@@ -148,6 +152,8 @@ gs_app_tile_destroy (GtkWidget *widget)
 
        priv = gs_app_tile_get_instance_private (tile);
 
+       if (priv->app)
+               g_signal_handlers_disconnect_by_func (priv->app, app_state_changed, tile);
        g_clear_object (&priv->app);
 
        GTK_WIDGET_CLASS (gs_app_tile_parent_class)->destroy (widget);
diff --git a/src/gs-app-widget.c b/src/gs-app-widget.c
index e58ac11..39e7507 100644
--- a/src/gs-app-widget.c
+++ b/src/gs-app-widget.c
@@ -39,6 +39,7 @@ struct _GsAppWidgetPrivate
        GtkWidget       *button_box;
        GtkWidget       *button;
        GtkWidget       *spinner;
+       GtkWidget       *label;
        gboolean         colorful;
        gboolean         show_update;
 };
@@ -112,20 +113,30 @@ gs_app_widget_refresh (GsAppWidget *app_widget)
                                           gs_app_get_pixbuf (priv->app));
        gtk_widget_set_visible (priv->button, FALSE);
        gtk_widget_set_sensitive (priv->button, TRUE);
+       gtk_widget_set_visible (priv->spinner, FALSE);
+       gtk_widget_set_visible (priv->label, FALSE);
 
        context = gtk_widget_get_style_context (priv->button);
        gtk_style_context_remove_class (context, "destructive-action");
 
        switch (gs_app_get_state (app_widget->priv->app)) {
        case GS_APP_STATE_UNAVAILABLE:
-               gtk_widget_set_visible (priv->spinner, FALSE);
                gtk_widget_set_visible (priv->button, TRUE);
                /* TRANSLATORS: this is a button next to the search results that
                 * allows the application to be easily installed */
                gtk_button_set_label (GTK_BUTTON (priv->button), _("Visit website"));
                break;
+       case GS_APP_STATE_QUEUED:
+               gtk_widget_set_visible (priv->label, TRUE);
+               gtk_widget_set_visible (priv->button, TRUE);
+               /* TRANSLATORS: this is a button next to the search results that
+                * allows to cancel a queued install of the application */
+               gtk_button_set_label (GTK_BUTTON (priv->button), _("Cancel"));
+               /* TRANSLATORS: this is a label that describes an application
+                * that has been queued for installation */
+               gtk_label_set_label (GTK_LABEL (priv->label), _("Pending"));
+               break;
        case GS_APP_STATE_AVAILABLE:
-               gtk_widget_set_visible (priv->spinner, FALSE);
                gtk_widget_set_visible (priv->button, TRUE);
                /* TRANSLATORS: this is a button next to the search results that
                 * allows the application to be easily installed */
@@ -133,7 +144,6 @@ gs_app_widget_refresh (GsAppWidget *app_widget)
                break;
        case GS_APP_STATE_UPDATABLE:
        case GS_APP_STATE_INSTALLED:
-               gtk_widget_set_visible (priv->spinner, FALSE);
                if (gs_app_get_kind (app_widget->priv->app) != GS_APP_KIND_SYSTEM)
                        gtk_widget_set_visible (priv->button, TRUE);
                /* TRANSLATORS: this is a button next to the search results that
@@ -229,6 +239,7 @@ gs_app_widget_class_init (GsAppWidgetClass *klass)
        gtk_widget_class_bind_template_child_private (widget_class, GsAppWidget, button_box);
        gtk_widget_class_bind_template_child_private (widget_class, GsAppWidget, button);
        gtk_widget_class_bind_template_child_private (widget_class, GsAppWidget, spinner);
+       gtk_widget_class_bind_template_child_private (widget_class, GsAppWidget, label);
 }
 
 static void
diff --git a/src/gs-app.c b/src/gs-app.c
index 8ce35c4..ae5a774 100644
--- a/src/gs-app.c
+++ b/src/gs-app.c
@@ -156,6 +156,8 @@ gs_app_state_to_string (GsAppState state)
                return "installed";
        if (state == GS_APP_STATE_AVAILABLE)
                return "available";
+       if (state == GS_APP_STATE_QUEUED)
+               return "queued";
        if (state == GS_APP_STATE_INSTALLING)
                return "installing";
        if (state == GS_APP_STATE_REMOVING)
@@ -308,6 +310,7 @@ gs_app_get_state (GsApp *app)
  * UPDATABLE --> REMOVING   --> AVAILABLE
  * INSTALLED --> REMOVING   --> AVAILABLE
  * AVAILABLE --> INSTALLING --> INSTALLED
+ * AVAILABLE <--> QUEUED --> INSTALLING --> INSTALLED
  * UNKNOWN   --> UNAVAILABLE
  */
 void
@@ -325,6 +328,7 @@ gs_app_set_state (GsApp *app, GsAppState state)
        case GS_APP_STATE_UNKNOWN:
                /* unknown has to go into one of the stable states */
                if (state == GS_APP_STATE_INSTALLED ||
+                   state == GS_APP_STATE_QUEUED ||
                    state == GS_APP_STATE_AVAILABLE ||
                    state == GS_APP_STATE_UPDATABLE ||
                    state == GS_APP_STATE_UNAVAILABLE)
@@ -336,9 +340,16 @@ gs_app_set_state (GsApp *app, GsAppState state)
                    state == GS_APP_STATE_REMOVING)
                        state_change_ok = TRUE;
                break;
+       case GS_APP_STATE_QUEUED:
+               if (state == GS_APP_STATE_UNKNOWN ||
+                   state == GS_APP_STATE_INSTALLING ||
+                   state == GS_APP_STATE_AVAILABLE)
+                       state_change_ok = TRUE;
+               break;
        case GS_APP_STATE_AVAILABLE:
                /* available has to go into an action state */
                if (state == GS_APP_STATE_UNKNOWN ||
+                   state == GS_APP_STATE_QUEUED ||
                    state == GS_APP_STATE_INSTALLING)
                        state_change_ok = TRUE;
                break;
@@ -378,6 +389,11 @@ gs_app_set_state (GsApp *app, GsAppState state)
        }
 
        priv->state = state;
+
+       if (state == GS_APP_STATE_UNKNOWN ||
+            state == GS_APP_STATE_AVAILABLE)
+               app->priv->install_date = 0;
+
        g_object_notify (G_OBJECT (app), "state");
        g_signal_emit (app, signals[SIGNAL_STATE_CHANGED], 0);
 }
diff --git a/src/gs-app.h b/src/gs-app.h
index f59db77..b05369f 100644
--- a/src/gs-app.h
+++ b/src/gs-app.h
@@ -70,6 +70,7 @@ typedef enum {
        GS_APP_STATE_UNKNOWN,
        GS_APP_STATE_INSTALLED,
        GS_APP_STATE_AVAILABLE,
+       GS_APP_STATE_QUEUED,
        GS_APP_STATE_INSTALLING,
        GS_APP_STATE_REMOVING,
        GS_APP_STATE_UPDATABLE,
diff --git a/src/gs-application.c b/src/gs-application.c
index c642f9e..199c429 100644
--- a/src/gs-application.c
+++ b/src/gs-application.c
@@ -75,6 +75,7 @@ network_changed_cb (GNetworkMonitor *monitor,
                    GsApplication *app)
 {
        g_debug ("*** Network status change: %s", available ? "online" : "offline");
+       gs_plugin_loader_set_network_status (app->plugin_loader, available);
 }
 
 static void
diff --git a/src/gs-plugin-loader.c b/src/gs-plugin-loader.c
index 836c2cc..4a365f8 100644
--- a/src/gs-plugin-loader.c
+++ b/src/gs-plugin-loader.c
@@ -44,6 +44,9 @@ struct GsPluginLoaderPrivate
        GMutex                   app_cache_mutex;
        GHashTable              *app_cache;
        gchar                   **compatible_projects;
+
+       GList                   *queued_installs;
+       gboolean                 online; 
 };
 
 G_DEFINE_TYPE (GsPluginLoader, gs_plugin_loader, G_TYPE_OBJECT)
@@ -836,6 +839,8 @@ gs_plugin_loader_get_installed_thread_cb (GSimpleAsyncResult *res,
                                                    "gs_plugin_add_installed",
                                                    cancellable,
                                                    &error);
+       state->list = g_list_concat (state->list, g_list_copy_deep (plugin_loader->priv->queued_installs, 
(GCopyFunc)g_object_ref, NULL));
+
        if (state->list == NULL) {
                gs_plugin_loader_get_all_state_finish (state, error);
                g_error_free (error);
@@ -905,6 +910,7 @@ gs_plugin_loader_get_installed_async (GsPluginLoader *plugin_loader,
        if (cancellable != NULL)
                state->cancellable = g_object_ref (cancellable);
 
+
        /* run in a thread */
        tmp = g_cancellable_new ();
        g_object_set_data (G_OBJECT (tmp), "state", state);
@@ -1939,6 +1945,108 @@ gs_plugin_loader_app_action_thread_cb (GSimpleAsyncResult *res,
        gs_plugin_loader_app_action_state_finish (state, NULL);
 }
 
+static void
+load_install_queue (GsPluginLoader *plugin_loader)
+{
+       static gboolean loaded = FALSE;
+       gchar *file;
+       gchar *contents;
+
+       if (loaded)
+               return;
+
+       loaded = TRUE;
+
+       file = g_build_filename (g_get_user_data_dir (), "gnome-software", "install-queue", NULL);
+       g_debug ("loading install queue from %s\n", file);
+       if (g_file_get_contents (file, &contents, NULL, NULL)) {
+               gint i;
+               gchar **names;
+               names = g_strsplit (contents, "\n", 0);
+
+               for (i = 0; names[i]; i++) {
+                       GsApp *app;
+                       if (strlen (names[i]) == 0)
+                               continue;
+                       app = gs_app_new (names[i]);
+                       gs_app_set_state (app, GS_APP_STATE_QUEUED);
+                       g_hash_table_insert (plugin_loader->priv->app_cache,
+                                            g_strdup (gs_app_get_id (app)),
+                                            g_object_ref (app));
+               
+                       g_mutex_lock (&plugin_loader->priv->pending_apps_mutex);
+                       g_ptr_array_add (plugin_loader->priv->pending_apps, app);
+                       g_mutex_unlock (&plugin_loader->priv->pending_apps_mutex);
+                       gs_plugin_loader_app_refine_async (plugin_loader, app, 0, NULL, NULL, NULL);
+               }
+               g_free (contents);
+               g_strfreev (names);
+       }
+
+       g_free (file);
+}
+
+static void
+save_install_queue (GsPluginLoader *plugin_loader)
+{
+       GString *s;
+       GPtrArray *pending_apps;
+       gint i;
+       gchar *file;
+
+       s = g_string_new ("");
+       pending_apps = plugin_loader->priv->pending_apps;
+       g_mutex_lock (&plugin_loader->priv->pending_apps_mutex);
+       for (i = pending_apps->len - 1; i >= 0; i--) {
+               GsApp *app;
+               app = g_ptr_array_index (pending_apps, i);
+               if (gs_app_get_state (app) == GS_APP_STATE_QUEUED) {
+                       g_string_append (s, gs_app_get_id (app));
+                       g_string_append_c (s, '\n');
+               }
+       }
+       g_mutex_unlock (&plugin_loader->priv->pending_apps_mutex);
+
+       file = g_build_filename (g_get_user_data_dir (), "gnome-software", "install-queue", NULL);
+       g_debug ("saving install queue to %s\n", file);
+       g_file_set_contents (file, s->str, s->len, NULL);
+       g_free (file);
+       g_string_free (s, TRUE);
+}
+
+static void
+add_app_to_install_queue (GsPluginLoader *plugin_loader,
+                         GsApp *app)
+{
+       /* FIXME: persist this */
+       g_mutex_lock (&plugin_loader->priv->pending_apps_mutex);
+       g_ptr_array_add (plugin_loader->priv->pending_apps, g_object_ref (app));
+       g_mutex_unlock (&plugin_loader->priv->pending_apps_mutex);
+
+       gs_app_set_state (app, GS_APP_STATE_QUEUED);
+       g_idle_add (emit_pending_apps_idle, g_object_ref (plugin_loader));
+       save_install_queue (plugin_loader);
+}
+
+static gboolean
+remove_app_from_install_queue (GsPluginLoader *plugin_loader,
+                              GsApp *app)
+{
+       gboolean ret;
+
+       g_mutex_lock (&plugin_loader->priv->pending_apps_mutex);
+       ret = g_ptr_array_remove (plugin_loader->priv->pending_apps, app);
+       g_mutex_unlock (&plugin_loader->priv->pending_apps_mutex);
+
+       if (ret) {
+               gs_app_set_state (app, GS_APP_STATE_AVAILABLE);
+               g_idle_add (emit_pending_apps_idle, g_object_ref (plugin_loader));
+               save_install_queue (plugin_loader);
+       }
+
+       return ret;
+}
+
 /**
  * gs_plugin_loader_app_action_async:
  *
@@ -1960,6 +2068,34 @@ gs_plugin_loader_app_action_async (GsPluginLoader *plugin_loader,
        g_return_if_fail (GS_IS_APP (app));
        g_return_if_fail (cancellable == NULL || G_IS_CANCELLABLE (cancellable));
 
+       if (action == GS_PLUGIN_LOADER_ACTION_REMOVE) {
+               if (remove_app_from_install_queue (plugin_loader, app)) {
+                       GSimpleAsyncResult *res;
+                       res = g_simple_async_result_new (G_OBJECT (plugin_loader),
+                                                       callback,
+                                                       user_data,
+                                                       gs_plugin_loader_app_action_async);
+                       g_simple_async_result_set_op_res_gboolean (res, TRUE);
+                       g_simple_async_result_complete (res);
+                       g_object_unref (res);
+                       return;
+               }
+       }
+
+       if (action == GS_PLUGIN_LOADER_ACTION_INSTALL &&
+           !plugin_loader->priv->online) {
+               GSimpleAsyncResult *res;
+               add_app_to_install_queue (plugin_loader, app);
+               res = g_simple_async_result_new (G_OBJECT (plugin_loader),
+                                               callback,
+                                               user_data,
+                                               gs_plugin_loader_app_action_async);
+               g_simple_async_result_set_op_res_gboolean (res, TRUE);
+               g_simple_async_result_complete (res);
+               g_object_unref (res);
+               return;
+       }
+
        /* save state */
        state = g_slice_new0 (GsPluginLoaderAsyncState);
        state->res = g_simple_async_result_new (G_OBJECT (plugin_loader),
@@ -2274,6 +2410,9 @@ gs_plugin_loader_setup (GsPluginLoader *plugin_loader, GError **error)
 
        /* run the plugins */
        gs_plugin_loader_run (plugin_loader, "gs_plugin_initialize");
+
+       /* now we can load the install-queue */
+       load_install_queue (plugin_loader);
 out:
        gs_profile_stop (plugin_loader->priv->profile, "GsPlugin::setup");
        if (dir != NULL)
@@ -2419,4 +2558,43 @@ gs_plugin_loader_new (void)
        return GS_PLUGIN_LOADER (plugin_loader);
 }
 
+void
+gs_plugin_loader_set_network_status (GsPluginLoader *plugin_loader,
+                                    gboolean online)
+{
+       if (plugin_loader->priv->online == online)
+               return;
+
+       plugin_loader->priv->online = online;
+
+       if (online) {
+               GPtrArray *pending_apps;
+               gint i;
+               GsApp *app;
+               GList *queue, *l;
+
+               queue = NULL;
+               pending_apps = plugin_loader->priv->pending_apps;
+               g_mutex_lock (&plugin_loader->priv->pending_apps_mutex);
+               for (i = pending_apps->len - 1; i >= 0; i--) {
+                       app = g_ptr_array_index (pending_apps, i);
+                       if (gs_app_get_state (app) == GS_APP_STATE_QUEUED) {
+                               queue = g_list_prepend (queue, g_object_ref (app));
+                               g_ptr_array_remove_index_fast (pending_apps, i);
+                       }
+               }               
+               g_mutex_unlock (&plugin_loader->priv->pending_apps_mutex);
+               for (l = queue; l; l = l->next) {
+                       app = l->data;
+                       gs_plugin_loader_app_action_async (plugin_loader,
+                                                          app,
+                                                          GS_PLUGIN_LOADER_ACTION_INSTALL,
+                                                          NULL,
+                                                          NULL,
+                                                          NULL);               
+               }
+               g_list_free_full (queue, g_object_unref);
+       }
+}
+
 /* vim: set noexpandtab: */
diff --git a/src/gs-plugin-loader.h b/src/gs-plugin-loader.h
index fd26ce8..108fbd5 100644
--- a/src/gs-plugin-loader.h
+++ b/src/gs-plugin-loader.h
@@ -165,6 +165,8 @@ GsAppState   gs_plugin_loader_get_state_for_app     (GsPluginLoader *plugin_loader,
 GPtrArray      *gs_plugin_loader_get_pending           (GsPluginLoader *plugin_loader);
 GsApp          *gs_plugin_loader_dedupe                (GsPluginLoader *plugin_loader,
                                                         GsApp          *app);
+void            gs_plugin_loader_set_network_status    (GsPluginLoader *plugin_loader,
+                                                        gboolean        online);
 
 G_END_DECLS
 
diff --git a/src/gs-shell-details.c b/src/gs-shell-details.c
index a1f0fd3..8720885 100644
--- a/src/gs-shell-details.c
+++ b/src/gs-shell-details.c
@@ -84,6 +84,18 @@ gs_shell_details_refresh (GsShellDetails *shell_details)
        kind = gs_app_get_kind (priv->app);
        state = gs_app_get_state (priv->app);
 
+       /* label */
+       widget = GTK_WIDGET (gtk_builder_get_object (priv->builder, "header_label"));
+       switch (state) {
+       case GS_APP_STATE_QUEUED:
+               gtk_widget_set_visible (widget, TRUE);
+               gtk_label_set_label (GTK_LABEL (widget), _("Pending"));
+               break;
+       default:
+               gtk_widget_set_visible (widget, FALSE);
+               break;
+       }
+
        /* install button */
        widget = GTK_WIDGET (gtk_builder_get_object (priv->builder, "button_install"));
        switch (state) {
@@ -95,6 +107,9 @@ gs_shell_details_refresh (GsShellDetails *shell_details)
                 * can be installed */
                gtk_button_set_label (GTK_BUTTON (widget), _("Install"));
                break;
+       case GS_APP_STATE_QUEUED:
+               gtk_widget_set_visible (widget, FALSE);
+               break;
        case GS_APP_STATE_INSTALLING:
                gtk_widget_set_visible (widget, TRUE);
                gtk_widget_set_sensitive (widget, FALSE);
@@ -136,6 +151,12 @@ gs_shell_details_refresh (GsShellDetails *shell_details)
                        /* TRANSLATORS: button text in the header when an application can be installed */
                        gtk_button_set_label (GTK_BUTTON (widget), _("Removing"));
                        break;
+               case GS_APP_STATE_QUEUED:
+                       gtk_widget_set_visible (widget, TRUE);
+                       gtk_widget_set_sensitive (widget, TRUE);
+                       gtk_style_context_remove_class (gtk_widget_get_style_context (widget), 
"destructive-action");
+                       gtk_button_set_label (GTK_BUTTON (widget), _("Cancel"));
+                       break;
                case GS_APP_STATE_AVAILABLE:
                case GS_APP_STATE_INSTALLING:
                case GS_APP_STATE_UNAVAILABLE:
@@ -157,6 +178,7 @@ gs_shell_details_refresh (GsShellDetails *shell_details)
                switch (state) {
                case GS_APP_STATE_INSTALLED:
                case GS_APP_STATE_AVAILABLE:
+               case GS_APP_STATE_QUEUED:
                case GS_APP_STATE_UPDATABLE:
                case GS_APP_STATE_UNAVAILABLE:
                        gtk_widget_set_visible (widget, FALSE);
@@ -628,7 +650,8 @@ gs_shell_details_app_installed_cb (GObject *source,
                return;
        }
 
-       gs_app_notify_installed (helper->app);
+       if (gs_app_get_state (helper->app) != GS_APP_STATE_QUEUED)
+               gs_app_notify_installed (helper->app);
        g_object_unref (helper->shell_details);
        g_object_unref (helper->app);
        g_free (helper);
@@ -702,7 +725,10 @@ gs_shell_details_app_remove_button_cb (GtkWidget *widget, GsShellDetails *shell_
                                                    gs_app_get_name (priv->app));
        /* TRANSLATORS: this is button text to remove the application */
        gtk_dialog_add_button (GTK_DIALOG (dialog), _("Remove"), GTK_RESPONSE_OK);
-       response = gtk_dialog_run (GTK_DIALOG (dialog));
+       if (gs_app_get_state (priv->app) == GS_APP_STATE_INSTALLED)
+               response = gtk_dialog_run (GTK_DIALOG (dialog));
+       else
+               response = GTK_RESPONSE_OK; /* pending install */
        if (response == GTK_RESPONSE_OK) {
                g_debug ("remove %s", gs_app_get_id (priv->app));
                helper = g_new0 (GsShellDetailsHelper, 1);
diff --git a/src/gs-shell-installed.c b/src/gs-shell-installed.c
index fe2f784..cc2bfb2 100644
--- a/src/gs-shell-installed.c
+++ b/src/gs-shell-installed.c
@@ -31,6 +31,9 @@
 #include "gs-utils.h"
 #include "gs-app-widget.h"
 
+#define INSTALL_DATE_QUEUED     (G_MAXUINT - 1)
+#define INSTALL_DATE_INSTALLING (G_MAXUINT - 2)
+
 static void    gs_shell_installed_finalize     (GObject        *object);
 static void    gs_shell_installed_remove_row   (GtkListBox     *list_box,
                                                 GtkWidget      *child);
@@ -52,6 +55,9 @@ struct GsShellInstalledPrivate
 
 G_DEFINE_TYPE (GsShellInstalled, gs_shell_installed, G_TYPE_OBJECT)
 
+static void gs_shell_installed_pending_apps_changed_cb (GsPluginLoader *plugin_loader,
+                                                       GsShellInstalled *shell_installed);
+
 /**
  * gs_shell_installed_invalidate:
  **/
@@ -137,6 +143,8 @@ gs_shell_installed_app_removed_cb (GObject *source,
                g_error_free (error);
        } else {
                /* remove from the list */
+               app = gs_app_widget_get_app (helper->app_widget);
+               g_debug ("removed %s", gs_app_get_id (app));
                gs_shell_installed_remove_row (GTK_LIST_BOX (priv->list_box_installed),
                                               GTK_WIDGET (helper->app_widget));
        }
@@ -183,9 +191,12 @@ gs_shell_installed_app_remove_cb (GsAppWidget *app_widget,
                                                    gs_app_get_name (app));
        /* TRANSLATORS: this is button text to remove the application */
        gtk_dialog_add_button (GTK_DIALOG (dialog), _("Remove"), GTK_RESPONSE_OK);
-       response = gtk_dialog_run (GTK_DIALOG (dialog));
+       if (gs_app_get_state (app) == GS_APP_STATE_QUEUED)
+               response = GTK_RESPONSE_OK; /* pending install */
+       else
+               response = gtk_dialog_run (GTK_DIALOG (dialog));
        if (response == GTK_RESPONSE_OK) {
-               g_debug ("remove %s", gs_app_get_id (app));
+               g_debug ("removing %s", gs_app_get_id (app));
                helper = g_new0 (GsShellInstalledHelper, 1);
                helper->shell_installed = g_object_ref (shell_installed);
                helper->app_widget = g_object_ref (app_widget);
@@ -265,6 +276,7 @@ gs_shell_installed_get_installed_cb (GObject *source_object,
        }
 out:
        gs_plugin_list_free (list);
+       gs_shell_installed_pending_apps_changed_cb (plugin_loader, shell_installed);
 }
 
 /**
@@ -489,12 +501,20 @@ gs_shell_installed_pending_apps_changed_cb (GsPluginLoader *plugin_loader,
        }
        for (i = 0; i < pending->len; i++) {
                app = GS_APP (g_ptr_array_index (pending, i));
-               if (gs_app_get_state (app) == GS_APP_STATE_INSTALLING) {
-                       /* sort installing apps above removing and
-                        * installed apps
-                        */
-                       gs_app_set_install_date (app, G_MAXUINT - 1);
-                       gs_shell_installed_add_app (shell_installed, app);
+               /* Sort installing apps above removing and
+                * installed apps. Be careful not to add
+                * pending apps more than once.
+                */
+               if (gs_app_get_state (app) == GS_APP_STATE_QUEUED) {
+                       if (gs_app_get_install_date (app) != INSTALL_DATE_QUEUED) {
+                               gs_app_set_install_date (app, INSTALL_DATE_QUEUED);
+                               gs_shell_installed_add_app (shell_installed, app);
+                       }
+               } else if (gs_app_get_state (app) == GS_APP_STATE_INSTALLING) {
+                       if (gs_app_get_install_date (app) != INSTALL_DATE_INSTALLING) {
+                               gs_app_set_install_date (app, INSTALL_DATE_INSTALLING);
+                               gs_shell_installed_add_app (shell_installed, app);
+                       }
                }
        }
 
diff --git a/src/gs-shell.c b/src/gs-shell.c
index 5e41c55..b060fcd 100644
--- a/src/gs-shell.c
+++ b/src/gs-shell.c
@@ -113,6 +113,8 @@ gs_shell_change_mode (GsShell *shell,
        widget = GTK_WIDGET (gtk_builder_get_object (priv->builder, "header_spinner"));
        gtk_spinner_stop (GTK_SPINNER (widget));
        gtk_widget_hide (widget);
+       widget = GTK_WIDGET (gtk_builder_get_object (priv->builder, "header_label"));
+       gtk_widget_hide (widget);
        widget = GTK_WIDGET (gtk_builder_get_object (priv->builder, "button_install"));
        gtk_widget_hide (widget);
        widget = GTK_WIDGET (gtk_builder_get_object (priv->builder, "button_remove"));


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