[gnome-software/wip/ubuntu-3-24: 16/21] Add an APT plugin
- From: Robert Ancell <rancell src gnome org>
- To: commits-list gnome org
- Cc:
- Subject: [gnome-software/wip/ubuntu-3-24: 16/21] Add an APT plugin
- Date: Thu, 15 Jun 2017 04:52:28 +0000 (UTC)
commit 295d66cbf80595ff246decf5771fc48eca7780d8
Author: Robert Ancell <robert ancell canonical com>
Date: Mon Nov 7 16:12:59 2016 +1300
Add an APT plugin
configure.ac | 28 +
meson.build | 54 +-
meson_options.txt | 1 +
plugins/Makefile.am | 4 +
plugins/apt/Makefile.am | 28 +
plugins/apt/gs-plugin-apt.cc | 1430 ++++++++++++++++++++
.../org.gnome.Software.Plugin.Apt.metainfo.xml.in | 12 +
plugins/meson.build | 12 +
po/POTFILES.in | 1 +
9 files changed, 1548 insertions(+), 22 deletions(-)
---
diff --git a/configure.ac b/configure.ac
index 377b0e0..dc09764 100644
--- a/configure.ac
+++ b/configure.ac
@@ -11,6 +11,7 @@ AC_CONFIG_MACRO_DIR([m4])
m4_ifdef([AM_SILENT_RULES],[AM_SILENT_RULES([yes])])
AC_PROG_CC_C99
+AC_PROG_CXX
AC_PROG_INSTALL
LT_INIT
AM_PROG_CC_C_O
@@ -92,6 +93,7 @@ PKG_CHECK_MODULES(SOUP, libsoup-2.4 >= 2.51.92)
PKG_CHECK_MODULES(GSETTINGS_DESKTOP_SCHEMAS, gsettings-desktop-schemas >= 3.11.5)
PKG_CHECK_MODULES(LIBSECRET, libsecret-1)
PKG_CHECK_MODULES(VALGRIND, valgrind)
+PKG_CHECK_MODULES(GIO_UNIX, gio-unix-2.0)
AC_PATH_PROG(APPSTREAM_UTIL, [appstream-util], [unfound])
# Default, but it's ≥ 0.9.1 if we're building tests because there we need --timestamp
@@ -211,6 +213,30 @@ AS_IF([test "x$have_packagekit" = "xyes"], [
])
AM_CONDITIONAL(HAVE_PACKAGEKIT, test "$have_packagekit" != no)
+# libapt
+AC_ARG_ENABLE(apt,
+ [AS_HELP_STRING([--enable-apt],
+ [enable apt support [default=auto]])],,
+ enable_apt=maybe)
+AS_IF([test "x$enable_apt" != "xno"], [
+ AC_LANG_PUSH([C++])
+ AC_CHECK_HEADERS([apt-pkg/init.h],
+ [have_apt=yes],
+ [have_apt=no])
+ AC_LANG_POP
+], [
+ have_apt=no
+])
+
+AS_IF([test "x$have_apt" = "xyes"], [
+ AC_DEFINE(HAVE_APT,1,[Build apt support])
+], [
+ AS_IF([test "x$enable_apt" = "xyes"], [
+ AC_MSG_ERROR([apt support requested but 'libapt-pkg' was not found])
+ ])
+])
+AM_CONDITIONAL(HAVE_APT, test "$have_apt" != no)
+
# PolicyKit
AC_ARG_ENABLE(polkit,
[AS_HELP_STRING([--enable-polkit],
@@ -494,6 +520,7 @@ doc/api/Makefile
lib/Makefile
lib/gnome-software.pc
plugins/Makefile
+plugins/apt/Makefile
plugins/core/Makefile
plugins/dpkg/Makefile
plugins/dummy/Makefile
@@ -533,6 +560,7 @@ echo "
Dogtail: ${enable_dogtail}
Self tests: ${enable_tests}
PackageKit support: ${have_packagekit}
+ APT support: ${have_apt}
PolicyKit support: ${have_polkit}
Firmware support: ${have_firmware}
Limba support: ${have_limba}
diff --git a/meson.build b/meson.build
index da0871f..0f06e4e 100644
--- a/meson.build
+++ b/meson.build
@@ -1,4 +1,4 @@
-project('gnome-software', 'c',
+project('gnome-software', ['c', 'cpp'],
version : '3.24.2',
default_options : ['warning_level=1'],
meson_version : '>=0.37.0'
@@ -63,29 +63,33 @@ test_args = [
'-Wunused-but-set-variable',
'-Wwrite-strings'
]
-cc = meson.get_compiler('c')
-foreach arg: test_args
- if cc.has_argument(arg)
- add_project_arguments(arg, language : 'c')
- endif
+foreach lang : ['c', 'cpp']
+ cc = meson.get_compiler(lang)
+ foreach arg: test_args
+ if cc.has_argument(arg)
+ add_project_arguments(arg, language : lang)
+ endif
+ endforeach
+
+ # enable full RELRO where possible
+ # FIXME: until https://github.com/mesonbuild/meson/issues/1140 is fixed
+ global_link_args = []
+ test_link_args = [
+ '-Wl,-z,relro',
+ '-Wl,-z,now',
+ ]
+ foreach arg: test_link_args
+ if cc.has_argument(arg)
+ global_link_args += arg
+ endif
+ endforeach
+ add_global_link_arguments(
+ global_link_args,
+ language: lang
+ )
endforeach
-# enable full RELRO where possible
-# FIXME: until https://github.com/mesonbuild/meson/issues/1140 is fixed
-global_link_args = []
-test_link_args = [
- '-Wl,-z,relro',
- '-Wl,-z,now',
-]
-foreach arg: test_link_args
- if cc.has_argument(arg)
- global_link_args += arg
- endif
-endforeach
-add_global_link_arguments(
- global_link_args,
- language: 'c'
-)
+add_project_arguments('-Wno-aggregate-return', language: 'cpp')
appstream_glib = dependency('appstream-glib', version : '>= 0.6.5')
gdk_pixbuf = dependency('gdk-pixbuf-2.0', version : '>= 2.31.5')
@@ -121,6 +125,12 @@ if get_option('enable-packagekit')
language : 'c')
endif
+if get_option('enable-apt')
+ add_project_arguments('-DI_KNOW_THE_GNOME_SOFTWARE_API_IS_SUBJECT_TO_CHANGE',
+ language : 'cpp')
+ cpp = meson.get_compiler('cpp')
+ libapt = cpp.find_library('apt-pkg', required : true)
+endif
if get_option('enable-firmware')
fwupd = dependency('fwupd', version : '>= 0.7.0')
endif
diff --git a/meson_options.txt b/meson_options.txt
index 9fb3cc9..53dbee7 100644
--- a/meson_options.txt
+++ b/meson_options.txt
@@ -17,3 +17,4 @@ option('enable-webapps', type : 'boolean', value : true, description : 'enable w
option('enable-gudev', type : 'boolean', value : true, description : 'enable GUdev support')
option('enable-snap', type : 'boolean', value : false, description : 'enable Snap support')
option('enable-external-appstream', type : 'boolean', value : false, description : 'enable external
AppStream support')
+option('enable-apt', type : 'boolean', value : false, description : 'enable APT support')
diff --git a/plugins/Makefile.am b/plugins/Makefile.am
index eca45bd..736082c 100644
--- a/plugins/Makefile.am
+++ b/plugins/Makefile.am
@@ -36,6 +36,10 @@ if HAVE_EXTERNAL_APPSTREAM
SUBDIRS += external-appstream
endif
+if HAVE_APT
+SUBDIRS += apt
+endif
+
if HAVE_SNAP
SUBDIRS += snap
endif
diff --git a/plugins/apt/Makefile.am b/plugins/apt/Makefile.am
new file mode 100644
index 0000000..4abd66c
--- /dev/null
+++ b/plugins/apt/Makefile.am
@@ -0,0 +1,28 @@
+AM_CPPFLAGS = \
+ -DG_LOG_DOMAIN=\"GsPluginApt\"
+
+plugindir = $(GS_PLUGIN_DIR)
+plugin_LTLIBRARIES = libgs_plugin_apt.la
+
+libgs_plugin_apt_la_SOURCES = \
+ gs-plugin-apt.cc
+libgs_plugin_apt_la_LIBADD = \
+ $(GS_PLUGIN_LIBS) \
+ $(GIO_UNIX_LIBS) \
+ -lapt-pkg
+libgs_plugin_apt_la_LDFLAGS = -module -avoid-version
+libgs_plugin_apt_la_CPPFLAGS = \
+ $(GS_PLUGIN_CFLAGS) \
+ $(GIO_UNIX_CFLAGS) \
+ $(WARN_CFLAGS)
+
+%.metainfo.xml: %.metainfo.xml.in
+ $(AM_V_GEN) msgfmt --xml -d $(top_srcdir)/po --template $< -o $@
+
+appdata_in_files = org.gnome.Software.Plugin.Apt.metainfo.xml.in
+appdatadir = $(datadir)/appdata
+appdata_DATA = $(appdata_in_files:.xml.in=.xml)
+EXTRA_DIST = $(appdata_in_files)
+CLEANFILES = $(appdata_DATA)
+
+-include $(top_srcdir)/git.mk
diff --git a/plugins/apt/gs-plugin-apt.cc b/plugins/apt/gs-plugin-apt.cc
new file mode 100644
index 0000000..71c75eb
--- /dev/null
+++ b/plugins/apt/gs-plugin-apt.cc
@@ -0,0 +1,1430 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*-
+ *
+ * Copyright (C) 2016 Canonical Ltd
+ *
+ * Licensed under the GNU General Public License Version 2
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#include <config.h>
+
+#include <stdlib.h>
+#include <string.h>
+#include <ctype.h>
+#include <gio/gunixsocketaddress.h>
+#include <glib-unix.h>
+
+#include <apt-pkg/init.h>
+#include <apt-pkg/cachefile.h>
+#include <apt-pkg/cmndline.h>
+#include <apt-pkg/version.h>
+
+#include <assert.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string>
+#include <set>
+#include <vector>
+#include <fstream>
+#include <iomanip>
+#include <algorithm>
+#include <stdexcept>
+
+#include <gnome-software.h>
+
+#define LICENSE_URL "http://www.ubuntu.com/about/about-ubuntu/licensing"
+
+#define INFO_DIR "/var/lib/dpkg/info"
+
+typedef struct
+{
+ GMutex pending_mutex;
+ GCond pending_cond;
+
+ GMutex dispatched_mutex;
+ GCond dispatched_cond;
+
+ GMutex hashtable_mutex;
+
+ guint still_to_read;
+ guint dispatched_reads;
+
+ GsPlugin *plugin;
+} ReadListData;
+
+typedef struct {
+ gchar *name;
+ gchar *section;
+ gchar *installed_version;
+ gchar *update_version;
+ gchar *origin;
+ gchar *release;
+ gchar *component;
+ guint64 installed_size;
+ guint64 download_size;
+} PackageInfo;
+
+struct GsPluginData {
+ GMutex mutex;
+ gboolean loaded;
+ GHashTable *package_info;
+ GHashTable *installed_files;
+ GList *installed_packages;
+ GList *updatable_packages;
+};
+
+static void
+free_package_info (gpointer data)
+{
+ PackageInfo *info = (PackageInfo *) data;
+ g_free (info->section);
+ g_free (info->installed_version);
+ g_free (info->update_version);
+ g_free (info->origin);
+ g_free (info->release);
+ g_free (info->component);
+ g_free (info->name);
+ g_free (info);
+}
+
+void
+gs_plugin_initialize (GsPlugin *plugin)
+{
+ GsPluginData *priv = gs_plugin_alloc_data (plugin, sizeof(GsPluginData));
+
+ priv->package_info = g_hash_table_new_full (g_str_hash,
+ g_str_equal,
+ NULL,
+ free_package_info);
+
+ priv->installed_files = g_hash_table_new_full (g_str_hash,
+ g_str_equal,
+ g_free,
+ g_free);
+
+ g_mutex_init (&priv->mutex);
+
+ pkgInitConfig (*_config);
+ pkgInitSystem (*_config, _system);
+
+ gs_plugin_add_rule (plugin, GS_PLUGIN_RULE_RUN_AFTER, "appstream");
+
+ gs_plugin_add_rule (plugin, GS_PLUGIN_RULE_CONFLICTS, "packagekit");
+ gs_plugin_add_rule (plugin, GS_PLUGIN_RULE_CONFLICTS, "packagekit-history");
+ gs_plugin_add_rule (plugin, GS_PLUGIN_RULE_CONFLICTS, "packagekit-offline");
+ gs_plugin_add_rule (plugin, GS_PLUGIN_RULE_CONFLICTS, "packagekit-origin");
+ gs_plugin_add_rule (plugin, GS_PLUGIN_RULE_CONFLICTS, "packagekit-proxy");
+ gs_plugin_add_rule (plugin, GS_PLUGIN_RULE_CONFLICTS, "packagekit-refine");
+ gs_plugin_add_rule (plugin, GS_PLUGIN_RULE_CONFLICTS, "packagekit-refresh");
+ gs_plugin_add_rule (plugin, GS_PLUGIN_RULE_CONFLICTS, "systemd-updates");
+}
+
+void
+gs_plugin_destroy (GsPlugin *plugin)
+{
+ GsPluginData *priv = gs_plugin_get_data (plugin);
+
+ g_mutex_lock (&priv->mutex);
+ priv->loaded = FALSE;
+ g_clear_pointer (&priv->package_info, g_hash_table_unref);
+ g_clear_pointer (&priv->installed_files, g_hash_table_unref);
+ g_clear_pointer (&priv->installed_packages, g_list_free);
+ g_clear_pointer (&priv->updatable_packages, g_list_free);
+ g_mutex_unlock (&priv->mutex);
+ g_mutex_clear (&priv->mutex);
+}
+
+void
+gs_plugin_adopt_app (GsPlugin *plugin, GsApp *app)
+{
+ if (gs_app_get_bundle_kind (app) == AS_BUNDLE_KIND_PACKAGE &&
+ gs_app_get_scope (app) == AS_APP_SCOPE_SYSTEM) {
+ gs_app_set_management_plugin (app, "apt");
+ return;
+ }
+}
+
+static void
+read_list_file_cb (GObject *object,
+ GAsyncResult *res,
+ gpointer user_data)
+{
+ g_autoptr(GFileInputStream) stream = NULL;
+ g_autoptr(GFile) file = NULL;
+ ReadListData *data;
+ GsPluginData *priv;
+ g_autofree gchar *buffer = NULL;
+ g_autofree gchar *filename = NULL;
+ g_autoptr(GFileInfo) info = NULL;
+ g_auto(GStrv) file_lines = NULL;
+ g_auto(GStrv) file_components = NULL;
+
+ file = G_FILE (object);
+ data = (ReadListData *) user_data;
+ priv = gs_plugin_get_data (data->plugin);
+ stream = g_file_read_finish (file, res, NULL);
+
+ info = g_file_input_stream_query_info (stream,
+ G_FILE_ATTRIBUTE_STANDARD_SIZE,
+ NULL,
+ NULL);
+
+ if (!info)
+ return;
+
+ if (!g_file_info_has_attribute (info, G_FILE_ATTRIBUTE_STANDARD_SIZE))
+ return;
+
+ buffer = (gchar *) g_malloc0 (g_file_info_get_size (info) + 1);
+
+ if (!g_input_stream_read_all (G_INPUT_STREAM (stream),
+ buffer,
+ g_file_info_get_size (info),
+ NULL,
+ NULL,
+ NULL))
+ return;
+
+ g_input_stream_close (G_INPUT_STREAM (stream), NULL, NULL);
+
+ file_lines = g_strsplit (buffer, "\n", -1);
+
+ filename = g_file_get_basename (file);
+ file_components = g_strsplit (filename, ".", 2);
+
+ for (int i = 0; file_lines[i]; ++i)
+ if (g_str_has_suffix (file_lines[i], ".desktop") ||
+ g_str_has_suffix (file_lines[i], ".metainfo.xml") ||
+ g_str_has_suffix (file_lines[i], ".appdata.xml"))
+ {
+ g_mutex_lock (&data->hashtable_mutex);
+ /* filename -> package */
+ g_hash_table_insert (priv->installed_files,
+ g_strdup (file_lines[i]),
+ g_strdup (file_components[0]));
+ g_mutex_unlock (&data->hashtable_mutex);
+ }
+
+ g_mutex_lock (&data->dispatched_mutex);
+ (data->dispatched_reads)--;
+ g_cond_signal(&data->dispatched_cond);
+ g_mutex_unlock(&data->dispatched_mutex);
+
+ g_mutex_lock (&data->pending_mutex);
+ (data->still_to_read)--;
+ g_cond_signal (&data->pending_cond);
+ g_mutex_unlock (&data->pending_mutex);
+}
+
+static void
+read_list_file (GList *files,
+ ReadListData *data)
+{
+ GFile *gfile;
+ GList *files_iter;
+
+ for (files_iter = files; files_iter; files_iter = files_iter->next)
+ {
+ /* freed in read_list_file_cb */
+ gfile = g_file_new_for_path ((gchar *) files_iter->data);
+
+ g_mutex_lock (&data->dispatched_mutex);
+ g_file_read_async (gfile,
+ G_PRIORITY_DEFAULT,
+ NULL,
+ read_list_file_cb,
+ data);
+
+ (data->dispatched_reads)++;
+
+ while (data->dispatched_reads >= 500)
+ g_cond_wait (&data->dispatched_cond, &data->dispatched_mutex);
+
+ g_mutex_unlock (&data->dispatched_mutex);
+ }
+}
+
+static void
+look_for_files (GsPlugin *plugin)
+{
+ ReadListData data;
+ GList *files = NULL;
+
+ data.still_to_read = 0;
+ data.dispatched_reads = 0;
+ g_cond_init (&data.pending_cond);
+ g_mutex_init (&data.pending_mutex);
+ g_cond_init (&data.dispatched_cond);
+ g_mutex_init (&data.dispatched_mutex);
+ g_mutex_init (&data.hashtable_mutex);
+ data.plugin = plugin;
+
+ g_autoptr (GDir) dir = NULL;
+ const gchar *file;
+
+ dir = g_dir_open (INFO_DIR, 0, NULL);
+
+ while ((file = g_dir_read_name (dir)))
+ if (g_str_has_suffix (file, ".list") &&
+ /* app-install-data contains loads of .desktop files, but they aren't installed by it */
+ (g_strcmp0 (file, "app-install-data.list") != 0))
+ {
+ files = g_list_append (files, g_build_filename (INFO_DIR, file, NULL));
+ data.still_to_read++;
+ }
+
+ read_list_file (files, &data);
+
+ /* Wait until all the reads are done */
+ g_mutex_lock (&data.pending_mutex);
+ while (data.still_to_read > 0)
+ g_cond_wait (&data.pending_cond, &data.pending_mutex);
+ g_mutex_unlock (&data.pending_mutex);
+
+ g_mutex_clear (&data.pending_mutex);
+ g_cond_clear (&data.pending_cond);
+ g_mutex_clear (&data.dispatched_mutex);
+ g_cond_clear (&data.dispatched_cond);
+ g_mutex_clear (&data.hashtable_mutex);
+
+ g_list_free_full (files, g_free);
+}
+
+static gboolean
+version_newer (const gchar *v0, const gchar *v1)
+{
+ return v0 ? _system->VS->CmpVersion(v0, v1) < 0 : TRUE;
+}
+
+/* return FALSE for a fatal error */
+static gboolean
+look_at_pkg (const pkgCache::PkgIterator &P,
+ pkgSourceList *list,
+ pkgPolicy *policy,
+ GsPlugin *plugin,
+ GError **error)
+{
+ GsPluginData *priv = gs_plugin_get_data (plugin);
+ pkgCache::VerIterator current = P.CurrentVer();
+ pkgCache::VerIterator candidate = policy->GetCandidateVer(P);
+ pkgCache::VerFileIterator VF;
+ FileFd PkgF;
+ pkgTagSection Tags;
+ gchar *name;
+
+ PackageInfo *info;
+
+ if (!candidate || !candidate.IsGood () || !candidate.FileList ())
+ return TRUE;
+
+ name = g_strdup (P.Name ());
+ info = (PackageInfo *) g_hash_table_lookup (priv->package_info, name);
+ if (info == NULL) {
+ info = g_new0 (PackageInfo, 1);
+ info->name = name;
+ g_hash_table_insert (priv->package_info, name, info);
+ } else
+ g_free (name);
+
+ for (VF = candidate.FileList (); VF.IsGood (); VF++) {
+ // see InRelease for the fields
+ if (VF.File ().Archive ())
+ info->release = g_strdup (VF.File ().Archive ());
+ if (VF.File ().Origin ())
+ info->origin = g_strdup (VF.File ().Origin ());
+ if (VF.File ().Component ())
+ info->component = g_strdup (VF.File ().Component ());
+ // also available: Codename, Label
+ break;
+ }
+
+ pkgCache::PkgFileIterator I = VF.File ();
+
+ if (I.IsOk () == false) {
+ g_set_error (error,
+ GS_PLUGIN_ERROR,
+ GS_PLUGIN_ERROR_FAILED,
+ ("apt DB load failed: package file %s is out of sync."),
+ I.FileName ());
+ return FALSE;
+ }
+
+ PkgF.Open (I.FileName (), FileFd::ReadOnly, FileFd::Extension);
+
+ pkgTagFile TagF (&PkgF);
+
+ if (!current.IsGood() || TagF.Jump (Tags, current.FileList ()->Offset) == false) {
+ if (TagF.Jump (Tags, candidate.FileList ()->Offset) == false)
+ return TRUE;
+ }
+
+ if (Tags.FindI ("Installed-Size") > 0)
+ info->installed_size = Tags.FindI ("Installed-Size")*1024;
+ else
+ info->installed_size = GS_APP_SIZE_UNKNOWABLE;
+
+ if (Tags.FindI ("Size") > 0)
+ info->download_size = Tags.FindI ("Size");
+ else
+ info->download_size = GS_APP_SIZE_UNKNOWABLE;
+
+ if (current && current.IsGood ())
+ info->installed_version = g_strdup (current.VerStr ());
+
+ if (candidate)
+ info->update_version = g_strdup (candidate.VerStr ());
+
+ info->section = g_strdup (candidate.Section ());
+
+ if (info->installed_version) {
+ priv->installed_packages =
+ g_list_append (priv->installed_packages, info);
+ }
+
+ /* no upgrade */
+ if (g_strcmp0 (info->installed_version, info->update_version) == 0)
+ g_clear_pointer (&info->update_version, g_free);
+
+ if (info->installed_version &&
+ info->update_version &&
+ P->SelectedState != pkgCache::State::Hold)
+ priv->updatable_packages =
+ g_list_append (priv->updatable_packages, info);
+
+ return TRUE;
+}
+
+static gboolean
+load_apt_db (GsPlugin *plugin, GError **error)
+{
+ GsPluginData *priv = gs_plugin_get_data (plugin);
+ pkgSourceList *list;
+ pkgPolicy *policy;
+ pkgCacheFile cachefile;
+ pkgCache *cache;
+ pkgCache::PkgIterator P;
+ g_autoptr(GMutexLocker) locker = NULL;
+
+ if (priv->loaded)
+ return TRUE;
+
+ locker = g_mutex_locker_new (&priv->mutex);
+
+ _error->Discard();
+ cache = cachefile.GetPkgCache();
+ list = cachefile.GetSourceList();
+ policy = cachefile.GetPolicy();
+ if (cache == NULL || _error->PendingError()) {
+ _error->DumpErrors();
+ g_set_error (error,
+ GS_PLUGIN_ERROR,
+ GS_PLUGIN_ERROR_FAILED,
+ "apt DB load failed: error while initialising");
+ return FALSE;
+ }
+
+ for (pkgCache::GrpIterator grp = cache->GrpBegin(); grp != cache->GrpEnd(); grp++) {
+ P = grp.FindPreferredPkg();
+ if (P.end())
+ continue;
+ if (!look_at_pkg (P, list, policy, plugin, error))
+ return FALSE;
+ }
+
+ /* load filename -> package map into priv->installed_files */
+ look_for_files (plugin);
+
+ priv->loaded = TRUE;
+ return TRUE;
+}
+
+static void
+unload_apt_db (GsPlugin *plugin)
+{
+ GsPluginData *priv = gs_plugin_get_data (plugin);
+ g_autoptr(GMutexLocker) locker = g_mutex_locker_new (&priv->mutex);
+
+ priv->loaded = FALSE;
+ g_hash_table_remove_all (priv->package_info);
+ g_hash_table_remove_all (priv->installed_files);
+ g_clear_pointer (&priv->installed_packages, g_list_free);
+ g_clear_pointer (&priv->updatable_packages, g_list_free);
+}
+
+static void
+get_changelog (GsPlugin *plugin, GsApp *app)
+{
+ guint i;
+ guint status_code;
+ g_autofree gchar *binary_source = NULL;
+ g_autofree gchar *changelog_prefix = NULL;
+ g_autofree gchar *current_version = NULL;
+ g_autofree gchar *source_prefix = NULL;
+ g_autofree gchar *update_version = NULL;
+ g_autofree gchar *uri = NULL;
+ g_auto(GStrv) lines = NULL;
+ g_autoptr(GString) details = NULL;
+ g_autoptr(SoupMessage) msg = NULL;
+
+ // Need to know the source and version to download changelog
+ binary_source = g_strdup (gs_app_get_source_default (app));
+ current_version = g_strdup (gs_app_get_version (app));
+ update_version = g_strdup (gs_app_get_update_version (app));
+ if (binary_source == NULL || update_version == NULL)
+ return;
+
+ if (g_str_has_prefix (binary_source, "lib"))
+ source_prefix = g_strdup_printf ("lib%c", binary_source[3]);
+ else
+ source_prefix = g_strdup_printf ("%c", binary_source[0]);
+
+ uri = g_strdup_printf ("http://changelogs.ubuntu.com/changelogs/binary/%s/%s/%s/changelog",
+ source_prefix,
+ binary_source,
+ update_version);
+
+ /* download file */
+ msg = soup_message_new (SOUP_METHOD_GET, uri);
+ status_code = soup_session_send_message (gs_plugin_get_soup_session (plugin),
+ msg);
+ if (status_code != SOUP_STATUS_OK) {
+ g_warning ("Failed to get changelog for %s version %s from changelogs.ubuntu.com: %s",
+ binary_source,
+ update_version,
+ soup_status_get_phrase (status_code));
+ return;
+ }
+
+ // Extract changelog entries newer than our current version
+ lines = g_strsplit (msg->response_body->data, "\n", -1);
+ details = g_string_new ("");
+ for (i = 0; lines[i] != NULL; i++) {
+ gchar *line = lines[i];
+ const gchar *version_start, *version_end;
+ g_autofree gchar *v = NULL;
+
+ // First line is in the form "package (version) distribution(s); urgency=urgency"
+ version_start = strchr (line, '(');
+ version_end = strchr (line, ')');
+ if (line[0] == ' ' ||
+ version_start == NULL ||
+ version_end == NULL ||
+ version_end < version_start)
+ continue;
+
+ v = g_strdup_printf ("%.*s",
+ (int) (version_end - version_start - 1),
+ version_start + 1);
+
+ // We're only interested in new versions
+ if (!version_newer (current_version, v))
+ break;
+
+ g_string_append_printf (details, "%s\n", v);
+ for (i++; lines[i] != NULL; i++) {
+ // Last line is in the form " -- maintainer name <email address> date"
+ if (g_str_has_prefix (lines[i], " -- "))
+ break;
+ g_string_append_printf (details, "%s\n", lines[i]);
+ }
+ }
+
+ gs_app_set_update_details (app, details->str);
+}
+
+static gboolean
+is_open_source (PackageInfo *info)
+{
+ const gchar *open_source_components[] = { "main", "universe", NULL };
+
+ /* There's no valid apps in the libs section */
+ return info->component != NULL &&
+ g_strv_contains (open_source_components, info->component);
+}
+
+static gchar *
+get_origin (PackageInfo *info)
+{
+ if (!info->origin)
+ return NULL;
+
+ g_autofree gchar *origin_lower = g_strdup (info->origin);
+ for (int i = 0; origin_lower[i]; ++i)
+ origin_lower[i] = g_ascii_tolower (origin_lower[i]);
+
+ return g_strdup_printf ("%s-%s-%s", origin_lower, info->release, info->component);
+}
+
+gboolean
+gs_plugin_url_to_app (GsPlugin *plugin,
+ GsAppList *list,
+ const gchar *url,
+ GCancellable *cancellable,
+ GError **error)
+{
+ g_autofree gchar *scheme = NULL;
+ g_autofree gchar *path = NULL;
+ g_autoptr(GsApp) app = NULL;
+
+ scheme = gs_utils_get_url_scheme (url);
+ if (g_strcmp0 (scheme, "apt") != 0)
+ return TRUE;
+
+ /* create app */
+ path = gs_utils_get_url_path (url);
+
+ app = gs_app_new (NULL);
+ gs_app_add_source (app, path);
+
+ gs_app_list_add (list, app);
+
+ return TRUE;
+}
+
+gboolean
+gs_plugin_refine_app (GsPlugin *plugin,
+ GsApp *app,
+ GsPluginRefineFlags flags,
+ GCancellable *cancellable,
+ GError **error)
+{
+ GsPluginData *priv = gs_plugin_get_data (plugin);
+ PackageInfo *info;
+ const gchar *tmp;
+ g_autoptr(GMutexLocker) locker = NULL;
+ g_autofree gchar *fn = NULL;
+ gchar *package = NULL;
+
+ if (!load_apt_db (plugin, error))
+ return FALSE;
+
+ locker = g_mutex_locker_new (&priv->mutex);
+
+ /* Before we can do anything, we need to find out which package this app belongs to. */
+ tmp = gs_app_get_id (app);
+ if (gs_app_get_source_id_default (app) == NULL && tmp) {
+ switch (gs_app_get_kind (app)) {
+ case AS_APP_KIND_DESKTOP:
+ fn = g_strdup_printf ("/usr/share/applications/%s", tmp);
+ break;
+ case AS_APP_KIND_ADDON:
+ fn = g_strdup_printf ("/usr/share/appdata/%s.metainfo.xml", tmp);
+ break;
+ default:
+ break;
+ }
+
+ if (!fn || !g_file_test (fn, G_FILE_TEST_EXISTS)) {
+ g_debug ("ignoring %s as does not exist", fn);
+ } else {
+ package = (gchar *) g_hash_table_lookup (priv->installed_files,
+ fn);
+ if (package != NULL) {
+ gs_app_add_source (app, package);
+ gs_app_set_management_plugin (app, "apt");
+ }
+ }
+ }
+
+ /* We don't know which package this app belongs to, so there's nothing we can do. */
+ if (gs_app_get_source_default (app) == NULL)
+ return TRUE;
+
+ info = (PackageInfo *) g_hash_table_lookup (priv->package_info,
+ gs_app_get_source_default (app));
+ if (info == NULL)
+ return TRUE;
+
+ if (gs_app_get_state (app) == AS_APP_STATE_UNKNOWN) {
+ if (info->installed_version != NULL) {
+ if (info->update_version != NULL) {
+ gs_app_set_state (app, AS_APP_STATE_UPDATABLE_LIVE);
+ } else {
+ gs_app_set_state (app, AS_APP_STATE_INSTALLED);
+ }
+ } else {
+ gs_app_set_state (app, AS_APP_STATE_AVAILABLE);
+ }
+ }
+
+ if ((flags & GS_PLUGIN_REFINE_FLAGS_REQUIRE_ORIGIN) != 0) {
+ g_autofree gchar *origin = get_origin (info);
+ gs_app_set_origin (app, origin);
+ if (g_strcmp0 (info->origin, "Ubuntu") == 0)
+ gs_app_set_origin_hostname (app, "https://www.ubuntu.com");
+ }
+
+ if ((flags & GS_PLUGIN_REFINE_FLAGS_REQUIRE_SIZE) != 0) {
+ if (gs_app_get_size_installed (app) == 0) {
+ gs_app_set_size_installed (app, info->installed_size);
+ }
+
+ if (gs_app_get_size_download (app) == 0) {
+ gs_app_set_size_download (app, info->download_size);
+ }
+ }
+
+ if ((flags & GS_PLUGIN_REFINE_FLAGS_REQUIRE_VERSION) != 0) {
+ if (info->installed_version != NULL) {
+ gs_app_set_version (app, info->installed_version);
+ } else {
+ gs_app_set_version (app, info->update_version);
+ }
+
+ if (info->update_version != NULL) {
+ gs_app_set_update_version (app, info->update_version);
+ }
+ }
+
+ if ((flags & GS_PLUGIN_REFINE_FLAGS_REQUIRE_LICENSE) != 0 && is_open_source(info)) {
+ gs_app_set_license (app, GS_APP_QUALITY_LOWEST, "@LicenseRef-free=" LICENSE_URL);
+ }
+
+ if ((flags & GS_PLUGIN_REFINE_FLAGS_REQUIRE_CHANGELOG) != 0) {
+ get_changelog (plugin, app);
+ }
+
+ return TRUE;
+}
+
+static gboolean
+is_allowed_section (PackageInfo *info)
+{
+ const gchar *section_blacklist[] = { "libs", NULL };
+
+ /* There's no valid apps in the libs section */
+ return info->section == NULL || !g_strv_contains (section_blacklist, info->section);
+}
+
+gboolean
+gs_plugin_add_installed (GsPlugin *plugin,
+ GsAppList *list,
+ GCancellable *cancellable,
+ GError **error)
+{
+ GsPluginData *priv = gs_plugin_get_data (plugin);
+ GList *link;
+ g_autoptr(GMutexLocker) locker = NULL;
+
+ if (!load_apt_db (plugin, error))
+ return FALSE;
+
+ locker = g_mutex_locker_new (&priv->mutex);
+
+ for (link = priv->installed_packages; link; link = link->next) {
+ PackageInfo *info = (PackageInfo *) link->data;
+ g_autofree gchar *origin = get_origin (info);
+ g_autoptr(GsApp) app = NULL;
+
+ if (!is_allowed_section (info))
+ continue;
+
+ app = gs_app_new (NULL);
+ gs_app_set_management_plugin (app, "apt");
+ gs_app_set_name (app, GS_APP_QUALITY_LOWEST, info->name);
+ gs_app_add_source (app, info->name);
+ gs_app_set_state (app, AS_APP_STATE_INSTALLED);
+ gs_app_list_add (list, app);
+ }
+
+ return TRUE;
+}
+
+typedef struct {
+ GCancellable *cancellable;
+ GSocket *debconf_connection;
+ GSource *debconf_read_source;
+ GSource *debconf_client_read_source;
+ GPid debconf_pid;
+ gint stdin_pipe;
+ gint stdout_pipe;
+ gint stderr_pipe;
+} DebconfData;
+
+static DebconfData *
+debconf_data_new (GCancellable *cancellable)
+{
+ DebconfData *data;
+
+ data = g_slice_new0 (DebconfData);
+ data->cancellable = (GCancellable *) g_object_ref (cancellable);
+ data->stdin_pipe = -1;
+ data->stdout_pipe = -1;
+ data->stderr_pipe = -1;
+
+ return data;
+}
+
+static void
+debconf_data_free (DebconfData *data)
+{
+ if (data->debconf_read_source != NULL)
+ g_source_destroy (data->debconf_read_source);
+ if (data->debconf_client_read_source != NULL)
+ g_source_destroy (data->debconf_client_read_source);
+
+ g_clear_object (&data->cancellable);
+ if (data->debconf_connection != NULL)
+ g_socket_close (data->debconf_connection, NULL);
+ g_clear_object (&data->debconf_connection);
+ g_clear_pointer (&data->debconf_read_source, g_source_unref);
+ g_clear_pointer (&data->debconf_client_read_source, g_source_unref);
+ if (data->stdin_pipe > 0)
+ close (data->stdin_pipe);
+ if (data->stdout_pipe > 0)
+ close (data->stdout_pipe);
+ if (data->stderr_pipe > 0)
+ close (data->stderr_pipe);
+ if (data->debconf_pid > 0)
+ kill (data->debconf_pid, SIGTERM);
+ g_slice_free (DebconfData, data);
+}
+
+typedef struct {
+ GsPlugin *plugin;
+ GCancellable *cancellable;
+ GsApp *app;
+ GsAppList *apps;
+ gchar *result;
+ GMainContext *context;
+ GMainLoop *loop;
+ GSocket *debconf_socket;
+ GList *debconf_connections;
+} TransactionData;
+
+static TransactionData *
+transaction_data_new (GsPlugin *plugin, GCancellable *cancellable)
+{
+ TransactionData *data;
+
+ data = g_slice_new0 (TransactionData);
+ data->plugin = (GsPlugin *) g_object_ref (plugin);
+ data->cancellable = (GCancellable *) g_object_ref (cancellable);
+ data->context = g_main_context_new ();
+ data->loop = g_main_loop_new (data->context, FALSE);
+
+ return data;
+}
+
+static void
+transaction_data_free (TransactionData *data)
+{
+ g_clear_object (&data->plugin);
+ g_clear_object (&data->cancellable);
+ g_free (data->result);
+ g_clear_pointer (&data->context, g_main_context_unref);
+ g_clear_pointer (&data->loop, g_main_loop_unref);
+ if (data->debconf_socket != NULL)
+ g_socket_close (data->debconf_socket, NULL);
+ g_clear_object (&data->debconf_socket);
+ g_list_free_full (data->debconf_connections, (GDestroyNotify) debconf_data_free);
+ g_slice_free (TransactionData, data);
+}
+
+G_DEFINE_AUTOPTR_CLEANUP_FUNC(TransactionData, transaction_data_free)
+
+static void
+transaction_property_changed_cb (GDBusConnection *connection,
+ const gchar *sender_name,
+ const gchar *object_path,
+ const gchar *interface_name,
+ const gchar *signal_name,
+ GVariant *parameters,
+ gpointer user_data)
+{
+ TransactionData *data = (TransactionData *) user_data;
+ const gchar *name;
+ guint length;
+ g_autoptr(GVariant) value = NULL;
+
+ if (g_variant_is_of_type (parameters, G_VARIANT_TYPE ("(sv)"))) {
+ g_variant_get (parameters, "(&sv)", &name, &value);
+ if (g_strcmp0 (name, "Progress") == 0) {
+ if (data->app)
+ gs_app_set_progress (data->app, g_variant_get_int32 (value));
+ if (data->apps) {
+ length = gs_app_list_length (data->apps);
+ for (guint i = 0; i < length; ++i)
+ gs_app_set_progress (gs_app_list_index (data->apps, i),
+ g_variant_get_int32 (value));
+ }
+ }
+ } else {
+ g_warning ("Unknown parameters in %s.%s: %s",
+ interface_name,
+ signal_name,
+ g_variant_get_type_string (parameters));
+ }
+}
+
+static void
+transaction_finished_cb (GDBusConnection *connection,
+ const gchar *sender_name,
+ const gchar *object_path,
+ const gchar *interface_name,
+ const gchar *signal_name,
+ GVariant *parameters,
+ gpointer user_data)
+{
+ TransactionData *data = (TransactionData *) user_data;
+
+ if (g_variant_is_of_type (parameters, G_VARIANT_TYPE ("(s)")))
+ g_variant_get (parameters, "(s)", &data->result);
+ else
+ g_warning ("Unknown parameters in %s.%s: %s",
+ interface_name,
+ signal_name,
+ g_variant_get_type_string (parameters));
+
+ g_main_loop_quit (data->loop);
+}
+
+static void
+notify_unity_launcher (GsApp *app, const gchar *transaction_path)
+{
+ g_autoptr(GDBusConnection) conn = NULL;
+
+ g_return_if_fail (GS_IS_APP (app));
+ g_return_if_fail (transaction_path);
+
+ conn = g_bus_get_sync (G_BUS_TYPE_SYSTEM, NULL, NULL);
+ if (conn == NULL)
+ return;
+
+ g_dbus_connection_call_sync (conn,
+ "com.canonical.Unity.Launcher",
+ "/com/canonical/Unity/Launcher",
+ "com.canonical.Unity.Launcher",
+ "AddLauncherItem",
+ g_variant_new ("(ss)", gs_app_get_id (app), transaction_path),
+ G_VARIANT_TYPE ("()"),
+ G_DBUS_CALL_FLAGS_NONE,
+ -1,
+ NULL,
+ NULL);
+}
+
+static gboolean
+debconf_read_cb (GSocket *socket, GIOCondition condition, gpointer user_data)
+{
+ DebconfData *data = (DebconfData *) user_data;
+ gchar buffer[1024];
+ gssize n_read;
+ g_autoptr(GError) error = NULL;
+
+ n_read = g_socket_receive (socket, buffer, 1024, data->cancellable, &error);
+ if (n_read == 0) {
+ close (data->stdin_pipe);
+ data->stdin_pipe = -1;
+ return G_SOURCE_REMOVE;
+ }
+ if (n_read < 0) {
+ g_warning ("Error reading from debconf socket: %s\n", g_strerror (errno));
+ return G_SOURCE_CONTINUE;
+ }
+
+ if (write (data->stdin_pipe, buffer, n_read) < 0) {
+ g_warning ("Error writing to debconf client: %s\n", error->message);
+ return G_SOURCE_CONTINUE;
+ }
+
+ return G_SOURCE_CONTINUE;
+}
+
+static gboolean
+debconf_client_read_cb (gint fd, GIOCondition condition, gpointer user_data)
+{
+ DebconfData *data = (DebconfData *) user_data;
+ gchar buffer[1024];
+ gssize n_read;
+ g_autoptr(GError) error = NULL;
+
+ n_read = read (fd, buffer, 1024);
+ if (n_read < 0) {
+ g_warning ("Error reading from debconf client: %s\n", g_strerror (errno));
+ return G_SOURCE_CONTINUE;
+ }
+ if (n_read == 0) {
+ g_socket_close (data->debconf_connection, NULL);
+ return G_SOURCE_REMOVE;
+ }
+
+ if (!g_socket_send (data->debconf_connection, buffer, n_read, data->cancellable, &error)) {
+ g_warning ("Error writing to debconf socket: %s\n", error->message);
+ return G_SOURCE_CONTINUE;
+ }
+
+ return G_SOURCE_CONTINUE;
+}
+
+static gboolean
+debconf_accept_cb (GSocket *socket, GIOCondition condition, gpointer user_data)
+{
+ TransactionData *data = (TransactionData *) user_data;
+ DebconfData *debconf_data;
+ g_autoptr(GPtrArray) argv = NULL;
+ g_auto(GStrv) envp = NULL;
+ g_autoptr(GError) error = NULL;
+
+ debconf_data = debconf_data_new (data->cancellable);
+ data->debconf_connections = g_list_append (data->debconf_connections, debconf_data);
+ debconf_data->debconf_connection = g_socket_accept (socket, data->cancellable, &error);
+ if (debconf_data->debconf_connection == NULL) {
+ g_warning ("Failed to accept debconf connection: %s", error->message);
+ return G_SOURCE_CONTINUE;
+ }
+ debconf_data->debconf_read_source = g_socket_create_source (debconf_data->debconf_connection,
G_IO_IN, data->cancellable);
+ g_source_set_callback (debconf_data->debconf_read_source, (GSourceFunc) debconf_read_cb,
debconf_data, NULL);
+ g_source_attach (debconf_data->debconf_read_source, data->context);
+
+ argv = g_ptr_array_new ();
+ g_ptr_array_add (argv, (gpointer) "debconf-communicate");
+ g_ptr_array_add (argv, NULL);
+ envp = g_get_environ ();
+ envp = g_environ_setenv (envp, "DEBCONF_DB_REPLACE", "configdb", TRUE);
+ envp = g_environ_setenv (envp, "DEBCONF_DB_OVERRIDE", "Pipe{infd:none outfd:none}", TRUE);
+ envp = g_environ_setenv (envp, "DEBIAN_FRONTEND", "gnome", TRUE);
+ if (!g_spawn_async_with_pipes (NULL,
+ (gchar **) argv->pdata,
+ envp,
+ G_SPAWN_SEARCH_PATH,
+ NULL, NULL,
+ &debconf_data->debconf_pid,
+ &debconf_data->stdin_pipe,
+ &debconf_data->stdout_pipe,
+ &debconf_data->stderr_pipe,
+ &error)) {
+ g_warning ("Failed to launch debconf-communicate: %s", error->message);
+ g_socket_close (debconf_data->debconf_connection, NULL);
+ return G_SOURCE_CONTINUE;
+ }
+ debconf_data->debconf_client_read_source = g_unix_fd_source_new (debconf_data->stdout_pipe, G_IO_IN);
+ g_source_set_callback (debconf_data->debconf_client_read_source, (GSourceFunc)
debconf_client_read_cb, debconf_data, NULL);
+ g_source_attach (debconf_data->debconf_client_read_source, data->context);
+
+ return G_SOURCE_CONTINUE;
+}
+
+static gboolean
+aptd_transaction (GsPlugin *plugin,
+ const gchar *method,
+ GsApp *app,
+ GsAppList *apps,
+ GVariant *parameters,
+ GCancellable *cancellable,
+ GError **error)
+{
+ g_autoptr(GDBusConnection) conn = NULL;
+ g_autoptr(GVariant) result = NULL;
+ g_autofree gchar *transaction_path = NULL, *temp_dir = NULL, *debconf_socket_path = NULL;
+ g_autoptr(GSocketAddress) address = NULL;
+ g_autoptr(GSource) accept_source = NULL;
+ guint property_signal, finished_signal;
+ g_autoptr(TransactionData) data = NULL;
+
+ conn = g_bus_get_sync (G_BUS_TYPE_SYSTEM, cancellable, error);
+ if (conn == NULL)
+ return FALSE;
+
+ if (parameters == NULL && app != NULL)
+ parameters = g_variant_new_parsed ("([%s],)", gs_app_get_source_default (app));
+
+ result = g_dbus_connection_call_sync (conn,
+ "org.debian.apt",
+ "/org/debian/apt",
+ "org.debian.apt",
+ method,
+ parameters,
+ G_VARIANT_TYPE ("(s)"),
+ G_DBUS_CALL_FLAGS_NONE,
+ -1,
+ cancellable,
+ error);
+ if (result == NULL)
+ return FALSE;
+ g_variant_get (result, "(s)", &transaction_path);
+ g_clear_pointer (&result, g_variant_unref);
+
+ data = transaction_data_new (plugin, cancellable);
+ data->app = app;
+ data->apps = apps;
+
+ temp_dir = g_dir_make_tmp ("gnome-software-XXXXXX", NULL);
+ debconf_socket_path = g_build_filename (temp_dir, "debconf.socket", NULL);
+ data->debconf_socket = g_socket_new (G_SOCKET_FAMILY_UNIX, G_SOCKET_TYPE_STREAM,
G_SOCKET_PROTOCOL_DEFAULT, error);
+ address = g_unix_socket_address_new (debconf_socket_path);
+ if (data->debconf_socket == NULL || !g_socket_bind (data->debconf_socket, address, FALSE, error))
+ return FALSE;
+ accept_source = g_socket_create_source (data->debconf_socket, G_IO_IN, cancellable);
+ g_source_set_callback (accept_source, (GSourceFunc) debconf_accept_cb, data, NULL);
+ g_source_attach (accept_source, data->context);
+ if (!g_socket_listen (data->debconf_socket, error))
+ return FALSE;
+ result = g_dbus_connection_call_sync (conn,
+ "org.debian.apt",
+ transaction_path,
+ "org.freedesktop.DBus.Properties",
+ "Set",
+ g_variant_new ("(ssv)", "org.debian.apt.transaction",
"DebconfSocket", g_variant_new_string (debconf_socket_path)),
+ G_VARIANT_TYPE ("()"),
+ G_DBUS_CALL_FLAGS_NONE,
+ -1,
+ cancellable,
+ error);
+ if (result == NULL)
+ return FALSE;
+ g_clear_pointer (&result, g_variant_unref);
+
+ if (!g_strcmp0(method, "InstallPackages"))
+ notify_unity_launcher (app, transaction_path);
+
+ property_signal = g_dbus_connection_signal_subscribe (conn,
+ "org.debian.apt",
+ "org.debian.apt.transaction",
+ "PropertyChanged",
+ transaction_path,
+ NULL,
+ G_DBUS_SIGNAL_FLAGS_NONE,
+ transaction_property_changed_cb,
+ data,
+ NULL);
+
+ finished_signal = g_dbus_connection_signal_subscribe (conn,
+ "org.debian.apt",
+ "org.debian.apt.transaction",
+ "Finished",
+ transaction_path,
+ NULL,
+ G_DBUS_SIGNAL_FLAGS_NONE,
+ transaction_finished_cb,
+ data,
+ NULL);
+
+ result = g_dbus_connection_call_sync (conn,
+ "org.debian.apt",
+ transaction_path,
+ "org.debian.apt.transaction",
+ "Run",
+ g_variant_new ("()"),
+ G_VARIANT_TYPE ("()"),
+ G_DBUS_CALL_FLAGS_NONE,
+ -1,
+ cancellable,
+ error);
+
+ if (result)
+ g_main_loop_run (data->loop);
+
+ g_dbus_connection_signal_unsubscribe (conn, property_signal);
+ g_dbus_connection_signal_unsubscribe (conn, finished_signal);
+
+ if (result == NULL)
+ return FALSE;
+
+ if (g_strcmp0 (data->result, "exit-success") != 0) {
+ g_set_error (error,
+ GS_PLUGIN_ERROR,
+ GS_PLUGIN_ERROR_FAILED,
+ "apt transaction returned result %s", data->result);
+ return FALSE;
+ }
+
+ return TRUE;
+}
+
+static gboolean
+app_is_ours (GsApp *app)
+{
+ const gchar *management_plugin = gs_app_get_management_plugin (app);
+
+ return g_strcmp0 (management_plugin, "apt") == 0;
+}
+
+gboolean
+gs_plugin_app_install (GsPlugin *plugin,
+ GsApp *app,
+ GCancellable *cancellable,
+ GError **error)
+{
+ g_autofree gchar *filename = NULL;
+ gboolean success = FALSE;
+
+ if (!app_is_ours (app))
+ return TRUE;
+
+ if (gs_app_get_source_default (app) == NULL)
+ return TRUE;
+
+ switch (gs_app_get_state (app)) {
+ case AS_APP_STATE_AVAILABLE:
+ case AS_APP_STATE_UPDATABLE:
+ gs_app_set_state (app, AS_APP_STATE_INSTALLING);
+ success = aptd_transaction (plugin, "InstallPackages", app, NULL, NULL, cancellable, error);
+ break;
+ case AS_APP_STATE_AVAILABLE_LOCAL:
+ filename = g_file_get_path (gs_app_get_local_file (app));
+ gs_app_set_state (app, AS_APP_STATE_INSTALLING);
+ success = aptd_transaction (plugin, "InstallFile", app, NULL,
+ g_variant_new_parsed ("(%s, true)", filename),
+ cancellable, error);
+ break;
+ default:
+ g_set_error (error,
+ GS_PLUGIN_ERROR,
+ GS_PLUGIN_ERROR_FAILED,
+ "do not know how to install app in state %s",
+ as_app_state_to_string (gs_app_get_state (app)));
+ return FALSE;
+ }
+
+
+ if (success)
+ gs_app_set_state (app, AS_APP_STATE_INSTALLED);
+ else
+ gs_app_set_state (app, AS_APP_STATE_AVAILABLE);
+
+ return success;
+}
+
+gboolean
+gs_plugin_app_remove (GsPlugin *plugin,
+ GsApp *app,
+ GCancellable *cancellable,
+ GError **error)
+{
+ if (!app_is_ours (app))
+ return TRUE;
+
+ if (gs_app_get_source_default (app) == NULL)
+ return TRUE;
+
+ gs_app_set_state (app, AS_APP_STATE_REMOVING);
+ if (aptd_transaction (plugin, "RemovePackages", app, NULL, NULL, cancellable, error))
+ gs_app_set_state (app, AS_APP_STATE_AVAILABLE);
+ else {
+ gs_app_set_state (app, AS_APP_STATE_INSTALLED);
+ return FALSE;
+ }
+
+ return TRUE;
+}
+
+gboolean
+gs_plugin_refresh (GsPlugin *plugin,
+ guint cache_age,
+ GsPluginRefreshFlags flags,
+ GCancellable *cancellable,
+ GError **error)
+{
+ if ((flags & GS_PLUGIN_REFRESH_FLAGS_METADATA) == 0)
+ return TRUE;
+
+ if (!aptd_transaction (plugin, "UpdateCache", NULL, NULL, NULL, cancellable, error))
+ return FALSE;
+
+ unload_apt_db (plugin);
+
+ gs_plugin_updates_changed (plugin);
+
+ return TRUE;
+}
+
+gboolean
+gs_plugin_add_updates (GsPlugin *plugin,
+ GsAppList *list,
+ GCancellable *cancellable,
+ GError **error)
+{
+ GsPluginData *priv = gs_plugin_get_data (plugin);
+ GList *link;
+ g_autoptr(GMutexLocker) locker = NULL;
+
+ if (!load_apt_db (plugin, error))
+ return FALSE;
+
+ locker = g_mutex_locker_new (&priv->mutex);
+
+ for (link = priv->updatable_packages; link; link = link->next) {
+ PackageInfo *info = (PackageInfo *) link->data;
+ g_autoptr(GsApp) app = NULL;
+
+ if (!is_allowed_section (info))
+ continue;
+
+ app = gs_app_new (NULL);
+ gs_app_set_management_plugin (app, "apt");
+ gs_app_set_name (app, GS_APP_QUALITY_LOWEST, info->name);
+ gs_app_set_kind (app, AS_APP_KIND_GENERIC);
+ gs_app_set_version (app, info->installed_version);
+ gs_app_set_update_version (app, info->update_version);
+ gs_app_add_source (app, info->name);
+ gs_app_list_add (list, app);
+ }
+
+ return TRUE;
+}
+
+static void
+set_list_state (GsAppList *apps,
+ AsAppState state)
+{
+ guint length;
+ guint j;
+ GsApp *app_i;
+ GsApp *app_j;
+ GPtrArray *related;
+
+ length = gs_app_list_length (apps);
+
+ for (guint i = 0; i < length; ++i) {
+ app_i = gs_app_list_index (apps, i);
+ gs_app_set_state (app_i, state);
+
+ if (g_strcmp0 (gs_app_get_id (app_i), "os-update.virtual") == 0) {
+ related = gs_app_get_related (app_i);
+
+ for (j = 0; j < related->len; j++) {
+ app_j = GS_APP (g_ptr_array_index (related, j));
+ gs_app_set_state (app_j, state);
+ }
+ }
+ }
+}
+
+gboolean
+gs_plugin_update (GsPlugin *plugin,
+ GsAppList *apps,
+ GCancellable *cancellable,
+ GError **error)
+{
+ GsApp *app_i;
+ guint length;
+
+ length = gs_app_list_length (apps);
+
+ for (guint i = 0; i < length; ++i) {
+ app_i = gs_app_list_index (apps, i);
+
+ if (g_strcmp0 (gs_app_get_id (app_i), "org.gnome.Software.OsUpdate") == 0) {
+ set_list_state (apps, AS_APP_STATE_INSTALLING);
+
+ if (aptd_transaction (plugin,
+ "UpgradeSystem",
+ NULL,
+ apps,
+ g_variant_new_parsed ("(false,)"),
+ cancellable, error)) {
+ set_list_state (apps, AS_APP_STATE_INSTALLED);
+
+ unload_apt_db (plugin);
+
+ gs_plugin_updates_changed (plugin);
+
+ return TRUE;
+ } else {
+ set_list_state (apps, AS_APP_STATE_UPDATABLE_LIVE);
+
+ return FALSE;
+ }
+ }
+ }
+
+ return TRUE;
+}
+
+gboolean
+gs_plugin_update_app (GsPlugin *plugin,
+ GsApp *app,
+ GCancellable *cancellable,
+ GError **error)
+{
+ GPtrArray *apps;
+ GsApp *app_i;
+ guint i;
+ GVariantBuilder builder;
+
+ if (g_strcmp0 (gs_app_get_id (app), "org.gnome.Software.OsUpdate") == 0) {
+ apps = gs_app_get_related (app);
+
+ g_variant_builder_init (&builder, G_VARIANT_TYPE ("(as)"));
+ g_variant_builder_open (&builder, G_VARIANT_TYPE ("as"));
+
+ gs_app_set_state (app, AS_APP_STATE_INSTALLING);
+
+ for (i = 0; i < apps->len; i++) {
+ app_i = GS_APP (g_ptr_array_index (apps, i));
+ gs_app_set_state (app_i, AS_APP_STATE_INSTALLING);
+ g_variant_builder_add (&builder, "s", gs_app_get_source_default (app_i));
+ }
+
+ g_variant_builder_close (&builder);
+
+ if (aptd_transaction (plugin,
+ "UpgradePackages",
+ app,
+ NULL,
+ g_variant_builder_end (&builder),
+ cancellable, error)) {
+ gs_app_set_state (app, AS_APP_STATE_INSTALLED);
+
+ for (i = 0; i < apps->len; i++)
+ gs_app_set_state (GS_APP (g_ptr_array_index (apps, i)),
+ AS_APP_STATE_INSTALLED);
+
+ unload_apt_db (plugin);
+
+ gs_plugin_updates_changed (plugin);
+ } else {
+ gs_app_set_state (app, AS_APP_STATE_UPDATABLE_LIVE);
+
+ for (i = 0; i < apps->len; i++)
+ gs_app_set_state (GS_APP (g_ptr_array_index (apps, i)),
+ AS_APP_STATE_UPDATABLE_LIVE);
+
+ return FALSE;
+ }
+ } else if (app_is_ours (app)) {
+ gs_app_set_state (app, AS_APP_STATE_INSTALLING);
+
+ if (aptd_transaction (plugin, "UpgradePackages", app, NULL, NULL, cancellable, error)) {
+ gs_app_set_state (app, AS_APP_STATE_INSTALLED);
+
+ unload_apt_db (plugin);
+
+ gs_plugin_updates_changed (plugin);
+ } else {
+ gs_app_set_state (app, AS_APP_STATE_UPDATABLE_LIVE);
+
+ return FALSE;
+ }
+ }
+
+ return TRUE;
+}
+
+gboolean
+gs_plugin_launch (GsPlugin *plugin,
+ GsApp *app,
+ GCancellable *cancellable,
+ GError **error)
+{
+ if (!app_is_ours (app))
+ return TRUE;
+
+ return gs_plugin_app_launch (plugin, app, error);
+}
+
+/* vim: set noexpandtab ts=8 sw=8: */
diff --git a/plugins/apt/org.gnome.Software.Plugin.Apt.metainfo.xml.in
b/plugins/apt/org.gnome.Software.Plugin.Apt.metainfo.xml.in
new file mode 100644
index 0000000..dc4ca7c
--- /dev/null
+++ b/plugins/apt/org.gnome.Software.Plugin.Apt.metainfo.xml.in
@@ -0,0 +1,12 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- Copyright 2017 Canonical Ltd -->
+<component type="addon">
+ <id>org.gnome.Software.Plugin.Apt</id>
+ <extends>org.gnome.Software.desktop</extends>
+ <name>APT Support</name>
+ <summary>APT package support</summary>
+ <url type="homepage">https://www.ubuntu.com/desktop</url>
+ <metadata_license>CC0-1.0</metadata_license>
+ <project_license>GPL-2.0+</project_license>
+ <update_contact>ubuntu-desktop_at_lists.ubuntu.com</update_contact>
+</component>
diff --git a/plugins/meson.build b/plugins/meson.build
index 24a0ad4..43f17f9 100644
--- a/plugins/meson.build
+++ b/plugins/meson.build
@@ -34,6 +34,18 @@ shared_module('gs_plugin_dpkg',
dependencies : plugin_libs
)
+shared_module('gs_plugin_apt',
+ sources : 'gs-plugin-apt.cc',
+ include_directories : [
+ include_directories('..'),
+ include_directories('../..'),
+ ],
+ install : true,
+ install_dir: plugin_dir,
+ cpp_args : cargs,
+ dependencies : [plugin_libs, libapt ]
+)
+
shared_module('gs_plugin_generic-updates',
sources : 'gs-plugin-generic-updates.c',
include_directories : [
diff --git a/po/POTFILES.in b/po/POTFILES.in
index a18019f..8655feb 100644
--- a/po/POTFILES.in
+++ b/po/POTFILES.in
@@ -69,6 +69,7 @@ plugins/core/gs-desktop-common.c
plugins/external-appstream/gs-install-appstream.c
plugins/fedora-distro-upgrades/gs-plugin-fedora-distro-upgrades.c
plugins/core/gs-plugin-generic-updates.c
+plugins/apt/org.gnome.Software.Plugin.Apt.metainfo.xml.in
plugins/epiphany/org.gnome.Software.Plugin.Epiphany.metainfo.xml.in
plugins/flatpak/org.gnome.Software.Plugin.Flatpak.metainfo.xml.in
plugins/fwupd/org.gnome.Software.Plugin.Fwupd.metainfo.xml.in
[
Date Prev][
Date Next] [
Thread Prev][
Thread Next]
[
Thread Index]
[
Date Index]
[
Author Index]