[gnome-software] Allow the GsAppList to report global progress and state
- From: Richard Hughes <rhughes src gnome org>
- To: commits-list gnome org
- Cc:
- Subject: [gnome-software] Allow the GsAppList to report global progress and state
- Date: Wed, 18 Jul 2018 18:49:01 +0000 (UTC)
commit 6742e6d2e3c01eb8287e1c07f70d211d487b70c4
Author: Richard Hughes <richard hughsie com>
Date: Wed Jul 18 15:42:54 2018 +0100
Allow the GsAppList to report global progress and state
lib/gs-app-list-private.h | 16 ++-
lib/gs-app-list.c | 265 ++++++++++++++++++++++++++++++++++++++++++++++
lib/gs-self-test.c | 53 ++++++++++
3 files changed, 331 insertions(+), 3 deletions(-)
---
diff --git a/lib/gs-app-list-private.h b/lib/gs-app-list-private.h
index e88ef338..3cd89761 100644
--- a/lib/gs-app-list-private.h
+++ b/lib/gs-app-list-private.h
@@ -28,9 +28,12 @@ G_BEGIN_DECLS
/**
* GsAppListFlags:
- * @GS_APP_LIST_FLAG_NONE: No flags set
- * @GS_APP_LIST_FLAG_IS_RANDOMIZED: List has been randomized
- * @GS_APP_LIST_FLAG_IS_TRUNCATED: List has been truncated
+ * @GS_APP_LIST_FLAG_NONE: No flags set
+ * @GS_APP_LIST_FLAG_IS_RANDOMIZED: List has been randomized
+ * @GS_APP_LIST_FLAG_IS_TRUNCATED: List has been truncated
+ * @GS_APP_LIST_FLAG_WATCH_APPS: Applications will be monitored
+ * @GS_APP_LIST_FLAG_WATCH_APPS_RELATED: Applications related apps will be monitored
+ * @GS_APP_LIST_FLAG_WATCH_APPS_ADDONS: Applications addon apps will be monitored
*
* Flags used to describe the list.
**/
@@ -38,6 +41,9 @@ typedef enum {
GS_APP_LIST_FLAG_NONE = 0,
GS_APP_LIST_FLAG_IS_RANDOMIZED = 1 << 0,
GS_APP_LIST_FLAG_IS_TRUNCATED = 1 << 1,
+ GS_APP_LIST_FLAG_WATCH_APPS = 1 << 2,
+ GS_APP_LIST_FLAG_WATCH_APPS_RELATED = 1 << 3,
+ GS_APP_LIST_FLAG_WATCH_APPS_ADDONS = 1 << 4,
/*< private >*/
GS_APP_LIST_FLAG_LAST
} GsAppListFlags;
@@ -71,6 +77,10 @@ void gs_app_list_truncate (GsAppList *list,
guint length);
gboolean gs_app_list_has_flag (GsAppList *list,
GsAppListFlags flag);
+void gs_app_list_add_flag (GsAppList *list,
+ GsAppListFlags flag);
+AsAppState gs_app_list_get_state (GsAppList *list);
+guint gs_app_list_get_progress (GsAppList *list);
G_END_DECLS
diff --git a/lib/gs-app-list.c b/lib/gs-app-list.c
index a3f9bc13..4d39a5c2 100644
--- a/lib/gs-app-list.c
+++ b/lib/gs-app-list.c
@@ -36,6 +36,7 @@
#include "gs-app-private.h"
#include "gs-app-list-private.h"
+#include "gs-app-collation.h"
struct _GsAppList
{
@@ -45,10 +46,180 @@ struct _GsAppList
GMutex mutex;
guint size_peak;
GsAppListFlags flags;
+ AsAppState state;
+ guint progress;
};
G_DEFINE_TYPE (GsAppList, gs_app_list, G_TYPE_OBJECT)
+enum {
+ PROP_0,
+ PROP_STATE,
+ PROP_PROGRESS,
+ PROP_LAST
+};
+
+/**
+ * gs_app_list_get_state:
+ * @list: A #GsAppList
+ *
+ * Gets the state of the list.
+ *
+ * This method will only return a valid result if gs_app_list_add_flag() has
+ * been called with %GS_APP_LIST_FLAG_WATCH_APPS.
+ *
+ * Returns: the #AsAppState, e.g. %AS_APP_STATE_INSTALLED
+ *
+ * Since: 3.30
+ **/
+AsAppState
+gs_app_list_get_state (GsAppList *list)
+{
+ g_return_val_if_fail (GS_IS_APP_LIST (list), AS_APP_STATE_UNKNOWN);
+ return list->state;
+}
+
+/**
+ * gs_app_list_get_progress:
+ * @list: A #GsAppList
+ *
+ * Gets the average percentage completion of all apps in the list.
+ *
+ * This method will only return a valid result if gs_app_list_add_flag() has
+ * been called with %GS_APP_LIST_FLAG_WATCH_APPS.
+ *
+ * Returns: the percentage completion, or 0 for unknown
+ *
+ * Since: 3.30
+ **/
+guint
+gs_app_list_get_progress (GsAppList *list)
+{
+ g_return_val_if_fail (GS_IS_APP_LIST (list), 0);
+ return list->progress;
+}
+
+static void
+gs_app_list_add_watched_for_app (GsAppList *list, GPtrArray *apps, GsApp *app)
+{
+ if (list->flags & GS_APP_LIST_FLAG_WATCH_APPS)
+ g_ptr_array_add (apps, app);
+ if (list->flags & GS_APP_LIST_FLAG_WATCH_APPS_ADDONS) {
+ GsAppList *list2 = gs_app_get_addons (app);
+ for (guint i = 0; i < gs_app_list_length (list2); i++) {
+ GsApp *app2 = gs_app_list_index (list2, i);
+ g_ptr_array_add (apps, app2);
+ }
+ }
+ if (list->flags & GS_APP_LIST_FLAG_WATCH_APPS_RELATED) {
+ GsAppList *list2 = gs_app_get_related (app);
+ for (guint i = 0; i < gs_app_list_length (list2); i++) {
+ GsApp *app2 = gs_app_list_index (list2, i);
+ g_ptr_array_add (apps, app2);
+ }
+ }
+}
+
+static GPtrArray *
+gs_app_list_get_watched_for_app (GsAppList *list, GsApp *app)
+{
+ GPtrArray *apps = g_ptr_array_new ();
+ gs_app_list_add_watched_for_app (list, apps, app);
+ return apps;
+}
+
+static GPtrArray *
+gs_app_list_get_watched (GsAppList *list)
+{
+ GPtrArray *apps = g_ptr_array_new ();
+ for (guint i = 0; i < list->array->len; i++) {
+ GsApp *app_tmp = g_ptr_array_index (list->array, i);
+ gs_app_list_add_watched_for_app (list, apps, app_tmp);
+ }
+ return apps;
+}
+
+static void
+gs_app_list_invalidate_progress (GsAppList *self)
+{
+ guint progress = 0;
+ g_autoptr(GPtrArray) apps = gs_app_list_get_watched (self);
+
+ /* find the average percentage complete of the list */
+ if (apps->len > 0) {
+ guint64 pc_cnt = 0;
+ for (guint i = 0; i < apps->len; i++) {
+ GsApp *app_tmp = g_ptr_array_index (apps, i);
+ pc_cnt += gs_app_get_progress (app_tmp);
+ }
+ progress = pc_cnt / apps->len;
+ }
+ if (self->progress != progress) {
+ self->progress = progress;
+ g_object_notify (G_OBJECT (self), "progress");
+ }
+}
+
+static void
+gs_app_list_invalidate_state (GsAppList *self)
+{
+ AsAppState state = AS_APP_STATE_UNKNOWN;
+ g_autoptr(GPtrArray) apps = gs_app_list_get_watched (self);
+
+ /* find any action state of the list */
+ for (guint i = 0; i < apps->len; i++) {
+ GsApp *app_tmp = g_ptr_array_index (apps, i);
+ AsAppState state_tmp = gs_app_get_state (app_tmp);
+ if (state_tmp == AS_APP_STATE_INSTALLING ||
+ state_tmp == AS_APP_STATE_REMOVING ||
+ state_tmp == AS_APP_STATE_PURCHASING) {
+ state = state_tmp;
+ break;
+ }
+ }
+ if (self->state != state) {
+ self->state = state;
+ g_object_notify (G_OBJECT (self), "state");
+ }
+}
+
+static void
+gs_app_list_progress_notify_cb (GsApp *app, GParamSpec *pspec, GsAppList *self)
+{
+ gs_app_list_invalidate_progress (self);
+}
+
+static void
+gs_app_list_state_notify_cb (GsApp *app, GParamSpec *pspec, GsAppList *self)
+{
+ gs_app_list_invalidate_state (self);
+}
+
+static void
+gs_app_list_maybe_watch_app (GsAppList *list, GsApp *app)
+{
+ g_autoptr(GPtrArray) apps = gs_app_list_get_watched_for_app (list, app);
+ for (guint i = 0; i < apps->len; i++) {
+ GsApp *app_tmp = g_ptr_array_index (apps, i);
+ g_signal_connect_object (app_tmp, "notify::progress",
+ G_CALLBACK (gs_app_list_progress_notify_cb),
+ list, 0);
+ g_signal_connect_object (app_tmp, "notify::state",
+ G_CALLBACK (gs_app_list_state_notify_cb),
+ list, 0);
+ }
+}
+
+static void
+gs_app_list_maybe_unwatch_app (GsAppList *list, GsApp *app)
+{
+ g_autoptr(GPtrArray) apps = gs_app_list_get_watched_for_app (list, app);
+ for (guint i = 0; i < apps->len; i++) {
+ GsApp *app_tmp = g_ptr_array_index (apps, i);
+ g_signal_handlers_disconnect_by_data (app_tmp, list);
+ }
+}
+
/**
* gs_app_list_get_size_peak:
* @list: A #GsAppList
@@ -101,6 +272,31 @@ gs_app_list_has_flag (GsAppList *list, GsAppListFlags flag)
return (list->flags & flag) > 0;
}
+/**
+ * gs_app_list_add_flag:
+ * @list: A #GsAppList
+ * @flag: A flag to test, e.g. %GS_APP_LIST_FLAG_IS_TRUNCATED
+ *
+ * Gets if a specific flag is set.
+ *
+ * Returns: %TRUE if the flag is set
+ *
+ * Since: 3.30
+ **/
+void
+gs_app_list_add_flag (GsAppList *list, GsAppListFlags flag)
+{
+ if (list->flags & flag)
+ return;
+ list->flags |= flag;
+
+ /* turn this on for existing apps */
+ for (guint i = 0; i < list->array->len; i++) {
+ GsApp *app = g_ptr_array_index (list->array, i);
+ gs_app_list_maybe_watch_app (list, app);
+ }
+}
+
static gboolean
gs_app_list_check_for_duplicate (GsAppList *list, GsApp *app)
{
@@ -141,6 +337,7 @@ gs_app_list_add_safe (GsAppList *list, GsApp *app)
/* if we're lazy-loading the ID then we can't filter for duplicates */
id = gs_app_get_unique_id (app);
if (id == NULL) {
+ gs_app_list_maybe_watch_app (list, app);
g_ptr_array_add (list->array, g_object_ref (app));
return;
}
@@ -150,6 +347,7 @@ gs_app_list_add_safe (GsAppList *list, GsApp *app)
return;
/* just use the ref */
+ gs_app_list_maybe_watch_app (list, app);
g_ptr_array_add (list->array, g_object_ref (app));
g_hash_table_insert (list->hash_by_id, g_strdup (id), g_object_ref (app));
@@ -181,6 +379,10 @@ gs_app_list_add (GsAppList *list, GsApp *app)
g_return_if_fail (GS_IS_APP (app));
locker = g_mutex_locker_new (&list->mutex);
gs_app_list_add_safe (list, app);
+
+ /* recalculate global state */
+ gs_app_list_invalidate_state (list);
+ gs_app_list_invalidate_progress (list);
}
/**
@@ -213,9 +415,15 @@ gs_app_list_remove (GsAppList *list, GsApp *app)
return;
g_hash_table_remove (list->hash_by_id, unique_id);
g_ptr_array_remove (list->array, app_tmp);
+ gs_app_list_maybe_unwatch_app (list, app_tmp);
} else {
g_ptr_array_remove (list->array, app);
+ gs_app_list_maybe_unwatch_app (list, app);
}
+
+ /* recalculate global state */
+ gs_app_list_invalidate_state (list);
+ gs_app_list_invalidate_progress (list);
}
/**
@@ -244,6 +452,10 @@ gs_app_list_add_list (GsAppList *list, GsAppList *donor)
GsApp *app = gs_app_list_index (donor, i);
gs_app_list_add_safe (list, app);
}
+
+ /* recalculate global state */
+ gs_app_list_invalidate_state (list);
+ gs_app_list_invalidate_progress (list);
}
/**
@@ -283,8 +495,14 @@ gs_app_list_length (GsAppList *list)
static void
gs_app_list_remove_all_safe (GsAppList *list)
{
+ for (guint i = 0; i < list->array->len; i++) {
+ GsApp *app = g_ptr_array_index (list->array, i);
+ gs_app_list_maybe_unwatch_app (list, app);
+ }
g_ptr_array_set_size (list->array, 0);
g_hash_table_remove_all (list->hash_by_id);
+ gs_app_list_invalidate_state (list);
+ gs_app_list_invalidate_progress (list);
}
/**
@@ -609,6 +827,33 @@ gs_app_list_copy (GsAppList *list)
return new;
}
+static void
+gs_app_list_get_property (GObject *object, guint prop_id, GValue *value, GParamSpec *pspec)
+{
+ GsAppList *self = GS_APP_LIST (object);
+ switch (prop_id) {
+ case PROP_STATE:
+ g_value_set_uint (value, self->state);
+ break;
+ case PROP_PROGRESS:
+ g_value_set_uint (value, self->progress);
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ break;
+ }
+}
+
+static void
+gs_app_list_set_property (GObject *object, guint prop_id, const GValue *value, GParamSpec *pspec)
+{
+ switch (prop_id) {
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ break;
+ }
+}
+
static void
gs_app_list_finalize (GObject *object)
{
@@ -622,8 +867,28 @@ gs_app_list_finalize (GObject *object)
static void
gs_app_list_class_init (GsAppListClass *klass)
{
+ GParamSpec *pspec;
GObjectClass *object_class = G_OBJECT_CLASS (klass);
+ object_class->get_property = gs_app_list_get_property;
+ object_class->set_property = gs_app_list_set_property;
object_class->finalize = gs_app_list_finalize;
+
+ /**
+ * GsAppList:state:
+ */
+ pspec = g_param_spec_uint ("state", NULL, NULL,
+ AS_APP_STATE_UNKNOWN,
+ AS_APP_STATE_LAST,
+ AS_APP_STATE_UNKNOWN,
+ G_PARAM_READABLE);
+ g_object_class_install_property (object_class, PROP_STATE, pspec);
+
+ /**
+ * GsAppList:progress:
+ */
+ pspec = g_param_spec_uint ("progress", NULL, NULL, 0, 100, 0,
+ G_PARAM_READABLE);
+ g_object_class_install_property (object_class, PROP_PROGRESS, pspec);
}
static void
diff --git a/lib/gs-self-test.c b/lib/gs-self-test.c
index c6d9dad6..868d5a50 100644
--- a/lib/gs-self-test.c
+++ b/lib/gs-self-test.c
@@ -683,6 +683,57 @@ gs_auth_secret_func (void)
g_assert_cmpstr (gs_auth_get_metadata_item (auth2, "day"), ==, "monday");
}
+static void
+gs_app_list_func (void)
+{
+ g_autoptr(GsAppList) list = gs_app_list_new ();
+ g_autoptr(GsApp) app1 = gs_app_new ("app1");
+ g_autoptr(GsApp) app2 = gs_app_new ("app2");
+
+ /* turn on */
+ gs_app_list_add_flag (list, GS_APP_LIST_FLAG_WATCH_APPS);
+
+ g_assert_cmpint (gs_app_list_get_progress (list), ==, 0);
+ g_assert_cmpint (gs_app_list_get_state (list), ==, AS_APP_STATE_UNKNOWN);
+ gs_app_list_add (list, app1);
+ gs_app_set_progress (app1, 75);
+ gs_app_set_state (app1, AS_APP_STATE_AVAILABLE);
+ gs_app_set_state (app1, AS_APP_STATE_INSTALLING);
+ gs_test_flush_main_context ();
+ g_assert_cmpint (gs_app_list_get_progress (list), ==, 75);
+ g_assert_cmpint (gs_app_list_get_state (list), ==, AS_APP_STATE_INSTALLING);
+
+ gs_app_list_add (list, app2);
+ gs_app_set_progress (app2, 25);
+ gs_test_flush_main_context ();
+ g_assert_cmpint (gs_app_list_get_progress (list), ==, 50);
+ g_assert_cmpint (gs_app_list_get_state (list), ==, AS_APP_STATE_INSTALLING);
+
+ gs_app_list_remove (list, app1);
+ g_assert_cmpint (gs_app_list_get_progress (list), ==, 25);
+ g_assert_cmpint (gs_app_list_get_state (list), ==, AS_APP_STATE_UNKNOWN);
+}
+
+static void
+gs_app_list_related_func (void)
+{
+ g_autoptr(GsAppList) list = gs_app_list_new ();
+ g_autoptr(GsApp) app = gs_app_new ("app");
+ g_autoptr(GsApp) related = gs_app_new ("related");
+
+ /* turn on */
+ gs_app_list_add_flag (list,
+ GS_APP_LIST_FLAG_WATCH_APPS |
+ GS_APP_LIST_FLAG_WATCH_APPS_RELATED);
+ gs_app_add_related (app, related);
+ gs_app_list_add (list, app);
+
+ gs_app_set_progress (app, 75);
+ gs_app_set_progress (related, 25);
+ gs_test_flush_main_context ();
+ g_assert_cmpint (gs_app_list_get_progress (list), ==, 50);
+}
+
int
main (int argc, char **argv)
{
@@ -704,6 +755,8 @@ main (int argc, char **argv)
g_test_add_func ("/gnome-software/lib/app{addons}", gs_app_addons_func);
g_test_add_func ("/gnome-software/lib/app{unique-id}", gs_app_unique_id_func);
g_test_add_func ("/gnome-software/lib/app{thread}", gs_app_thread_func);
+ g_test_add_func ("/gnome-software/lib/app{list}", gs_app_list_func);
+ g_test_add_func ("/gnome-software/lib/app{list-related}", gs_app_list_related_func);
g_test_add_func ("/gnome-software/lib/plugin", gs_plugin_func);
g_test_add_func ("/gnome-software/lib/plugin{download-rewrite}", gs_plugin_download_rewrite_func);
g_test_add_func ("/gnome-software/lib/auth{secret}", gs_auth_secret_func);
[
Date Prev][
Date Next] [
Thread Prev][
Thread Next]
[
Thread Index]
[
Date Index]
[
Author Index]