[gnome-software/gnome-3-20] Do not set invalid application states on error



commit 49713dcaa97e5fb0e772b0e470cf8703257e8790
Author: Richard Hughes <richard hughsie com>
Date:   Sun Apr 17 11:32:40 2016 +0100

    Do not set invalid application states on error
    
    Now plugins can fail without stopping the loader we have to make them
    responsible for setting the correct non-transient states when completed.
    
    Provide a helper called gs_app_set_state_recover() which can be used to un-set
    any transient states in the error handler.

 src/gs-app.c                                |   27 ++++++++++++
 src/gs-app.h                                |    1 +
 src/gs-plugin-loader.c                      |   60 +++++++++------------------
 src/gs-self-test.c                          |   41 ++++++++++++++++++
 src/plugins/gs-plugin-dummy.c               |   51 +++++++++++++++++++++++
 src/plugins/gs-plugin-fedora-tagger-usage.c |    4 ++
 src/plugins/gs-plugin-fwupd.c               |    4 +-
 src/plugins/gs-plugin-packagekit.c          |   37 ++++++++++++++---
 src/plugins/gs-plugin-xdg-app.c             |   41 +++++++++++++-----
 9 files changed, 208 insertions(+), 58 deletions(-)
---
diff --git a/src/gs-app.c b/src/gs-app.c
index f6df7c1..8c53514 100644
--- a/src/gs-app.c
+++ b/src/gs-app.c
@@ -89,6 +89,7 @@ struct _GsApp
        guint64                  size;
        AsAppKind                kind;
        AsAppState               state;
+       AsAppState               state_recover;
        guint                    progress;
        GHashTable              *metadata;
        GdkPixbuf               *pixbuf;
@@ -436,6 +437,19 @@ gs_app_get_progress (GsApp *app)
 }
 
 /**
+ * gs_app_set_state_recover:
+ *
+ * Sets the application state to the last status value that was not
+ * transient.
+ */
+void
+gs_app_set_state_recover (GsApp *app)
+{
+       if (app->state_recover != AS_APP_STATE_UNKNOWN)
+               gs_app_set_state (app, app->state_recover);
+}
+
+/**
  * gs_app_set_state_internal:
  */
 static gboolean
@@ -540,6 +554,19 @@ gs_app_set_state_internal (GsApp *app, AsAppState state)
            state == AS_APP_STATE_AVAILABLE)
                app->install_date = 0;
 
+       /* save this to simplify error handling in the plugins */
+       switch (state) {
+       case AS_APP_STATE_INSTALLING:
+       case AS_APP_STATE_REMOVING:
+               /* transient, so ignore */
+               break;
+       default:
+               g_debug ("non-transient state now %s",
+                        as_app_state_to_string (state));
+               app->state_recover = state;
+               break;
+       }
+
        return TRUE;
 }
 
diff --git a/src/gs-app.h b/src/gs-app.h
index 79becf7..bab93c3 100644
--- a/src/gs-app.h
+++ b/src/gs-app.h
@@ -99,6 +99,7 @@ void           gs_app_set_kind                (GsApp          *app,
 AsAppState      gs_app_get_state               (GsApp          *app);
 void            gs_app_set_state               (GsApp          *app,
                                                 AsAppState      state);
+void            gs_app_set_state_recover       (GsApp          *app);
 guint           gs_app_get_progress            (GsApp          *app);
 void            gs_app_set_progress            (GsApp          *app,
                                                 guint           percentage);
diff --git a/src/gs-plugin-loader.c b/src/gs-plugin-loader.c
index 8d63855..c1ac319 100644
--- a/src/gs-plugin-loader.c
+++ b/src/gs-plugin-loader.c
@@ -74,8 +74,6 @@ typedef struct {
        GsCategory                      *category;
        GsApp                           *app;
        GsReview                        *review;
-       AsAppState                       state_success;
-       AsAppState                       state_failure;
 } GsPluginLoaderAsyncState;
 
 static void
@@ -2319,16 +2317,12 @@ gs_plugin_loader_app_action_thread_cb (GTask *task,
                                           cancellable,
                                           &error);
        if (ret) {
-               if (state->state_success != AS_APP_STATE_UNKNOWN) {
-                       gs_app_set_state (state->app, state->state_success);
-                       addons = gs_app_get_addons (state->app);
-                       for (i = 0; i < addons->len; i++) {
-                               GsApp *addon = g_ptr_array_index (addons, i);
-                               if (gs_app_get_to_be_installed (addon)) {
-                                       gs_app_set_state (addon, state->state_success);
-                                       gs_app_set_to_be_installed (addon, FALSE);
-                               }
-                       }
+               /* unstage addons */
+               addons = gs_app_get_addons (state->app);
+               for (i = 0; i < addons->len; i++) {
+                       GsApp *addon = g_ptr_array_index (addons, i);
+                       if (gs_app_get_to_be_installed (addon))
+                               gs_app_set_to_be_installed (addon, FALSE);
                }
 
                /* refine again to make sure we pick up new source id */
@@ -2345,20 +2339,22 @@ gs_plugin_loader_app_action_thread_cb (GTask *task,
                        g_task_return_error (task, error);
                }
        } else {
-               if (state->state_failure != AS_APP_STATE_UNKNOWN) {
-                       gs_app_set_state (state->app, state->state_failure);
-                       addons = gs_app_get_addons (state->app);
-                       for (i = 0; i < addons->len; i++) {
-                               GsApp *addon = g_ptr_array_index (addons, i);
-                               if (gs_app_get_to_be_installed (addon)) {
-                                       gs_app_set_state (addon, state->state_failure);
-                                       gs_app_set_to_be_installed (addon, FALSE);
-                               }
-                       }
-               }
                g_task_return_error (task, error);
        }
 
+       /* check the app is not still in an action state */
+       switch (gs_app_get_state (state->app)) {
+       case AS_APP_STATE_INSTALLING:
+       case AS_APP_STATE_REMOVING:
+               g_warning ("application %s left in %s state",
+                          gs_app_get_id (state->app),
+                          as_app_state_to_string (gs_app_get_state (state->app)));
+               gs_app_set_state (state->app, AS_APP_STATE_UNKNOWN);
+               break;
+       default:
+               break;
+       }
+
        /* remove from list */
        g_mutex_lock (&priv->pending_apps_mutex);
        g_ptr_array_remove (priv->pending_apps, state->app);
@@ -2635,43 +2631,27 @@ gs_plugin_loader_app_action_async (GsPluginLoader *plugin_loader,
        switch (action) {
        case GS_PLUGIN_LOADER_ACTION_INSTALL:
                state->function_name = "gs_plugin_app_install";
-               state->state_success = AS_APP_STATE_INSTALLED;
-               state->state_failure = AS_APP_STATE_AVAILABLE;
                break;
        case GS_PLUGIN_LOADER_ACTION_REMOVE:
                state->function_name = "gs_plugin_app_remove";
-               state->state_success = AS_APP_STATE_AVAILABLE;
-               state->state_failure = AS_APP_STATE_INSTALLED;
                break;
        case GS_PLUGIN_LOADER_ACTION_SET_RATING:
                state->function_name = "gs_plugin_app_set_rating";
-               state->state_success = AS_APP_STATE_UNKNOWN;
-               state->state_failure = AS_APP_STATE_UNKNOWN;
                break;
        case GS_PLUGIN_LOADER_ACTION_UPDATE:
                state->function_name = "gs_plugin_app_update";
-               state->state_success = AS_APP_STATE_INSTALLED;
-               state->state_failure = AS_APP_STATE_UPDATABLE_LIVE;
                break;
        case GS_PLUGIN_LOADER_ACTION_UPGRADE_DOWNLOAD:
                state->function_name = "gs_plugin_app_upgrade_download";
-               state->state_success = AS_APP_STATE_UPDATABLE;
-               state->state_failure = AS_APP_STATE_AVAILABLE;
                break;
        case GS_PLUGIN_LOADER_ACTION_UPGRADE_TRIGGER:
                state->function_name = "gs_plugin_app_upgrade_trigger";
-               state->state_success = AS_APP_STATE_UNKNOWN;
-               state->state_failure = AS_APP_STATE_UNKNOWN;
                break;
        case GS_PLUGIN_LOADER_ACTION_LAUNCH:
                state->function_name = "gs_plugin_launch";
-               state->state_success = AS_APP_STATE_UNKNOWN;
-               state->state_failure = AS_APP_STATE_UNKNOWN;
                break;
        case GS_PLUGIN_LOADER_ACTION_OFFLINE_UPDATE_CANCEL:
                state->function_name = "gs_plugin_offline_update_cancel";
-               state->state_success = AS_APP_STATE_UNKNOWN;
-               state->state_failure = AS_APP_STATE_UNKNOWN;
                break;
        default:
                g_assert_not_reached ();
@@ -3444,7 +3424,7 @@ gs_plugin_loader_set_network_status (GsPluginLoader *plugin_loader,
        if (priv->online == online)
                return;
 
-       g_debug ("*** Network status change: %s", online ? "online" : "offline");
+       g_debug ("network status change: %s", online ? "online" : "offline");
 
        priv->online = online;
 
diff --git a/src/gs-self-test.c b/src/gs-self-test.c
index d058e78..154907e 100644
--- a/src/gs-self-test.c
+++ b/src/gs-self-test.c
@@ -144,6 +144,14 @@ gs_app_func (void)
        g_assert_cmpstr (gs_app_get_name (app), ==, "dave");
        gs_app_set_name (app, GS_APP_QUALITY_HIGHEST, "hugh");
        g_assert_cmpstr (gs_app_get_name (app), ==, "hugh");
+
+       /* check non-transient state saving */
+       gs_app_set_state (app, AS_APP_STATE_INSTALLED);
+       g_assert_cmpint (gs_app_get_state (app), ==, AS_APP_STATE_INSTALLED);
+       gs_app_set_state (app, AS_APP_STATE_REMOVING);
+       g_assert_cmpint (gs_app_get_state (app), ==, AS_APP_STATE_REMOVING);
+       gs_app_set_state_recover (app); // simulate an error
+       g_assert_cmpint (gs_app_get_state (app), ==, AS_APP_STATE_INSTALLED);
 }
 
 static guint _status_changed_cnt = 0;
@@ -157,6 +165,35 @@ gs_plugin_loader_status_changed_cb (GsPluginLoader *plugin_loader,
        _status_changed_cnt++;
 }
 
+static void
+gs_plugin_loader_install_func (GsPluginLoader *plugin_loader)
+{
+       gboolean ret;
+       g_autoptr(GsApp) app = NULL;
+       g_autoptr(GError) error = NULL;
+
+       /* install */
+       app = gs_app_new ("chiron.desktop");
+       gs_app_set_management_plugin (app, "dummy");
+       gs_app_set_state (app, AS_APP_STATE_AVAILABLE);
+       ret = gs_plugin_loader_app_action (plugin_loader, app,
+                                          GS_PLUGIN_LOADER_ACTION_INSTALL,
+                                          NULL,
+                                          &error);
+       g_assert_no_error (error);
+       g_assert (ret);
+       g_assert_cmpint (gs_app_get_state (app), ==, AS_APP_STATE_INSTALLED);
+
+       /* remove -- we're really testing for return code UNKNOWN,
+        * but dummy::refine() sets it */
+       ret = gs_plugin_loader_app_action (plugin_loader, app,
+                                          GS_PLUGIN_LOADER_ACTION_REMOVE,
+                                          NULL,
+                                          &error);
+       g_assert_no_error (error);
+       g_assert (ret);
+       g_assert_cmpint (gs_app_get_state (app), ==, AS_APP_STATE_INSTALLED);
+}
 
 static void
 gs_plugin_loader_refine_func (GsPluginLoader *plugin_loader)
@@ -456,6 +493,7 @@ main (int argc, char **argv)
 
        /* we can only load this once per process */
        plugin_loader = gs_plugin_loader_new ();
+       gs_plugin_loader_set_network_status (plugin_loader, TRUE);
        g_signal_connect (plugin_loader, "status-changed",
                          G_CALLBACK (gs_plugin_loader_status_changed_cb), NULL);
        gs_plugin_loader_set_location (plugin_loader, "./plugins/.libs");
@@ -474,6 +512,9 @@ main (int argc, char **argv)
        g_test_add_data_func ("/gnome-software/plugin-loader{search}",
                              plugin_loader,
                              (GTestDataFunc) gs_plugin_loader_search_func);
+       g_test_add_data_func ("/gnome-software/plugin-loader{install}",
+                             plugin_loader,
+                             (GTestDataFunc) gs_plugin_loader_install_func);
        g_test_add_data_func ("/gnome-software/plugin-loader{installed}",
                              plugin_loader,
                              (GTestDataFunc) gs_plugin_loader_installed_func);
diff --git a/src/plugins/gs-plugin-dummy.c b/src/plugins/gs-plugin-dummy.c
index 91a93a6..4e10b4f 100644
--- a/src/plugins/gs-plugin-dummy.c
+++ b/src/plugins/gs-plugin-dummy.c
@@ -234,6 +234,56 @@ gs_plugin_add_installed (GsPlugin *plugin,
 }
 
 /**
+ * gs_plugin_app_remove:
+ */
+gboolean
+gs_plugin_app_remove (GsPlugin *plugin,
+                     GsApp *app,
+                     GCancellable *cancellable,
+                     GError **error)
+{
+       /* only process this app if was created by this plugin */
+       if (g_strcmp0 (gs_app_get_management_plugin (app), plugin->name) != 0)
+               return TRUE;
+
+       /* remove app */
+       if (g_strcmp0 (gs_app_get_id (app), "chiron.desktop") == 0) {
+               gs_app_set_state (app, AS_APP_STATE_REMOVING);
+               if (!gs_plugin_dummy_delay (plugin, app, 500, cancellable, error)) {
+                       gs_app_set_state_recover (app);
+                       return FALSE;
+               }
+               gs_app_set_state (app, AS_APP_STATE_UNKNOWN);
+       }
+       return TRUE;
+}
+
+/**
+ * gs_plugin_app_install:
+ */
+gboolean
+gs_plugin_app_install (GsPlugin *plugin,
+                      GsApp *app,
+                      GCancellable *cancellable,
+                      GError **error)
+{
+       /* only process this app if was created by this plugin */
+       if (g_strcmp0 (gs_app_get_management_plugin (app), plugin->name) != 0)
+               return TRUE;
+
+       /* install app */
+       if (g_strcmp0 (gs_app_get_id (app), "chiron.desktop") == 0) {
+               gs_app_set_state (app, AS_APP_STATE_INSTALLING);
+               if (!gs_plugin_dummy_delay (plugin, app, 500, cancellable, error)) {
+                       gs_app_set_state_recover (app);
+                       return FALSE;
+               }
+               gs_app_set_state (app, AS_APP_STATE_INSTALLED);
+       }
+       return TRUE;
+}
+
+/**
  * gs_plugin_refine_app:
  */
 gboolean
@@ -406,6 +456,7 @@ gs_plugin_app_upgrade_download (GsPlugin *plugin, GsApp *app,
        gs_app_set_state (app, AS_APP_STATE_INSTALLING);
        if (!gs_plugin_dummy_delay (plugin, app, 5000, cancellable, error))
                return FALSE;
+       gs_app_set_state (app, AS_APP_STATE_UPDATABLE);
        return TRUE;
 }
 
diff --git a/src/plugins/gs-plugin-fedora-tagger-usage.c b/src/plugins/gs-plugin-fedora-tagger-usage.c
index a71f8a5..e2665ab 100644
--- a/src/plugins/gs-plugin-fedora-tagger-usage.c
+++ b/src/plugins/gs-plugin-fedora-tagger-usage.c
@@ -164,6 +164,8 @@ gs_plugin_app_install (GsPlugin *plugin,
                       GCancellable *cancellable,
                       GError **error)
 {
+       if (gs_app_get_state (app) != AS_APP_STATE_INSTALLED)
+               return TRUE;
        return gs_plugin_app_set_usage_app (plugin, app, TRUE, error);
 }
 
@@ -176,5 +178,7 @@ gs_plugin_app_remove (GsPlugin *plugin,
                      GCancellable *cancellable,
                      GError **error)
 {
+       if (gs_app_get_state (app) != AS_APP_STATE_AVAILABLE)
+               return TRUE;
        return gs_plugin_app_set_usage_app (plugin, app, FALSE, error);
 }
diff --git a/src/plugins/gs-plugin-fwupd.c b/src/plugins/gs-plugin-fwupd.c
index 65c39e6..ff4b75a 100644
--- a/src/plugins/gs-plugin-fwupd.c
+++ b/src/plugins/gs-plugin-fwupd.c
@@ -927,8 +927,10 @@ gs_plugin_fwupd_install (GsPlugin *plugin,
 
        gs_app_set_state (app, AS_APP_STATE_INSTALLING);
        if (!gs_plugin_fwupd_upgrade (plugin, filename, FWUPD_DEVICE_ID_ANY, offline,
-                                     cancellable, error))
+                                     cancellable, error)) {
+               gs_app_set_state_recover (app);
                return FALSE;
+       }
        gs_app_set_state (app, AS_APP_STATE_INSTALLED);
        return TRUE;
 }
diff --git a/src/plugins/gs-plugin-packagekit.c b/src/plugins/gs-plugin-packagekit.c
index 1d9735a..b301d8b 100644
--- a/src/plugins/gs-plugin-packagekit.c
+++ b/src/plugins/gs-plugin-packagekit.c
@@ -364,13 +364,15 @@ gs_plugin_app_install (GsPlugin *plugin,
                                                         gs_plugin_packagekit_progress_cb, &data,
                                                         error);
                if (results == NULL) {
-                       gs_app_set_state (app, AS_APP_STATE_AVAILABLE);
+                       gs_app_set_state_recover (app);
                        return FALSE;
                }
 
+               /* state is known */
+               gs_app_set_state (app, AS_APP_STATE_INSTALLED);
+
                /* no longer valid */
                gs_app_clear_source_ids (app);
-               gs_app_set_state (app, AS_APP_STATE_INSTALLED);
                return TRUE;
        }
 
@@ -430,8 +432,14 @@ gs_plugin_app_install (GsPlugin *plugin,
                                                         cancellable,
                                                         gs_plugin_packagekit_progress_cb, &data,
                                                         error);
-               if (results == NULL)
+               if (results == NULL) {
+                       gs_app_set_state_recover (app);
                        return FALSE;
+               }
+
+               /* state is known */
+               gs_app_set_state (app, AS_APP_STATE_INSTALLED);
+
                break;
        case AS_APP_STATE_AVAILABLE_LOCAL:
                package_id = gs_app_get_metadata_item (app, "packagekit::local-filename");
@@ -449,8 +457,13 @@ gs_plugin_app_install (GsPlugin *plugin,
                                                      cancellable,
                                                      gs_plugin_packagekit_progress_cb, &data,
                                                      error);
-               if (results == NULL)
+               if (results == NULL) {
+                       gs_app_set_state_recover (app);
                        return FALSE;
+               }
+
+               /* state is known */
+               gs_app_set_state (app, AS_APP_STATE_INSTALLED);
 
                /* get the new icon from the package */
                gs_app_set_metadata (app, "packagekit::local-filename", NULL);
@@ -610,8 +623,13 @@ gs_plugin_app_remove (GsPlugin *plugin,
                                                cancellable,
                                                gs_plugin_packagekit_progress_cb, &data,
                                                error);
-       if (results == NULL)
+       if (results == NULL) {
+               gs_app_set_state_recover (app);
                return FALSE;
+       }
+
+       /* state is not known: we don't know if we can re-install this app */
+       gs_app_set_state (app, AS_APP_STATE_UNKNOWN);
 
        /* no longer valid */
        gs_app_clear_source_ids (app);
@@ -669,7 +687,14 @@ gs_plugin_app_upgrade_download (GsPlugin *plugin,
                                            cancellable,
                                            gs_plugin_packagekit_progress_cb, &data,
                                            error);
-       return results != NULL;
+       if (results == NULL) {
+               gs_app_set_state_recover (app);
+               return FALSE;
+       }
+
+       /* state is known */
+       gs_app_set_state (app, AS_APP_STATE_UPDATABLE);
+       return TRUE;
 }
 
 /**
diff --git a/src/plugins/gs-plugin-xdg-app.c b/src/plugins/gs-plugin-xdg-app.c
index 340c4b2..ddc9ead 100644
--- a/src/plugins/gs-plugin-xdg-app.c
+++ b/src/plugins/gs-plugin-xdg-app.c
@@ -1208,13 +1208,20 @@ gs_plugin_app_remove (GsPlugin *plugin,
 
        /* remove */
        gs_app_set_state (app, AS_APP_STATE_REMOVING);
-       return xdg_app_installation_uninstall (plugin->priv->installation,
-                                              XDG_APP_REF_KIND_APP,
-                                              gs_app_get_xdgapp_name (app),
-                                              gs_app_get_xdgapp_arch (app),
-                                              gs_app_get_xdgapp_branch (app),
-                                              gs_plugin_xdg_app_progress_cb, &helper,
-                                              cancellable, error);
+       if (!xdg_app_installation_uninstall (plugin->priv->installation,
+                                            XDG_APP_REF_KIND_APP,
+                                            gs_app_get_xdgapp_name (app),
+                                            gs_app_get_xdgapp_arch (app),
+                                            gs_app_get_xdgapp_branch (app),
+                                            gs_plugin_xdg_app_progress_cb, &helper,
+                                            cancellable, error)) {
+               gs_app_set_state_recover (app);
+               return FALSE;
+       }
+
+       /* state is not known: we don't know if we can re-install this app */
+       gs_app_set_state (app, AS_APP_STATE_UNKNOWN);
+       return TRUE;
 }
 
 /**
@@ -1279,7 +1286,7 @@ gs_plugin_app_install (GsPlugin *plugin,
                                                             gs_plugin_xdg_app_progress_cb, &helper,
                                                             cancellable, error);
                        if (xref == NULL) {
-                               gs_app_set_state (runtime, AS_APP_STATE_AVAILABLE);
+                               gs_app_set_state_recover (runtime);
                                return FALSE;
                        }
                        gs_app_set_state (runtime, AS_APP_STATE_INSTALLED);
@@ -1309,9 +1316,14 @@ gs_plugin_app_install (GsPlugin *plugin,
                                                     gs_plugin_xdg_app_progress_cb, &helper,
                                                     cancellable, error);
        }
+       if (xref == NULL) {
+               gs_app_set_state_recover (app);
+               return FALSE;
+       }
 
-       /* now the main application */
-       return xref != NULL;
+       /* state is known */
+       gs_app_set_state (app, AS_APP_STATE_INSTALLED);
+       return TRUE;
 }
 
 /**
@@ -1346,7 +1358,14 @@ gs_plugin_app_update (GsPlugin *plugin,
                                            gs_app_get_xdgapp_branch (app),
                                            gs_plugin_xdg_app_progress_cb, &helper,
                                            cancellable, error);
-       return xref != NULL;
+       if (xref == NULL) {
+               gs_app_set_state_recover (app);
+               return FALSE;
+       }
+
+       /* state is known */
+       gs_app_set_state (app, AS_APP_STATE_INSTALLED);
+       return TRUE;
 }
 
 /**


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