diff options
author | Richard Hughes <richard@hughsie.com> | 2014-07-17 11:57:11 +0100 |
---|---|---|
committer | Richard Hughes <richard@hughsie.com> | 2014-07-17 11:57:11 +0100 |
commit | 6feb98005ec61f9e77ed25f0cf50f29179f1ef0b (patch) | |
tree | 3776c0456fb9058ec000812bd3dd151eb0ecc08b | |
parent | 1ba3be23a2b0b874281f53a1f48fd5a666c22686 (diff) | |
download | appstream-glib-6feb98005ec61f9e77ed25f0cf50f29179f1ef0b.tar.gz |
Show the builder progress in a ncurses-style panel
-rw-r--r-- | libappstream-builder/Makefile.am | 2 | ||||
-rw-r--r-- | libappstream-builder/asb-context.c | 6 | ||||
-rw-r--r-- | libappstream-builder/asb-panel.c | 379 | ||||
-rw-r--r-- | libappstream-builder/asb-panel.h | 66 | ||||
-rw-r--r-- | libappstream-builder/asb-task.c | 35 | ||||
-rw-r--r-- | libappstream-builder/asb-task.h | 3 |
6 files changed, 487 insertions, 4 deletions
diff --git a/libappstream-builder/Makefile.am b/libappstream-builder/Makefile.am index 219899e..ecf9ce4 100644 --- a/libappstream-builder/Makefile.am +++ b/libappstream-builder/Makefile.am @@ -49,6 +49,8 @@ libappstream_builder_la_SOURCES = \ asb-package-deb.c \ asb-package-deb.h \ asb-package.h \ + asb-panel.c \ + asb-panel.h \ asb-task.c \ asb-task.h \ asb-utils.c \ diff --git a/libappstream-builder/asb-context.c b/libappstream-builder/asb-context.c index 2f734e5..43e5f02 100644 --- a/libappstream-builder/asb-context.c +++ b/libappstream-builder/asb-context.c @@ -36,6 +36,7 @@ #include "as-cleanup.h" #include "asb-context.h" #include "asb-context-private.h" +#include "asb-panel.h" #include "asb-plugin.h" #include "asb-plugin-loader.h" #include "asb-task.h" @@ -55,6 +56,7 @@ struct _AsbContextPrivate GMutex apps_mutex; /* for ->apps */ GPtrArray *file_globs; /* of AsbPackage */ GPtrArray *packages; /* of AsbPackage */ + AsbPanel *panel; AsbPluginLoader *plugin_loader; gboolean add_cache_id; gboolean no_net; @@ -767,6 +769,7 @@ asb_context_process (AsbContext *ctx, GError **error) /* add each package */ g_print ("Processing packages...\n"); + asb_panel_set_job_total (priv->panel, priv->packages->len); tasks = g_ptr_array_new_with_free_func ((GDestroyNotify) g_object_unref); for (i = 0; i < priv->packages->len; i++) { pkg = g_ptr_array_index (priv->packages, i); @@ -793,6 +796,7 @@ asb_context_process (AsbContext *ctx, GError **error) task = asb_task_new (ctx); asb_task_set_id (task, i); asb_task_set_package (task, pkg); + asb_task_set_panel (task, priv->panel); g_ptr_array_add (tasks, task); /* add task to pool */ @@ -961,6 +965,7 @@ asb_context_finalize (GObject *object) g_object_unref (priv->old_md_cache); g_object_unref (priv->plugin_loader); + g_object_unref (priv->panel); g_ptr_array_unref (priv->packages); g_list_foreach (priv->apps, (GFunc) g_object_unref, NULL); g_list_free (priv->apps); @@ -990,6 +995,7 @@ asb_context_init (AsbContext *ctx) AsbContextPrivate *priv = GET_PRIVATE (ctx); priv->plugin_loader = asb_plugin_loader_new (ctx); + priv->panel = asb_panel_new (); priv->packages = g_ptr_array_new_with_free_func ((GDestroyNotify) g_object_unref); g_mutex_init (&priv->apps_mutex); priv->old_md_cache = as_store_new (); diff --git a/libappstream-builder/asb-panel.c b/libappstream-builder/asb-panel.c new file mode 100644 index 0000000..d40809f --- /dev/null +++ b/libappstream-builder/asb-panel.c @@ -0,0 +1,379 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- + * + * Copyright (C) 2014 Richard Hughes <richard@hughsie.com> + * + * Licensed under the GNU Lesser General Public License Version 2.1 + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +/** + * SECTION:asb-panel + * @short_description: A panel for showing parallel tasks. + * @stability: Unstable + * + * This object provides a console panel showing tasks and thier statuses. + */ + +#include "config.h" + +#include <string.h> +#include <sys/types.h> +#include <sys/stat.h> +#include <fcntl.h> + +#include "as-cleanup.h" +#include "asb-panel.h" + +typedef struct _AsbPanelPrivate AsbPanelPrivate; +struct _AsbPanelPrivate +{ + GPtrArray *items; + GMutex mutex; + gint tty_fd; + guint job_number_max; + guint job_total; + guint line_width_max; + guint number_cleared; + guint title_width; +}; + +G_DEFINE_TYPE_WITH_PRIVATE (AsbPanel, asb_panel, G_TYPE_OBJECT) + +#define GET_PRIVATE(o) (asb_panel_get_instance_private (o)) + +typedef struct { + guint job_number; + gchar *title; + gchar *status; + GThread *thread; +} AsbPanelItem; + +/** + * asb_panel_item_free: + **/ +static void +asb_panel_item_free (AsbPanelItem *item) +{ + g_free (item->title); + g_free (item->status); + g_slice_free (AsbPanelItem, item); +} + +/** + * asb_panel_print_raw: + **/ +static gssize +asb_panel_print_raw (AsbPanel *panel, const gchar *tmp) +{ + AsbPanelPrivate *priv = GET_PRIVATE (panel); + gssize count; + gssize wrote; + + count = strlen (tmp) + 1; + wrote = write (priv->tty_fd, tmp, count); + if (wrote != count) { + g_warning ("Only wrote %" G_GSSIZE_FORMAT + " of %" G_GSSIZE_FORMAT " bytes", + wrote, count); + } + return count; +} + +/** + * asb_panel_print: + **/ +G_GNUC_PRINTF(2,3) static void +asb_panel_print (AsbPanel *panel, const gchar *fmt, ...) +{ + AsbPanelPrivate *priv = GET_PRIVATE (panel); + va_list args; + gsize len; + guint i; + _cleanup_free_ gchar *startline = NULL; + _cleanup_free_ gchar *tmp = NULL; + _cleanup_string_free_ GString *str = NULL; + + va_start (args, fmt); + tmp = g_strdup_vprintf (fmt, args); + va_end (args); + + /* pad out to the largest line we've seen */ + str = g_string_new (tmp); + for (i = str->len; i < priv->line_width_max; i++) + g_string_append (str, " "); + + /* is this bigger than anything else we've seen? */ + len = asb_panel_print_raw (panel, str->str); + if (len - 1 > priv->line_width_max) + priv->line_width_max = len - 1; + + /* go back to the start of the line and drop down one line */ + startline = g_strdup_printf ("\033[%" G_GSIZE_FORMAT "D\033[1B", len); + asb_panel_print_raw (panel, startline); +} + +/** + * asb_panel_refresh: + **/ +static void +asb_panel_refresh (AsbPanel *panel) +{ + AsbPanelItem *item = NULL; + AsbPanelPrivate *priv = GET_PRIVATE (panel); + guint i; + guint j; + + g_mutex_lock (&priv->mutex); + + /* clear four lines of blank */ + if (priv->number_cleared < priv->items->len) { + for (i = 0; i < priv->items->len + 1; i++) + asb_panel_print_raw (panel, "\n"); + for (i = 0; i < priv->items->len + 1; i++) + asb_panel_print_raw (panel, "\033[1A"); + priv->number_cleared = priv->items->len; + } + + /* find existing and print status lines */ + for (i = 0; i < priv->items->len; i++) { + _cleanup_string_free_ GString *str = NULL; + item = g_ptr_array_index (priv->items, i); + str = g_string_new (item->title); + for (j = str->len; j < priv->title_width; j++) + g_string_append (str, " "); + if (priv->title_width < str->len) + priv->title_width = str->len; + if (item->status != NULL) { + g_string_append (str, " "); + g_string_append (str, item->status); + } + asb_panel_print (panel, "%s", str->str); + } + + /* any slots now unused */ + for (i = i; i < priv->number_cleared; i++) + asb_panel_print (panel, "Thread unused"); + + /* print percentage completion */ + if (item != NULL) { + asb_panel_print (panel, "Done: %.1f%%", + (gdouble) priv->job_number_max * 100.f / + (gdouble) priv->job_total); + } else { + asb_panel_print (panel, "Done: 100.0%%"); + } + + /* go back up to the start */ + for (i = 0; i < priv->number_cleared + 1; i++) + asb_panel_print_raw (panel, "\033[1A"); + + g_mutex_unlock (&priv->mutex); +} + +/** + * asb_panel_ensure_item: + **/ +static AsbPanelItem * +asb_panel_ensure_item (AsbPanel *panel) +{ + AsbPanelPrivate *priv = GET_PRIVATE (panel); + AsbPanelItem *item; + guint i; + + /* find existing */ + g_mutex_lock (&priv->mutex); + for (i = 0; i < priv->items->len; i++) { + item = g_ptr_array_index (priv->items, i); + if (item->thread == g_thread_self ()) + goto out; + } + + /* create */ + item = g_slice_new0 (AsbPanelItem); + item->thread = g_thread_self (); + g_ptr_array_add (priv->items, item); +out: + g_mutex_unlock (&priv->mutex); + return item; +} + +/** + * asb_panel_set_job_number: + * @panel: A #AsbPanel + * @job_number: numeric identifier + * + * Sets the job number for the task. + * + * Since: 0.2.3 + **/ +void +asb_panel_set_job_number (AsbPanel *panel, guint job_number) +{ + AsbPanelItem *item; + AsbPanelPrivate *priv = GET_PRIVATE (panel); + + /* record the highest seen job number for % calculations */ + if (job_number > priv->job_number_max) + priv->job_number_max = job_number; + + item = asb_panel_ensure_item (panel); + item->job_number = job_number; + asb_panel_refresh (panel); +} + +/** + * asb_panel_remove: + * @panel: A #AsbPanel + * + * Remove the currently running task. + * + * Since: 0.2.3 + **/ +void +asb_panel_remove (AsbPanel *panel) +{ + AsbPanelItem *item; + AsbPanelPrivate *priv = GET_PRIVATE (panel); + + item = asb_panel_ensure_item (panel); + g_mutex_lock (&priv->mutex); + g_ptr_array_remove (priv->items, item); + g_mutex_unlock (&priv->mutex); + asb_panel_refresh (panel); +} + +/** + * asb_panel_set_title: + * @panel: A #AsbPanel + * @title: text to display + * + * Sets the title for the currently running task. + * + * Since: 0.2.3 + **/ +void +asb_panel_set_title (AsbPanel *panel, const gchar *title) +{ + AsbPanelItem *item; + item = asb_panel_ensure_item (panel); + g_free (item->title); + item->title = g_strdup (title); + asb_panel_refresh (panel); +} + +/** + * asb_panel_set_status: + * @panel: A #AsbPanel + * @fmt: format string + * @...: varargs + * + * Sets the status for the currently running task. + * + * Since: 0.2.3 + **/ +void +asb_panel_set_status (AsbPanel *panel, const gchar *fmt, ...) +{ + AsbPanelItem *item; + va_list args; + + item = asb_panel_ensure_item (panel); + g_free (item->status); + + va_start (args, fmt); + item->status = g_strdup_vprintf (fmt, args); + va_end (args); + + asb_panel_refresh (panel); +} + +/** + * asb_panel_set_job_total: + * @panel: A #AsbPanel + * @job_number: numeric identifier + * + * Sets the largest job number for all of the tasks. + * + * Since: 0.2.3 + **/ +void +asb_panel_set_job_total (AsbPanel *panel, guint job_total) +{ + AsbPanelPrivate *priv = GET_PRIVATE (panel); + priv->job_total = job_total; +} + +/** + * asb_panel_finalize: + **/ +static void +asb_panel_finalize (GObject *object) +{ + AsbPanel *panel = ASB_PANEL (object); + AsbPanelPrivate *priv = GET_PRIVATE (panel); + + g_ptr_array_unref (priv->items); + g_mutex_clear (&priv->mutex); + + G_OBJECT_CLASS (asb_panel_parent_class)->finalize (object); +} + +/** + * asb_panel_init: + **/ +static void +asb_panel_init (AsbPanel *panel) +{ + AsbPanelPrivate *priv = GET_PRIVATE (panel); + priv->items = g_ptr_array_new_with_free_func ((GDestroyNotify) asb_panel_item_free); + priv->title_width = 20; + g_mutex_init (&priv->mutex); + + /* find an actual TTY */ + priv->tty_fd = open ("/dev/tty", O_RDWR, 0); + if (priv->tty_fd < 0) + priv->tty_fd = open ("/dev/console", O_RDWR, 0); + if (priv->tty_fd < 0) + priv->tty_fd = open ("/dev/stdout", O_RDWR, 0); + g_assert (priv->tty_fd > 0); +} + +/** + * asb_panel_class_init: + **/ +static void +asb_panel_class_init (AsbPanelClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + object_class->finalize = asb_panel_finalize; +} + +/** + * asb_panel_new: + * + * Creates a new panel. + * + * Returns: A #AsbPanel + * + * Since: 0.2.3 + **/ +AsbPanel * +asb_panel_new (void) +{ + AsbPanel *panel; + panel = g_object_new (ASB_TYPE_PANEL, NULL); + return ASB_PANEL (panel); +} diff --git a/libappstream-builder/asb-panel.h b/libappstream-builder/asb-panel.h new file mode 100644 index 0000000..e42da3f --- /dev/null +++ b/libappstream-builder/asb-panel.h @@ -0,0 +1,66 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- + * + * Copyright (C) 2014 Richard Hughes <richard@hughsie.com> + * + * Licensed under the GNU Lesser General Public License Version 2.1 + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#ifndef ASB_PANEL_H +#define ASB_PANEL_H + +#include <glib-object.h> + +#define ASB_TYPE_PANEL (asb_panel_get_type()) +#define ASB_PANEL(obj) (G_TYPE_CHECK_INSTANCE_CAST((obj), ASB_TYPE_PANEL, AsbPanel)) +#define ASB_PANEL_CLASS(cls) (G_TYPE_CHECK_CLASS_CAST((cls), ASB_TYPE_PANEL, AsbPanelClass)) +#define ASB_IS_PANEL(obj) (G_TYPE_CHECK_INSTANCE_TYPE((obj), ASB_TYPE_PANEL)) +#define ASB_IS_PANEL_CLASS(cls) (G_TYPE_CHECK_CLASS_TYPE((cls), ASB_TYPE_PANEL)) +#define ASB_PANEL_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS((obj), ASB_TYPE_PANEL, AsbPanelClass)) + +G_BEGIN_DECLS + +typedef struct _AsbPanel AsbPanel; +typedef struct _AsbPanelClass AsbPanelClass; + +struct _AsbPanel +{ + GObject parent; +}; + +struct _AsbPanelClass +{ + GObjectClass parent_class; +}; + +GType asb_panel_get_type (void); + +AsbPanel *asb_panel_new (void); +void asb_panel_remove (AsbPanel *panel); +void asb_panel_set_title (AsbPanel *panel, + const gchar *title); +void asb_panel_set_status (AsbPanel *panel, + const gchar *fmt, + ...) + G_GNUC_PRINTF(2,3); +void asb_panel_set_job_number (AsbPanel *panel, + guint job_number); +void asb_panel_set_job_total (AsbPanel *panel, + guint job_total); + +G_END_DECLS + +#endif /* ASB_PANEL_H */ diff --git a/libappstream-builder/asb-task.c b/libappstream-builder/asb-task.c index 8644960..26d7d07 100644 --- a/libappstream-builder/asb-task.c +++ b/libappstream-builder/asb-task.c @@ -43,6 +43,7 @@ struct _AsbTaskPrivate { AsbContext *ctx; AsbPackage *pkg; + AsbPanel *panel; GPtrArray *plugins_to_run; gchar *filename; gchar *tmpdir; @@ -111,6 +112,8 @@ asb_task_explode_extra_package (AsbTask *task, asb_package_get_source (priv->pkg)) != 0)) return TRUE; + asb_panel_set_status (priv->panel, "Decompressing extra pkg %s", + asb_package_get_name (pkg_extra)); asb_package_log (priv->pkg, ASB_PACKAGE_LOG_LEVEL_INFO, "Adding extra package %s for %s", @@ -224,6 +227,10 @@ asb_task_process (AsbTask *task, GError **error_not_used) /* reset the profile timer */ asb_package_log_start (priv->pkg); + asb_panel_set_job_number (priv->panel, priv->id + 1); + asb_panel_set_title (priv->panel, asb_package_get_name (priv->pkg)); + asb_panel_set_status (priv->panel, "Starting"); + /* did we get a file match on any plugin */ basename = g_path_get_basename (priv->filename); asb_package_log (priv->pkg, @@ -244,6 +251,7 @@ asb_task_process (AsbTask *task, GError **error_not_used) } /* explode tree */ + asb_panel_set_status (priv->panel, "Decompressing files"); asb_package_log (priv->pkg, ASB_PACKAGE_LOG_LEVEL_DEBUG, "Exploding tree for %s", @@ -266,6 +274,7 @@ asb_task_process (AsbTask *task, GError **error_not_used) goto skip; /* run plugins */ + asb_panel_set_status (priv->panel, "Examining"); for (i = 0; i < priv->plugins_to_run->len; i++) { plugin = g_ptr_array_index (priv->plugins_to_run, i); asb_package_log (priv->pkg, @@ -286,6 +295,7 @@ asb_task_process (AsbTask *task, GError **error_not_used) goto skip; /* print */ + asb_panel_set_status (priv->panel, "Processing"); for (l = apps; l != NULL; l = l->next) { app = l->data; @@ -426,6 +436,7 @@ skip: } /* delete tree */ + asb_panel_set_status (priv->panel, "Deleting temp files"); if (!asb_utils_rmtree (priv->tmpdir, &error)) { asb_package_log (priv->pkg, ASB_PACKAGE_LOG_LEVEL_WARNING, @@ -435,6 +446,7 @@ skip: } /* write log */ + asb_panel_set_status (priv->panel, "Writing log"); if (!asb_package_log_flush (priv->pkg, &error)) { asb_package_log (priv->pkg, ASB_PACKAGE_LOG_LEVEL_WARNING, @@ -444,10 +456,7 @@ skip: } /* update UI */ - g_print ("Processed %i/%i %s\n", - priv->id + 1, - asb_context_get_packages(priv->ctx)->len, - asb_package_get_name (priv->pkg)); + asb_panel_remove (priv->panel); out: g_list_free_full (apps, (GDestroyNotify) g_object_unref); return TRUE; @@ -466,6 +475,8 @@ asb_task_finalize (GObject *object) g_ptr_array_unref (priv->plugins_to_run); if (priv->pkg != NULL) g_object_unref (priv->pkg); + if (priv->panel != NULL) + g_object_unref (priv->panel); g_free (priv->filename); g_free (priv->tmpdir); @@ -492,6 +503,22 @@ asb_task_set_package (AsbTask *task, AsbPackage *pkg) } /** + * asb_task_set_panel: (skip): + * @task: A #AsbTask + * @panel: A #AsbPanel + * + * Sets the panel used for the task. + * + * Since: 0.2.3 + **/ +void +asb_task_set_panel (AsbTask *task, AsbPanel *panel) +{ + AsbTaskPrivate *priv = GET_PRIVATE (task); + priv->panel = g_object_ref (panel); +} + +/** * asb_task_set_id: * @task: A #AsbTask * @id: numeric identifier diff --git a/libappstream-builder/asb-task.h b/libappstream-builder/asb-task.h index b813b28..3f7a8c8 100644 --- a/libappstream-builder/asb-task.h +++ b/libappstream-builder/asb-task.h @@ -25,6 +25,7 @@ #include <glib-object.h> #include "asb-package.h" +#include "asb-panel.h" #include "asb-context.h" #define ASB_TYPE_TASK (asb_task_get_type()) @@ -57,6 +58,8 @@ gboolean asb_task_process (AsbTask *task, GError **error_not_used); void asb_task_set_package (AsbTask *task, AsbPackage *pkg); +void asb_task_set_panel (AsbTask *task, + AsbPanel *panel); void asb_task_set_id (AsbTask *task, guint id); |