summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorRichard Hughes <richard@hughsie.com>2014-07-17 11:57:11 +0100
committerRichard Hughes <richard@hughsie.com>2014-07-17 11:57:11 +0100
commit6feb98005ec61f9e77ed25f0cf50f29179f1ef0b (patch)
tree3776c0456fb9058ec000812bd3dd151eb0ecc08b
parent1ba3be23a2b0b874281f53a1f48fd5a666c22686 (diff)
downloadappstream-glib-6feb98005ec61f9e77ed25f0cf50f29179f1ef0b.tar.gz
Show the builder progress in a ncurses-style panel
-rw-r--r--libappstream-builder/Makefile.am2
-rw-r--r--libappstream-builder/asb-context.c6
-rw-r--r--libappstream-builder/asb-panel.c379
-rw-r--r--libappstream-builder/asb-panel.h66
-rw-r--r--libappstream-builder/asb-task.c35
-rw-r--r--libappstream-builder/asb-task.h3
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);