summaryrefslogtreecommitdiff
path: root/libappstream-builder
diff options
context:
space:
mode:
authorRichard Hughes <richard@hughsie.com>2014-06-17 11:52:49 +0100
committerRichard Hughes <richard@hughsie.com>2014-06-17 12:01:32 +0100
commit1284b3fa0addf21070c6d9ce697623a04862cdde (patch)
treea9ecf1b4d984e41e56179ebd6453288dd712843e /libappstream-builder
parent0ec1dd10ee2281d4f6e102e4e8da8365332540b1 (diff)
downloadappstream-glib-1284b3fa0addf21070c6d9ce697623a04862cdde.tar.gz
Add libappstream-builder from the createrepo_as project
Diffstat (limited to 'libappstream-builder')
-rw-r--r--libappstream-builder/Makefile.am135
-rw-r--r--libappstream-builder/appstream-builder.h32
-rw-r--r--libappstream-builder/appstream-builder.pc.in12
-rw-r--r--libappstream-builder/asb-app.c494
-rw-r--r--libappstream-builder/asb-app.h86
-rw-r--r--libappstream-builder/asb-context-private.h33
-rw-r--r--libappstream-builder/asb-context.c979
-rw-r--r--libappstream-builder/asb-context.h111
-rw-r--r--libappstream-builder/asb-package-deb.c227
-rw-r--r--libappstream-builder/asb-package-deb.h60
-rw-r--r--libappstream-builder/asb-package-rpm.c657
-rw-r--r--libappstream-builder/asb-package-rpm.h60
-rw-r--r--libappstream-builder/asb-package.c780
-rw-r--r--libappstream-builder/asb-package.h135
-rw-r--r--libappstream-builder/asb-plugin-loader.c383
-rw-r--r--libappstream-builder/asb-plugin-loader.h48
-rw-r--r--libappstream-builder/asb-plugin.c120
-rw-r--r--libappstream-builder/asb-plugin.h102
-rw-r--r--libappstream-builder/asb-task.c557
-rw-r--r--libappstream-builder/asb-task.h65
-rw-r--r--libappstream-builder/asb-utils.c571
-rw-r--r--libappstream-builder/asb-utils.h64
22 files changed, 5711 insertions, 0 deletions
diff --git a/libappstream-builder/Makefile.am b/libappstream-builder/Makefile.am
new file mode 100644
index 0000000..f8d6167
--- /dev/null
+++ b/libappstream-builder/Makefile.am
@@ -0,0 +1,135 @@
+if HAVE_INTROSPECTION
+-include $(INTROSPECTION_MAKEFILE)
+INTROSPECTION_GIRS =
+INTROSPECTION_SCANNER_ARGS = \
+ --add-include-path=$(srcdir) \
+ --add-include-path=$(top_builddir)/libappstream-glib
+INTROSPECTION_COMPILER_ARGS = \
+ --includedir=$(srcdir) \
+ --includedir=$(top_builddir)/libappstream-glib
+
+endif
+
+AM_CPPFLAGS = \
+ $(GLIB_CFLAGS) \
+ $(GDKPIXBUF_CFLAGS) \
+ $(SOUP_CFLAGS) \
+ -I$(top_srcdir)/libappstream-glib \
+ -I$(top_builddir)/libappstream-glib \
+ -I. \
+ -DAS_COMPILATION \
+ -DTESTDATADIR=\""$(top_srcdir)/data/tests"\" \
+ -DASB_PLUGIN_DIR=\"$(libdir)/asb-plugins\" \
+ -DG_LOG_DOMAIN=\"Asb\"
+
+AS_GLIB_LIBS = \
+ $(top_builddir)/libappstream-glib/libappstream-glib.la
+
+pkgconfigdir = $(libdir)/pkgconfig
+pkgconfig_DATA = \
+ appstream-builder.pc
+
+lib_LTLIBRARIES = \
+ libappstream-builder.la
+
+libappstream_builder_includedir = $(includedir)/libappstream-builder
+libappstream_builder_include_HEADERS = \
+ appstream-builder.h \
+ asb-app.h
+
+libappstream_builder_la_SOURCES = \
+ asb-app.c \
+ asb-app.h \
+ asb-context.c \
+ asb-context.h \
+ asb-context-private.h \
+ asb-package.c \
+ asb-package-deb.c \
+ asb-package-deb.h \
+ asb-package.h \
+ asb-task.c \
+ asb-task.h \
+ asb-utils.c \
+ asb-utils.h \
+ asb-plugin.c \
+ asb-plugin.h \
+ asb-plugin-loader.c \
+ asb-plugin-loader.h
+
+if HAVE_RPM
+libappstream_builder_la_SOURCES += \
+ asb-package-rpm.c \
+ asb-package-rpm.h
+endif
+
+libappstream_builder_la_LIBADD = \
+ $(AS_GLIB_LIBS) \
+ $(RPM_LIBS) \
+ $(GLIB_LIBS)
+
+libappstream_builder_la_LDFLAGS = \
+ -version-info $(LT_CURRENT):$(LT_REVISION):$(LT_AGE) \
+ -export-dynamic \
+ -no-undefined \
+ -export-symbols-regex '^asb_.*'
+
+libappstream_builder_la_CFLAGS = \
+ $(WARNINGFLAGS_C)
+
+if HAVE_INTROSPECTION
+introspection_sources = \
+ asb-app.c \
+ asb-app.h \
+ asb-context.c \
+ asb-context.h \
+ asb-context-private.h \
+ asb-package.c \
+ asb-package.h \
+ asb-task.c \
+ asb-task.h
+
+AppStreamBuilder-1.0.gir: libappstream-builder.la
+AppStreamBuilder_1_0_gir_INCLUDES = \
+ AppStreamGlib-1.0 \
+ GdkPixbuf-2.0 \
+ Gio-2.0 \
+ GObject-2.0
+AppStreamBuilder_1_0_gir_CFLAGS = $(AM_CPPFLAGS)
+AppStreamBuilder_1_0_gir_SCANNERFLAGS = --identifier-prefix=Asb \
+ --symbol-prefix=asb_ \
+ --warn-all \
+ --add-include-path=$(srcdir)
+AppStreamBuilder_1_0_gir_EXPORT_PACKAGES = appstream-builder
+AppStreamBuilder_1_0_gir_LIBS = \
+ $(AS_GLIB_LIBS) \
+ libappstream-builder.la \
+ archive
+if HAVE_RPM
+AppStreamBuilder_1_0_gir_LIBS += \
+ rpmio \
+ rpm
+endif
+AppStreamBuilder_1_0_gir_FILES = $(introspection_sources)
+INTROSPECTION_GIRS += AppStreamBuilder-1.0.gir
+
+girdir = $(datadir)/gir-1.0
+gir_DATA = $(INTROSPECTION_GIRS)
+
+typelibdir = $(libdir)/girepository-1.0
+typelib_DATA = $(INTROSPECTION_GIRS:.gir=.typelib)
+
+CLEANFILES = $(gir_DATA) $(typelib_DATA)
+endif
+
+DISTCLEANFILES = \
+ *.log \
+ *.trs
+
+EXTRA_DIST = \
+ appstream-builder.pc.in
+
+clean-local:
+ rm -f *~
+ rm -f $(CLEANFILES)
+
+-include $(top_srcdir)/git.mk
diff --git a/libappstream-builder/appstream-builder.h b/libappstream-builder/appstream-builder.h
new file mode 100644
index 0000000..9c07f8a
--- /dev/null
+++ b/libappstream-builder/appstream-builder.h
@@ -0,0 +1,32 @@
+/* -*- 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 __APPSTREAM_BUILDER_H
+#define __APPSTREAM_BUILDER_H
+
+#define __APPSTREAM_BUILDER_H_INSIDE__
+
+#include <asb-context.h>
+
+#undef __APPSTREAM_BUILDER_H_INSIDE__
+
+#endif /* __APPSTREAM_BUILDER_H */
+
diff --git a/libappstream-builder/appstream-builder.pc.in b/libappstream-builder/appstream-builder.pc.in
new file mode 100644
index 0000000..5bdc755
--- /dev/null
+++ b/libappstream-builder/appstream-builder.pc.in
@@ -0,0 +1,12 @@
+prefix=@prefix@
+exec_prefix=@exec_prefix@
+libdir=@libdir@
+includedir=@includedir@
+
+Name: appstream-glib
+Description: Objects and helper methods to help reading and writing AppStream metadata
+Version: @VERSION@
+Requires: glib-2.0, gobject-2.0, gdk-pixbuf-2.0
+Libs: -L${libdir} -lappstream-glib
+Cflags: -I${includedir}/libappstream-glib
+
diff --git a/libappstream-builder/asb-app.c b/libappstream-builder/asb-app.c
new file mode 100644
index 0000000..2f8fd7b
--- /dev/null
+++ b/libappstream-builder/asb-app.c
@@ -0,0 +1,494 @@
+/* -*- 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-app
+ * @short_description: Application object.
+ * @stability: Unstable
+ *
+ * This is an application object that wraps AsApp and provides further features.
+ */
+
+#include "config.h"
+
+#include <stdlib.h>
+#include <appstream-glib.h>
+
+#include "asb-app.h"
+#include "as-cleanup.h"
+
+typedef struct _AsbAppPrivate AsbAppPrivate;
+struct _AsbAppPrivate
+{
+ GPtrArray *vetos;
+ GPtrArray *requires_appdata;
+ GdkPixbuf *pixbuf;
+ AsbPackage *pkg;
+};
+
+G_DEFINE_TYPE_WITH_PRIVATE (AsbApp, asb_app, AS_TYPE_APP)
+
+#define GET_PRIVATE(o) (asb_app_get_instance_private (o))
+
+/**
+ * asb_app_finalize:
+ **/
+static void
+asb_app_finalize (GObject *object)
+{
+ AsbApp *app = ASB_APP (object);
+ AsbAppPrivate *priv = GET_PRIVATE (app);
+
+ g_ptr_array_unref (priv->vetos);
+ g_ptr_array_unref (priv->requires_appdata);
+ if (priv->pixbuf != NULL)
+ g_object_unref (priv->pixbuf);
+ if (priv->pkg != NULL)
+ g_object_unref (priv->pkg);
+
+ G_OBJECT_CLASS (asb_app_parent_class)->finalize (object);
+}
+
+/**
+ * asb_app_init:
+ **/
+static void
+asb_app_init (AsbApp *app)
+{
+ AsbAppPrivate *priv = GET_PRIVATE (app);
+ priv->vetos = g_ptr_array_new_with_free_func (g_free);
+ priv->requires_appdata = g_ptr_array_new_with_free_func (g_free);
+}
+
+/**
+ * asb_app_class_init:
+ **/
+static void
+asb_app_class_init (AsbAppClass *klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+ object_class->finalize = asb_app_finalize;
+}
+
+/**
+ * asb_app_to_xml:
+ * @app: A #AsbApp
+ *
+ * Converts the application to it's XML representation.
+ *
+ * Returns: allocated string
+ *
+ * Since: 0.1.0
+ **/
+gchar *
+asb_app_to_xml (AsbApp *app)
+{
+ GString *str;
+ _cleanup_object_unref_ AsStore *store;
+
+ store = as_store_new ();
+ as_store_add_app (store, AS_APP (app));
+ str = as_store_to_xml (store,
+ AS_NODE_TO_XML_FLAG_FORMAT_INDENT |
+ AS_NODE_TO_XML_FLAG_FORMAT_MULTILINE);
+ return g_string_free (str, FALSE);
+}
+
+/**
+ * asb_app_add_veto:
+ * @app: A #AsbApp
+ * @fmt: format string
+ * @...: varargs
+ *
+ * Adds a reason to not include the application.
+ *
+ * Since: 0.1.0
+ **/
+void
+asb_app_add_veto (AsbApp *app, const gchar *fmt, ...)
+{
+ AsbAppPrivate *priv = GET_PRIVATE (app);
+ gchar *tmp;
+ va_list args;
+ va_start (args, fmt);
+ tmp = g_strdup_vprintf (fmt, args);
+ va_end (args);
+ g_ptr_array_add (priv->vetos, tmp);
+}
+
+/**
+ * asb_app_add_requires_appdata:
+ * @app: A #AsbApp
+ * @fmt: format string
+ * @...: varargs
+ *
+ * Adds a reason that AppData is required.
+ *
+ * Since: 0.1.0
+ **/
+void
+asb_app_add_requires_appdata (AsbApp *app, const gchar *fmt, ...)
+{
+ AsbAppPrivate *priv = GET_PRIVATE (app);
+ gchar *tmp;
+ va_list args;
+ va_start (args, fmt);
+ tmp = g_strdup_vprintf (fmt, args);
+ va_end (args);
+ g_ptr_array_add (priv->requires_appdata, tmp);
+}
+
+/**
+ * asb_app_set_requires_appdata:
+ * @app: A #AsbApp
+ * @requires_appdata: boolean
+ *
+ * Sets (or clears) the requirement for AppData.
+ *
+ * Since: 0.1.0
+ **/
+void
+asb_app_set_requires_appdata (AsbApp *app, gboolean requires_appdata)
+{
+ AsbAppPrivate *priv = GET_PRIVATE (app);
+ if (requires_appdata) {
+ g_ptr_array_add (priv->requires_appdata, NULL);
+ } else {
+ g_ptr_array_set_size (priv->requires_appdata, 0);
+ }
+}
+
+/**
+ * asb_app_set_pixbuf:
+ * @app: A #AsbApp
+ * @pixbuf: a #GdkPixbuf
+ *
+ * Sets the icon for the application.
+ *
+ * Since: 0.1.0
+ **/
+void
+asb_app_set_pixbuf (AsbApp *app, GdkPixbuf *pixbuf)
+{
+ AsbAppPrivate *priv = GET_PRIVATE (app);
+ if (priv->pixbuf != NULL)
+ g_object_ref (priv->pixbuf);
+ priv->pixbuf = g_object_ref (pixbuf);
+
+ /* does the icon not have an alpha channel */
+ if (!gdk_pixbuf_get_has_alpha (priv->pixbuf)) {
+ asb_package_log (priv->pkg,
+ ASB_PACKAGE_LOG_LEVEL_WARNING,
+ "icon does not have an alpha channel");
+ }
+}
+
+/**
+ * asb_app_get_requires_appdata:
+ * @app: A #AsbApp
+ *
+ * Gets if AppData is still required for the application.
+ *
+ * Returns: (transfer none) (element-type utf8): A list of reasons
+ *
+ * Since: 0.1.0
+ **/
+GPtrArray *
+asb_app_get_requires_appdata (AsbApp *app)
+{
+ AsbAppPrivate *priv = GET_PRIVATE (app);
+ return priv->requires_appdata;
+}
+
+/**
+ * asb_app_get_vetos:
+ * @app: A #AsbApp
+ *
+ * Gets the list of vetos.
+ *
+ * Returns: (transfer none) (element-type utf8): A list of vetos
+ *
+ * Since: 0.1.0
+ **/
+GPtrArray *
+asb_app_get_vetos (AsbApp *app)
+{
+ AsbAppPrivate *priv = GET_PRIVATE (app);
+ return priv->vetos;
+}
+
+/**
+ * asb_app_get_package:
+ * @app: A #AsbApp
+ *
+ * Gets the package that backs the application.
+ *
+ * Returns: (transfer none): package
+ *
+ * Since: 0.1.0
+ **/
+AsbPackage *
+asb_app_get_package (AsbApp *app)
+{
+ AsbAppPrivate *priv = GET_PRIVATE (app);
+ return priv->pkg;
+}
+
+/**
+ * asb_app_save_resources_image:
+ **/
+static gboolean
+asb_app_save_resources_image (AsbApp *app,
+ AsImage *image,
+ GError **error)
+{
+ const gchar *output_dir;
+ gboolean ret = TRUE;
+ _cleanup_free_ gchar *filename = NULL;
+ _cleanup_free_ gchar *size_str;
+
+ /* treat source images differently */
+ if (as_image_get_kind (image) == AS_IMAGE_KIND_SOURCE) {
+ size_str = g_strdup ("source");
+ } else {
+ size_str = g_strdup_printf ("%ix%i",
+ as_image_get_width (image),
+ as_image_get_height (image));
+ }
+
+ /* does screenshot already exist */
+ output_dir = asb_package_get_config (asb_app_get_package (app), "OutputDir");
+ filename = g_build_filename (output_dir,
+ "screenshots",
+ size_str,
+ as_image_get_basename (image),
+ NULL);
+ if (g_file_test (filename, G_FILE_TEST_EXISTS)) {
+ asb_package_log (asb_app_get_package (app),
+ ASB_PACKAGE_LOG_LEVEL_DEBUG,
+ "%s screenshot already exists", size_str);
+ return TRUE;
+ }
+
+ /* thumbnails will already be 16:9 */
+ ret = as_image_save_filename (image,
+ filename,
+ 0, 0,
+ AS_IMAGE_SAVE_FLAG_NONE,
+ error);
+ if (!ret)
+ return FALSE;
+
+ /* set new AppStream compatible screenshot name */
+ asb_package_log (asb_app_get_package (app),
+ ASB_PACKAGE_LOG_LEVEL_DEBUG,
+ "saved %s screenshot", size_str);
+ return TRUE;
+
+}
+
+/**
+ * asb_app_save_resources_screenshot:
+ **/
+static gboolean
+asb_app_save_resources_screenshot (AsbApp *app,
+ AsScreenshot *screenshot,
+ GError **error)
+{
+ AsImage *im;
+ GPtrArray *images;
+ guint i;
+
+ images = as_screenshot_get_images (screenshot);
+ for (i = 0; i < images->len; i++) {
+ im = g_ptr_array_index (images, i);
+ if (!asb_app_save_resources_image (app, im, error))
+ return FALSE;
+ }
+ return TRUE;
+}
+
+/**
+ * asb_app_save_resources:
+ * @app: A #AsbApp
+ * @error: A #GError or %NULL
+ *
+ * Saves to disk any resources set for the application.
+ *
+ * Returns: %TRUE for success, %FALSE otherwise
+ *
+ * Since: 0.1.0
+ **/
+gboolean
+asb_app_save_resources (AsbApp *app, GError **error)
+{
+ AsbAppPrivate *priv = GET_PRIVATE (app);
+ AsScreenshot *ss;
+ guint i;
+ GPtrArray *screenshots;
+
+ /* any non-stock icon set */
+ if (priv->pixbuf != NULL) {
+ const gchar *tmpdir;
+ _cleanup_free_ gchar *filename = NULL;
+
+ tmpdir = asb_package_get_config (priv->pkg, "TempDir");
+ filename = g_build_filename (tmpdir,
+ "icons",
+ as_app_get_icon (AS_APP (app)),
+ NULL);
+ if (!gdk_pixbuf_save (priv->pixbuf, filename, "png", error, NULL))
+ return FALSE;
+
+ /* set new AppStream compatible icon name */
+ asb_package_log (priv->pkg,
+ ASB_PACKAGE_LOG_LEVEL_DEBUG,
+ "Saved icon %s", filename);
+ }
+
+ /* save any screenshots */
+ screenshots = as_app_get_screenshots (AS_APP (app));
+ for (i = 0; i < screenshots->len; i++) {
+ ss = g_ptr_array_index (screenshots, i);
+ if (!asb_app_save_resources_screenshot (app, ss, error))
+ return FALSE;
+ }
+ return TRUE;
+}
+
+/**
+ * asb_app_add_screenshot_source:
+ * @app: A #AsbApp
+ * @filename: filename to the source image
+ * @error: A #GError or %NULL
+ *
+ * Adds a screenshot from a previously saved image.
+ *
+ * Returns: %TRUE for success, %FALSE otherwise
+ *
+ * Since: 0.1.0
+ **/
+gboolean
+asb_app_add_screenshot_source (AsbApp *app, const gchar *filename, GError **error)
+{
+ gboolean is_default;
+ guint sizes[] = { 624, 351, 112, 63, 752, 423, 0 };
+ const gchar *mirror_uri;
+ guint i;
+ _cleanup_free_ gchar *basename = NULL;
+ _cleanup_object_unref_ AsImage *im_src;
+ _cleanup_object_unref_ AsScreenshot *ss = NULL;
+
+ im_src = as_image_new ();
+ if (!as_image_load_filename (im_src, filename, error))
+ return FALSE;
+
+ /* is the aspect ratio of the source perfectly 16:9 */
+ if ((as_image_get_width (im_src) / 16) * 9 !=
+ as_image_get_height (im_src)) {
+ asb_package_log (asb_app_get_package (app),
+ ASB_PACKAGE_LOG_LEVEL_WARNING,
+ "%s is not in 16:9 aspect ratio",
+ filename);
+ }
+
+ ss = as_screenshot_new ();
+ is_default = as_app_get_screenshots(AS_APP(app))->len == 0;
+ as_screenshot_set_kind (ss, is_default ? AS_SCREENSHOT_KIND_DEFAULT :
+ AS_SCREENSHOT_KIND_NORMAL);
+
+ /* include the app-id in the basename */
+ basename = g_strdup_printf ("%s-%s.png",
+ as_app_get_id (AS_APP (app)),
+ as_image_get_md5 (im_src));
+ as_image_set_basename (im_src, basename);
+
+ /* only fonts have full sized screenshots */
+ mirror_uri = asb_package_get_config (asb_app_get_package (app), "MirrorURI");
+ if (as_app_get_id_kind (AS_APP (app)) == AS_ID_KIND_FONT) {
+ _cleanup_free_ gchar *url_tmp;
+ url_tmp = g_build_filename (mirror_uri,
+ "source",
+ basename,
+ NULL);
+ as_image_set_url (im_src, url_tmp, -1);
+ as_image_set_kind (im_src, AS_IMAGE_KIND_SOURCE);
+ as_screenshot_add_image (ss, im_src);
+ } else {
+ for (i = 0; sizes[i] != 0; i += 2) {
+ _cleanup_free_ gchar *size_str;
+ _cleanup_free_ gchar *url_tmp;
+ _cleanup_object_unref_ AsImage *im_tmp;
+ _cleanup_object_unref_ GdkPixbuf *pixbuf;
+
+ size_str = g_strdup_printf ("%ix%i",
+ sizes[i],
+ sizes[i+1]);
+ url_tmp = g_build_filename (mirror_uri,
+ size_str,
+ basename,
+ NULL);
+ pixbuf = as_image_save_pixbuf (im_src,
+ sizes[i],
+ sizes[i+1],
+ AS_IMAGE_SAVE_FLAG_PAD_16_9);
+ im_tmp = as_image_new ();
+ as_image_set_width (im_tmp, sizes[i]);
+ as_image_set_height (im_tmp, sizes[i+1]);
+ as_image_set_url (im_tmp, url_tmp, -1);
+ as_image_set_pixbuf (im_tmp, pixbuf);
+ as_image_set_kind (im_tmp, AS_IMAGE_KIND_THUMBNAIL);
+ as_image_set_basename (im_tmp, basename);
+ as_screenshot_add_image (ss, im_tmp);
+ }
+ }
+ as_app_add_screenshot (AS_APP (app), ss);
+ return TRUE;
+}
+
+/**
+ * asb_app_new:
+ * @pkg: A #AsbPackage
+ * @id_full: The ID for the package
+ *
+ * Creates a new application object.
+ *
+ * Returns: a #AsbApp
+ *
+ * Since: 0.1.0
+ **/
+AsbApp *
+asb_app_new (AsbPackage *pkg, const gchar *id_full)
+{
+ AsbApp *app;
+ AsbAppPrivate *priv;
+
+ app = g_object_new (ASB_TYPE_APP, NULL);
+ priv = GET_PRIVATE (app);
+ if (pkg != NULL) {
+ priv->pkg = g_object_ref (pkg);
+ as_app_add_pkgname (AS_APP (app),
+ asb_package_get_name (pkg), -1);
+ }
+ if (id_full != NULL)
+ as_app_set_id_full (AS_APP (app), id_full, -1);
+ return ASB_APP (app);
+}
diff --git a/libappstream-builder/asb-app.h b/libappstream-builder/asb-app.h
new file mode 100644
index 0000000..f31f579
--- /dev/null
+++ b/libappstream-builder/asb-app.h
@@ -0,0 +1,86 @@
+/* -*- 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_APP_H
+#define ASB_APP_H
+
+#include <stdarg.h>
+#include <glib-object.h>
+#include <appstream-glib.h>
+#include <gdk-pixbuf/gdk-pixbuf.h>
+
+#include "asb-package.h"
+
+#define ASB_TYPE_APP (asb_app_get_type())
+#define ASB_APP(obj) (G_TYPE_CHECK_INSTANCE_CAST((obj), ASB_TYPE_APP, AsbApp))
+#define ASB_APP_CLASS(cls) (G_TYPE_CHECK_CLASS_CAST((cls), ASB_TYPE_APP, AsbAppClass))
+#define ASB_IS_APP(obj) (G_TYPE_CHECK_INSTANCE_TYPE((obj), ASB_TYPE_APP))
+#define ASB_IS_APP_CLASS(cls) (G_TYPE_CHECK_CLASS_TYPE((cls), ASB_TYPE_APP))
+#define ASB_APP_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS((obj), ASB_TYPE_APP, AsbAppClass))
+
+G_BEGIN_DECLS
+
+typedef struct _AsbApp AsbApp;
+typedef struct _AsbAppClass AsbAppClass;
+
+struct _AsbApp
+{
+ AsApp parent;
+};
+
+struct _AsbAppClass
+{
+ AsAppClass parent_class;
+};
+
+GType asb_app_get_type (void);
+
+
+AsbApp *asb_app_new (AsbPackage *pkg,
+ const gchar *id_full);
+gchar *asb_app_to_xml (AsbApp *app);
+void asb_app_add_veto (AsbApp *app,
+ const gchar *fmt,
+ ...)
+ G_GNUC_PRINTF(2,3);
+void asb_app_add_requires_appdata (AsbApp *app,
+ const gchar *fmt,
+ ...)
+ G_GNUC_PRINTF(2,3);
+void asb_app_set_requires_appdata (AsbApp *app,
+ gboolean requires_appdata);
+void asb_app_set_pixbuf (AsbApp *app,
+ GdkPixbuf *pixbuf);
+gboolean asb_app_add_screenshot_source (AsbApp *app,
+ const gchar *filename,
+ GError **error);
+
+GPtrArray *asb_app_get_requires_appdata (AsbApp *app);
+GPtrArray *asb_app_get_vetos (AsbApp *app);
+AsbPackage *asb_app_get_package (AsbApp *app);
+
+gboolean asb_app_save_resources (AsbApp *app,
+ GError **error);
+
+
+G_END_DECLS
+
+#endif /* ASB_APP_H */
diff --git a/libappstream-builder/asb-context-private.h b/libappstream-builder/asb-context-private.h
new file mode 100644
index 0000000..523b3ee
--- /dev/null
+++ b/libappstream-builder/asb-context-private.h
@@ -0,0 +1,33 @@
+/* -*- 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_CONTEXT_INTERNAL_H
+#define ASB_CONTEXT_INTERNAL_H
+
+#include "asb-context.h"
+
+GPtrArray *asb_context_get_file_globs (AsbContext *ctx);
+GPtrArray *asb_context_get_packages (AsbContext *ctx);
+GPtrArray *asb_context_get_plugins (AsbContext *ctx);
+
+G_END_DECLS
+
+#endif /* GPLASB_CONTEXT_INTERNAL_H */
diff --git a/libappstream-builder/asb-context.c b/libappstream-builder/asb-context.c
new file mode 100644
index 0000000..f5441e3
--- /dev/null
+++ b/libappstream-builder/asb-context.c
@@ -0,0 +1,979 @@
+/* -*- 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-context
+ * @short_description: High level interface for creating metadata.
+ * @stability: Unstable
+ *
+ * This high level object can be used to build metadata given some package
+ * filenames.
+ */
+
+#include "config.h"
+
+#include <stdlib.h>
+#include <appstream-glib.h>
+
+#include "as-cleanup.h"
+#include "asb-context.h"
+#include "asb-context-private.h"
+#include "asb-plugin.h"
+#include "asb-plugin-loader.h"
+#include "asb-task.h"
+#include "asb-utils.h"
+
+#ifdef HAVE_RPM
+#include "asb-package-rpm.h"
+#endif
+
+#include "asb-package-deb.h"
+
+typedef struct _AsbContextPrivate AsbContextPrivate;
+struct _AsbContextPrivate
+{
+ AsStore *old_md_cache;
+ GList *apps; /* of AsbApp */
+ GMutex apps_mutex; /* for ->apps */
+ GPtrArray *blacklisted_pkgs; /* of AsbGlobValue */
+ GPtrArray *extra_pkgs; /* of AsbGlobValue */
+ GPtrArray *file_globs; /* of AsbPackage */
+ GPtrArray *packages; /* of AsbPackage */
+ GPtrArray *plugins; /* of AsbPlugin */
+ gboolean add_cache_id;
+ gboolean extra_checks;
+ gboolean no_net;
+ gboolean use_package_cache;
+ guint max_threads;
+ gdouble api_version;
+ gchar *old_metadata;
+ gchar *extra_appstream;
+ gchar *extra_appdata;
+ gchar *extra_screenshots;
+ gchar *screenshot_uri;
+ gchar *log_dir;
+ gchar *cache_dir;
+ gchar *temp_dir;
+ gchar *output_dir;
+ gchar *basename;
+};
+
+G_DEFINE_TYPE_WITH_PRIVATE (AsbContext, asb_context, G_TYPE_OBJECT)
+
+#define GET_PRIVATE(o) (asb_context_get_instance_private (o))
+
+/**
+ * asb_context_set_no_net:
+ * @ctx: A #AsbContext
+ * @no_net: if network is disallowed
+ *
+ * Sets if network access is disallowed.
+ *
+ * Since: 0.1.0
+ **/
+void
+asb_context_set_no_net (AsbContext *ctx, gboolean no_net)
+{
+ AsbContextPrivate *priv = GET_PRIVATE (ctx);
+ priv->no_net = no_net;
+}
+
+/**
+ * asb_context_set_api_version:
+ * @ctx: A #AsbContext
+ * @api_version: the AppStream API version
+ *
+ * Sets the version of the metadata to write.
+ *
+ * Since: 0.1.0
+ **/
+void
+asb_context_set_api_version (AsbContext *ctx, gdouble api_version)
+{
+ AsbContextPrivate *priv = GET_PRIVATE (ctx);
+ priv->api_version = api_version;
+}
+
+/**
+ * asb_context_set_add_cache_id:
+ * @ctx: A #AsbContext
+ * @add_cache_id: boolean
+ *
+ * Sets if the cache id should be included in the metadata.
+ *
+ * Since: 0.1.0
+ **/
+void
+asb_context_set_add_cache_id (AsbContext *ctx, gboolean add_cache_id)
+{
+ AsbContextPrivate *priv = GET_PRIVATE (ctx);
+ priv->add_cache_id = add_cache_id;
+}
+
+/**
+ * asb_context_set_extra_checks:
+ * @ctx: A #AsbContext
+ * @extra_checks: boolean
+ *
+ * Sets if extra checks should be performed when building the metadata.
+ * Doing this requires internet access and may take a lot longer.
+ *
+ * Since: 0.1.0
+ **/
+void
+asb_context_set_extra_checks (AsbContext *ctx, gboolean extra_checks)
+{
+ AsbContextPrivate *priv = GET_PRIVATE (ctx);
+ priv->extra_checks = extra_checks;
+}
+
+/**
+ * asb_context_set_use_package_cache:
+ * @ctx: A #AsbContext
+ * @use_package_cache: boolean
+ *
+ * Sets if the package cache should be used.
+ *
+ * Since: 0.1.0
+ **/
+void
+asb_context_set_use_package_cache (AsbContext *ctx, gboolean use_package_cache)
+{
+ AsbContextPrivate *priv = GET_PRIVATE (ctx);
+ priv->use_package_cache = use_package_cache;
+}
+
+/**
+ * asb_context_set_max_threads:
+ * @ctx: A #AsbContext
+ * @max_threads: integer
+ *
+ * Sets the maximum number of threads to use when processing packages.
+ *
+ * Since: 0.1.0
+ **/
+void
+asb_context_set_max_threads (AsbContext *ctx, guint max_threads)
+{
+ AsbContextPrivate *priv = GET_PRIVATE (ctx);
+ priv->max_threads = max_threads;
+}
+
+/**
+ * asb_context_set_old_metadata:
+ * @ctx: A #AsbContext
+ * @old_metadata: filename, or %NULL
+ *
+ * Sets the filename location of the old metadata file.
+ * Note: the old metadata must have been built with cache-ids to be useful.
+ *
+ * Since: 0.1.0
+ **/
+void
+asb_context_set_old_metadata (AsbContext *ctx, const gchar *old_metadata)
+{
+ AsbContextPrivate *priv = GET_PRIVATE (ctx);
+ priv->old_metadata = g_strdup (old_metadata);
+}
+
+/**
+ * asb_context_set_extra_appstream:
+ * @ctx: A #AsbContext
+ * @extra_appstream: directory name, or %NULL
+ *
+ * Sets the location of a directory which is used for supplimental AppStream
+ * files.
+ *
+ * Since: 0.1.0
+ **/
+void
+asb_context_set_extra_appstream (AsbContext *ctx, const gchar *extra_appstream)
+{
+ AsbContextPrivate *priv = GET_PRIVATE (ctx);
+ priv->extra_appstream = g_strdup (extra_appstream);
+}
+
+/**
+ * asb_context_set_extra_appdata:
+ * @ctx: A #AsbContext
+ * @extra_appdata: directory name, or %NULL
+ *
+ * Sets the location of a directory which is used for supplimental AppData
+ * files.
+ *
+ * Since: 0.1.0
+ **/
+void
+asb_context_set_extra_appdata (AsbContext *ctx, const gchar *extra_appdata)
+{
+ AsbContextPrivate *priv = GET_PRIVATE (ctx);
+ priv->extra_appdata = g_strdup (extra_appdata);
+}
+
+/**
+ * asb_context_set_extra_screenshots:
+ * @ctx: A #AsbContext
+ * @extra_screenshots: directory name, or %NULL
+ *
+ * Sets the location of a directory which is used for supplimental screenshot
+ * files.
+ *
+ * Since: 0.1.0
+ **/
+void
+asb_context_set_extra_screenshots (AsbContext *ctx, const gchar *extra_screenshots)
+{
+ AsbContextPrivate *priv = GET_PRIVATE (ctx);
+ priv->extra_screenshots = g_strdup (extra_screenshots);
+}
+
+/**
+ * asb_context_set_screenshot_uri:
+ * @ctx: A #AsbContext
+ * @screenshot_uri: Remote URI root, e.g. "http://www.mysite.com/screenshots/"
+ *
+ * Sets the remote screenshot URI for screenshots.
+ *
+ * Since: 0.1.0
+ **/
+void
+asb_context_set_screenshot_uri (AsbContext *ctx, const gchar *screenshot_uri)
+{
+ AsbContextPrivate *priv = GET_PRIVATE (ctx);
+ priv->screenshot_uri = g_strdup (screenshot_uri);
+}
+
+/**
+ * asb_context_set_log_dir:
+ * @ctx: A #AsbContext
+ * @log_dir: directory
+ *
+ * Sets the log directory to use when building metadata.
+ *
+ * Since: 0.1.0
+ **/
+void
+asb_context_set_log_dir (AsbContext *ctx, const gchar *log_dir)
+{
+ AsbContextPrivate *priv = GET_PRIVATE (ctx);
+ priv->log_dir = g_strdup (log_dir);
+}
+
+/**
+ * asb_context_set_cache_dir:
+ * @ctx: A #AsbContext
+ * @cache_dir: directory
+ *
+ * Sets the cache directory to use when building metadata.
+ *
+ * Since: 0.1.0
+ **/
+void
+asb_context_set_cache_dir (AsbContext *ctx, const gchar *cache_dir)
+{
+ AsbContextPrivate *priv = GET_PRIVATE (ctx);
+ priv->cache_dir = g_strdup (cache_dir);
+}
+
+/**
+ * asb_context_set_temp_dir:
+ * @ctx: A #AsbContext
+ * @temp_dir: directory
+ *
+ * Sets the temporary directory to use when building metadata.
+ *
+ * Since: 0.1.0
+ **/
+void
+asb_context_set_temp_dir (AsbContext *ctx, const gchar *temp_dir)
+{
+ AsbContextPrivate *priv = GET_PRIVATE (ctx);
+ priv->temp_dir = g_strdup (temp_dir);
+}
+
+/**
+ * asb_context_set_output_dir:
+ * @ctx: A #AsbContext
+ * @output_dir: directory
+ *
+ * Sets the output directory to use when building metadata.
+ *
+ * Since: 0.1.0
+ **/
+void
+asb_context_set_output_dir (AsbContext *ctx, const gchar *output_dir)
+{
+ AsbContextPrivate *priv = GET_PRIVATE (ctx);
+ priv->output_dir = g_strdup (output_dir);
+}
+
+/**
+ * asb_context_set_basename:
+ * @ctx: A #AsbContext
+ * @basename: AppStream basename, e.g. "fedora-21"
+ *
+ * Sets the basename for the two metadata files.
+ *
+ * Since: 0.1.0
+ **/
+void
+asb_context_set_basename (AsbContext *ctx, const gchar *basename)
+{
+ AsbContextPrivate *priv = GET_PRIVATE (ctx);
+ priv->basename = g_strdup (basename);
+}
+
+/**
+ * asb_context_get_extra_package:
+ * @ctx: A #AsbContext
+ * @pkgname: package name
+ *
+ * Gets an extra package that should be used when processing an application.
+ *
+ * Returns: a pakage name, or %NULL
+ *
+ * Since: 0.1.0
+ **/
+const gchar *
+asb_context_get_extra_package (AsbContext *ctx, const gchar *pkgname)
+{
+ AsbContextPrivate *priv = GET_PRIVATE (ctx);
+ return asb_glob_value_search (priv->extra_pkgs, pkgname);
+}
+
+/**
+ * asb_context_get_use_package_cache:
+ * @ctx: A #AsbContext
+ *
+ * Gets if the package cache should be used.
+ *
+ * Returns: boolean
+ *
+ * Since: 0.1.0
+ **/
+gboolean
+asb_context_get_use_package_cache (AsbContext *ctx)
+{
+ AsbContextPrivate *priv = GET_PRIVATE (ctx);
+ return priv->use_package_cache;
+}
+
+/**
+ * asb_context_get_extra_checks:
+ * @ctx: A #AsbContext
+ *
+ * Gets if extra checks should be performed.
+ *
+ * Returns: boolean
+ *
+ * Since: 0.1.0
+ **/
+gboolean
+asb_context_get_extra_checks (AsbContext *ctx)
+{
+ AsbContextPrivate *priv = GET_PRIVATE (ctx);
+ return priv->extra_checks;
+}
+
+/**
+ * asb_context_get_add_cache_id:
+ * @ctx: A #AsbContext
+ *
+ * Gets if the cache_id should be added to the metadata.
+ *
+ * Returns: boolean
+ *
+ * Since: 0.1.0
+ **/
+gboolean
+asb_context_get_add_cache_id (AsbContext *ctx)
+{
+ AsbContextPrivate *priv = GET_PRIVATE (ctx);
+ return priv->add_cache_id;
+}
+
+/**
+ * asb_context_get_temp_dir:
+ * @ctx: A #AsbContext
+ *
+ * Gets the temporary directory to use
+ *
+ * Returns: directory
+ *
+ * Since: 0.1.0
+ **/
+const gchar *
+asb_context_get_temp_dir (AsbContext *ctx)
+{
+ AsbContextPrivate *priv = GET_PRIVATE (ctx);
+ return priv->temp_dir;
+}
+
+/**
+ * asb_context_get_plugins:
+ * @ctx: A #AsbContext
+ *
+ * Gets the plugins available to the metadata extractor.
+ *
+ * Returns: array of plugins
+ *
+ * Since: 0.1.0
+ **/
+GPtrArray *
+asb_context_get_plugins (AsbContext *ctx)
+{
+ AsbContextPrivate *priv = GET_PRIVATE (ctx);
+ return priv->plugins;
+}
+
+/**
+ * asb_context_get_packages:
+ * @ctx: A #AsbContext
+ *
+ * Returns the packages already added to the context.
+ *
+ * Returns: (transfer none) (element-type AsbPackage): array of packages
+ *
+ * Since: 0.1.0
+ **/
+GPtrArray *
+asb_context_get_packages (AsbContext *ctx)
+{
+ AsbContextPrivate *priv = GET_PRIVATE (ctx);
+ return priv->packages;
+}
+
+/**
+ * asb_context_add_filename:
+ * @ctx: A #AsbContext
+ * @filename: package filename
+ * @error: A #GError or %NULL
+ *
+ * Adds a filename to the list of packages to be processed
+ *
+ * Returns: %TRUE for success, %FALSE otherwise
+ *
+ * Since: 0.1.0
+ **/
+gboolean
+asb_context_add_filename (AsbContext *ctx, const gchar *filename, GError **error)
+{
+ AsbContextPrivate *priv = GET_PRIVATE (ctx);
+ _cleanup_object_unref_ AsbPackage *pkg = NULL;
+
+ /* open */
+#if HAVE_RPM
+ if (g_str_has_suffix (filename, ".rpm"))
+ pkg = asb_package_rpm_new ();
+#endif
+ if (g_str_has_suffix (filename, ".deb"))
+ pkg = asb_package_deb_new ();
+ if (pkg == NULL) {
+ g_set_error (error,
+ ASB_PLUGIN_ERROR,
+ ASB_PLUGIN_ERROR_FAILED,
+ "No idea how to handle %s",
+ filename);
+ return FALSE;
+ }
+ if (!asb_package_open (pkg, filename, error))
+ return FALSE;
+
+ /* is package name blacklisted */
+ if (asb_glob_value_search (priv->blacklisted_pkgs,
+ asb_package_get_name (pkg)) != NULL) {
+ asb_package_log (pkg,
+ ASB_PACKAGE_LOG_LEVEL_INFO,
+ "%s is blacklisted",
+ asb_package_get_filename (pkg));
+ return TRUE;
+ }
+
+ /* add to array */
+ g_ptr_array_add (priv->packages, g_object_ref (pkg));
+ return TRUE;
+}
+
+/**
+ * asb_context_get_file_globs:
+ * @ctx: A #AsbContext
+ *
+ * Gets the list of file globs added by plugins.
+ *
+ * Returns: file globs
+ *
+ * Since: 0.1.0
+ **/
+GPtrArray *
+asb_context_get_file_globs (AsbContext *ctx)
+{
+ AsbContextPrivate *priv = GET_PRIVATE (ctx);
+ return priv->file_globs;
+}
+
+/**
+ * asb_context_setup:
+ * @ctx: A #AsbContext
+ * @error: A #GError or %NULL
+ *
+ * Sets up the context ready for use.
+ *
+ * Returns: %TRUE for success, %FALSE otherwise
+ *
+ * Since: 0.1.0
+ **/
+gboolean
+asb_context_setup (AsbContext *ctx, GError **error)
+{
+ AsbContextPrivate *priv = GET_PRIVATE (ctx);
+
+ /* load plugins */
+ if (!asb_plugin_loader_setup (priv->plugins, error))
+ return FALSE;
+
+ /* get a cache of the file globs */
+ priv->file_globs = asb_plugin_loader_get_globs (priv->plugins);
+
+ /* add old metadata */
+ if (priv->old_metadata != NULL) {
+ _cleanup_object_unref_ GFile *file = NULL;
+ file = g_file_new_for_path (priv->old_metadata);
+ if (!as_store_from_file (priv->old_md_cache, file,
+ NULL, NULL, error))
+ return FALSE;
+ }
+
+ /* add any extra applications */
+ if (priv->extra_appstream != NULL &&
+ g_file_test (priv->extra_appstream, G_FILE_TEST_EXISTS)) {
+ if (!asb_utils_add_apps_from_dir (&priv->apps,
+ priv->extra_appstream,
+ error))
+ return FALSE;
+ g_print ("Added extra %i apps\n", g_list_length (priv->apps));
+ }
+
+ return TRUE;
+}
+
+/**
+ * asb_task_process_func:
+ **/
+static void
+asb_task_process_func (gpointer data, gpointer user_data)
+{
+ AsbTask *task = (AsbTask *) data;
+ _cleanup_error_free_ GError *error = NULL;
+
+ /* just run the task */
+ if (!asb_task_process (task, &error))
+ g_warning ("Failed to run task: %s", error->message);
+}
+
+/**
+ * asb_context_write_icons:
+ **/
+static gboolean
+asb_context_write_icons (AsbContext *ctx,
+ const gchar *temp_dir,
+ const gchar *output_dir,
+ const gchar *basename,
+ GError **error)
+{
+ _cleanup_free_ gchar *filename;
+ _cleanup_free_ gchar *icons_dir;
+
+ icons_dir = g_build_filename (temp_dir, "icons", NULL);
+ filename = g_strdup_printf ("%s/%s-icons.tar.gz", output_dir, basename);
+ g_print ("Writing %s...\n", filename);
+ return asb_utils_write_archive_dir (filename, icons_dir, error);
+}
+
+/**
+ * asb_context_write_xml:
+ **/
+static gboolean
+asb_context_write_xml (AsbContext *ctx,
+ const gchar *output_dir,
+ const gchar *basename,
+ GError **error)
+{
+ AsApp *app;
+ AsbContextPrivate *priv = GET_PRIVATE (ctx);
+ GList *l;
+ _cleanup_free_ gchar *filename = NULL;
+ _cleanup_object_unref_ AsStore *store;
+ _cleanup_object_unref_ GFile *file;
+
+ store = as_store_new ();
+ for (l = priv->apps; l != NULL; l = l->next) {
+ app = AS_APP (l->data);
+ if (ASB_IS_APP (app)) {
+ if (asb_app_get_vetos(ASB_APP(app))->len > 0)
+ continue;
+ }
+ as_store_add_app (store, app);
+ }
+ filename = g_strdup_printf ("%s/%s.xml.gz", output_dir, basename);
+ file = g_file_new_for_path (filename);
+
+ g_print ("Writing %s...\n", filename);
+ as_store_set_origin (store, basename);
+ as_store_set_api_version (store, priv->api_version);
+ return as_store_to_file (store,
+ file,
+ AS_NODE_TO_XML_FLAG_ADD_HEADER |
+ AS_NODE_TO_XML_FLAG_FORMAT_INDENT |
+ AS_NODE_TO_XML_FLAG_FORMAT_MULTILINE,
+ NULL, error);
+}
+
+/**
+ * asb_context_process:
+ * @ctx: A #AsbContext
+ * @error: A #GError or %NULL
+ *
+ * Processes all the packages that have been added to the context.
+ *
+ * Returns: %TRUE for success, %FALSE otherwise
+ *
+ * Since: 0.1.0
+ **/
+gboolean
+asb_context_process (AsbContext *ctx, GError **error)
+{
+ AsbContextPrivate *priv = GET_PRIVATE (ctx);
+ AsbPackage *pkg;
+ AsbTask *task;
+ GThreadPool *pool;
+ gboolean ret = FALSE;
+ guint i;
+ _cleanup_ptrarray_unref_ GPtrArray *tasks = NULL;
+
+ /* create thread pool */
+ pool = g_thread_pool_new (asb_task_process_func,
+ ctx,
+ priv->max_threads,
+ TRUE,
+ error);
+ if (pool == NULL)
+ goto out;
+
+ /* add each package */
+ g_print ("Processing packages...\n");
+ 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);
+ if (!asb_package_get_enabled (pkg)) {
+ asb_package_log (pkg,
+ ASB_PACKAGE_LOG_LEVEL_DEBUG,
+ "%s is not enabled",
+ asb_package_get_nevr (pkg));
+ asb_package_log_flush (pkg, NULL);
+ continue;
+ }
+
+ /* set locations of external resources */
+ asb_package_set_config (pkg, "AppDataExtra", priv->extra_appdata);
+ asb_package_set_config (pkg, "ScreenshotsExtra", priv->extra_screenshots);
+ asb_package_set_config (pkg, "MirrorURI", priv->screenshot_uri);
+ asb_package_set_config (pkg, "LogDir", priv->log_dir);
+ asb_package_set_config (pkg, "CacheDir", priv->cache_dir);
+ asb_package_set_config (pkg, "TempDir", priv->temp_dir);
+ asb_package_set_config (pkg, "OutputDir", priv->output_dir);
+
+ /* create task */
+ task = asb_task_new (ctx);
+ asb_task_set_id (task, i);
+ asb_task_set_package (task, pkg);
+ g_ptr_array_add (tasks, task);
+
+ /* add task to pool */
+ ret = g_thread_pool_push (pool, task, error);
+ if (!ret)
+ goto out;
+ }
+
+ /* wait for them to finish */
+ g_thread_pool_free (pool, FALSE, TRUE);
+
+ /* merge */
+ g_print ("Merging applications...\n");
+ asb_plugin_loader_merge (priv->plugins, &priv->apps);
+
+ /* write XML file */
+ ret = asb_context_write_xml (ctx, priv->output_dir, priv->basename, error);
+ if (!ret)
+ goto out;
+
+ /* write icons archive */
+ ret = asb_context_write_icons (ctx,
+ priv->temp_dir,
+ priv->output_dir,
+ priv->basename,
+ error);
+ if (!ret)
+ goto out;
+out:
+ return ret;
+}
+
+/**
+ * asb_context_disable_older_pkgs:
+ * @ctx: A #AsbContext
+ *
+ * Disable older packages that have been added to the context.
+ *
+ * Since: 0.1.0
+ **/
+void
+asb_context_disable_older_pkgs (AsbContext *ctx)
+{
+ AsbContextPrivate *priv = GET_PRIVATE (ctx);
+ AsbPackage *found;
+ AsbPackage *pkg;
+ const gchar *key;
+ guint i;
+ _cleanup_hashtable_unref_ GHashTable *newest;
+
+ newest = g_hash_table_new_full (g_str_hash, g_str_equal,
+ g_free, (GDestroyNotify) g_object_unref);
+ for (i = 0; i < priv->packages->len; i++) {
+ pkg = ASB_PACKAGE (g_ptr_array_index (priv->packages, i));
+ key = asb_package_get_name (pkg);
+ if (key == NULL)
+ continue;
+ found = g_hash_table_lookup (newest, key);
+ if (found != NULL) {
+ if (asb_package_compare (pkg, found) < 0) {
+ asb_package_set_enabled (pkg, FALSE);
+ continue;
+ }
+ asb_package_set_enabled (found, FALSE);
+ }
+ g_hash_table_insert (newest, g_strdup (key), g_object_ref (pkg));
+ }
+}
+
+/**
+ * asb_context_find_in_cache:
+ * @ctx: A #AsbContext
+ * @filename: cache-id
+ *
+ * Finds an application in the cache. This will only return results if
+ * asb_context_set_old_metadata() has been used.
+ *
+ * Returns: %TRUE for success, %FALSE otherwise
+ *
+ * Since: 0.1.0
+ **/
+gboolean
+asb_context_find_in_cache (AsbContext *ctx, const gchar *filename)
+{
+ AsApp *app;
+ AsbContextPrivate *priv = GET_PRIVATE (ctx);
+ guint i;
+ _cleanup_free_ gchar *cache_id;
+ _cleanup_ptrarray_unref_ GPtrArray *apps;
+
+ cache_id = asb_utils_get_cache_id_for_filename (filename);
+ apps = as_store_get_apps_by_metadata (priv->old_md_cache,
+ "X-CreaterepoAsCacheID",
+ cache_id);
+ if (apps->len == 0)
+ return FALSE;
+ for (i = 0; i < apps->len; i++) {
+ app = g_ptr_array_index (apps, i);
+ asb_context_add_app (ctx, (AsbApp *) app);
+ }
+ return TRUE;
+}
+
+/**
+ * asb_context_find_by_pkgname:
+ * @ctx: A #AsbContext
+ * @pkgname: a package name
+ *
+ * Find a package from its name.
+ *
+ * Returns: (transfer none): a #AsbPackage, or %NULL for not found.
+ *
+ * Since: 0.1.0
+ **/
+AsbPackage *
+asb_context_find_by_pkgname (AsbContext *ctx, const gchar *pkgname)
+{
+ AsbContextPrivate *priv = GET_PRIVATE (ctx);
+ AsbPackage *pkg;
+ guint i;
+
+ for (i = 0; i < priv->packages->len; i++) {
+ pkg = g_ptr_array_index (priv->packages, i);
+ if (g_strcmp0 (asb_package_get_name (pkg), pkgname) == 0)
+ return pkg;
+ }
+ return NULL;
+}
+
+/**
+ * asb_context_add_extra_pkg:
+ **/
+static void
+asb_context_add_extra_pkg (AsbContext *ctx, const gchar *pkg1, const gchar *pkg2)
+{
+ AsbContextPrivate *priv = GET_PRIVATE (ctx);
+ g_ptr_array_add (priv->extra_pkgs, asb_glob_value_new (pkg1, pkg2));
+}
+
+/**
+ * asb_context_add_blacklist_pkg:
+ **/
+static void
+asb_context_add_blacklist_pkg (AsbContext *ctx, const gchar *pkg)
+{
+ AsbContextPrivate *priv = GET_PRIVATE (ctx);
+ g_ptr_array_add (priv->blacklisted_pkgs, asb_glob_value_new (pkg, ""));
+}
+
+/**
+ * asb_context_add_app:
+ * @ctx: A #AsbContext
+ * @app: A #AsbApp
+ *
+ * Adds an application to the context.
+ *
+ * Since: 0.1.0
+ **/
+void
+asb_context_add_app (AsbContext *ctx, AsbApp *app)
+{
+ AsbContextPrivate *priv = GET_PRIVATE (ctx);
+ g_mutex_lock (&priv->apps_mutex);
+ asb_plugin_add_app (&priv->apps, app);
+ g_mutex_unlock (&priv->apps_mutex);
+}
+
+/**
+ * asb_context_finalize:
+ **/
+static void
+asb_context_finalize (GObject *object)
+{
+ AsbContext *ctx = ASB_CONTEXT (object);
+ AsbContextPrivate *priv = GET_PRIVATE (ctx);
+
+ g_object_unref (priv->old_md_cache);
+ asb_plugin_loader_free (priv->plugins);
+ g_ptr_array_unref (priv->packages);
+ g_ptr_array_unref (priv->extra_pkgs);
+ g_list_foreach (priv->apps, (GFunc) g_object_unref, NULL);
+ g_list_free (priv->apps);
+ g_ptr_array_unref (priv->blacklisted_pkgs);
+ g_ptr_array_unref (priv->file_globs);
+ g_mutex_clear (&priv->apps_mutex);
+ g_free (priv->old_metadata);
+ g_free (priv->extra_appstream);
+ g_free (priv->extra_appdata);
+ g_free (priv->extra_screenshots);
+ g_free (priv->screenshot_uri);
+ g_free (priv->log_dir);
+ g_free (priv->cache_dir);
+ g_free (priv->temp_dir);
+ g_free (priv->output_dir);
+ g_free (priv->basename);
+
+ G_OBJECT_CLASS (asb_context_parent_class)->finalize (object);
+}
+
+/**
+ * asb_context_init:
+ **/
+static void
+asb_context_init (AsbContext *ctx)
+{
+ AsbContextPrivate *priv = GET_PRIVATE (ctx);
+
+ priv->blacklisted_pkgs = asb_glob_value_array_new ();
+ priv->plugins = asb_plugin_loader_new ();
+ priv->packages = g_ptr_array_new_with_free_func ((GDestroyNotify) g_object_unref);
+ priv->extra_pkgs = asb_glob_value_array_new ();
+ g_mutex_init (&priv->apps_mutex);
+ priv->old_md_cache = as_store_new ();
+ priv->max_threads = 1;
+
+ /* add extra data */
+ asb_context_add_extra_pkg (ctx, "alliance-libs", "alliance");
+ asb_context_add_extra_pkg (ctx, "beneath-a-steel-sky*", "scummvm");
+ asb_context_add_extra_pkg (ctx, "coq-coqide", "coq");
+ asb_context_add_extra_pkg (ctx, "drascula*", "scummvm");
+ asb_context_add_extra_pkg (ctx, "efte-*", "efte-common");
+ asb_context_add_extra_pkg (ctx, "fcitx-*", "fcitx-data");
+ asb_context_add_extra_pkg (ctx, "flight-of-the-amazon-queen", "scummvm");
+ asb_context_add_extra_pkg (ctx, "gcin", "gcin-data");
+ asb_context_add_extra_pkg (ctx, "hotot-gtk", "hotot-common");
+ asb_context_add_extra_pkg (ctx, "hotot-qt", "hotot-common");
+ asb_context_add_extra_pkg (ctx, "java-1.7.0-openjdk-devel", "java-1.7.0-openjdk");
+ asb_context_add_extra_pkg (ctx, "kchmviewer-qt", "kchmviewer");
+ asb_context_add_extra_pkg (ctx, "libreoffice-*", "libreoffice-core");
+ asb_context_add_extra_pkg (ctx, "lure", "scummvm");
+ asb_context_add_extra_pkg (ctx, "nntpgrab-gui", "nntpgrab-core");
+ asb_context_add_extra_pkg (ctx, "projectM-*", "libprojectM-qt");
+ asb_context_add_extra_pkg (ctx, "scummvm-tools", "scummvm");
+ asb_context_add_extra_pkg (ctx, "speed-dreams", "speed-dreams-robots-base");
+ asb_context_add_extra_pkg (ctx, "switchdesk-gui", "switchdesk");
+ asb_context_add_extra_pkg (ctx, "transmission-*", "transmission-common");
+ asb_context_add_extra_pkg (ctx, "calligra-krita", "calligra-core");
+
+ /* add blacklisted packages */
+ asb_context_add_blacklist_pkg (ctx, "beneath-a-steel-sky-cd");
+ asb_context_add_blacklist_pkg (ctx, "anaconda");
+ asb_context_add_blacklist_pkg (ctx, "mate-control-center");
+ asb_context_add_blacklist_pkg (ctx, "lxde-common");
+ asb_context_add_blacklist_pkg (ctx, "xscreensaver-*");
+ asb_context_add_blacklist_pkg (ctx, "bmpanel2-cfg");
+}
+
+/**
+ * asb_context_class_init:
+ **/
+static void
+asb_context_class_init (AsbContextClass *klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+ object_class->finalize = asb_context_finalize;
+}
+
+/**
+ * asb_context_new:
+ *
+ * Creates a new high-level instance.
+ *
+ * Returns: a #AsbContext
+ *
+ * Since: 0.1.0
+ **/
+AsbContext *
+asb_context_new (void)
+{
+ AsbContext *context;
+ context = g_object_new (ASB_TYPE_CONTEXT, NULL);
+ return ASB_CONTEXT (context);
+}
diff --git a/libappstream-builder/asb-context.h b/libappstream-builder/asb-context.h
new file mode 100644
index 0000000..2da3115
--- /dev/null
+++ b/libappstream-builder/asb-context.h
@@ -0,0 +1,111 @@
+/* -*- 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_CONTEXT_H
+#define ASB_CONTEXT_H
+
+#include <glib-object.h>
+
+#include "asb-app.h"
+#include "asb-package.h"
+
+#define ASB_TYPE_CONTEXT (asb_context_get_type())
+#define ASB_CONTEXT(obj) (G_TYPE_CHECK_INSTANCE_CAST((obj), ASB_TYPE_CONTEXT, AsbContext))
+#define ASB_CONTEXT_CLASS(cls) (G_TYPE_CHECK_CLASS_CAST((cls), ASB_TYPE_CONTEXT, AsbContextClass))
+#define ASB_IS_CONTEXT(obj) (G_TYPE_CHECK_INSTANCE_TYPE((obj), ASB_TYPE_CONTEXT))
+#define ASB_IS_CONTEXT_CLASS(cls) (G_TYPE_CHECK_CLASS_TYPE((cls), ASB_TYPE_CONTEXT))
+#define ASB_CONTEXT_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS((obj), ASB_TYPE_CONTEXT, AsbContextClass))
+
+G_BEGIN_DECLS
+
+typedef struct _AsbContext AsbContext;
+typedef struct _AsbContextClass AsbContextClass;
+
+struct _AsbContext
+{
+ GObject parent;
+};
+
+struct _AsbContextClass
+{
+ GObjectClass parent_class;
+};
+
+GType asb_context_get_type (void);
+
+AsbContext *asb_context_new (void);
+AsbPackage *asb_context_find_by_pkgname (AsbContext *ctx,
+ const gchar *pkgname);
+void asb_context_add_app (AsbContext *ctx,
+ AsbApp *app);
+void asb_context_set_no_net (AsbContext *ctx,
+ gboolean no_net);
+void asb_context_set_api_version (AsbContext *ctx,
+ gdouble api_version);
+void asb_context_set_add_cache_id (AsbContext *ctx,
+ gboolean add_cache_id);
+void asb_context_set_extra_checks (AsbContext *ctx,
+ gboolean extra_checks);
+void asb_context_set_use_package_cache (AsbContext *ctx,
+ gboolean use_package_cache);
+void asb_context_set_max_threads (AsbContext *ctx,
+ guint max_threads);
+void asb_context_set_old_metadata (AsbContext *ctx,
+ const gchar *old_metadata);
+void asb_context_set_extra_appstream (AsbContext *ctx,
+ const gchar *extra_appstream);
+void asb_context_set_extra_appdata (AsbContext *ctx,
+ const gchar *extra_appdata);
+void asb_context_set_extra_screenshots (AsbContext *ctx,
+ const gchar *extra_screenshots);
+void asb_context_set_screenshot_uri (AsbContext *ctx,
+ const gchar *screenshot_uri);
+void asb_context_set_log_dir (AsbContext *ctx,
+ const gchar *log_dir);
+void asb_context_set_cache_dir (AsbContext *ctx,
+ const gchar *cache_dir);
+void asb_context_set_temp_dir (AsbContext *ctx,
+ const gchar *temp_dir);
+void asb_context_set_output_dir (AsbContext *ctx,
+ const gchar *output_dir);
+void asb_context_set_basename (AsbContext *ctx,
+ const gchar *basename);
+const gchar *asb_context_get_temp_dir (AsbContext *ctx);
+gboolean asb_context_get_add_cache_id (AsbContext *ctx);
+gboolean asb_context_get_extra_checks (AsbContext *ctx);
+gboolean asb_context_get_use_package_cache (AsbContext *ctx);
+
+gboolean asb_context_setup (AsbContext *ctx,
+ GError **error);
+gboolean asb_context_process (AsbContext *ctx,
+ GError **error);
+gboolean asb_context_add_filename (AsbContext *ctx,
+ const gchar *filename,
+ GError **error);
+void asb_context_disable_older_pkgs (AsbContext *ctx);
+gboolean asb_context_find_in_cache (AsbContext *ctx,
+ const gchar *filename);
+const gchar *asb_context_get_extra_package (AsbContext *ctx,
+ const gchar *pkgname);
+
+G_END_DECLS
+
+#endif /* ASB_CONTEXT_H */
diff --git a/libappstream-builder/asb-package-deb.c b/libappstream-builder/asb-package-deb.c
new file mode 100644
index 0000000..d663a60
--- /dev/null
+++ b/libappstream-builder/asb-package-deb.c
@@ -0,0 +1,227 @@
+/* -*- 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-package-deb
+ * @short_description: Object representing a .DEB package file.
+ * @stability: Unstable
+ *
+ * This object represents one .deb package file.
+ */
+
+#include "config.h"
+
+#include "as-cleanup.h"
+#include "asb-package-deb.h"
+#include "asb-plugin.h"
+
+
+G_DEFINE_TYPE (AsbPackageDeb, asb_package_deb, ASB_TYPE_PACKAGE)
+
+/**
+ * asb_package_deb_init:
+ **/
+static void
+asb_package_deb_init (AsbPackageDeb *pkg)
+{
+}
+
+/**
+ * asb_package_deb_ensure_simple:
+ **/
+static gboolean
+asb_package_deb_ensure_simple (AsbPackage *pkg, GError **error)
+{
+ const gchar *argv[4] = { "dpkg", "--field", "fn", NULL };
+ gchar *tmp;
+ gchar **vr;
+ guint i;
+ guint j;
+ _cleanup_free_ gchar *output = NULL;
+ _cleanup_ptrarray_unref_ GPtrArray *deps = NULL;
+ _cleanup_strv_free_ gchar **lines = NULL;
+
+ /* spawn sync */
+ argv[2] = asb_package_get_filename (pkg);
+ if (!g_spawn_sync (NULL, (gchar **) argv, NULL,
+ G_SPAWN_SEARCH_PATH,
+ NULL, NULL,
+ &output, NULL, NULL, error))
+ return FALSE;
+
+ /* parse output */
+ deps = g_ptr_array_new_with_free_func (g_free);
+ lines = g_strsplit (output, "\n", -1);
+ for (i = 0; lines[i] != NULL; i++) {
+ if (g_str_has_prefix (lines[i], "Package: ")) {
+ asb_package_set_name (pkg, lines[i] + 9);
+ continue;
+ }
+ if (g_str_has_prefix (lines[i], "Source: ")) {
+ asb_package_set_source (pkg, lines[i] + 8);
+ continue;
+ }
+ if (g_str_has_prefix (lines[i], "Version: ")) {
+ vr = g_strsplit (lines[i] + 9, "-", 2);
+ tmp = g_strstr_len (vr[0], -1, ":");
+ if (tmp == NULL) {
+ asb_package_set_version (pkg, vr[0]);
+ } else {
+ *tmp = '\0';
+ j = g_ascii_strtoll (vr[0], NULL, 10);
+ asb_package_set_epoch (pkg, j);
+ asb_package_set_version (pkg, tmp + 1);
+ }
+ asb_package_set_release (pkg, vr[1]);
+ g_strfreev (vr);
+ continue;
+ }
+ if (g_str_has_prefix (lines[i], "Depends: ")) {
+ vr = g_strsplit (lines[i] + 9, ", ", -1);
+ for (j = 0; vr[j] != NULL; j++) {
+ tmp = g_strstr_len (vr[j], -1, " ");
+ if (tmp != NULL)
+ *tmp = '\0';
+ g_ptr_array_add (deps, vr[j]);
+ }
+ continue;
+ }
+ }
+ g_ptr_array_add (deps, NULL);
+ asb_package_set_deps (pkg, (gchar **) deps->pdata);
+ return TRUE;
+}
+
+/**
+ * asb_package_deb_ensure_filelists:
+ **/
+static gboolean
+asb_package_deb_ensure_filelists (AsbPackage *pkg, GError **error)
+{
+ const gchar *argv[4] = { "dpkg", "--contents", "fn", NULL };
+ const gchar *fn;
+ guint i;
+ _cleanup_free_ gchar *output = NULL;
+ _cleanup_ptrarray_unref_ GPtrArray *files = NULL;
+ _cleanup_strv_free_ gchar **lines = NULL;
+
+ /* spawn sync */
+ argv[2] = asb_package_get_filename (pkg);
+ if (!g_spawn_sync (NULL, (gchar **) argv, NULL,
+ G_SPAWN_SEARCH_PATH,
+ NULL, NULL,
+ &output, NULL, NULL, error))
+ return FALSE;
+
+ /* parse output */
+ files = g_ptr_array_new_with_free_func (g_free);
+ lines = g_strsplit (output, "\n", -1);
+ for (i = 0; lines[i] != NULL; i++) {
+ fn = g_strrstr (lines[i], " ");
+ if (fn == NULL)
+ continue;
+ /* ignore directories */
+ if (g_str_has_suffix (fn, "/"))
+ continue;
+ g_ptr_array_add (files, g_strdup (fn + 2));
+ }
+
+ /* save */
+ g_ptr_array_add (files, NULL);
+ asb_package_set_filelist (pkg, (gchar **) files->pdata);
+ return TRUE;
+}
+
+/**
+ * asb_package_deb_open:
+ **/
+static gboolean
+asb_package_deb_open (AsbPackage *pkg, const gchar *filename, GError **error)
+{
+ /* read package stuff */
+ if (!asb_package_deb_ensure_simple (pkg, error))
+ return FALSE;
+ if (!asb_package_deb_ensure_filelists (pkg, error))
+ return FALSE;
+ return TRUE;
+}
+
+/**
+ * asb_package_deb_explode:
+ **/
+static gboolean
+asb_package_deb_explode (AsbPackage *pkg,
+ const gchar *dir,
+ GPtrArray *glob,
+ GError **error)
+{
+ guint i;
+ const gchar *data_names[] = { "data.tar.xz",
+ "data.tar.bz2",
+ "data.tar.gz",
+ "data.tar.lzma",
+ "data.tar",
+ NULL };
+
+ /* first decompress the main deb */
+ if (!asb_utils_explode (asb_package_get_filename (pkg),
+ dir, NULL, error))
+ return FALSE;
+
+ /* then decompress the data file */
+ for (i = 0; data_names[i] != NULL; i++) {
+ _cleanup_free_ gchar *data_fn = NULL;
+ data_fn = g_build_filename (dir, data_names[i], NULL);
+ if (g_file_test (data_fn, G_FILE_TEST_EXISTS)) {
+ if (!asb_utils_explode (data_fn, dir, glob, error))
+ return FALSE;
+ }
+ }
+ return TRUE;
+}
+
+/**
+ * asb_package_deb_class_init:
+ **/
+static void
+asb_package_deb_class_init (AsbPackageDebClass *klass)
+{
+ AsbPackageClass *package_class = ASB_PACKAGE_CLASS (klass);
+ package_class->open = asb_package_deb_open;
+ package_class->explode = asb_package_deb_explode;
+}
+
+/**
+ * asb_package_deb_new:
+ *
+ * Creates a new DEB package.
+ *
+ * Returns: a package
+ *
+ * Since: 0.1.0
+ **/
+AsbPackage *
+asb_package_deb_new (void)
+{
+ AsbPackage *pkg;
+ pkg = g_object_new (ASB_TYPE_PACKAGE_DEB, NULL);
+ return ASB_PACKAGE (pkg);
+}
diff --git a/libappstream-builder/asb-package-deb.h b/libappstream-builder/asb-package-deb.h
new file mode 100644
index 0000000..f1b8b24
--- /dev/null
+++ b/libappstream-builder/asb-package-deb.h
@@ -0,0 +1,60 @@
+/* -*- 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_PACKAGE_DEB_H
+#define ASB_PACKAGE_DEB_H
+
+#include <glib-object.h>
+
+#include <stdarg.h>
+#include <appstream-glib.h>
+
+#include "asb-package.h"
+
+#define ASB_TYPE_PACKAGE_DEB (asb_package_deb_get_type())
+#define ASB_PACKAGE_DEB(obj) (G_TYPE_CHECK_INSTANCE_CAST((obj), ASB_TYPE_PACKAGE_DEB, AsbPackageDeb))
+#define ASB_PACKAGE_DEB_CLASS(cls) (G_TYPE_CHECK_CLASS_CAST((cls), ASB_TYPE_PACKAGE_DEB, AsbPackageDebClass))
+#define ASB_IS_PACKAGE_DEB(obj) (G_TYPE_CHECK_INSTANCE_TYPE((obj), ASB_TYPE_PACKAGE_DEB))
+#define ASB_IS_PACKAGE_DEB_CLASS(cls) (G_TYPE_CHECK_CLASS_TYPE((cls), ASB_TYPE_PACKAGE_DEB))
+#define ASB_PACKAGE_DEB_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS((obj), ASB_TYPE_PACKAGE_DEB, AsbPackageDebClass))
+
+G_BEGIN_DECLS
+
+typedef struct _AsbPackageDeb AsbPackageDeb;
+typedef struct _AsbPackageDebClass AsbPackageDebClass;
+
+struct _AsbPackageDeb
+{
+ AsbPackage parent;
+};
+
+struct _AsbPackageDebClass
+{
+ AsbPackageClass parent_class;
+};
+
+GType asb_package_deb_get_type (void);
+
+AsbPackage *asb_package_deb_new (void);
+
+G_END_DECLS
+
+#endif /* ASB_PACKAGE_DEB_H */
diff --git a/libappstream-builder/asb-package-rpm.c b/libappstream-builder/asb-package-rpm.c
new file mode 100644
index 0000000..0ceb7f4
--- /dev/null
+++ b/libappstream-builder/asb-package-rpm.c
@@ -0,0 +1,657 @@
+/* -*- 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-package-rpm
+ * @short_description: Object representing a .RPM package file.
+ * @stability: Unstable
+ *
+ * This object represents one .rpm package file.
+ */
+
+#include "config.h"
+
+#include <limits.h>
+#include <archive.h>
+#include <archive_entry.h>
+
+#include <rpm/rpmlib.h>
+#include <rpm/rpmts.h>
+
+#include "as-cleanup.h"
+#include "asb-package-rpm.h"
+#include "asb-plugin.h"
+
+typedef struct _AsbPackageRpmPrivate AsbPackageRpmPrivate;
+struct _AsbPackageRpmPrivate
+{
+ Header h;
+};
+
+G_DEFINE_TYPE_WITH_PRIVATE (AsbPackageRpm, asb_package_rpm, ASB_TYPE_PACKAGE)
+
+#define GET_PRIVATE(o) (asb_package_rpm_get_instance_private (o))
+
+/**
+ * asb_package_rpm_finalize:
+ **/
+static void
+asb_package_rpm_finalize (GObject *object)
+{
+ AsbPackageRpm *pkg = ASB_PACKAGE_RPM (object);
+ AsbPackageRpmPrivate *priv = GET_PRIVATE (pkg);
+
+ headerFree (priv->h);
+
+ G_OBJECT_CLASS (asb_package_rpm_parent_class)->finalize (object);
+}
+
+/**
+ * asb_package_rpm_init:
+ **/
+static void
+asb_package_rpm_init (AsbPackageRpm *pkg)
+{
+}
+
+/**
+ * asb_package_rpm_set_license:
+ **/
+static void
+asb_package_rpm_set_license (AsbPackage *pkg, const gchar *license)
+{
+ const gchar *tmp;
+ guint i;
+ guint j;
+ _cleanup_strv_free_ gchar **split = NULL;
+ _cleanup_string_free_ GString *new = NULL;
+ struct {
+ const gchar *fedora;
+ const gchar *spdx;
+ } convert[] = {
+ { "AGPLv3", "AGPL-3.0" },
+ { "AGPLv3+", "AGPL-3.0" },
+ { "AGPLv3 with exceptions", "AGPL-3.0" },
+ { "AGPLv3+ with exceptions", "AGPL-3.0" },
+ { "Array", NULL },
+ { "Artistic 2.0", "Artistic-2.0" },
+ { "Artistic", "Artistic-1.0" },
+ { "Artistic clarified", "Artistic-2.0" },
+ { "ASL 1.1", "Apache-1.1" },
+ { "ASL 2.0", "Apache-2.0" },
+ { "Baekmuk", NULL },
+ { "Bitstream Vera", NULL },
+ { "Boost", "BSL-1.0" },
+ { "BSD", "BSD-3-Clause" },
+ { "BSD with advertising", "BSD-3-Clause" },
+ { "CC0", "CC0-1.0" },
+ { "CC-BY", "CC-BY-3.0" },
+ { "CC-BY-SA", "CC-BY-SA-3.0" },
+ { "CDDL", "CDDL-1.0" },
+ { "CeCILL-C", "CECILL-C" },
+ { "CeCILL", "CECILL-2.0" },
+ { "Copyright only", NULL },
+ { "Crystal Stacker", NULL },
+ { "EPL", "EPL-1.0" },
+ { "Free Art", "ClArtistic" },
+ { "Freely redistributable without restriction", NULL },
+ { "GFDL", "GFDL-1.3" },
+ { "GPL+", "GPL-1.0+" },
+ { "GPLv2", "GPL-2.0" },
+ { "GPLv2+", "GPL-2.0+" },
+ { "GPLV2", "GPL-2.0" },
+ { "GPLv2 with exceptions", "GPL-2.0-with-font-exception" },
+ { "GPLv2+ with exceptions", "GPL-2.0-with-font-exception" },
+ { "GPLv3", "GPL-3.0" },
+ { "GPLv3+", "GPL-3.0+" },
+ { "GPLV3+", "GPL-3.0+" },
+ { "GPLv3+ with exceptions", "GPL-3.0+" },
+ { "GPLv3 with exceptions", "GPL-3.0-with-GCC-exception" },
+ { "GPL+ with exceptions", "GPL-2.0-with-font-exception" },
+ { "IBM", "IPL-1.0" },
+ { "LGPL+", "LGPL-2.1+" },
+ { "LGPLv2.1", "LGPL-2.1" },
+ { "LGPLv2", "LGPL-2.1" },
+ { "LGPLv2+", "LGPL-2.1+" },
+ { "LGPLv2 with exceptions", "LGPL-2.0" },
+ { "LGPLv2+ with exceptions", "LGPL-2.0+" },
+ { "LGPLv3", "LGPL-3.0" },
+ { "LGPLv3+", "LGPL-3.0+" },
+ { "Liberation", NULL },
+ { "LPPL", "LPPL-1.3c" },
+ { "MgOpen", NULL },
+ { "MIT with advertising", "MIT" },
+ { "mplus", NULL },
+ { "MPLv1.0", "MPL-1.0" },
+ { "MPLv1.1", "MPL-1.1" },
+ { "MPLv2.0", "MPL-2.0" },
+ { "Netscape", "NPL-1.1" },
+ { "OFL", "OFL-1.1" },
+ { "Public domain", NULL },
+ { "Public Domain", NULL },
+ { "Python", "Python-2.0" },
+ { "QPL", "QPL-1.0" },
+ { "QPL with exceptions", "QPL-1.0" },
+ { "SPL", "SPL-1.0" },
+ { "zlib", "Zlib" },
+ { "ZPLv2.0", "ZPL-2.0" },
+ { NULL, NULL } };
+
+ /* this isn't supposed to happen */
+ if (license == NULL) {
+ asb_package_log (pkg, ASB_PACKAGE_LOG_LEVEL_WARNING,
+ "no license!");
+ return;
+ }
+
+ /* tokenize the license string and try to convert the Fedora license
+ * string to a SPDX license the best we can */
+ new = g_string_sized_new (strlen (license) * 2);
+ split = as_utils_spdx_license_tokenize (license);
+ for (i = 0; split[i] != NULL; i++) {
+
+ /* convert */
+ tmp = split[i];
+ for (j = 0; convert[j].fedora != NULL; j++) {
+ if (g_strcmp0 (split[i], convert[j].fedora) == 0) {
+ tmp = convert[j].spdx;
+ asb_package_log (pkg,
+ ASB_PACKAGE_LOG_LEVEL_DEBUG,
+ "Converting Fedora license "
+ "'%s' to SPDX '%s'",
+ convert[j].fedora,
+ convert[j].spdx);
+ break;
+ }
+ }
+
+ /* any operation */
+ if (g_str_has_prefix (split[i], "#")) {
+ g_string_append (new, split[i] + 1);
+ continue;
+ }
+
+ /* no matching SPDX entry */
+ if (tmp == NULL) {
+ asb_package_log (pkg,
+ ASB_PACKAGE_LOG_LEVEL_WARNING,
+ "Unable to currently map Fedora "
+ "license '%s' to SPDX", split[i]);
+ tmp = split[i];
+ } else if (!as_utils_is_spdx_license_id (tmp)) {
+ asb_package_log (pkg,
+ ASB_PACKAGE_LOG_LEVEL_WARNING,
+ "License '%s' is not an SPDX ID", tmp);
+ }
+ g_string_append (new, tmp);
+ }
+
+ asb_package_set_license (pkg, new->str);
+}
+
+/**
+ * asb_package_rpm_set_source:
+ **/
+static void
+asb_package_rpm_set_source (AsbPackage *pkg, const gchar *source)
+{
+ gchar *tmp;
+ _cleanup_free_ gchar *srcrpm = NULL;
+
+ /* this isn't supposed to happen */
+ if (source == NULL) {
+ asb_package_log (pkg, ASB_PACKAGE_LOG_LEVEL_WARNING,
+ "no source!");
+ return;
+ }
+ srcrpm = g_strdup (source);
+ tmp = g_strstr_len (srcrpm, -1, ".src.rpm");
+ if (tmp != NULL)
+ *tmp = '\0';
+ asb_package_set_source (pkg, srcrpm);
+}
+
+/**
+ * asb_package_rpm_ensure_simple:
+ **/
+static gboolean
+asb_package_rpm_ensure_simple (AsbPackage *pkg, GError **error)
+{
+ AsbPackageRpm *pkg_rpm = ASB_PACKAGE_RPM (pkg);
+ AsbPackageRpmPrivate *priv = GET_PRIVATE (pkg_rpm);
+ gboolean ret = TRUE;
+ rpmtd td;
+
+ /* get the simple stuff */
+ td = rpmtdNew ();
+ headerGet (priv->h, RPMTAG_NAME, td, HEADERGET_MINMEM);
+ asb_package_set_name (pkg, rpmtdGetString (td));
+ headerGet (priv->h, RPMTAG_VERSION, td, HEADERGET_MINMEM);
+ asb_package_set_version (pkg, rpmtdGetString (td));
+ headerGet (priv->h, RPMTAG_RELEASE, td, HEADERGET_MINMEM);
+ asb_package_set_release (pkg, rpmtdGetString (td));
+ headerGet (priv->h, RPMTAG_ARCH, td, HEADERGET_MINMEM);
+ asb_package_set_arch (pkg, rpmtdGetString (td));
+ headerGet (priv->h, RPMTAG_EPOCH, td, HEADERGET_MINMEM);
+ asb_package_set_epoch (pkg, rpmtdGetNumber (td));
+ headerGet (priv->h, RPMTAG_URL, td, HEADERGET_MINMEM);
+ asb_package_set_url (pkg, rpmtdGetString (td));
+ headerGet (priv->h, RPMTAG_LICENSE, td, HEADERGET_MINMEM);
+ asb_package_rpm_set_license (pkg, rpmtdGetString (td));
+ headerGet (priv->h, RPMTAG_SOURCERPM, td, HEADERGET_MINMEM);
+ asb_package_rpm_set_source (pkg, rpmtdGetString (td));
+ rpmtdFree (td);
+ return ret;
+}
+
+/**
+ * asb_package_rpm_release_set_text:
+ **/
+static gboolean
+asb_package_rpm_release_set_text (AsRelease *release,
+ const gchar *text)
+{
+ guint i;
+ _cleanup_free_ gchar *markup = NULL;
+ const gchar *blacklisted[] = { " BR ",
+ " >= ",
+ "BuildRequires",
+ "Buildroot",
+ "Bump release",
+ "compile fixes",
+ "%configure",
+ "%doc",
+ "ExcludeArch",
+ "fix build",
+ "fix typo",
+ "FTBFS",
+ "initial version",
+ "Latest upstream",
+ "missing BR",
+ "New release",
+ "New upstream",
+ "New version",
+ "%post",
+ "rebuild",
+ "Rebuild",
+ "rebuilt",
+ "Rebuilt",
+ " Requires ",
+ "revbump",
+ "scriptlets",
+ "spec file",
+ "subpackage",
+ "Updated to ",
+ "Update to ",
+ "Upgrade to ",
+ "Upstream new release",
+ "upstream release",
+ "Upstream update",
+ "vendor prefix",
+ as_release_get_version (release),
+ NULL };
+ for (i = 0; blacklisted[i] != NULL; i++) {
+ if (g_strstr_len (text, -1, blacklisted[i]) != NULL)
+ return FALSE;
+ }
+ /* remove prefix */
+ if (g_str_has_prefix (text, "- "))
+ text += 2;
+ markup = g_markup_printf_escaped ("<p>%s</p>", text);
+ as_release_set_description (release, NULL, markup, -1);
+ return TRUE;
+}
+
+/**
+ * asb_package_rpm_add_release:
+ **/
+static void
+asb_package_rpm_add_release (AsbPackage *pkg,
+ guint64 timestamp,
+ const gchar *name,
+ const gchar *text)
+{
+ AsRelease *release;
+ const gchar *version;
+ _cleanup_free_ gchar *name_dup;
+ gchar *tmp;
+ gchar *vr;
+
+ /* get last string chunk */
+ name_dup = g_strchomp (g_strdup (name));
+ vr = g_strrstr (name_dup, " ");
+ if (vr == NULL)
+ return;
+
+ /* get last string chunk */
+ version = vr + 1;
+ tmp = g_strstr_len (version, -1, "-");
+ if (tmp == NULL) {
+ if (g_strstr_len (version, -1, ">") != NULL)
+ return;
+ } else {
+ *tmp = '\0';
+ }
+
+ /* remove any epoch */
+ tmp = g_strstr_len (version, -1, ":");
+ if (tmp != NULL)
+ version = tmp + 1;
+
+ /* is version already in the database */
+ release = asb_package_get_release (pkg, version);
+ if (release != NULL) {
+ /* use the earlier timestamp to ignore auto-rebuilds with just
+ * a bumped release */
+ if (timestamp < as_release_get_timestamp (release))
+ as_release_set_timestamp (release, timestamp);
+
+ /* we didn't have anything interesting before; try now */
+ if (as_release_get_description (release, NULL) == NULL)
+ asb_package_rpm_release_set_text (release, text);
+ } else {
+ release = as_release_new ();
+ as_release_set_version (release, version, -1);
+ as_release_set_timestamp (release, timestamp);
+ asb_package_rpm_release_set_text (release, text);
+ asb_package_add_release (pkg, version, release);
+ g_object_unref (release);
+ }
+}
+
+/**
+ * asb_package_rpm_ensure_releases:
+ **/
+static gboolean
+asb_package_rpm_ensure_releases (AsbPackage *pkg, GError **error)
+{
+ AsbPackageRpm *pkg_rpm = ASB_PACKAGE_RPM (pkg);
+ AsbPackageRpmPrivate *priv = GET_PRIVATE (pkg_rpm);
+ guint i;
+ rpmtd td[3] = { NULL, NULL, NULL };
+
+ /* read out the file list */
+ for (i = 0; i < 3; i++)
+ td[i] = rpmtdNew ();
+ /* get the ChangeLog info */
+ headerGet (priv->h, RPMTAG_CHANGELOGTIME, td[0], HEADERGET_MINMEM);
+ headerGet (priv->h, RPMTAG_CHANGELOGNAME, td[1], HEADERGET_MINMEM);
+ headerGet (priv->h, RPMTAG_CHANGELOGTEXT, td[2], HEADERGET_MINMEM);
+ while (rpmtdNext (td[0]) != -1 &&
+ rpmtdNext (td[1]) != -1 &&
+ rpmtdNext (td[2]) != -1) {
+ asb_package_rpm_add_release (pkg,
+ rpmtdGetNumber (td[0]),
+ rpmtdGetString (td[1]),
+ rpmtdGetString (td[2]));
+ }
+ for (i = 0; i < 3; i++) {
+ rpmtdFreeData (td[i]);
+ rpmtdFree (td[i]);
+ }
+ return TRUE;
+}
+
+/**
+ * asb_package_rpm_ensure_deps:
+ **/
+static gboolean
+asb_package_rpm_ensure_deps (AsbPackage *pkg, GError **error)
+{
+ AsbPackageRpm *pkg_rpm = ASB_PACKAGE_RPM (pkg);
+ AsbPackageRpmPrivate *priv = GET_PRIVATE (pkg_rpm);
+ const gchar *dep;
+ gboolean ret = TRUE;
+ gchar *tmp;
+ gint rc;
+ guint i = 0;
+ rpmtd td = NULL;
+ _cleanup_strv_free_ gchar **deps = NULL;
+
+ /* read out the dep list */
+ td = rpmtdNew ();
+ rc = headerGet (priv->h, RPMTAG_REQUIRENAME, td, HEADERGET_MINMEM);
+ if (!rc) {
+ ret = FALSE;
+ g_set_error (error,
+ ASB_PLUGIN_ERROR,
+ ASB_PLUGIN_ERROR_FAILED,
+ "Failed to read list of requires %s",
+ asb_package_get_filename (pkg));
+ goto out;
+ }
+ deps = g_new0 (gchar *, rpmtdCount (td) + 1);
+ while (rpmtdNext (td) != -1) {
+ dep = rpmtdGetString (td);
+ if (g_str_has_prefix (dep, "rpmlib"))
+ continue;
+ if (g_strcmp0 (dep, "/bin/sh") == 0)
+ continue;
+ deps[i] = g_strdup (dep);
+ tmp = g_strstr_len (deps[i], -1, "(");
+ if (tmp != NULL)
+ *tmp = '\0';
+ /* TODO: deduplicate */
+ i++;
+ }
+ asb_package_set_deps (pkg, deps);
+out:
+ rpmtdFreeData (td);
+ rpmtdFree (td);
+ return ret;
+}
+
+/**
+ * asb_package_rpm_ensure_filelists:
+ **/
+static gboolean
+asb_package_rpm_ensure_filelists (AsbPackage *pkg, GError **error)
+{
+ AsbPackageRpm *pkg_rpm = ASB_PACKAGE_RPM (pkg);
+ AsbPackageRpmPrivate *priv = GET_PRIVATE (pkg_rpm);
+ gboolean ret = TRUE;
+ gint rc;
+ guint i;
+ rpmtd td[3] = { NULL, NULL, NULL };
+ _cleanup_free_ const gchar **dirnames = NULL;
+ _cleanup_free_ gint32 *dirindex = NULL;
+ _cleanup_strv_free_ gchar **filelist = NULL;
+
+ /* read out the file list */
+ for (i = 0; i < 3; i++)
+ td[i] = rpmtdNew ();
+ rc = headerGet (priv->h, RPMTAG_DIRNAMES, td[0], HEADERGET_MINMEM);
+ if (rc)
+ rc = headerGet (priv->h, RPMTAG_BASENAMES, td[1], HEADERGET_MINMEM);
+ if (rc)
+ rc = headerGet (priv->h, RPMTAG_DIRINDEXES, td[2], HEADERGET_MINMEM);
+ if (!rc) {
+ ret = FALSE;
+ g_set_error (error,
+ ASB_PLUGIN_ERROR,
+ ASB_PLUGIN_ERROR_FAILED,
+ "Failed to read package file list %s",
+ asb_package_get_filename (pkg));
+ goto out;
+ }
+ i = 0;
+ dirnames = g_new0 (const gchar *, rpmtdCount (td[0]) + 1);
+ while (rpmtdNext (td[0]) != -1)
+ dirnames[i++] = rpmtdGetString (td[0]);
+ i = 0;
+ dirindex = g_new0 (gint32, rpmtdCount (td[2]) + 1);
+ while (rpmtdNext (td[2]) != -1)
+ dirindex[i++] = rpmtdGetNumber (td[2]);
+ i = 0;
+ filelist = g_new0 (gchar *, rpmtdCount (td[1]) + 1);
+ while (rpmtdNext (td[1]) != -1) {
+ filelist[i] = g_build_filename (dirnames[dirindex[i]],
+ rpmtdGetString (td[1]),
+ NULL);
+ i++;
+ }
+ asb_package_set_filelist (pkg, filelist);
+out:
+ for (i = 0; i < 3; i++) {
+ rpmtdFreeData (td[i]);
+ rpmtdFree (td[i]);
+ }
+ return ret;
+}
+
+/**
+ * asb_package_rpm_strerror:
+ **/
+static const gchar *
+asb_package_rpm_strerror (rpmRC rc)
+{
+ const gchar *str;
+ switch (rc) {
+ case RPMRC_OK:
+ str = "Generic success";
+ break;
+ case RPMRC_NOTFOUND:
+ str = "Generic not found";
+ break;
+ case RPMRC_FAIL:
+ str = "Generic failure";
+ break;
+ case RPMRC_NOTTRUSTED:
+ str = "Signature is OK, but key is not trusted";
+ break;
+ case RPMRC_NOKEY:
+ str = "Public key is unavailable";
+ break;
+ default:
+ str = "unknown";
+ break;
+ }
+ return str;
+}
+
+/**
+ * asb_package_rpm_open:
+ **/
+static gboolean
+asb_package_rpm_open (AsbPackage *pkg, const gchar *filename, GError **error)
+{
+ AsbPackageRpm *pkg_rpm = ASB_PACKAGE_RPM (pkg);
+ AsbPackageRpmPrivate *priv = GET_PRIVATE (pkg_rpm);
+ FD_t fd;
+ gboolean ret = TRUE;
+ rpmRC rc;
+ rpmts ts;
+
+ /* open the file */
+ ts = rpmtsCreate ();
+ fd = Fopen (filename, "r");
+ if (fd <= 0) {
+ ret = FALSE;
+ g_set_error (error,
+ ASB_PLUGIN_ERROR,
+ ASB_PLUGIN_ERROR_FAILED,
+ "Failed to open package %s", filename);
+ goto out;
+ }
+
+ /* create package */
+ rc = rpmReadPackageFile (ts, fd, filename, &priv->h);
+ if (rc == RPMRC_FAIL) {
+ ret = FALSE;
+ g_set_error (error,
+ ASB_PLUGIN_ERROR,
+ ASB_PLUGIN_ERROR_FAILED,
+ "Failed to read package %s: %s",
+ filename, asb_package_rpm_strerror (rc));
+ goto out;
+ }
+
+ /* read package stuff */
+ ret = asb_package_rpm_ensure_simple (pkg, error);
+ if (!ret)
+ goto out;
+ ret = asb_package_rpm_ensure_releases (pkg, error);
+ if (!ret)
+ goto out;
+ ret = asb_package_rpm_ensure_deps (pkg, error);
+ if (!ret)
+ goto out;
+ ret = asb_package_rpm_ensure_filelists (pkg, error);
+ if (!ret)
+ goto out;
+out:
+ rpmtsFree (ts);
+ Fclose (fd);
+ return ret;
+}
+
+/**
+ * asb_package_rpm_compare:
+ **/
+static gint
+asb_package_rpm_compare (AsbPackage *pkg1, AsbPackage *pkg2)
+{
+ return rpmvercmp (asb_package_get_evr (pkg1),
+ asb_package_get_evr (pkg2));
+}
+
+/**
+ * asb_package_rpm_class_init:
+ **/
+static void
+asb_package_rpm_class_init (AsbPackageRpmClass *klass)
+{
+ AsbPackageClass *package_class = ASB_PACKAGE_CLASS (klass);
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+
+ object_class->finalize = asb_package_rpm_finalize;
+ package_class->open = asb_package_rpm_open;
+ package_class->compare = asb_package_rpm_compare;
+}
+
+/**
+ * asb_package_rpm_init_cb:
+ **/
+static gpointer
+asb_package_rpm_init_cb (gpointer user_data)
+{
+ rpmReadConfigFiles (NULL, NULL);
+ return NULL;
+}
+
+/**
+ * asb_package_rpm_new:
+ *
+ * Creates a new RPM package.
+ *
+ * Returns: a package
+ *
+ * Since: 0.1.0
+ **/
+AsbPackage *
+asb_package_rpm_new (void)
+{
+ AsbPackage *pkg;
+ static GOnce rpm_init = G_ONCE_INIT;
+ g_once (&rpm_init, asb_package_rpm_init_cb, NULL);
+ pkg = g_object_new (ASB_TYPE_PACKAGE_RPM, NULL);
+ return ASB_PACKAGE (pkg);
+}
diff --git a/libappstream-builder/asb-package-rpm.h b/libappstream-builder/asb-package-rpm.h
new file mode 100644
index 0000000..da534fb
--- /dev/null
+++ b/libappstream-builder/asb-package-rpm.h
@@ -0,0 +1,60 @@
+/* -*- 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_PACKAGE_RPM_H
+#define ASB_PACKAGE_RPM_H
+
+#include <glib-object.h>
+
+#include <stdarg.h>
+#include <appstream-glib.h>
+
+#include "asb-package.h"
+
+#define ASB_TYPE_PACKAGE_RPM (asb_package_rpm_get_type())
+#define ASB_PACKAGE_RPM(obj) (G_TYPE_CHECK_INSTANCE_CAST((obj), ASB_TYPE_PACKAGE_RPM, AsbPackageRpm))
+#define ASB_PACKAGE_RPM_CLASS(cls) (G_TYPE_CHECK_CLASS_CAST((cls), ASB_TYPE_PACKAGE_RPM, AsbPackageRpmClass))
+#define ASB_IS_PACKAGE_RPM(obj) (G_TYPE_CHECK_INSTANCE_TYPE((obj), ASB_TYPE_PACKAGE_RPM))
+#define ASB_IS_PACKAGE_RPM_CLASS(cls) (G_TYPE_CHECK_CLASS_TYPE((cls), ASB_TYPE_PACKAGE_RPM))
+#define ASB_PACKAGE_RPM_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS((obj), ASB_TYPE_PACKAGE_RPM, AsbPackageRpmClass))
+
+G_BEGIN_DECLS
+
+typedef struct _AsbPackageRpm AsbPackageRpm;
+typedef struct _AsbPackageRpmClass AsbPackageRpmClass;
+
+struct _AsbPackageRpm
+{
+ AsbPackage parent;
+};
+
+struct _AsbPackageRpmClass
+{
+ AsbPackageClass parent_class;
+};
+
+GType asb_package_rpm_get_type (void);
+
+AsbPackage *asb_package_rpm_new (void);
+
+G_END_DECLS
+
+#endif /* ASB_PACKAGE_RPM_H */
diff --git a/libappstream-builder/asb-package.c b/libappstream-builder/asb-package.c
new file mode 100644
index 0000000..c61de78
--- /dev/null
+++ b/libappstream-builder/asb-package.c
@@ -0,0 +1,780 @@
+/* -*- 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-package
+ * @short_description: Object representing a package file.
+ * @stability: Unstable
+ *
+ * This object represents one package file.
+ */
+
+#include "config.h"
+
+#include <limits.h>
+
+#include "as-cleanup.h"
+#include "asb-package.h"
+#include "asb-plugin.h"
+
+typedef struct _AsbPackagePrivate AsbPackagePrivate;
+struct _AsbPackagePrivate
+{
+ gboolean enabled;
+ gchar **filelist;
+ gchar **deps;
+ gchar *filename;
+ gchar *basename;
+ gchar *name;
+ guint epoch;
+ gchar *version;
+ gchar *release;
+ gchar *arch;
+ gchar *url;
+ gchar *nevr;
+ gchar *evr;
+ gchar *license;
+ gchar *source;
+ GString *log;
+ GHashTable *configs;
+ GTimer *timer;
+ gdouble last_log;
+ GPtrArray *releases;
+ GHashTable *releases_hash;
+};
+
+G_DEFINE_TYPE_WITH_PRIVATE (AsbPackage, asb_package, G_TYPE_OBJECT)
+
+#define GET_PRIVATE(o) (asb_package_get_instance_private (o))
+
+/**
+ * asb_package_finalize:
+ **/
+static void
+asb_package_finalize (GObject *object)
+{
+ AsbPackage *pkg = ASB_PACKAGE (object);
+ AsbPackagePrivate *priv = GET_PRIVATE (pkg);
+
+ g_strfreev (priv->filelist);
+ g_strfreev (priv->deps);
+ g_free (priv->filename);
+ g_free (priv->basename);
+ g_free (priv->name);
+ g_free (priv->version);
+ g_free (priv->release);
+ g_free (priv->arch);
+ g_free (priv->url);
+ g_free (priv->nevr);
+ g_free (priv->evr);
+ g_free (priv->license);
+ g_free (priv->source);
+ g_string_free (priv->log, TRUE);
+ g_timer_destroy (priv->timer);
+ g_hash_table_unref (priv->configs);
+ g_ptr_array_unref (priv->releases);
+ g_hash_table_unref (priv->releases_hash);
+
+ G_OBJECT_CLASS (asb_package_parent_class)->finalize (object);
+}
+
+/**
+ * asb_package_init:
+ **/
+static void
+asb_package_init (AsbPackage *pkg)
+{
+ AsbPackagePrivate *priv = GET_PRIVATE (pkg);
+ priv->enabled = TRUE;
+ priv->log = g_string_sized_new (1024);
+ priv->timer = g_timer_new ();
+ priv->configs = g_hash_table_new_full (g_str_hash, g_str_equal,
+ g_free, g_free);
+ priv->releases = g_ptr_array_new_with_free_func ((GDestroyNotify) g_object_unref);
+ priv->releases_hash = g_hash_table_new_full (g_str_hash, g_str_equal,
+ g_free, (GDestroyNotify) g_object_unref);
+}
+
+/**
+ * asb_package_get_enabled:
+ * @pkg: A #AsbPackage
+ *
+ * Gets if the package is enabled.
+ *
+ * Returns: enabled status
+ *
+ * Since: 0.1.0
+ **/
+gboolean
+asb_package_get_enabled (AsbPackage *pkg)
+{
+ AsbPackagePrivate *priv = GET_PRIVATE (pkg);
+ return priv->enabled;
+}
+
+/**
+ * asb_package_set_enabled:
+ * @pkg: A #AsbPackage
+ * @enabled: boolean
+ *
+ * Enables or disables the package.
+ *
+ * Since: 0.1.0
+ **/
+void
+asb_package_set_enabled (AsbPackage *pkg, gboolean enabled)
+{
+ AsbPackagePrivate *priv = GET_PRIVATE (pkg);
+ priv->enabled = enabled;
+}
+
+/**
+ * asb_package_log_start:
+ * @pkg: A #AsbPackage
+ *
+ * Starts the log timer.
+ *
+ * Since: 0.1.0
+ **/
+void
+asb_package_log_start (AsbPackage *pkg)
+{
+ AsbPackagePrivate *priv = GET_PRIVATE (pkg);
+ g_timer_reset (priv->timer);
+}
+
+/**
+ * asb_package_log:
+ * @pkg: A #AsbPackage
+ * @log_level: a #AsbPackageLogLevel
+ * @fmt: Format string
+ * @...: varargs
+ *
+ * Logs a message.
+ *
+ * Since: 0.1.0
+ **/
+void
+asb_package_log (AsbPackage *pkg,
+ AsbPackageLogLevel log_level,
+ const gchar *fmt, ...)
+{
+ AsbPackagePrivate *priv = GET_PRIVATE (pkg);
+ va_list args;
+ gdouble now;
+ _cleanup_free_ gchar *tmp;
+
+ va_start (args, fmt);
+ tmp = g_strdup_vprintf (fmt, args);
+ va_end (args);
+ if (g_getenv ("ASB_PROFILE") != NULL) {
+ now = g_timer_elapsed (priv->timer, NULL) * 1000;
+ g_string_append_printf (priv->log,
+ "%05.0f\t+%05.0f\t",
+ now, now - priv->last_log);
+ priv->last_log = now;
+ }
+ switch (log_level) {
+ case ASB_PACKAGE_LOG_LEVEL_INFO:
+ g_debug ("INFO: %s", tmp);
+ g_string_append_printf (priv->log, "INFO: %s\n", tmp);
+ break;
+ case ASB_PACKAGE_LOG_LEVEL_DEBUG:
+ g_debug ("DEBUG: %s", tmp);
+ if (g_getenv ("ASB_PROFILE") != NULL)
+ g_string_append_printf (priv->log, "DEBUG: %s\n", tmp);
+ break;
+ case ASB_PACKAGE_LOG_LEVEL_WARNING:
+ g_debug ("WARNING: %s", tmp);
+ g_string_append_printf (priv->log, "WARNING: %s\n", tmp);
+ break;
+ default:
+ g_debug ("%s", tmp);
+ g_string_append_printf (priv->log, "%s\n", tmp);
+ break;
+ }
+}
+
+/**
+ * asb_package_log_flush:
+ * @pkg: A #AsbPackage
+ * @error: A #GError or %NULL
+ *
+ * Flushes the log queue.
+ *
+ * Returns: %TRUE for success, %FALSE otherwise
+ *
+ * Since: 0.1.0
+ **/
+gboolean
+asb_package_log_flush (AsbPackage *pkg, GError **error)
+{
+ AsbPackagePrivate *priv = GET_PRIVATE (pkg);
+ _cleanup_free_ gchar *logfile;
+
+ /* overwrite old log */
+ logfile = g_strdup_printf ("%s/%s.log",
+ asb_package_get_config (pkg, "LogDir"),
+ asb_package_get_name (pkg));
+ return g_file_set_contents (logfile, priv->log->str, -1, error);
+}
+
+/**
+ * asb_package_get_filename:
+ * @pkg: A #AsbPackage
+ *
+ * Gets the filename of the package.
+ *
+ * Returns: utf8 filename
+ *
+ * Since: 0.1.0
+ **/
+const gchar *
+asb_package_get_filename (AsbPackage *pkg)
+{
+ AsbPackagePrivate *priv = GET_PRIVATE (pkg);
+ return priv->filename;
+}
+
+/**
+ * asb_package_get_basename:
+ * @pkg: A #AsbPackage
+ *
+ * Gets the package basename.
+ *
+ * Returns: utf8 string
+ *
+ * Since: 0.1.0
+ **/
+const gchar *
+asb_package_get_basename (AsbPackage *pkg)
+{
+ AsbPackagePrivate *priv = GET_PRIVATE (pkg);
+ return priv->basename;
+}
+
+/**
+ * asb_package_get_name:
+ * @pkg: A #AsbPackage
+ *
+ * Gets the package name
+ *
+ * Returns: utf8 string
+ *
+ * Since: 0.1.0
+ **/
+const gchar *
+asb_package_get_name (AsbPackage *pkg)
+{
+ AsbPackagePrivate *priv = GET_PRIVATE (pkg);
+ return priv->name;
+}
+
+/**
+ * asb_package_get_url:
+ * @pkg: A #AsbPackage
+ *
+ * Gets the package homepage URL
+ *
+ * Returns: utf8 string
+ *
+ * Since: 0.1.0
+ **/
+const gchar *
+asb_package_get_url (AsbPackage *pkg)
+{
+ AsbPackagePrivate *priv = GET_PRIVATE (pkg);
+ return priv->url;
+}
+
+/**
+ * asb_package_get_license:
+ * @pkg: A #AsbPackage
+ *
+ * Gets the package license.
+ *
+ * Returns: utf8 string
+ *
+ * Since: 0.1.0
+ **/
+const gchar *
+asb_package_get_license (AsbPackage *pkg)
+{
+ AsbPackagePrivate *priv = GET_PRIVATE (pkg);
+ return priv->license;
+}
+
+/**
+ * asb_package_get_source:
+ * @pkg: A #AsbPackage
+ *
+ * Gets the package source name.
+ *
+ * Returns: utf8 string
+ *
+ * Since: 0.1.0
+ **/
+const gchar *
+asb_package_get_source (AsbPackage *pkg)
+{
+ AsbPackagePrivate *priv = GET_PRIVATE (pkg);
+ return priv->source;
+}
+
+/**
+ * asb_package_get_filelist:
+ * @pkg: A #AsbPackage
+ *
+ * Gets the package filelist.
+ *
+ * Returns: (transfer none) (element-type utf8): filelist
+ *
+ * Since: 0.1.0
+ **/
+gchar **
+asb_package_get_filelist (AsbPackage *pkg)
+{
+ AsbPackagePrivate *priv = GET_PRIVATE (pkg);
+ return priv->filelist;
+}
+
+/**
+ * asb_package_get_deps:
+ * @pkg: A #AsbPackage
+ *
+ * Get the package dependancy list.
+ *
+ * Returns: (transfer none) (element-type utf8): deplist
+ *
+ * Since: 0.1.0
+ **/
+gchar **
+asb_package_get_deps (AsbPackage *pkg)
+{
+ AsbPackagePrivate *priv = GET_PRIVATE (pkg);
+ return priv->deps;
+}
+
+/**
+ * asb_package_set_name:
+ * @pkg: A #AsbPackage
+ * @name: package name
+ *
+ * Sets the package name.
+ *
+ * Since: 0.1.0
+ **/
+void
+asb_package_set_name (AsbPackage *pkg, const gchar *name)
+{
+ AsbPackagePrivate *priv = GET_PRIVATE (pkg);
+ g_free (priv->name);
+ priv->name = g_strdup (name);
+}
+
+/**
+ * asb_package_set_version:
+ * @pkg: A #AsbPackage
+ * @version: package version
+ *
+ * Sets the package version.
+ *
+ * Since: 0.1.0
+ **/
+void
+asb_package_set_version (AsbPackage *pkg, const gchar *version)
+{
+ AsbPackagePrivate *priv = GET_PRIVATE (pkg);
+ g_free (priv->version);
+ priv->version = g_strdup (version);
+}
+
+/**
+ * asb_package_set_release:
+ * @pkg: A #AsbPackage
+ * @release: package release
+ *
+ * Sets the package release.
+ *
+ * Since: 0.1.0
+ **/
+void
+asb_package_set_release (AsbPackage *pkg, const gchar *release)
+{
+ AsbPackagePrivate *priv = GET_PRIVATE (pkg);
+ g_free (priv->release);
+ priv->release = g_strdup (release);
+}
+
+/**
+ * asb_package_set_arch:
+ * @pkg: A #AsbPackage
+ * @arch: package architecture
+ *
+ * Sets the package architecture.
+ *
+ * Since: 0.1.0
+ **/
+void
+asb_package_set_arch (AsbPackage *pkg, const gchar *arch)
+{
+ AsbPackagePrivate *priv = GET_PRIVATE (pkg);
+ g_free (priv->arch);
+ priv->arch = g_strdup (arch);
+}
+
+/**
+ * asb_package_set_epoch:
+ * @pkg: A #AsbPackage
+ * @epoch: epoch, or 0 for unset
+ *
+ * Sets the package epoch
+ *
+ * Since: 0.1.0
+ **/
+void
+asb_package_set_epoch (AsbPackage *pkg, guint epoch)
+{
+ AsbPackagePrivate *priv = GET_PRIVATE (pkg);
+ priv->epoch = epoch;
+}
+
+/**
+ * asb_package_set_url:
+ * @pkg: A #AsbPackage
+ * @url: homepage URL
+ *
+ * Sets the package URL.
+ *
+ * Since: 0.1.0
+ **/
+void
+asb_package_set_url (AsbPackage *pkg, const gchar *url)
+{
+ AsbPackagePrivate *priv = GET_PRIVATE (pkg);
+ g_free (priv->url);
+ priv->url = g_strdup (url);
+}
+
+/**
+ * asb_package_set_license:
+ * @pkg: A #AsbPackage
+ * @license: license string
+ *
+ * Sets the package license.
+ *
+ * Since: 0.1.0
+ **/
+void
+asb_package_set_license (AsbPackage *pkg, const gchar *license)
+{
+ AsbPackagePrivate *priv = GET_PRIVATE (pkg);
+ g_free (priv->license);
+ priv->license = g_strdup (license);
+}
+
+/**
+ * asb_package_set_source:
+ * @pkg: A #AsbPackage
+ * @source: source string, e.g. the srpm name
+ *
+ * Sets the package source name, which is usually the parent of a set of
+ * subpackages.
+ *
+ * Since: 0.1.0
+ **/
+void
+asb_package_set_source (AsbPackage *pkg, const gchar *source)
+{
+ AsbPackagePrivate *priv = GET_PRIVATE (pkg);
+ g_free (priv->source);
+ priv->source = g_strdup (source);
+}
+
+/**
+ * asb_package_set_deps:
+ * @pkg: A #AsbPackage
+ * @deps: package deps
+ *
+ * Sets the package dependancies.
+ *
+ * Since: 0.1.0
+ **/
+void
+asb_package_set_deps (AsbPackage *pkg, gchar **deps)
+{
+ AsbPackagePrivate *priv = GET_PRIVATE (pkg);
+ g_strfreev (priv->deps);
+ priv->deps = g_strdupv (deps);
+}
+
+/**
+ * asb_package_set_filelist:
+ * @pkg: A #AsbPackage
+ * @filelist: package filelist
+ *
+ * Sets the package filelist.
+ *
+ * Since: 0.1.0
+ **/
+void
+asb_package_set_filelist (AsbPackage *pkg, gchar **filelist)
+{
+ AsbPackagePrivate *priv = GET_PRIVATE (pkg);
+ g_strfreev (priv->filelist);
+ priv->filelist = g_strdupv (filelist);
+}
+
+/**
+ * asb_package_get_nevr:
+ * @pkg: A #AsbPackage
+ *
+ * Gets the package NEVR.
+ *
+ * Returns: utf8 string
+ *
+ * Since: 0.1.0
+ **/
+const gchar *
+asb_package_get_nevr (AsbPackage *pkg)
+{
+ AsbPackagePrivate *priv = GET_PRIVATE (pkg);
+ if (priv->nevr == NULL) {
+ if (priv->epoch == 0) {
+ priv->nevr = g_strdup_printf ("%s-%s-%s",
+ priv->name,
+ priv->version,
+ priv->release);
+ } else {
+ priv->nevr = g_strdup_printf ("%s-%i:%s-%s",
+ priv->name,
+ priv->epoch,
+ priv->version,
+ priv->release);
+ }
+ }
+ return priv->nevr;
+}
+
+/**
+ * asb_package_get_evr:
+ * @pkg: A #AsbPackage
+ *
+ * Gets the package EVR.
+ *
+ * Returns: utf8 string
+ *
+ * Since: 0.1.0
+ **/
+const gchar *
+asb_package_get_evr (AsbPackage *pkg)
+{
+ AsbPackagePrivate *priv = GET_PRIVATE (pkg);
+ if (priv->evr == NULL) {
+ if (priv->epoch == 0) {
+ priv->evr = g_strdup_printf ("%s-%s",
+ priv->version,
+ priv->release);
+ } else {
+ priv->evr = g_strdup_printf ("%i:%s-%s",
+ priv->epoch,
+ priv->version,
+ priv->release);
+ }
+ }
+ return priv->evr;
+}
+
+/**
+ * asb_package_class_init:
+ **/
+static void
+asb_package_class_init (AsbPackageClass *klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+ object_class->finalize = asb_package_finalize;
+}
+
+/**
+ * asb_package_open:
+ * @pkg: A #AsbPackage
+ * @filename: package filename
+ * @error: A #GError or %NULL
+ *
+ * Opens a package and parses the contents.
+ *
+ * Returns: %TRUE for success, %FALSE otherwise
+ *
+ * Since: 0.1.0
+ **/
+gboolean
+asb_package_open (AsbPackage *pkg, const gchar *filename, GError **error)
+{
+ AsbPackageClass *klass = ASB_PACKAGE_GET_CLASS (pkg);
+ AsbPackagePrivate *priv = GET_PRIVATE (pkg);
+
+ /* cache here */
+ priv->filename = g_strdup (filename);
+ priv->basename = g_path_get_basename (filename);
+
+ /* call distro-specific method */
+ if (klass->open != NULL)
+ return klass->open (pkg, filename, error);
+ return TRUE;
+}
+
+/**
+ * asb_package_explode:
+ * @pkg: A #AsbPackage
+ * @dir: directory to explode into
+ * @glob: (element-type utf8): the glob list, or %NULL
+ * @error: A #GError or %NULL
+ *
+ * Decompresses a package into a directory, optionally using a glob list.
+ *
+ * Returns: %TRUE for success, %FALSE otherwise
+ *
+ * Since: 0.1.0
+ **/
+gboolean
+asb_package_explode (AsbPackage *pkg,
+ const gchar *dir,
+ GPtrArray *glob,
+ GError **error)
+{
+ AsbPackageClass *klass = ASB_PACKAGE_GET_CLASS (pkg);
+ AsbPackagePrivate *priv = GET_PRIVATE (pkg);
+ if (klass->explode != NULL)
+ return klass->explode (pkg, dir, glob, error);
+ return asb_utils_explode (priv->filename, dir, glob, error);
+}
+
+/**
+ * asb_package_set_config:
+ * @pkg: A #AsbPackage
+ * @key: utf8 string
+ * @value: utf8 string
+ *
+ * Sets a config attribute on a package.
+ *
+ * Since: 0.1.0
+ **/
+void
+asb_package_set_config (AsbPackage *pkg, const gchar *key, const gchar *value)
+{
+ AsbPackagePrivate *priv = GET_PRIVATE (pkg);
+ g_hash_table_insert (priv->configs, g_strdup (key), g_strdup (value));
+}
+
+/**
+ * asb_package_get_config:
+ * @pkg: A #AsbPackage
+ * @key: utf8 string
+ *
+ * Gets a config attribute from a package.
+ *
+ * Returns: utf8 string
+ *
+ * Since: 0.1.0
+ **/
+const gchar *
+asb_package_get_config (AsbPackage *pkg, const gchar *key)
+{
+ AsbPackagePrivate *priv = GET_PRIVATE (pkg);
+ return g_hash_table_lookup (priv->configs, key);
+}
+
+/**
+ * asb_package_get_releases:
+ * @pkg: A #AsbPackage
+ *
+ * Gets the releases of the package.
+ *
+ * Returns: (transfer none) (element-type AsRelease): the release data
+ *
+ * Since: 0.1.0
+ **/
+GPtrArray *
+asb_package_get_releases (AsbPackage *pkg)
+{
+ AsbPackagePrivate *priv = GET_PRIVATE (pkg);
+ return priv->releases;
+}
+
+/**
+ * asb_package_compare:
+ * @pkg1: A #AsbPackage
+ * @pkg2: A #AsbPackage
+ *
+ * Compares one package with another.
+ *
+ * Returns: -1 for <, 0 for the same and +1 for >
+ *
+ * Since: 0.1.0
+ **/
+gint
+asb_package_compare (AsbPackage *pkg1, AsbPackage *pkg2)
+{
+ AsbPackageClass *klass = ASB_PACKAGE_GET_CLASS (pkg1);
+ if (klass->compare != NULL)
+ return klass->compare (pkg1, pkg2);
+ return 0;
+}
+
+/**
+ * asb_package_get_release:
+ * @pkg: A #AsbPackage
+ * @version: package version
+ *
+ * Gets the release for a specific version.
+ *
+ * Returns: (transfer none): an #AsRelease, or %NULL for not found
+ *
+ * Since: 0.1.0
+ **/
+AsRelease *
+asb_package_get_release (AsbPackage *pkg, const gchar *version)
+{
+ AsbPackagePrivate *priv = GET_PRIVATE (pkg);
+ return g_hash_table_lookup (priv->releases_hash, version);
+}
+
+/**
+ * asb_package_add_release:
+ * @pkg: A #AsbPackage
+ * @version: a package version
+ * @release: a package release
+ *
+ * Adds a (downstream) release to a package.
+ *
+ * Since: 0.1.0
+ **/
+void
+asb_package_add_release (AsbPackage *pkg,
+ const gchar *version,
+ AsRelease *release)
+{
+ AsbPackagePrivate *priv = GET_PRIVATE (pkg);
+ g_hash_table_insert (priv->releases_hash,
+ g_strdup (version),
+ g_object_ref (release));
+ g_ptr_array_add (priv->releases, g_object_ref (release));
+}
diff --git a/libappstream-builder/asb-package.h b/libappstream-builder/asb-package.h
new file mode 100644
index 0000000..60c87ad
--- /dev/null
+++ b/libappstream-builder/asb-package.h
@@ -0,0 +1,135 @@
+/* -*- 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_PACKAGE_H
+#define ASB_PACKAGE_H
+
+#include <glib-object.h>
+
+#include <stdarg.h>
+#include <appstream-glib.h>
+
+#define ASB_TYPE_PACKAGE (asb_package_get_type())
+#define ASB_PACKAGE(obj) (G_TYPE_CHECK_INSTANCE_CAST((obj), ASB_TYPE_PACKAGE, AsbPackage))
+#define ASB_PACKAGE_CLASS(cls) (G_TYPE_CHECK_CLASS_CAST((cls), ASB_TYPE_PACKAGE, AsbPackageClass))
+#define ASB_IS_PACKAGE(obj) (G_TYPE_CHECK_INSTANCE_TYPE((obj), ASB_TYPE_PACKAGE))
+#define ASB_IS_PACKAGE_CLASS(cls) (G_TYPE_CHECK_CLASS_TYPE((cls), ASB_TYPE_PACKAGE))
+#define ASB_PACKAGE_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS((obj), ASB_TYPE_PACKAGE, AsbPackageClass))
+
+G_BEGIN_DECLS
+
+typedef struct _AsbPackage AsbPackage;
+typedef struct _AsbPackageClass AsbPackageClass;
+
+struct _AsbPackage
+{
+ GObject parent;
+};
+
+struct _AsbPackageClass
+{
+ GObjectClass parent_class;
+ gboolean (*open) (AsbPackage *pkg,
+ const gchar *filename,
+ GError **error);
+ gboolean (*explode) (AsbPackage *pkg,
+ const gchar *dir,
+ GPtrArray *glob,
+ GError **error);
+ gint (*compare) (AsbPackage *pkg1,
+ AsbPackage *pkg2);
+};
+
+typedef enum {
+ ASB_PACKAGE_LOG_LEVEL_NONE,
+ ASB_PACKAGE_LOG_LEVEL_DEBUG,
+ ASB_PACKAGE_LOG_LEVEL_INFO,
+ ASB_PACKAGE_LOG_LEVEL_WARNING,
+ ASB_PACKAGE_LOG_LEVEL_LAST,
+} AsbPackageLogLevel;
+
+GType asb_package_get_type (void);
+
+void asb_package_log_start (AsbPackage *pkg);
+void asb_package_log (AsbPackage *pkg,
+ AsbPackageLogLevel log_level,
+ const gchar *fmt,
+ ...)
+ G_GNUC_PRINTF (3, 4);
+gboolean asb_package_log_flush (AsbPackage *pkg,
+ GError **error);
+gboolean asb_package_open (AsbPackage *pkg,
+ const gchar *filename,
+ GError **error);
+gboolean asb_package_explode (AsbPackage *pkg,
+ const gchar *dir,
+ GPtrArray *glob,
+ GError **error);
+const gchar *asb_package_get_filename (AsbPackage *pkg);
+const gchar *asb_package_get_basename (AsbPackage *pkg);
+const gchar *asb_package_get_name (AsbPackage *pkg);
+const gchar *asb_package_get_nevr (AsbPackage *pkg);
+const gchar *asb_package_get_evr (AsbPackage *pkg);
+const gchar *asb_package_get_url (AsbPackage *pkg);
+const gchar *asb_package_get_license (AsbPackage *pkg);
+const gchar *asb_package_get_source (AsbPackage *pkg);
+void asb_package_set_name (AsbPackage *pkg,
+ const gchar *name);
+void asb_package_set_version (AsbPackage *pkg,
+ const gchar *version);
+void asb_package_set_release (AsbPackage *pkg,
+ const gchar *release);
+void asb_package_set_arch (AsbPackage *pkg,
+ const gchar *arch);
+void asb_package_set_epoch (AsbPackage *pkg,
+ guint epoch);
+void asb_package_set_url (AsbPackage *pkg,
+ const gchar *url);
+void asb_package_set_license (AsbPackage *pkg,
+ const gchar *license);
+void asb_package_set_source (AsbPackage *pkg,
+ const gchar *source);
+void asb_package_set_deps (AsbPackage *pkg,
+ gchar **deps);
+void asb_package_set_filelist (AsbPackage *pkg,
+ gchar **filelist);
+gchar **asb_package_get_filelist (AsbPackage *pkg);
+gchar **asb_package_get_deps (AsbPackage *pkg);
+GPtrArray *asb_package_get_releases (AsbPackage *pkg);
+void asb_package_set_config (AsbPackage *pkg,
+ const gchar *key,
+ const gchar *value);
+const gchar *asb_package_get_config (AsbPackage *pkg,
+ const gchar *key);
+gint asb_package_compare (AsbPackage *pkg1,
+ AsbPackage *pkg2);
+gboolean asb_package_get_enabled (AsbPackage *pkg);
+void asb_package_set_enabled (AsbPackage *pkg,
+ gboolean enabled);
+AsRelease *asb_package_get_release (AsbPackage *pkg,
+ const gchar *version);
+void asb_package_add_release (AsbPackage *pkg,
+ const gchar *version,
+ AsRelease *release);
+
+G_END_DECLS
+
+#endif /* ASB_PACKAGE_H */
diff --git a/libappstream-builder/asb-plugin-loader.c b/libappstream-builder/asb-plugin-loader.c
new file mode 100644
index 0000000..1f4eb13
--- /dev/null
+++ b/libappstream-builder/asb-plugin-loader.c
@@ -0,0 +1,383 @@
+/* -*- 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-plugin-loader
+ * @short_description: Plugin loader.
+ * @stability: Unstable
+ *
+ * This module provides an array of plugins which can operate on an exploded
+ * package tree.
+ */
+
+#include "config.h"
+
+#include <glib.h>
+
+#include "as-cleanup.h"
+#include "asb-plugin.h"
+#include "asb-plugin-loader.h"
+
+/**
+ * asb_plugin_loader_plugin_free:
+ **/
+static void
+asb_plugin_loader_plugin_free (AsbPlugin *plugin)
+{
+ g_free (plugin->priv);
+ g_free (plugin->name);
+ g_module_close (plugin->module);
+ g_slice_free (AsbPlugin, plugin);
+}
+
+/**
+ * asb_plugin_loader_match_fn:
+ * @plugins: (element-type AsbPlugin): An array of plugins
+ * @filename: filename
+ *
+ * Processes the list of plugins finding a plugin that can process the filename.
+ *
+ * Returns: (transfer none): a plugin, or %NULL
+ *
+ * Since: 0.1.0
+ **/
+AsbPlugin *
+asb_plugin_loader_match_fn (GPtrArray *plugins, const gchar *filename)
+{
+ gboolean ret;
+ AsbPluginCheckFilenameFunc plugin_func = NULL;
+ AsbPlugin *plugin;
+ guint i;
+
+ /* run each plugin */
+ for (i = 0; i < plugins->len; i++) {
+ plugin = g_ptr_array_index (plugins, i);
+ ret = g_module_symbol (plugin->module,
+ "asb_plugin_check_filename",
+ (gpointer *) &plugin_func);
+ if (!ret)
+ continue;
+ if (plugin_func (plugin, filename))
+ return plugin;
+ }
+ return NULL;
+}
+
+/**
+ * asb_plugin_loader_process_app:
+ * @plugins: (element-type AsbPlugin): An array of plugins
+ * @pkg: The #AsbPackage
+ * @app: The #AsbApp to refine
+ * @tmpdir: A temporary location to use
+ * @error: A #GError or %NULL
+ *
+ * Processes an application object, refining any available data.
+ *
+ * Returns: %TRUE for success, %FALSE otherwise
+ *
+ * Since: 0.1.0
+ **/
+gboolean
+asb_plugin_loader_process_app (GPtrArray *plugins,
+ AsbPackage *pkg,
+ AsbApp *app,
+ const gchar *tmpdir,
+ GError **error)
+{
+ gboolean ret;
+ AsbPluginProcessAppFunc plugin_func = NULL;
+ AsbPlugin *plugin;
+ guint i;
+
+ /* run each plugin */
+ for (i = 0; i < plugins->len; i++) {
+ plugin = g_ptr_array_index (plugins, i);
+ ret = g_module_symbol (plugin->module,
+ "asb_plugin_process_app",
+ (gpointer *) &plugin_func);
+ if (!ret)
+ continue;
+ asb_package_log (pkg,
+ ASB_PACKAGE_LOG_LEVEL_DEBUG,
+ "Running asb_plugin_process_app() from %s",
+ plugin->name);
+ if (!plugin_func (plugin, pkg, app, tmpdir, error))
+ return FALSE;
+ }
+ return TRUE;
+}
+
+/**
+ * asb_plugin_loader_run:
+ **/
+static void
+asb_plugin_loader_run (GPtrArray *plugins, const gchar *function_name)
+{
+ gboolean ret;
+ AsbPluginFunc plugin_func = NULL;
+ AsbPlugin *plugin;
+ guint i;
+
+ /* run each plugin */
+ for (i = 0; i < plugins->len; i++) {
+ plugin = g_ptr_array_index (plugins, i);
+ ret = g_module_symbol (plugin->module,
+ function_name,
+ (gpointer *) &plugin_func);
+ if (!ret)
+ continue;
+ plugin_func (plugin);
+ }
+}
+
+/**
+ * asb_plugin_loader_get_globs:
+ * @plugins: (element-type AsbPlugin): An array of plugins
+ *
+ * Gets the list of globs.
+ *
+ * Returns: (transfer container) (element-type utf8): globs
+ *
+ * Since: 0.1.0
+ **/
+GPtrArray *
+asb_plugin_loader_get_globs (GPtrArray *plugins)
+{
+ gboolean ret;
+ AsbPluginGetGlobsFunc plugin_func = NULL;
+ AsbPlugin *plugin;
+ guint i;
+ GPtrArray *globs;
+
+ /* run each plugin */
+ globs = asb_glob_value_array_new ();
+ for (i = 0; i < plugins->len; i++) {
+ plugin = g_ptr_array_index (plugins, i);
+ ret = g_module_symbol (plugin->module,
+ "asb_plugin_add_globs",
+ (gpointer *) &plugin_func);
+ if (!ret)
+ continue;
+ plugin_func (plugin, globs);
+ }
+ return globs;
+}
+
+/**
+ * asb_plugin_loader_merge:
+ * @plugins: (element-type AsbPlugin): An array of plugins
+ * @apps: (element-type AsbApp): a list of applications that need merging
+ *
+ * Merge the list of applications using the plugins.
+ *
+ * Since: 0.1.0
+ **/
+void
+asb_plugin_loader_merge (GPtrArray *plugins, GList **apps)
+{
+ const gchar *key;
+ const gchar *tmp;
+ AsbApp *app;
+ AsbApp *found;
+ AsbPluginMergeFunc plugin_func = NULL;
+ AsbPlugin *plugin;
+ gboolean ret;
+ GList *l;
+ guint i;
+ _cleanup_hashtable_unref_ GHashTable *hash;
+
+ /* run each plugin */
+ for (i = 0; i < plugins->len; i++) {
+ plugin = g_ptr_array_index (plugins, i);
+ ret = g_module_symbol (plugin->module,
+ "asb_plugin_merge",
+ (gpointer *) &plugin_func);
+ if (!ret)
+ continue;
+ plugin_func (plugin, apps);
+ }
+
+ /* FIXME: move to font plugin */
+ for (l = *apps; l != NULL; l = l->next) {
+ if (!ASB_IS_APP (l->data))
+ continue;
+ app = ASB_APP (l->data);
+ as_app_remove_metadata (AS_APP (app), "FontFamily");
+ as_app_remove_metadata (AS_APP (app), "FontFullName");
+ as_app_remove_metadata (AS_APP (app), "FontIconText");
+ as_app_remove_metadata (AS_APP (app), "FontParent");
+ as_app_remove_metadata (AS_APP (app), "FontSampleText");
+ as_app_remove_metadata (AS_APP (app), "FontSubFamily");
+ as_app_remove_metadata (AS_APP (app), "FontClassifier");
+ }
+
+ /* deduplicate */
+ hash = g_hash_table_new (g_str_hash, g_str_equal);
+ for (l = *apps; l != NULL; l = l->next) {
+ if (!ASB_IS_APP (l->data))
+ continue;
+ app = ASB_APP (l->data);
+ key = as_app_get_id_full (AS_APP (app));
+ found = g_hash_table_lookup (hash, key);
+ if (found == NULL) {
+ g_hash_table_insert (hash,
+ (gpointer) key,
+ (gpointer) app);
+ continue;
+ }
+ tmp = asb_package_get_nevr (asb_app_get_package (found));
+ asb_app_add_veto (app, "duplicate of %s", tmp);
+ asb_package_log (asb_app_get_package (app),
+ ASB_PACKAGE_LOG_LEVEL_WARNING,
+ "duplicate %s not included as added from %s",
+ key, tmp);
+ }
+}
+
+/**
+ * asb_plugin_loader_open_plugin:
+ **/
+static AsbPlugin *
+asb_plugin_loader_open_plugin (GPtrArray *plugins,
+ const gchar *filename)
+{
+ gboolean ret;
+ GModule *module;
+ AsbPluginGetNameFunc plugin_name = NULL;
+ AsbPlugin *plugin = NULL;
+
+ module = g_module_open (filename, 0);
+ if (module == NULL) {
+ g_warning ("failed to open plugin %s: %s",
+ filename, g_module_error ());
+ return NULL;
+ }
+
+ /* get description */
+ ret = g_module_symbol (module,
+ "asb_plugin_get_name",
+ (gpointer *) &plugin_name);
+ if (!ret) {
+ g_warning ("Plugin %s requires name", filename);
+ g_module_close (module);
+ return NULL;
+ }
+
+ /* print what we know */
+ plugin = g_slice_new0 (AsbPlugin);
+ plugin->enabled = TRUE;
+ plugin->module = module;
+ plugin->name = g_strdup (plugin_name ());
+ g_debug ("opened plugin %s: %s", filename, plugin->name);
+
+ /* add to array */
+ g_ptr_array_add (plugins, plugin);
+ return plugin;
+}
+
+/**
+ * asb_plugin_loader_sort_cb:
+ **/
+static gint
+asb_plugin_loader_sort_cb (gconstpointer a, gconstpointer b)
+{
+ AsbPlugin **plugin_a = (AsbPlugin **) a;
+ AsbPlugin **plugin_b = (AsbPlugin **) b;
+ return -g_strcmp0 ((*plugin_a)->name, (*plugin_b)->name);
+}
+
+/**
+ * asb_plugin_loader_setup:
+ * @plugins: (element-type AsbPlugin): An array of plugins
+ * @error: A #GError or %NULL
+ *
+ * Set up the plugin loader.
+ *
+ * Returns: %TRUE for success, %FALSE otherwise
+ *
+ * Since: 0.1.0
+ **/
+gboolean
+asb_plugin_loader_setup (GPtrArray *plugins, GError **error)
+{
+ const gchar *filename_tmp;
+ const gchar *location = "./plugins/.libs/";
+ _cleanup_dir_close_ GDir *dir;
+
+ /* search system-wide if not found locally */
+ if (!g_file_test (location, G_FILE_TEST_EXISTS))
+ location = ASB_PLUGIN_DIR;
+
+ /* search in the plugin directory for plugins */
+ dir = g_dir_open (location, 0, error);
+ if (dir == NULL)
+ return FALSE;
+
+ /* try to open each plugin */
+ g_debug ("searching for plugins in %s", location);
+ do {
+ _cleanup_free_ gchar *filename_plugin = NULL;
+ filename_tmp = g_dir_read_name (dir);
+ if (filename_tmp == NULL)
+ break;
+ if (!g_str_has_suffix (filename_tmp, ".so"))
+ continue;
+ filename_plugin = g_build_filename (location,
+ filename_tmp,
+ NULL);
+ asb_plugin_loader_open_plugin (plugins, filename_plugin);
+ } while (TRUE);
+
+ /* run the plugins */
+ asb_plugin_loader_run (plugins, "asb_plugin_initialize");
+ g_ptr_array_sort (plugins, asb_plugin_loader_sort_cb);
+ return TRUE;
+}
+
+/**
+ * asb_plugin_loader_new:
+ *
+ * Creates a new plugin loader interface.
+ *
+ * Returns: (transfer container) (element-type AsbPlugin): state
+ *
+ * Since: 0.1.0
+ **/
+GPtrArray *
+asb_plugin_loader_new (void)
+{
+ return g_ptr_array_new_with_free_func ((GDestroyNotify) asb_plugin_loader_plugin_free);
+}
+
+/**
+ * asb_plugin_loader_free:
+ * @plugins: (element-type AsbPlugin): An array of plugins
+ *
+ * Destroy the plugin state.
+ *
+ * Since: 0.1.0
+ **/
+void
+asb_plugin_loader_free (GPtrArray *plugins)
+{
+ asb_plugin_loader_run (plugins, "asb_plugin_destroy");
+ g_ptr_array_unref (plugins);
+}
diff --git a/libappstream-builder/asb-plugin-loader.h b/libappstream-builder/asb-plugin-loader.h
new file mode 100644
index 0000000..1630d2c
--- /dev/null
+++ b/libappstream-builder/asb-plugin-loader.h
@@ -0,0 +1,48 @@
+/* -*- 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_PLUGIN_LOADER_H
+#define __ASB_PLUGIN_LOADER_H
+
+#include <glib.h>
+
+#include "asb-plugin.h"
+
+G_BEGIN_DECLS
+
+gboolean asb_plugin_loader_setup (GPtrArray *plugins,
+ GError **error);
+GPtrArray *asb_plugin_loader_get_globs (GPtrArray *plugins);
+void asb_plugin_loader_merge (GPtrArray *plugins,
+ GList **apps);
+gboolean asb_plugin_loader_process_app (GPtrArray *plugins,
+ AsbPackage *pkg,
+ AsbApp *app,
+ const gchar *tmpdir,
+ GError **error);
+GPtrArray *asb_plugin_loader_new (void);
+void asb_plugin_loader_free (GPtrArray *plugins);
+AsbPlugin *asb_plugin_loader_match_fn (GPtrArray *plugins,
+ const gchar *filename);
+
+G_END_DECLS
+
+#endif /* __ASB_PLUGIN_LOADER_H */
diff --git a/libappstream-builder/asb-plugin.c b/libappstream-builder/asb-plugin.c
new file mode 100644
index 0000000..19b3391
--- /dev/null
+++ b/libappstream-builder/asb-plugin.c
@@ -0,0 +1,120 @@
+/* -*- 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-plugin
+ * @short_description: Generic plugin helpers.
+ * @stability: Unstable
+ *
+ * Utilities for plugins.
+ */
+
+#include "config.h"
+
+#include <glib.h>
+
+#include "asb-plugin.h"
+#include "asb-utils.h"
+
+/**
+ * asb_plugin_set_enabled:
+ * @plugin: A #AsbPlugin
+ * @enabled: boolean
+ *
+ * Enables or disables a plugin.
+ *
+ * Since: 0.1.0
+ **/
+void
+asb_plugin_set_enabled (AsbPlugin *plugin, gboolean enabled)
+{
+ plugin->enabled = enabled;
+}
+
+/**
+ * asb_plugin_process:
+ * @plugin: A #AsbPlugin
+ * @pkg: A #AsbPackage
+ * @tmpdir: the temporary location
+ * @error: A #GError or %NULL
+ *
+ * Runs a function on a specific plugin.
+ *
+ * Returns: (transfer none) (element-type AsbApp): applications
+ *
+ * Since: 0.1.0
+ **/
+GList *
+asb_plugin_process (AsbPlugin *plugin,
+ AsbPackage *pkg,
+ const gchar *tmpdir,
+ GError **error)
+{
+ AsbPluginProcessFunc plugin_func = NULL;
+ gboolean ret;
+
+ /* run each plugin */
+ asb_package_log (pkg,
+ ASB_PACKAGE_LOG_LEVEL_DEBUG,
+ "Running asb_plugin_process() from %s",
+ plugin->name);
+ ret = g_module_symbol (plugin->module,
+ "asb_plugin_process",
+ (gpointer *) &plugin_func);
+ if (!ret) {
+ g_set_error_literal (error,
+ ASB_PLUGIN_ERROR,
+ ASB_PLUGIN_ERROR_FAILED,
+ "no asb_plugin_process");
+ return NULL;
+ }
+ return plugin_func (plugin, pkg, tmpdir, error);
+}
+
+/**
+ * asb_plugin_add_app:
+ * @list: (element-type AsbApp): A list of #AsbApp's
+ * @app: A #AsbApp
+ *
+ * Adds an application to a list.
+ *
+ * Since: 0.1.0
+ **/
+void
+asb_plugin_add_app (GList **list, AsbApp *app)
+{
+ *list = g_list_prepend (*list, g_object_ref (app));
+}
+
+/**
+ * asb_plugin_add_glob:
+ * @array: (element-type utf8): A #GPtrArray
+ * @glob: a filename glob
+ *
+ * Adds a glob from the plugin.
+ *
+ * Since: 0.1.0
+ **/
+void
+asb_plugin_add_glob (GPtrArray *array, const gchar *glob)
+{
+ g_ptr_array_add (array, asb_glob_value_new (glob, ""));
+}
diff --git a/libappstream-builder/asb-plugin.h b/libappstream-builder/asb-plugin.h
new file mode 100644
index 0000000..15ff1a1
--- /dev/null
+++ b/libappstream-builder/asb-plugin.h
@@ -0,0 +1,102 @@
+/* -*- 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_PLUGIN_H
+#define __ASB_PLUGIN_H
+
+#include <glib-object.h>
+#include <gmodule.h>
+#include <gio/gio.h>
+
+#include "asb-app.h"
+#include "as-cleanup.h"
+#include "asb-package.h"
+#include "asb-utils.h"
+
+G_BEGIN_DECLS
+
+typedef struct AsbPluginPrivate AsbPluginPrivate;
+typedef struct AsbPlugin AsbPlugin;
+
+struct AsbPlugin {
+ GModule *module;
+ gboolean enabled;
+ gchar *name;
+ AsbPluginPrivate *priv;
+};
+
+typedef enum {
+ ASB_PLUGIN_ERROR_FAILED,
+ ASB_PLUGIN_ERROR_NOT_SUPPORTED,
+ ASB_PLUGIN_ERROR_LAST
+} AsbPluginError;
+
+/* helpers */
+#define ASB_PLUGIN_ERROR 1
+#define ASB_PLUGIN_GET_PRIVATE(x) g_new0 (x,1)
+#define ASB_PLUGIN(x) ((AsbPlugin *) x);
+
+typedef const gchar *(*AsbPluginGetNameFunc) (void);
+typedef void (*AsbPluginFunc) (AsbPlugin *plugin);
+typedef void (*AsbPluginGetGlobsFunc) (AsbPlugin *plugin,
+ GPtrArray *array);
+typedef void (*AsbPluginMergeFunc) (AsbPlugin *plugin,
+ GList **apps);
+typedef gboolean (*AsbPluginCheckFilenameFunc) (AsbPlugin *plugin,
+ const gchar *filename);
+typedef GList *(*AsbPluginProcessFunc) (AsbPlugin *plugin,
+ AsbPackage *pkg,
+ const gchar *tmp_dir,
+ GError **error);
+typedef gboolean (*AsbPluginProcessAppFunc) (AsbPlugin *plugin,
+ AsbPackage *pkg,
+ AsbApp *app,
+ const gchar *tmpdir,
+ GError **error);
+
+const gchar *asb_plugin_get_name (void);
+void asb_plugin_initialize (AsbPlugin *plugin);
+void asb_plugin_destroy (AsbPlugin *plugin);
+void asb_plugin_set_enabled (AsbPlugin *plugin,
+ gboolean enabled);
+GList *asb_plugin_process (AsbPlugin *plugin,
+ AsbPackage *pkg,
+ const gchar *tmpdir,
+ GError **error);
+void asb_plugin_add_globs (AsbPlugin *plugin,
+ GPtrArray *globs);
+void asb_plugin_merge (AsbPlugin *plugin,
+ GList **list);
+gboolean asb_plugin_process_app (AsbPlugin *plugin,
+ AsbPackage *pkg,
+ AsbApp *app,
+ const gchar *tmp_dir,
+ GError **error);
+gboolean asb_plugin_check_filename (AsbPlugin *plugin,
+ const gchar *filename);
+void asb_plugin_add_app (GList **list,
+ AsbApp *app);
+void asb_plugin_add_glob (GPtrArray *array,
+ const gchar *glob);
+
+G_END_DECLS
+
+#endif /* __ASB_PLUGIN_H */
diff --git a/libappstream-builder/asb-task.c b/libappstream-builder/asb-task.c
new file mode 100644
index 0000000..0ea8376
--- /dev/null
+++ b/libappstream-builder/asb-task.c
@@ -0,0 +1,557 @@
+/* -*- 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-task
+ * @short_description: One specific task for building metadata.
+ * @stability: Unstable
+ *
+ * Thsi object represents a single task, typically a package which is created
+ * and then processed. Typically this is done in a number of threads.
+ */
+
+#include "config.h"
+
+#include "as-cleanup.h"
+#include "asb-context-private.h"
+#include "asb-task.h"
+#include "asb-package.h"
+#include "asb-utils.h"
+#include "asb-plugin.h"
+#include "asb-plugin-loader.h"
+
+typedef struct _AsbTaskPrivate AsbTaskPrivate;
+struct _AsbTaskPrivate
+{
+ AsbContext *ctx;
+ AsbPackage *pkg;
+ GPtrArray *plugins_to_run;
+ gchar *filename;
+ gchar *tmpdir;
+ guint id;
+};
+
+G_DEFINE_TYPE_WITH_PRIVATE (AsbTask, asb_task, G_TYPE_OBJECT)
+
+#define GET_PRIVATE(o) (asb_task_get_instance_private (o))
+
+/**
+ * asb_task_add_suitable_plugins:
+ **/
+static void
+asb_task_add_suitable_plugins (AsbTask *task, GPtrArray *plugins)
+{
+ AsbPlugin *plugin;
+ AsbTaskPrivate *priv = GET_PRIVATE (task);
+ gchar **filelist;
+ guint i;
+ guint j;
+
+ filelist = asb_package_get_filelist (priv->pkg);
+ if (filelist == NULL)
+ return;
+ for (i = 0; filelist[i] != NULL; i++) {
+ plugin = asb_plugin_loader_match_fn (plugins, filelist[i]);
+ if (plugin == NULL)
+ continue;
+
+ /* check not already added */
+ for (j = 0; j < priv->plugins_to_run->len; j++) {
+ if (g_ptr_array_index (priv->plugins_to_run, j) == plugin)
+ break;
+ }
+
+ /* add */
+ if (j == priv->plugins_to_run->len)
+ g_ptr_array_add (priv->plugins_to_run, plugin);
+ }
+}
+
+/**
+ * asb_task_explode_extra_package:
+ **/
+static gboolean
+asb_task_explode_extra_package (AsbTask *task, const gchar *pkg_name)
+{
+ AsbTaskPrivate *priv = GET_PRIVATE (task);
+ AsbPackage *pkg_extra;
+ gboolean ret = TRUE;
+ _cleanup_error_free_ GError *error = NULL;
+
+ /* if not found, that's fine */
+ pkg_extra = asb_context_find_by_pkgname (priv->ctx, pkg_name);
+ if (pkg_extra == NULL)
+ return TRUE;
+ asb_package_log (priv->pkg,
+ ASB_PACKAGE_LOG_LEVEL_DEBUG,
+ "Adding extra package %s for %s",
+ asb_package_get_name (pkg_extra),
+ asb_package_get_name (priv->pkg));
+ ret = asb_package_explode (pkg_extra, priv->tmpdir,
+ asb_context_get_file_globs (priv->ctx),
+ &error);
+ if (!ret) {
+ asb_package_log (priv->pkg,
+ ASB_PACKAGE_LOG_LEVEL_WARNING,
+ "Failed to explode extra file: %s",
+ error->message);
+ }
+ return ret;
+}
+
+/**
+ * asb_task_explode_extra_packages:
+ **/
+static gboolean
+asb_task_explode_extra_packages (AsbTask *task)
+{
+ AsbTaskPrivate *priv = GET_PRIVATE (task);
+ const gchar *tmp;
+ guint i;
+ _cleanup_ptrarray_unref_ GPtrArray *array;
+
+ /* anything hardcoded */
+ array = g_ptr_array_new_with_free_func (g_free);
+ tmp = asb_context_get_extra_package (priv->ctx, asb_package_get_name (priv->pkg));
+ if (tmp != NULL)
+ g_ptr_array_add (array, g_strdup (tmp));
+
+ /* add all variants of %NAME-common, %NAME-data etc */
+ tmp = asb_package_get_name (priv->pkg);
+ g_ptr_array_add (array, g_strdup_printf ("%s-data", tmp));
+ g_ptr_array_add (array, g_strdup_printf ("%s-common", tmp));
+ for (i = 0; i < array->len; i++) {
+ tmp = g_ptr_array_index (array, i);
+ if (!asb_task_explode_extra_package (task, tmp))
+ return FALSE;
+ }
+ return TRUE;
+}
+
+/**
+ * asb_task_check_urls:
+ **/
+static void
+asb_task_check_urls (AsApp *app, AsbPackage *pkg)
+{
+ const gchar *url;
+ gboolean ret;
+ guint i;
+ _cleanup_error_free_ GError *error = NULL;
+
+ for (i = 0; i < AS_URL_KIND_LAST; i++) {
+ url = as_app_get_url_item (app, i);
+ if (url == NULL)
+ continue;
+ ret = as_utils_check_url_exists (url, 5, &error);
+ if (!ret) {
+ asb_package_log (pkg,
+ ASB_PACKAGE_LOG_LEVEL_WARNING,
+ "%s URL %s invalid: %s",
+ as_url_kind_to_string (i),
+ url,
+ error->message);
+ g_clear_error (&error);
+ }
+ }
+}
+
+/**
+ * asb_task_process:
+ * @task: A #AsbTask
+ * @error_not_used: A #GError or %NULL
+ *
+ * Processes the task.
+ *
+ * Returns: %TRUE for success, %FALSE otherwise
+ *
+ * Since: 0.1.0
+ **/
+gboolean
+asb_task_process (AsbTask *task, GError **error_not_used)
+{
+ AsRelease *release;
+ AsbApp *app;
+ AsbPlugin *plugin = NULL;
+ AsbTaskPrivate *priv = GET_PRIVATE (task);
+ GList *apps = NULL;
+ GList *l;
+ GPtrArray *array;
+ const gchar * const *kudos;
+ gboolean ret;
+ gboolean valid;
+ gchar *cache_id;
+ gchar *tmp;
+ guint i;
+ guint nr_added = 0;
+ _cleanup_error_free_ GError *error = NULL;
+ _cleanup_free_ gchar *basename = NULL;
+
+ /* reset the profile timer */
+ asb_package_log_start (priv->pkg);
+
+ /* did we get a file match on any plugin */
+ basename = g_path_get_basename (priv->filename);
+ asb_package_log (priv->pkg,
+ ASB_PACKAGE_LOG_LEVEL_DEBUG,
+ "Getting filename match for %s",
+ basename);
+ asb_task_add_suitable_plugins (task, asb_context_get_plugins (priv->ctx));
+ if (priv->plugins_to_run->len == 0)
+ goto out;
+
+ /* delete old tree if it exists */
+ if (!asb_context_get_use_package_cache (priv->ctx)) {
+ ret = asb_utils_ensure_exists_and_empty (priv->tmpdir, &error);
+ if (!ret) {
+ asb_package_log (priv->pkg,
+ ASB_PACKAGE_LOG_LEVEL_WARNING,
+ "Failed to clear: %s", error->message);
+ goto out;
+ }
+ }
+
+ /* explode tree */
+ asb_package_log (priv->pkg,
+ ASB_PACKAGE_LOG_LEVEL_DEBUG,
+ "Exploding tree for %s",
+ asb_package_get_name (priv->pkg));
+ if (!asb_context_get_use_package_cache (priv->ctx) ||
+ !g_file_test (priv->tmpdir, G_FILE_TEST_EXISTS)) {
+ ret = asb_package_explode (priv->pkg,
+ priv->tmpdir,
+ asb_context_get_file_globs (priv->ctx),
+ &error);
+ if (!ret) {
+ asb_package_log (priv->pkg,
+ ASB_PACKAGE_LOG_LEVEL_WARNING,
+ "Failed to explode: %s", error->message);
+ g_clear_error (&error);
+ goto skip;
+ }
+
+ /* add extra packages */
+ ret = asb_task_explode_extra_packages (task);
+ if (!ret)
+ goto skip;
+ }
+
+ /* run plugins */
+ 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,
+ ASB_PACKAGE_LOG_LEVEL_DEBUG,
+ "Processing %s with %s",
+ basename,
+ plugin->name);
+ apps = asb_plugin_process (plugin, priv->pkg, priv->tmpdir, &error);
+ if (apps == NULL) {
+ asb_package_log (priv->pkg,
+ ASB_PACKAGE_LOG_LEVEL_WARNING,
+ "Failed to run process: %s",
+ error->message);
+ g_clear_error (&error);
+ }
+ }
+ if (apps == NULL)
+ goto skip;
+
+ /* print */
+ for (l = apps; l != NULL; l = l->next) {
+ app = l->data;
+
+ /* all apps assumed to be okay */
+ valid = TRUE;
+
+ /* never set */
+ if (as_app_get_id_full (AS_APP (app)) == NULL) {
+ asb_package_log (priv->pkg,
+ ASB_PACKAGE_LOG_LEVEL_INFO,
+ "app id not set for %s",
+ asb_package_get_name (priv->pkg));
+ continue;
+ }
+
+ /* copy data from pkg into app */
+ if (asb_package_get_url (priv->pkg) != NULL) {
+ as_app_add_url (AS_APP (app),
+ AS_URL_KIND_HOMEPAGE,
+ asb_package_get_url (priv->pkg), -1);
+ }
+ if (asb_package_get_license (priv->pkg) != NULL)
+ as_app_set_project_license (AS_APP (app),
+ asb_package_get_license (priv->pkg),
+ -1);
+
+ /* set all the releases on the app */
+ array = asb_package_get_releases (priv->pkg);
+ for (i = 0; i < array->len; i++) {
+ release = g_ptr_array_index (array, i);
+ as_app_add_release (AS_APP (app), release);
+ }
+
+ /* run each refine plugin on each app */
+ ret = asb_plugin_loader_process_app (asb_context_get_plugins (priv->ctx),
+ priv->pkg,
+ app,
+ priv->tmpdir,
+ &error);
+ if (!ret) {
+ asb_package_log (priv->pkg,
+ ASB_PACKAGE_LOG_LEVEL_WARNING,
+ "Failed to run process on %s: %s",
+ as_app_get_id (AS_APP (app)),
+ error->message);
+ g_clear_error (&error);
+ goto skip;
+ }
+
+ /* don't include components that have no name or comment */
+ if (as_app_get_name (AS_APP (app), "C") == NULL)
+ asb_app_add_veto (app, "Has no Name");
+ if (as_app_get_comment (AS_APP (app), "C") == NULL)
+ asb_app_add_veto (app, "Has no Comment");
+
+ /* don't include apps that have no icon */
+ if (as_app_get_id_kind (AS_APP (app)) != AS_ID_KIND_ADDON) {
+ if (as_app_get_icon (AS_APP (app)) == NULL)
+ asb_app_add_veto (app, "Has no Icon");
+ }
+
+ /* list all the reasons we're ignoring the app */
+ array = asb_app_get_vetos (app);
+ if (array->len > 0) {
+ asb_package_log (priv->pkg,
+ ASB_PACKAGE_LOG_LEVEL_WARNING,
+ "%s not included in metadata:",
+ as_app_get_id_full (AS_APP (app)));
+ for (i = 0; i < array->len; i++) {
+ tmp = g_ptr_array_index (array, i);
+ asb_package_log (priv->pkg,
+ ASB_PACKAGE_LOG_LEVEL_WARNING,
+ " - %s", tmp);
+ }
+ valid = FALSE;
+ }
+
+ /* don't include apps that *still* require appdata */
+ array = asb_app_get_requires_appdata (app);
+ if (array->len > 0) {
+ asb_package_log (priv->pkg,
+ ASB_PACKAGE_LOG_LEVEL_WARNING,
+ "%s required appdata but none provided",
+ as_app_get_id_full (AS_APP (app)));
+ for (i = 0; i < array->len; i++) {
+ tmp = g_ptr_array_index (array, i);
+ if (tmp == NULL)
+ continue;
+ asb_package_log (priv->pkg,
+ ASB_PACKAGE_LOG_LEVEL_WARNING,
+ " - %s", tmp);
+ }
+ valid = FALSE;
+ }
+ if (!valid)
+ continue;
+
+ /* verify URLs still exist */
+ if (asb_context_get_extra_checks (priv->ctx))
+ asb_task_check_urls (AS_APP (app), priv->pkg);
+
+ /* save icon and screenshots */
+ ret = asb_app_save_resources (app, &error);
+ if (!ret) {
+ asb_package_log (priv->pkg,
+ ASB_PACKAGE_LOG_LEVEL_WARNING,
+ "Failed to save resources: %s",
+ error->message);
+ g_clear_error (&error);
+ goto skip;
+ }
+
+ /* print Kudos the might have */
+ kudos = as_util_get_possible_kudos ();
+ for (i = 0; kudos[i] != NULL; i++) {
+ if (as_app_get_metadata_item (AS_APP (app), kudos[i]) != NULL)
+ continue;
+ asb_package_log (priv->pkg,
+ ASB_PACKAGE_LOG_LEVEL_INFO,
+ "Application does not have %s",
+ kudos[i]);
+ }
+
+ /* set cache-id in case we want to use the metadata directly */
+ if (asb_context_get_add_cache_id (priv->ctx)) {
+ cache_id = asb_utils_get_cache_id_for_filename (priv->filename);
+ as_app_add_metadata (AS_APP (app),
+ "X-CreaterepoAsCacheID",
+ cache_id, -1);
+ g_free (cache_id);
+ }
+
+ /* all okay */
+ asb_context_add_app (priv->ctx, app);
+ nr_added++;
+
+ /* log the XML in the log file */
+ tmp = asb_app_to_xml (app);
+ asb_package_log (priv->pkg, ASB_PACKAGE_LOG_LEVEL_NONE, "%s", tmp);
+ g_free (tmp);
+ }
+skip:
+ /* add a dummy element to the AppStream metadata so that we don't keep
+ * parsing this every time */
+ if (asb_context_get_add_cache_id (priv->ctx) && nr_added == 0) {
+ _cleanup_object_unref_ AsApp *dummy;
+ dummy = as_app_new ();
+ as_app_set_id_full (dummy, asb_package_get_name (priv->pkg), -1);
+ cache_id = asb_utils_get_cache_id_for_filename (priv->filename);
+ as_app_add_metadata (dummy,
+ "X-CreaterepoAsCacheID",
+ cache_id, -1);
+ asb_context_add_app (priv->ctx, (AsbApp *) dummy);
+ g_free (cache_id);
+ }
+
+ /* delete tree */
+ if (!asb_context_get_use_package_cache (priv->ctx)) {
+ if (!asb_utils_rmtree (priv->tmpdir, &error)) {
+ asb_package_log (priv->pkg,
+ ASB_PACKAGE_LOG_LEVEL_WARNING,
+ "Failed to delete tree: %s",
+ error->message);
+ goto out;
+ }
+ }
+
+ /* write log */
+ if (!asb_package_log_flush (priv->pkg, &error)) {
+ asb_package_log (priv->pkg,
+ ASB_PACKAGE_LOG_LEVEL_WARNING,
+ "Failed to write package log: %s",
+ error->message);
+ goto out;
+ }
+
+ /* 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));
+out:
+ g_list_free_full (apps, (GDestroyNotify) g_object_unref);
+ return TRUE;
+}
+
+/**
+ * asb_task_finalize:
+ **/
+static void
+asb_task_finalize (GObject *object)
+{
+ AsbTask *task = ASB_TASK (object);
+ AsbTaskPrivate *priv = GET_PRIVATE (task);
+
+ g_object_unref (priv->ctx);
+ g_ptr_array_unref (priv->plugins_to_run);
+ if (priv->pkg != NULL)
+ g_object_unref (priv->pkg);
+ g_free (priv->filename);
+ g_free (priv->tmpdir);
+
+ G_OBJECT_CLASS (asb_task_parent_class)->finalize (object);
+}
+
+/**
+ * asb_task_set_package:
+ * @task: A #AsbTask
+ * @pkg: A #AsbPackage
+ *
+ * Sets the package used for the task.
+ *
+ * Since: 0.1.0
+ **/
+void
+asb_task_set_package (AsbTask *task, AsbPackage *pkg)
+{
+ AsbTaskPrivate *priv = GET_PRIVATE (task);
+ priv->tmpdir = g_build_filename (asb_context_get_temp_dir (priv->ctx),
+ asb_package_get_nevr (pkg), NULL);
+ priv->filename = g_strdup (asb_package_get_filename (pkg));
+ priv->pkg = g_object_ref (pkg);
+}
+
+/**
+ * asb_task_set_id:
+ * @task: A #AsbTask
+ * @id: numeric identifier
+ *
+ * Sets the ID to use for the task.
+ *
+ * Since: 0.1.0
+ **/
+void
+asb_task_set_id (AsbTask *task, guint id)
+{
+ AsbTaskPrivate *priv = GET_PRIVATE (task);
+ priv->id = id;
+}
+
+/**
+ * asb_task_init:
+ **/
+static void
+asb_task_init (AsbTask *task)
+{
+ AsbTaskPrivate *priv = GET_PRIVATE (task);
+ priv->plugins_to_run = g_ptr_array_new ();
+}
+
+/**
+ * asb_task_class_init:
+ **/
+static void
+asb_task_class_init (AsbTaskClass *klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+ object_class->finalize = asb_task_finalize;
+}
+
+/**
+ * asb_task_new:
+ * @ctx: A #AsbContext
+ *
+ * Creates a new task.
+ *
+ * Returns: A #AsbTask
+ *
+ * Since: 0.1.0
+ **/
+AsbTask *
+asb_task_new (AsbContext *ctx)
+{
+ AsbTask *task;
+ AsbTaskPrivate *priv;
+ task = g_object_new (ASB_TYPE_TASK, NULL);
+ priv = GET_PRIVATE (task);
+ priv->ctx = g_object_ref (ctx);
+ return ASB_TASK (task);
+}
diff --git a/libappstream-builder/asb-task.h b/libappstream-builder/asb-task.h
new file mode 100644
index 0000000..b813b28
--- /dev/null
+++ b/libappstream-builder/asb-task.h
@@ -0,0 +1,65 @@
+/* -*- 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_TASK_H
+#define ASB_TASK_H
+
+#include <glib-object.h>
+
+#include "asb-package.h"
+#include "asb-context.h"
+
+#define ASB_TYPE_TASK (asb_task_get_type())
+#define ASB_TASK(obj) (G_TYPE_CHECK_INSTANCE_CAST((obj), ASB_TYPE_TASK, AsbTask))
+#define ASB_TASK_CLASS(cls) (G_TYPE_CHECK_CLASS_CAST((cls), ASB_TYPE_TASK, AsbTaskClass))
+#define ASB_IS_TASK(obj) (G_TYPE_CHECK_INSTANCE_TYPE((obj), ASB_TYPE_TASK))
+#define ASB_IS_TASK_CLASS(cls) (G_TYPE_CHECK_CLASS_TYPE((cls), ASB_TYPE_TASK))
+#define ASB_TASK_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS((obj), ASB_TYPE_TASK, AsbTaskClass))
+
+G_BEGIN_DECLS
+
+typedef struct _AsbTask AsbTask;
+typedef struct _AsbTaskClass AsbTaskClass;
+
+struct _AsbTask
+{
+ GObject parent;
+};
+
+struct _AsbTaskClass
+{
+ GObjectClass parent_class;
+};
+
+GType asb_task_get_type (void);
+
+
+AsbTask *asb_task_new (AsbContext *ctx);
+gboolean asb_task_process (AsbTask *task,
+ GError **error_not_used);
+void asb_task_set_package (AsbTask *task,
+ AsbPackage *pkg);
+void asb_task_set_id (AsbTask *task,
+ guint id);
+
+G_END_DECLS
+
+#endif /* ASB_TASK_H */
diff --git a/libappstream-builder/asb-utils.c b/libappstream-builder/asb-utils.c
new file mode 100644
index 0000000..a74d86e
--- /dev/null
+++ b/libappstream-builder/asb-utils.c
@@ -0,0 +1,571 @@
+/* -*- 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-utils
+ * @short_description: Helper functionality.
+ * @stability: Unstable
+ */
+
+#include "config.h"
+
+#include <glib/gstdio.h>
+#include <fnmatch.h>
+#include <archive.h>
+#include <archive_entry.h>
+#include <string.h>
+
+#include "as-cleanup.h"
+#include "asb-utils.h"
+#include "asb-plugin.h"
+
+#define ASB_METADATA_CACHE_VERSION 1
+
+/**
+ * asb_utils_get_cache_id_for_filename:
+ * @filename: utf8 filename
+ *
+ * Gets the cache-id for a given filename.
+ *
+ * Returns: utf8 string
+ *
+ * Since: 0.1.0
+ **/
+gchar *
+asb_utils_get_cache_id_for_filename (const gchar *filename)
+{
+ _cleanup_free_ gchar *basename;
+
+ /* only use the basename and the cache version */
+ basename = g_path_get_basename (filename);
+ return g_strdup_printf ("%s:%i",
+ basename,
+ ASB_METADATA_CACHE_VERSION);
+}
+
+/**
+ * asb_utils_rmtree:
+ * @directory: utf8 directory name
+ * @error: A #GError or %NULL
+ *
+ * Removes a directory tree.
+ *
+ * Returns: %TRUE for success, %FALSE otherwise
+ *
+ * Since: 0.1.0
+ **/
+gboolean
+asb_utils_rmtree (const gchar *directory, GError **error)
+{
+ gint rc;
+ gboolean ret;
+
+ ret = asb_utils_ensure_exists_and_empty (directory, error);
+ if (!ret)
+ return FALSE;
+ rc = g_remove (directory);
+ if (rc != 0) {
+ g_set_error (error,
+ ASB_PLUGIN_ERROR,
+ ASB_PLUGIN_ERROR_FAILED,
+ "Failed to delete: %s", directory);
+ return FALSE;
+ }
+ return TRUE;
+}
+
+/**
+ * asb_utils_ensure_exists_and_empty:
+ * @directory: utf8 directory name
+ * @error: A #GError or %NULL
+ *
+ * Ensures a directory exists and empty.
+ *
+ * Returns: %TRUE for success, %FALSE otherwise
+ *
+ * Since: 0.1.0
+ **/
+gboolean
+asb_utils_ensure_exists_and_empty (const gchar *directory, GError **error)
+{
+ const gchar *filename;
+ _cleanup_dir_close_ GDir *dir = NULL;
+
+ /* does directory exist */
+ if (!g_file_test (directory, G_FILE_TEST_EXISTS)) {
+ if (g_mkdir_with_parents (directory, 0700) != 0) {
+ g_set_error (error,
+ ASB_PLUGIN_ERROR,
+ ASB_PLUGIN_ERROR_FAILED,
+ "Failed to create: %s", directory);
+ return FALSE;
+ }
+ return TRUE;
+ }
+
+ /* try to open */
+ dir = g_dir_open (directory, 0, error);
+ if (dir == NULL)
+ return FALSE;
+
+ /* find each */
+ while ((filename = g_dir_read_name (dir))) {
+ _cleanup_free_ gchar *src;
+ src = g_build_filename (directory, filename, NULL);
+ if (g_file_test (src, G_FILE_TEST_IS_DIR)) {
+ if (!asb_utils_rmtree (src, error))
+ return FALSE;
+ } else {
+ if (g_unlink (src) != 0) {
+ g_set_error (error,
+ ASB_PLUGIN_ERROR,
+ ASB_PLUGIN_ERROR_FAILED,
+ "Failed to delete: %s", src);
+ return FALSE;
+ }
+ }
+ }
+ return TRUE;
+}
+
+/**
+ * asb_utils_explode_file:
+ **/
+static gboolean
+asb_utils_explode_file (struct archive_entry *entry,
+ const gchar *dir,
+ GPtrArray *glob)
+{
+ const gchar *tmp;
+ gchar buf[PATH_MAX];
+ _cleanup_free_ gchar *path = NULL;
+
+ /* no output file */
+ if (archive_entry_pathname (entry) == NULL)
+ return FALSE;
+
+ /* do we have to decompress this file */
+ tmp = archive_entry_pathname (entry);
+ if (glob != NULL) {
+ if (tmp[0] == '/') {
+ path = g_strdup (tmp);
+ } else if (tmp[0] == '.') {
+ path = g_strdup (tmp + 1);
+ } else {
+ path = g_strconcat ("/", tmp, NULL);
+ }
+ if (asb_glob_value_search (glob, path) == NULL)
+ return FALSE;
+ }
+
+ /* update output path */
+ g_snprintf (buf, PATH_MAX, "%s/%s", dir, tmp);
+ archive_entry_update_pathname_utf8 (entry, buf);
+
+ /* update hardlinks */
+ tmp = archive_entry_hardlink (entry);
+ if (tmp != NULL) {
+ g_snprintf (buf, PATH_MAX, "%s/%s", dir, tmp);
+ archive_entry_update_hardlink_utf8 (entry, buf);
+ }
+
+ /* update symlinks */
+ tmp = archive_entry_symlink (entry);
+ if (tmp != NULL) {
+ g_snprintf (buf, PATH_MAX, "%s/%s", dir, tmp);
+ archive_entry_update_symlink_utf8 (entry, buf);
+ }
+ return TRUE;
+}
+
+/**
+ * asb_utils_explode:
+ * @filename: package filename
+ * @dir: directory to decompress into
+ * @glob: (element-type utf8): filename globs, or %NULL
+ * @error: A #GError or %NULL
+ *
+ * Decompresses the package into a given directory.
+ *
+ * Returns: %TRUE for success, %FALSE otherwise
+ *
+ * Since: 0.1.0
+ **/
+gboolean
+asb_utils_explode (const gchar *filename,
+ const gchar *dir,
+ GPtrArray *glob,
+ GError **error)
+{
+ gboolean ret = TRUE;
+ gboolean valid;
+ gsize len;
+ int r;
+ struct archive *arch = NULL;
+ struct archive_entry *entry;
+ _cleanup_free_ gchar *data = NULL;
+
+ /* load file at once to avoid seeking */
+ ret = g_file_get_contents (filename, &data, &len, error);
+ if (!ret)
+ goto out;
+
+ /* read anything */
+ arch = archive_read_new ();
+ archive_read_support_format_all (arch);
+ archive_read_support_filter_all (arch);
+ r = archive_read_open_memory (arch, data, len);
+ if (r) {
+ ret = FALSE;
+ g_set_error (error,
+ ASB_PLUGIN_ERROR,
+ ASB_PLUGIN_ERROR_FAILED,
+ "Cannot open: %s",
+ archive_error_string (arch));
+ goto out;
+ }
+
+ /* decompress each file */
+ for (;;) {
+ r = archive_read_next_header (arch, &entry);
+ if (r == ARCHIVE_EOF)
+ break;
+ if (r != ARCHIVE_OK) {
+ ret = FALSE;
+ g_set_error (error,
+ ASB_PLUGIN_ERROR,
+ ASB_PLUGIN_ERROR_FAILED,
+ "Cannot read header: %s",
+ archive_error_string (arch));
+ goto out;
+ }
+
+ /* only extract if valid */
+ valid = asb_utils_explode_file (entry, dir, glob);
+ if (!valid)
+ continue;
+ r = archive_read_extract (arch, entry, 0);
+ if (r != ARCHIVE_OK) {
+ ret = FALSE;
+ g_set_error (error,
+ ASB_PLUGIN_ERROR,
+ ASB_PLUGIN_ERROR_FAILED,
+ "Cannot extract: %s",
+ archive_error_string (arch));
+ goto out;
+ }
+ }
+out:
+ if (arch != NULL) {
+ archive_read_close (arch);
+ archive_read_free (arch);
+ }
+ return ret;
+}
+
+/**
+ * asb_utils_write_archive:
+ **/
+static gboolean
+asb_utils_write_archive (const gchar *filename,
+ GPtrArray *files,
+ GError **error)
+{
+ const gchar *tmp;
+ gboolean ret = TRUE;
+ gsize len;
+ guint i;
+ struct archive *a;
+ struct archive_entry *entry;
+ struct stat st;
+
+ a = archive_write_new ();
+ archive_write_add_filter_gzip (a);
+ archive_write_set_format_pax_restricted (a);
+ archive_write_open_filename (a, filename);
+ for (i = 0; i < files->len; i++) {
+ _cleanup_free_ gchar *basename;
+ _cleanup_free_ gchar *data = NULL;
+ tmp = g_ptr_array_index (files, i);
+ stat (tmp, &st);
+ entry = archive_entry_new ();
+ basename = g_path_get_basename (tmp);
+ archive_entry_set_pathname (entry, basename);
+ archive_entry_set_size (entry, st.st_size);
+ archive_entry_set_filetype (entry, AE_IFREG);
+ archive_entry_set_perm (entry, 0644);
+ archive_write_header (a, entry);
+ ret = g_file_get_contents (tmp, &data, &len, error);
+ if (!ret)
+ goto out;
+ archive_write_data (a, data, len);
+ archive_entry_free (entry);
+ }
+out:
+ archive_write_close (a);
+ archive_write_free (a);
+ return ret;
+}
+
+/**
+ * asb_utils_write_archive_dir:
+ * @filename: archive filename
+ * @directory: source directory
+ * @error: A #GError or %NULL
+ *
+ * Writes an archive from a directory.
+ *
+ * Returns: %TRUE for success, %FALSE otherwise
+ *
+ * Since: 0.1.0
+ **/
+gboolean
+asb_utils_write_archive_dir (const gchar *filename,
+ const gchar *directory,
+ GError **error)
+{
+ GPtrArray *files = NULL;
+ GDir *dir;
+ const gchar *tmp;
+ gboolean ret = TRUE;
+
+ /* add all files in the directory to the archive */
+ dir = g_dir_open (directory, 0, error);
+ if (dir == NULL) {
+ ret = FALSE;
+ goto out;
+ }
+ files = g_ptr_array_new_with_free_func (g_free);
+ while ((tmp = g_dir_read_name (dir)) != NULL)
+ g_ptr_array_add (files, g_build_filename (directory, tmp, NULL));
+
+ /* write tar file */
+ ret = asb_utils_write_archive (filename, files, error);
+ if (!ret)
+ goto out;
+out:
+ if (dir != NULL)
+ g_dir_close (dir);
+ if (files != NULL)
+ g_ptr_array_unref (files);
+ return ret;
+}
+
+/**
+ * asb_utils_add_apps_from_file:
+ * @apps: (element-type AsbApp): applications
+ * @filename: XML file to load
+ * @error: A #GError or %NULL
+ *
+ * Add applications from a file.
+ *
+ * Returns: %TRUE for success, %FALSE otherwise
+ *
+ * Since: 0.1.0
+ **/
+gboolean
+asb_utils_add_apps_from_file (GList **apps, const gchar *filename, GError **error)
+{
+ AsApp *app;
+ AsStore *store;
+ GFile *file;
+ GPtrArray *array;
+ gboolean ret;
+ guint i;
+
+ /* parse file */
+ store = as_store_new ();
+ file = g_file_new_for_path (filename);
+ ret = as_store_from_file (store, file, NULL, NULL, error);
+ if (!ret)
+ goto out;
+
+ /* copy Asapp's into AsbApp's */
+ array = as_store_get_apps (store);
+ for (i = 0; i < array->len; i++) {
+ app = g_ptr_array_index (array, i);
+ asb_plugin_add_app (apps, (AsbApp *) app);
+ }
+out:
+ g_object_unref (file);
+ g_object_unref (store);
+ return ret;
+}
+
+/**
+ * asb_utils_add_apps_from_dir:
+ * @apps: (element-type AsbApp): applications
+ * @path: path to read
+ * @error: A #GError or %NULL
+ *
+ * Add applications from a directory.
+ *
+ * Returns: %TRUE for success, %FALSE otherwise
+ *
+ * Since: 0.1.0
+ **/
+gboolean
+asb_utils_add_apps_from_dir (GList **apps, const gchar *path, GError **error)
+{
+ const gchar *tmp;
+ gboolean ret = TRUE;
+ gchar *filename;
+ GDir *dir;
+
+ dir = g_dir_open (path, 0, error);
+ if (dir == NULL) {
+ ret = FALSE;
+ goto out;
+ }
+ while ((tmp = g_dir_read_name (dir)) != NULL) {
+ filename = g_build_filename (path, tmp, NULL);
+ ret = asb_utils_add_apps_from_file (apps, filename, error);
+ g_free (filename);
+ if (!ret)
+ goto out;
+ }
+out:
+ if (dir != NULL)
+ g_dir_close (dir);
+ return ret;
+}
+
+/**
+ * asb_string_replace:
+ * @string: Source string
+ * @search: utf8 string to search for
+ * @replace: utf8 string to replace with
+ *
+ * Does search/replace on a given string.
+ *
+ * Returns: the number of times the string was replaced
+ *
+ * Since: 0.1.0
+ **/
+guint
+asb_string_replace (GString *string, const gchar *search, const gchar *replace)
+{
+ gchar **split = NULL;
+ gchar *tmp = NULL;
+ guint count = 0;
+
+ /* quick search */
+ if (g_strstr_len (string->str, -1, search) == NULL)
+ goto out;
+
+ /* replace */
+ split = g_strsplit (string->str, search, -1);
+ tmp = g_strjoinv (replace, split);
+ g_string_assign (string, tmp);
+ count = g_strv_length (split);
+out:
+ g_strfreev (split);
+ g_free (tmp);
+ return count;
+}
+
+/******************************************************************************/
+
+struct AsbGlobValue {
+ gchar *glob;
+ gchar *value;
+};
+
+/**
+ * asb_glob_value_free: (skip)
+ * @kv: key-value
+ *
+ * Frees a #AsbGlobValue.
+ *
+ * Since: 0.1.0
+ **/
+void
+asb_glob_value_free (AsbGlobValue *kv)
+{
+ g_free (kv->glob);
+ g_free (kv->value);
+ g_slice_free (AsbGlobValue, kv);
+}
+
+/**
+ * asb_glob_value_array_new: (skip)
+ *
+ * Creates a new value array.
+ *
+ * Returns: (element-type AsbGlobValue): A new array
+ *
+ * Since: 0.1.0
+ **/
+GPtrArray *
+asb_glob_value_array_new (void)
+{
+ return g_ptr_array_new_with_free_func ((GDestroyNotify) asb_glob_value_free);
+}
+
+/**
+ * asb_glob_value_new: (skip)
+ * @glob: utf8 string
+ * @value: utf8 string
+ *
+ * Creates a new value.
+ *
+ * Returns: a #AsbGlobValue
+ *
+ * Since: 0.1.0
+ **/
+AsbGlobValue *
+asb_glob_value_new (const gchar *glob, const gchar *value)
+{
+ AsbGlobValue *kv;
+ kv = g_slice_new0 (AsbGlobValue);
+ kv->glob = g_strdup (glob);
+ kv->value = g_strdup (value);
+ return kv;
+}
+
+/**
+ * asb_glob_value_search: (skip)
+ * @array: of AsbGlobValue, keys may contain globs
+ * @search: may not be a glob
+ *
+ * Searches for a glob value.
+ *
+ * Returns: value, or %NULL
+ *
+ * Since: 0.1.0
+ */
+const gchar *
+asb_glob_value_search (GPtrArray *array, const gchar *search)
+{
+ const AsbGlobValue *tmp;
+ guint i;
+
+ /* invalid */
+ if (search == NULL)
+ return NULL;
+
+ for (i = 0; i < array->len; i++) {
+ tmp = g_ptr_array_index (array, i);
+ if (fnmatch (tmp->glob, search, 0) == 0)
+ return tmp->value;
+ }
+ return NULL;
+}
diff --git a/libappstream-builder/asb-utils.h b/libappstream-builder/asb-utils.h
new file mode 100644
index 0000000..3498c30
--- /dev/null
+++ b/libappstream-builder/asb-utils.h
@@ -0,0 +1,64 @@
+/* -*- 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_UTILS_H
+#define __ASB_UTILS_H
+
+#include <glib.h>
+#include <appstream-glib.h>
+
+G_BEGIN_DECLS
+
+typedef struct AsbGlobValue AsbGlobValue;
+
+gboolean asb_utils_rmtree (const gchar *directory,
+ GError **error);
+gboolean asb_utils_ensure_exists_and_empty (const gchar *directory,
+ GError **error);
+gboolean asb_utils_write_archive_dir (const gchar *filename,
+ const gchar *directory,
+ GError **error);
+gboolean asb_utils_explode (const gchar *filename,
+ const gchar *dir,
+ GPtrArray *glob,
+ GError **error);
+gchar *asb_utils_get_cache_id_for_filename (const gchar *filename);
+
+AsbGlobValue *asb_glob_value_new (const gchar *glob,
+ const gchar *value);
+void asb_glob_value_free (AsbGlobValue *kv);
+const gchar *asb_glob_value_search (GPtrArray *array,
+ const gchar *search);
+GPtrArray *asb_glob_value_array_new (void);
+guint asb_string_replace (GString *string,
+ const gchar *search,
+ const gchar *replace);
+
+gboolean asb_utils_add_apps_from_file (GList **apps,
+ const gchar *filename,
+ GError **error);
+gboolean asb_utils_add_apps_from_dir (GList **apps,
+ const gchar *path,
+ GError **error);
+
+G_END_DECLS
+
+#endif /* __ASB_UTILS_H */