diff options
25 files changed, 5739 insertions, 6 deletions
diff --git a/Makefile.am b/Makefile.am index d09015a..314f001 100644 --- a/Makefile.am +++ b/Makefile.am @@ -2,6 +2,7 @@ ACLOCAL_AMFLAGS = -I m4 SUBDIRS = \ libappstream-glib \ + libappstream-builder \ client \ data \ docs @@ -24,8 +24,9 @@ To install the libappstream-glib library you either need to install the `libappstream-glib` package from your distributor, or you can build a local copy. To do the latter just do: - dnf install automake autoconf libtool glib-devel \ - docbook-utils gtk-doc gobject-introspection-devel + dnf install automake autoconf libtool glib-devel docbook-utils \ + gtk-doc gobject-introspection-devel rpm-devel \ + gtk3-devel sqlite-devel libsoup-devel ./autogen.sh make make install diff --git a/configure.ac b/configure.ac index d0a0dc9..593d9e6 100644 --- a/configure.ac +++ b/configure.ac @@ -1,8 +1,8 @@ AC_PREREQ(2.63) m4_define([as_major_version], [0]) -m4_define([as_minor_version], [1]) -m4_define([as_micro_version], [8]) +m4_define([as_minor_version], [2]) +m4_define([as_micro_version], [0]) m4_define([as_version], [as_major_version.as_minor_version.as_micro_version]) @@ -114,14 +114,34 @@ if test x$GPERF != xno ; then fi AM_CONDITIONAL(HAVE_GPERF, [test x$GPERF != xno]) -PKG_CHECK_MODULES(GLIB, glib-2.0 >= 2.16.1 gio-2.0 gobject-2.0 gthread-2.0) +PKG_CHECK_MODULES(GLIB, glib-2.0 >= 2.16.1 gio-2.0 gobject-2.0 gthread-2.0 gio-unix-2.0 gmodule-2.0) PKG_CHECK_MODULES(LIBARCHIVE, libarchive) PKG_CHECK_MODULES(SOUP, libsoup-2.4 >= 2.24) -PKG_CHECK_MODULES(GDKPIXBUF, gdk-pixbuf-2.0 >= 2.14) +PKG_CHECK_MODULES(GDKPIXBUF, gdk-pixbuf-2.0 >= 2.14 gtk+-3.0) +PKG_CHECK_MODULES(SQLITE, sqlite3) +PKG_CHECK_MODULES(FREETYPE, pango fontconfig freetype2 >= 9.10.0) + +# rpm (default enabled) +AC_ARG_ENABLE(rpm, AS_HELP_STRING([--disable-rpm],[Disable rpm support]), enable_rpm=$enableval) +if test x$enable_rpm != xno; then + PKG_CHECK_MODULES(RPM, rpm, HAVE_RPM="yes", HAVE_RPM="no") + if test "x$HAVE_RPM" = "xyes"; then + AC_DEFINE(HAVE_RPM, 1, [define if RPM is installed]) + else + if test x$enable_rpm = xyes; then + AC_MSG_ERROR([rpm enabled but not found]) + fi + fi +else + HAVE_RPM=no +fi +AM_CONDITIONAL(HAVE_RPM, test x$HAVE_RPM = xyes) AC_CONFIG_FILES([ Makefile client/Makefile +libappstream-builder/Makefile +libappstream-builder/appstream-builder.pc libappstream-glib/Makefile libappstream-glib/appstream-glib.pc libappstream-glib/as-version.h 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 */ |