[gnome-software/wip/ubuntu-xenial: 204/208] Merge tag 'GNOME_SOFTWARE_3_20_2' into wip/ubuntu-xenial
- From: Robert Ancell <rancell src gnome org>
- To: commits-list gnome org
- Cc:
- Subject: [gnome-software/wip/ubuntu-xenial: 204/208] Merge tag 'GNOME_SOFTWARE_3_20_2' into wip/ubuntu-xenial
- Date: Mon, 15 Aug 2016 04:14:52 +0000 (UTC)
commit 341c0baeda013b8421afb20df309ce2961f1c262
Merge: ab2fa9c b937c30
Author: Robert Ancell <robert ancell canonical com>
Date: Mon Aug 15 15:54:26 2016 +1200
Merge tag 'GNOME_SOFTWARE_3_20_2' into wip/ubuntu-xenial
RELEASE | 6 +-
configure.ac | 19 +-
contrib/gnome-software.spec.in | 1 +
data/Makefile.am | 1 +
data/appdata/org.gnome.Software.appdata.xml.in | 27 +
data/upgrade-bg.png | Bin 0 -> 308691 bytes
doc/kudos.md | 42 +-
doc/update-fedora-tagger.sh | 1 -
po/LINGUAS | 1 +
po/POTFILES.in | 13 +-
po/fur.po | 2833 ++++++++++++++++++++
po/gd.po | 1673 ++++++++----
po/ja.po | 655 +++--
po/lv.po | 402 ++--
po/pt_BR.po | 55 +-
po/ru.po | 130 +-
po/sr.po | 16 +-
src/Makefile.am | 23 +-
src/gnome-software.gresource.xml | 18 +-
src/gs-app-folder-dialog.c | 2 +-
...pp-folder-dialog.ui => gs-app-folder-dialog.ui} | 0
src/gs-app-row.c | 11 +-
src/gs-app-tile.c | 2 +-
src/{app-tile.ui => gs-app-tile.ui} | 0
src/gs-app.c | 405 ++--
src/gs-app.h | 19 +-
src/gs-application.c | 4 +-
src/gs-category-tile.c | 2 +-
src/{category-tile.ui => gs-category-tile.ui} | 0
src/gs-category.c | 3 +
src/gs-cmd.c | 30 +-
src/gs-feature-tile.c | 2 +-
src/{feature-tile.ui => gs-feature-tile.ui} | 0
src/{menus.ui => gs-menus.ui} | 0
src/gs-page.c | 28 +
src/gs-plugin-loader-sync.c | 1 -
src/gs-plugin-loader.c | 444 ++--
src/gs-plugin-loader.h | 8 +-
src/gs-plugin.c | 137 +
src/gs-plugin.h | 54 +-
src/gs-popular-tile.c | 2 +-
src/{popular-tile.ui => gs-popular-tile.ui} | 0
src/gs-screenshot-image.c | 12 +-
...{screenshot-image.ui => gs-screenshot-image.ui} | 0
src/gs-self-test.c | 801 +++----
src/gs-shell-details.c | 34 +-
src/gs-shell-details.ui | 8 +-
src/gs-shell-extras.c | 1 -
src/gs-shell-installed.c | 9 +-
src/gs-shell-moderate.c | 2 +-
src/gs-shell-search.c | 2 +-
src/gs-shell-updates.c | 429 ++--
src/gs-shell-updates.ui | 48 +-
src/gs-shell.c | 1 +
src/gs-update-dialog.c | 23 +-
src/gs-update-monitor.c | 9 +-
src/gs-upgrade-banner.c | 112 +-
src/gs-upgrade-banner.h | 7 +-
src/gs-upgrade-banner.ui | 125 +-
src/gs-utils.c | 135 +-
src/gs-utils.h | 5 +
src/gtk-style.css | 13 +-
src/plugins/Makefile.am | 49 +-
src/plugins/gs-appstream.c | 120 +-
src/{ => plugins}/gs-markdown.c | 0
src/{ => plugins}/gs-markdown.h | 0
src/plugins/gs-plugin-appstream.c | 129 +-
src/plugins/gs-plugin-apt.cc | 2 +-
src/plugins/gs-plugin-dummy.c | 461 +++-
src/plugins/gs-plugin-epiphany.c | 52 +-
src/plugins/gs-plugin-fedora-distro-upgrades.c | 201 ++-
src/plugins/gs-plugin-fedora-tagger-usage.c | 4 +
src/plugins/gs-plugin-fwupd.c | 49 +-
src/plugins/gs-plugin-hardcoded-blacklist.c | 32 +-
src/plugins/gs-plugin-hardcoded-featured.c | 1 +
src/plugins/gs-plugin-icons.c | 59 +-
src/plugins/gs-plugin-limba.c | 70 +-
src/plugins/gs-plugin-menu-spec-refine.c | 46 +-
src/plugins/gs-plugin-moduleset.c | 4 +-
...s-plugin-xdg-app-reviews.c => gs-plugin-odrs.c} | 198 +-
src/plugins/gs-plugin-packagekit-history.c | 15 +-
src/plugins/gs-plugin-packagekit-offline.c | 2 +-
src/plugins/gs-plugin-packagekit-origin.c | 53 +-
src/plugins/gs-plugin-packagekit-proxy.c | 47 +-
src/plugins/gs-plugin-packagekit-refine.c | 91 +-
src/plugins/gs-plugin-packagekit-refresh.c | 89 +-
src/plugins/gs-plugin-packagekit-upgrade.c | 141 +
src/plugins/gs-plugin-packagekit.c | 134 +-
src/plugins/gs-plugin-provenance.c | 78 +-
src/plugins/gs-plugin-self-test.c | 68 -
src/plugins/gs-plugin-systemd-updates.c | 36 +-
src/plugins/gs-plugin-ubuntu-reviews.c | 36 +-
src/plugins/gs-plugin-xdg-app.c | 200 +-
src/plugins/gs-self-test.c | 185 ++-
src/plugins/menu-spec-common.c | 354 ++--
src/plugins/menu-spec-common.h | 1 +
src/plugins/packagekit-common.c | 102 +-
src/plugins/packagekit-common.h | 3 +
98 files changed, 8128 insertions(+), 3830 deletions(-)
---
diff --cc po/POTFILES.in
index bbbd21d,dcb1383..02f8681
--- a/po/POTFILES.in
+++ b/po/POTFILES.in
@@@ -55,15 -55,7 +55,10 @@@ src/gs-update-monitor.
src/gs-upgrade-banner.c
[type: gettext/glade]src/gs-upgrade-banner.ui
src/gs-utils.c
- [type: gettext/glade]src/menus.ui
+ [type: gettext/glade]src/gs-menus.ui
src/org.gnome.Software.desktop.in
- src/plugins/gs-plugin-appstream.c
- src/plugins/gs-plugin-epiphany.c
- src/plugins/gs-plugin-moduleset.c
- src/plugins/gs-plugin-packagekit.c
- src/plugins/gs-plugin-packagekit-refine.c
+src/plugins/gs-ubuntuone-dialog.c
+[type: gettext/glade]src/plugins/gs-ubuntuone-dialog.ui
+src/plugins/gs-plugin-snap.c
src/plugins/menu-spec-common.c
- [type: gettext/glade]src/popular-tile.ui
+ [type: gettext/glade]src/gs-popular-tile.ui
diff --cc src/gs-app-row.c
index 623d840,b40dab0..193e88e
--- a/src/gs-app-row.c
+++ b/src/gs-app-row.c
@@@ -85,6 -84,15 +84,16 @@@ gs_app_row_get_description (GsAppRow *a
const gchar *tmp = NULL;
g_autofree gchar *escaped = NULL;
+ /* convert the markdown update description into PangoMarkup */
- if (priv->show_update &&
++ /* NOTE: Disabled on Ubuntu since the Debian changelog doesn't display well */
++ /*if (priv->show_update &&
+ (gs_app_get_state (priv->app) == AS_APP_STATE_UPDATABLE ||
+ gs_app_get_state (priv->app) == AS_APP_STATE_UPDATABLE_LIVE)) {
+ tmp = gs_app_get_update_details (priv->app);
+ if (tmp != NULL && tmp[0] != '\0')
+ return g_string_new (tmp);
- }
++ }*/
+
if (gs_app_get_state (priv->app) == AS_APP_STATE_UNAVAILABLE)
return g_string_new (gs_app_get_summary_missing (priv->app));
diff --cc src/gs-app.c
index 2770a03,6effba8..ad29b87
--- a/src/gs-app.c
+++ b/src/gs-app.c
@@@ -101,9 -103,8 +103,9 @@@ struct _GsAp
guint64 kudos;
gboolean to_be_installed;
AsAppQuirk quirk;
- gboolean licence_is_free;
+ gboolean license_is_free;
GsApp *runtime;
+ GFile *local_file;
};
enum {
@@@ -237,33 -260,32 +261,36 @@@ gs_app_to_string (GsApp *app
im = as_screenshot_get_image (ss, 0, 0);
if (im == NULL)
continue;
- g_string_append_printf (str, "\tscreenshot-%02i:\t%s [%s]\n",
- i, as_image_get_url (im),
- tmp != NULL ? tmp : "<none>");
+ key = g_strdup_printf ("screenshot-%02i", i);
+ gs_app_kv_printf (str, key, "%s [%s]",
+ as_image_get_url (im),
+ tmp != NULL ? tmp : "<none>");
}
for (i = 0; i < app->sources->len; i++) {
+ g_autofree gchar *key = NULL;
tmp = g_ptr_array_index (app->sources, i);
- g_string_append_printf (str, "\tsource-%02i:\t%s\n", i, tmp);
+ key = g_strdup_printf ("source-%02i", i);
+ gs_app_kv_lpad (str, key, tmp);
}
for (i = 0; i < app->source_ids->len; i++) {
+ g_autofree gchar *key = NULL;
tmp = g_ptr_array_index (app->source_ids, i);
- g_string_append_printf (str, "\tsource-id-%02i:\t%s\n", i, tmp);
+ key = g_strdup_printf ("source-id-%02i", i);
+ gs_app_kv_lpad (str, key, tmp);
}
+ if (app->local_file != NULL) {
+ g_autofree gchar *fn = g_file_get_path (app->local_file);
- g_string_append_printf (str, "\tlocal-filename:\t%s\n", fn);
++ gs_app_kv_lpad (str, "local-filename", fn);
+ }
tmp = g_hash_table_lookup (app->urls, as_url_kind_to_string (AS_URL_KIND_HOMEPAGE));
if (tmp != NULL)
- g_string_append_printf (str, "\turl{homepage}:\t%s\n", tmp);
- if (app->licence != NULL)
- g_string_append_printf (str, "\tlicence:\t%s\n", app->licence);
- g_string_append_printf (str, "\topen source:\t%s\n",
- gs_app_get_license_is_free (app) ? "yes" : "no");
+ gs_app_kv_lpad (str, "url{homepage}", tmp);
+ if (app->license != NULL)
+ gs_app_kv_lpad (str, "license", app->license);
if (app->management_plugin != NULL)
- g_string_append_printf (str, "\tmanagement-plugin:\t%s\n", app->management_plugin);
+ gs_app_kv_lpad (str, "management-plugin", app->management_plugin);
if (app->summary_missing != NULL)
- g_string_append_printf (str, "\tsummary-missing:\t%s\n", app->summary_missing);
+ gs_app_kv_lpad (str, "summary-missing", app->summary_missing);
if (app->menu_path != NULL &&
app->menu_path[0] != NULL &&
app->menu_path[0][0] != '\0') {
@@@ -2359,8 -2306,8 +2354,10 @@@ gs_app_finalize (GObject *object
g_ptr_array_unref (app->categories);
if (app->keywords != NULL)
g_ptr_array_unref (app->keywords);
+ if (app->last_error != NULL)
+ g_error_free (app->last_error);
+ if (app->local_file != NULL)
+ g_object_unref (app->local_file);
G_OBJECT_CLASS (gs_app_parent_class)->finalize (object);
}
diff --cc src/gs-plugin-loader.c
index 131e14e,e128f0c..291726e
--- a/src/gs-plugin-loader.c
+++ b/src/gs-plugin-loader.c
@@@ -2721,15 -2660,9 +2670,9 @@@ gs_plugin_loader_app_action_async (GsPl
break;
case GS_PLUGIN_LOADER_ACTION_LAUNCH:
state->function_name = "gs_plugin_launch";
- state->state_success = AS_APP_STATE_UNKNOWN;
- state->state_failure = AS_APP_STATE_UNKNOWN;
break;
- case GS_PLUGIN_LOADER_ACTION_OFFLINE_UPDATE_CANCEL:
- state->function_name = "gs_plugin_offline_update_cancel";
+ case GS_PLUGIN_LOADER_ACTION_UPDATE_CANCEL:
+ state->function_name = "gs_plugin_update_cancel";
- state->state_success = AS_APP_STATE_UNKNOWN;
- state->state_failure = AS_APP_STATE_UNKNOWN;
break;
default:
g_assert_not_reached ();
@@@ -3940,8 -3823,7 +3887,7 @@@ gs_plugin_loader_update_async (GsPlugin
/* run in a thread */
task = g_task_new (plugin_loader, cancellable, callback, user_data);
g_task_set_task_data (task, state, (GDestroyNotify) gs_plugin_loader_free_async_state);
- g_task_set_return_on_cancel (task, TRUE);
- g_task_run_in_thread (task, gs_plugin_loader_offline_update_thread_cb);
+ g_task_run_in_thread (task, gs_plugin_loader_update_thread_cb);
}
/**
diff --cc src/gs-plugin.h
index 731a219,c1862e1..49d0272
--- a/src/gs-plugin.h
+++ b/src/gs-plugin.h
@@@ -116,9 -118,26 +118,28 @@@ typedef enum
GS_PLUGIN_REFINE_FLAGS_LAST
} GsPluginRefineFlags;
+ /**
+ * GsPluginRefreshFlags:
+ * @GS_PLUGIN_REFRESH_FLAGS_NONE: Generate new metadata if possible
+ * @GS_PLUGIN_REFRESH_FLAGS_METADATA: Download new metadata
+ * @GS_PLUGIN_REFRESH_FLAGS_PAYLOAD: Download any pending payload
+ *
+ * The flags used for refresh. Regeneration and downloading is only
+ * done if the cache is older than the %cache_age.
+ *
+ * The %GS_PLUGIN_REFRESH_FLAGS_METADATA can be used to make sure
+ * there's enough metadata to start the application.
+ * The %GS_PLUGIN_REFRESH_FLAGS_PAYLOAD flag should only be used when
+ * the session is idle and bandwidth is unmetered as the amount of data
+ * and IO may be large.
+ **/
typedef enum {
- GS_PLUGIN_REFRESH_FLAGS_UPDATES = 1 << 0,
- GS_PLUGIN_REFRESH_FLAGS_UI = 1 << 1,
+ GS_PLUGIN_REFRESH_FLAGS_NONE = 0,
+ GS_PLUGIN_REFRESH_FLAGS_METADATA = 1 << 0,
+ GS_PLUGIN_REFRESH_FLAGS_PAYLOAD = 1 << 1,
++ GS_PLUGIN_REFRESH_FLAGS_UPDATES = 1 << 2,
++ GS_PLUGIN_REFRESH_FLAGS_UI = 1 << 3,
+ /*< private >*/
GS_PLUGIN_REFRESH_FLAGS_LAST
} GsPluginRefreshFlags;
diff --cc src/gs-self-test.c
index 98b9911,7a0cb1b..820d9e9
--- a/src/gs-self-test.c
+++ b/src/gs-self-test.c
@@@ -690,44 -419,10 +419,43 @@@ gs_plugin_loader_webapps_func (GsPlugin
&error);
g_assert_no_error (error);
g_assert (ret);
- g_assert_cmpint (gs_app_get_state (app), ==, AS_APP_STATE_UNAVAILABLE);
-
- g_unlink (path);
+ g_assert_cmpint (gs_app_get_state (app), ==, AS_APP_STATE_AVAILABLE);
+ g_assert (gs_app_get_pixbuf (app) != NULL);
}
+static void
+gs_plugin_loader_dpkg_func (GsPluginLoader *plugin_loader)
+{
+ g_autoptr(GsApp) app = NULL;
+ g_autoptr(GError) error = NULL;
+ g_autofree gchar *fn = NULL;
+
+ /* no dpkg, abort */
+ if (!gs_plugin_loader_get_enabled (plugin_loader, "dpkg"))
+ return;
+
+ /* load local file */
+ fn = gs_test_get_filename ("tests/chiron-1.1-1.deb");
+ g_assert (fn != NULL);
+ app = gs_plugin_loader_filename_to_app (plugin_loader,
+ fn,
+ GS_PLUGIN_REFINE_FLAGS_DEFAULT,
+ NULL,
+ &error);
+ g_assert_no_error (error);
+ g_assert (app != NULL);
+ g_assert_cmpstr (gs_app_get_id (app), ==, NULL);
+ g_assert_cmpstr (gs_app_get_source_default (app), ==, "chiron");
+ g_assert_cmpstr (gs_app_get_url (app, AS_URL_KIND_HOMEPAGE), ==, "http://127.0.0.1/");
+ g_assert_cmpstr (gs_app_get_name (app), ==, "chiron");
+ g_assert_cmpstr (gs_app_get_version (app), ==, "1.1-1");
+ g_assert_cmpstr (gs_app_get_summary (app), ==, "Single line synopsis");
+ g_assert_cmpstr (gs_app_get_description (app), ==,
+ "This is the first paragraph in the example "
+ "package control file.\nThis is the second paragraph.");
+ g_assert (gs_app_get_local_file (app) != NULL);
+}
+
int
main (int argc, char **argv)
{
diff --cc src/gs-shell-details.c
index 7414701,7e1982f..09a6494
--- a/src/gs-shell-details.c
+++ b/src/gs-shell-details.c
@@@ -81,8 -80,7 +81,8 @@@ struct _GsShellDetail
GtkWidget *label_details_category_value;
GtkWidget *label_details_developer_title;
GtkWidget *label_details_developer_value;
- GtkWidget *label_details_licence_title;
- GtkWidget *label_details_licence_value;
++ GtkWidget *label_details_license_title;
+ GtkWidget *label_details_license_value;
GtkWidget *label_details_origin_title;
GtkWidget *label_details_origin_value;
GtkWidget *label_details_size_value;
@@@ -664,19 -662,15 +664,19 @@@ gs_shell_details_refresh_all (GsShellDe
gtk_widget_set_visible (self->label_details_developer_value, TRUE);
}
- /* set the licence */
+ /* set the license */
tmp = gs_app_get_license (self->app);
if (tmp == NULL) {
- /* TRANSLATORS: this is where the licence is not known */
- gtk_label_set_label (GTK_LABEL (self->label_details_licence_value), C_("license", "Unknown"));
- gtk_widget_set_tooltip_text (self->label_details_licence_value, NULL);
- gtk_widget_set_visible (self->label_details_licence_title, FALSE);
- gtk_widget_set_visible (self->label_details_licence_value, FALSE);
+ /* TRANSLATORS: this is where the license is not known */
+ gtk_label_set_label (GTK_LABEL (self->label_details_license_value), C_("license", "Unknown"));
+ gtk_widget_set_tooltip_text (self->label_details_license_value, NULL);
++ gtk_widget_set_visible (self->label_details_license_title, FALSE);
++ gtk_widget_set_visible (self->label_details_license_value, FALSE);
} else {
- gtk_label_set_markup (GTK_LABEL (self->label_details_licence_value), tmp);
- gtk_widget_set_tooltip_text (self->label_details_licence_value, NULL);
- gtk_widget_set_visible (self->label_details_licence_title, TRUE);
- gtk_widget_set_visible (self->label_details_licence_value, TRUE);
+ gtk_label_set_markup (GTK_LABEL (self->label_details_license_value), tmp);
+ gtk_widget_set_tooltip_text (self->label_details_license_value, NULL);
++ gtk_widget_set_visible (self->label_details_license_title, TRUE);
++ gtk_widget_set_visible (self->label_details_license_value, TRUE);
}
/* set version */
@@@ -1618,8 -1594,7 +1618,8 @@@ gs_shell_details_class_init (GsShellDet
gtk_widget_class_bind_template_child (widget_class, GsShellDetails, label_details_category_value);
gtk_widget_class_bind_template_child (widget_class, GsShellDetails, label_details_developer_title);
gtk_widget_class_bind_template_child (widget_class, GsShellDetails, label_details_developer_value);
- gtk_widget_class_bind_template_child (widget_class, GsShellDetails, label_details_licence_title);
- gtk_widget_class_bind_template_child (widget_class, GsShellDetails, label_details_licence_value);
++ gtk_widget_class_bind_template_child (widget_class, GsShellDetails, label_details_license_title);
+ gtk_widget_class_bind_template_child (widget_class, GsShellDetails, label_details_license_value);
gtk_widget_class_bind_template_child (widget_class, GsShellDetails, label_details_origin_title);
gtk_widget_class_bind_template_child (widget_class, GsShellDetails, label_details_origin_value);
gtk_widget_class_bind_template_child (widget_class, GsShellDetails, label_details_size_value);
diff --cc src/gs-shell-updates.c
index ceeceda,1e3156a..40a910f
--- a/src/gs-shell-updates.c
+++ b/src/gs-shell-updates.c
@@@ -1003,17 -979,32 +1025,34 @@@ gs_shell_updates_button_update_all_cb (
g_autoptr(GError) error = NULL;
g_autoptr(GList) apps = NULL;
+ gtk_widget_set_sensitive (GTK_WIDGET (self->button_update_all), FALSE);
+
/* do the offline update */
apps = gs_update_list_get_apps (GS_UPDATE_LIST (self->list_box_updates));
- gs_plugin_loader_offline_update_async (self->plugin_loader,
- apps,
- self->cancellable,
- (GAsyncReadyCallback) gs_shell_updates_offline_update_cb,
- self);
+ gs_plugin_loader_update_async (self->plugin_loader,
+ apps,
+ self->cancellable,
+ (GAsyncReadyCallback) gs_shell_updates_perform_update_cb,
+ self);
}
+ typedef struct {
+ GsApp *app;
+ GsShellUpdates *self;
+ } GsPageHelper;
+
+ static void
+ gs_page_helper_free (GsPageHelper *helper)
+ {
+ if (helper->app != NULL)
+ g_object_unref (helper->app);
+ if (helper->self != NULL)
+ g_object_unref (helper->self);
+ g_slice_free (GsPageHelper, helper);
+ }
+
+ G_DEFINE_AUTOPTR_CLEANUP_FUNC(GsPageHelper, gs_page_helper_free);
+
static void
upgrade_download_finished_cb (GObject *source,
GAsyncResult *res,
diff --cc src/gs-utils.c
index 9bc2572,956a721..7467b94
--- a/src/gs-utils.c
+++ b/src/gs-utils.c
@@@ -535,27 -564,63 +566,87 @@@ gs_utils_is_current_desktop (const gcha
}
/**
+ * gs_utils_get_desktop_app_info:
+ */
+GDesktopAppInfo *
+gs_utils_get_desktop_app_info (const gchar *id)
+{
+ GDesktopAppInfo *app_info;
+
+ /* try to get the standard app-id */
+ app_info = g_desktop_app_info_new (id);
+
+ /* KDE is a special project because it believes /usr/share/applications
+ * isn't KDE enough. For this reason we support falling back to the
+ * "kde4-" prefixed ID to avoid educating various self-righteous
+ * upstreams about the correct ID to use in the AppData file. */
+ if (app_info == NULL) {
+ g_autofree gchar *kde_id = NULL;
+ kde_id = g_strdup_printf ("%s-%s", "kde4", id);
+ app_info = g_desktop_app_info_new (kde_id);
+ }
+
+ return app_info;
+}
+
++/**
+ * gs_utils_widget_css_parsing_error_cb:
+ */
+ static void
+ gs_utils_widget_css_parsing_error_cb (GtkCssProvider *provider,
+ GtkCssSection *section,
+ GError *error,
+ gpointer user_data)
+ {
+ g_warning ("CSS parse error %i:%i: %s",
+ gtk_css_section_get_start_line (section),
+ gtk_css_section_get_start_position (section),
+ error->message);
+ }
+
+ /**
+ * gs_utils_widget_set_custom_css:
+ **/
+ void
+ gs_utils_widget_set_custom_css (GtkWidget *widget, const gchar *css)
+ {
+ GString *str = g_string_sized_new (1024);
+ GtkStyleContext *context;
+ g_autofree gchar *class_name = NULL;
+ g_autoptr(GtkCssProvider) provider = NULL;
+
+ /* invalid */
+ if (css == NULL)
+ return;
+
+ /* make into a proper CSS class */
+ class_name = g_strdup_printf ("themed-widget_%p", widget);
+ g_string_append_printf (str, ".%s {\n", class_name);
+ g_string_append_printf (str, "%s\n", css);
+ g_string_append (str, "}");
+
+ g_string_append_printf (str, ".%s:hover {\n", class_name);
+ g_string_append (str, " opacity: 0.9;\n");
+ g_string_append (str, "}\n");
+
+ g_debug ("using custom CSS %s", str->str);
+
+ /* set the custom CSS class */
+ context = gtk_widget_get_style_context (widget);
+ gtk_style_context_add_class (context, class_name);
+
+ /* set up custom provider and store on the widget */
+ provider = gtk_css_provider_new ();
+ g_signal_connect (provider, "parsing-error",
+ G_CALLBACK (gs_utils_widget_css_parsing_error_cb), NULL);
+ gtk_style_context_add_provider_for_screen (gdk_screen_get_default (),
+ GTK_STYLE_PROVIDER (provider),
+ GTK_STYLE_PROVIDER_PRIORITY_APPLICATION);
+ gtk_css_provider_load_from_data (provider, str->str, -1, NULL);
+ g_object_set_data_full (G_OBJECT (widget),
+ "GnomeSoftware::provider",
+ g_object_ref (provider),
+ g_object_unref);
+ }
+
/* vim: set noexpandtab: */
diff --cc src/gs-utils.h
index 0540a9f,89d9abf..c7cc175
--- a/src/gs-utils.h
+++ b/src/gs-utils.h
@@@ -65,11 -64,12 +65,16 @@@ gchar *gs_utils_get_cachedir (const g
GError **error);
gchar *gs_utils_get_user_hash (GError **error);
GPermission *gs_utils_get_permission (const gchar *id);
+ gboolean gs_utils_is_current_desktop (const gchar *name);
+
+ gboolean gs_utils_is_current_desktop (const gchar *name);
+ void gs_utils_widget_set_custom_css (GtkWidget *widget,
+ const gchar *css);
+gboolean gs_utils_is_current_desktop (const gchar *name);
+
+GDesktopAppInfo *gs_utils_get_desktop_app_info (const gchar *id);
+
G_END_DECLS
#endif /* __GS_UTILS_H */
diff --cc src/plugins/Makefile.am
index 68b0e0d,a256cec..bfcf2fc
--- a/src/plugins/Makefile.am
+++ b/src/plugins/Makefile.am
@@@ -41,16 -35,9 +38,15 @@@ plugin_LTLIBRARIES =
libgs_plugin_menu-spec-refine.la \
libgs_plugin_fedora_distro_upgrades.la \
libgs_plugin_provenance.la \
- libgs_plugin_ubuntu-reviews.la \
libgs_plugin_fedora_tagger_usage.la \
libgs_plugin_epiphany.la \
- libgs_plugin_icons.la
+ libgs_plugin_icons.la \
+ libgs_plugin_snap.la
+
+if HAVE_APT
+plugin_LTLIBRARIES += \
+ libgs_plugin_apt.la
+endif
if HAVE_PACKAGEKIT
plugin_LTLIBRARIES += \
@@@ -76,11 -68,9 +77,11 @@@ if HAVE_LIMB
plugin_LTLIBRARIES += libgs_plugin_limba.la
endif
+if HAVE_XDG_APP
if HAVE_ODRS
- plugin_LTLIBRARIES += libgs_plugin_xdg_app_reviews.la
+ plugin_LTLIBRARIES += libgs_plugin_odrs.la
endif
+endif
libgs_plugin_dummy_la_SOURCES = gs-plugin-dummy.c
libgs_plugin_dummy_la_LIBADD = $(GS_PLUGIN_LIBS)
@@@ -142,36 -122,15 +138,36 @@@ libgs_plugin_xdg_app_la_SOURCES = gs-pl
libgs_plugin_xdg_app_la_LIBADD = $(GS_PLUGIN_LIBS) $(XDG_APP_LIBS)
libgs_plugin_xdg_app_la_LDFLAGS = -module -avoid-version
libgs_plugin_xdg_app_la_CFLAGS = $(GS_PLUGIN_CFLAGS) $(WARN_CFLAGS)
+ endif
if HAVE_ODRS
- libgs_plugin_xdg_app_reviews_la_SOURCES = gs-plugin-xdg-app-reviews.c
- libgs_plugin_xdg_app_reviews_la_LIBADD = $(GS_PLUGIN_LIBS) $(JSON_GLIB_LIBS)
- libgs_plugin_xdg_app_reviews_la_LDFLAGS = -module -avoid-version
- libgs_plugin_xdg_app_reviews_la_CFLAGS = $(GS_PLUGIN_CFLAGS) $(WARN_CFLAGS)
- endif
+ libgs_plugin_odrs_la_SOURCES = gs-plugin-odrs.c
+ libgs_plugin_odrs_la_LIBADD = $(GS_PLUGIN_LIBS) $(JSON_GLIB_LIBS)
+ libgs_plugin_odrs_la_LDFLAGS = -module -avoid-version
+ libgs_plugin_odrs_la_CFLAGS = $(GS_PLUGIN_CFLAGS) $(WARN_CFLAGS)
endif
+if HAVE_APT
+ubuntu-unity-launcher-proxy.c ubuntu-unity-launcher-proxy.h: com.canonical.Unity.Launcher.xml Makefile
+ $(AM_V_GEN) gdbus-codegen --interface-prefix com.canonical.Unity.Launcher \
+ --generate-c-code ubuntu-unity-launcher-proxy \
+ --c-namespace UbuntuUnity \
+ --annotate 'com.canonical.Unity.Launcher' \
+ org.gtk.GDBus.C.Name \
+ Launcher \
+ $<
+
+CLEANFILES = ubuntu-unity-launcher-proxy.h ubuntu-unity-launcher-proxy.c
+
+libgs_plugin_apt_la_SOURCES = \
+ ubuntu-unity-launcher-proxy.c \
+ ubuntu-unity-launcher-proxy.h \
+ gs-plugin-apt.cc
+libgs_plugin_apt_la_LIBADD = $(GS_PLUGIN_LIBS) -lapt-pkg
+libgs_plugin_apt_la_LDFLAGS = -module -avoid-version
+libgs_plugin_apt_la_CFLAGS = $(GS_PLUGIN_CFLAGS) $(WARN_CFLAGS)
+endif
+
libgs_plugin_moduleset_la_SOURCES = \
gs-moduleset.c \
gs-moduleset.h \
@@@ -206,23 -165,13 +202,25 @@@ libgs_plugin_hardcoded_blacklist_la_LIB
libgs_plugin_hardcoded_blacklist_la_LDFLAGS = -module -avoid-version
libgs_plugin_hardcoded_blacklist_la_CFLAGS = $(GS_PLUGIN_CFLAGS) $(WARN_CFLAGS)
+ if HAVE_UBUNTU_REVIEWS
libgs_plugin_ubuntu_reviews_la_SOURCES = \
- gs-plugin-ubuntu-reviews.c
-libgs_plugin_ubuntu_reviews_la_LIBADD = $(GS_PLUGIN_LIBS) $(SOUP_LIBS) $(JSON_GLIB_LIBS) $(SQLITE_LIBS)
+ gs-plugin-ubuntu-reviews.c \
+ gs-ubuntuone.h \
+ gs-ubuntuone.c \
+ gs-ubuntuone-dialog.h \
+ gs-ubuntuone-dialog.c \
+ gs-snapd.h \
+ gs-snapd.c
+libgs_plugin_ubuntu_reviews_la_LIBADD = \
+ $(GS_PLUGIN_LIBS) \
+ $(SOUP_LIBS) \
+ $(JSON_GLIB_LIBS) \
+ $(OAUTH_LIBS) \
+ $(SQLITE_LIBS) \
+ $(LIBSECRET_LIBS)
libgs_plugin_ubuntu_reviews_la_LDFLAGS = -module -avoid-version
libgs_plugin_ubuntu_reviews_la_CFLAGS = $(GS_PLUGIN_CFLAGS) $(WARN_CFLAGS)
+ endif
libgs_plugin_packagekit_la_SOURCES = \
gs-plugin-packagekit.c \
@@@ -280,31 -239,16 +288,32 @@@ libgs_plugin_packagekit_proxy_la_LIBAD
libgs_plugin_packagekit_proxy_la_LDFLAGS = -module -avoid-version
libgs_plugin_packagekit_proxy_la_CFLAGS = $(GS_PLUGIN_CFLAGS) $(WARN_CFLAGS)
+libgs_plugin_snap_la_SOURCES = \
+ gs-plugin-snap.c \
+ gs-ubuntuone.h \
+ gs-ubuntuone.c \
+ gs-ubuntuone-dialog.h \
+ gs-ubuntuone-dialog.c \
+ gs-snapd.h \
+ gs-snapd.c
+libgs_plugin_snap_la_LIBADD = \
+ $(GS_PLUGIN_LIBS) \
+ $(SOUP_LIBS) \
+ $(JSON_GLIB_LIBS) \
+ $(LIBSECRET_LIBS)
+libgs_plugin_snap_la_LDFLAGS = -module -avoid-version
+libgs_plugin_snap_la_CFLAGS = $(GS_PLUGIN_CFLAGS) $(WARN_CFLAGS)
+
- check_PROGRAMS = \
+ check_PROGRAMS = \
gs-self-test
- gs_self_test_SOURCES = \
- gs-moduleset.c \
+ gs_self_test_SOURCES = \
+ gs-markdown.c \
+ gs-moduleset.c \
gs-self-test.c
- gs_self_test_LDADD = \
- $(GLIB_LIBS) \
+ gs_self_test_LDADD = \
+ $(GLIB_LIBS) \
$(GTK_LIBS)
gs_self_test_CFLAGS = $(WARN_CFLAGS)
diff --cc src/plugins/gs-plugin-appstream.c
index 159f0a4,6dd5413..d036cb7
--- a/src/plugins/gs-plugin-appstream.c
+++ b/src/plugins/gs-plugin-appstream.c
@@@ -69,24 -66,6 +67,21 @@@ gs_plugin_appstream_store_changed_cb (A
gs_plugin_updates_changed (plugin);
}
- static gboolean gs_plugin_appstream_startup (GsPlugin *plugin, GError **error);
-
+gboolean
+gs_plugin_refresh (GsPlugin *plugin,
+ guint cache_age,
+ GsPluginRefreshFlags flags,
+ GCancellable *cancellable,
+ GError **error)
+{
+ if (flags & GS_PLUGIN_REFRESH_FLAGS_UI) {
- plugin->priv->done_init = FALSE;
- gs_plugin_appstream_startup (plugin, NULL);
++ gs_plugin_setup (plugin, cancellable, NULL);
+ gs_plugin_updates_changed (plugin);
+ }
+
+ return TRUE;
+}
+
/**
* gs_plugin_initialize:
*/
diff --cc src/plugins/gs-plugin-apt.cc
index a0c547d,0000000..04451b3
mode 100644,000000..100644
--- a/src/plugins/gs-plugin-apt.cc
+++ b/src/plugins/gs-plugin-apt.cc
@@@ -1,1147 -1,0 +1,1147 @@@
+/* -*- 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 <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 <gs-plugin.h>
+#include <gs-utils.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;
+ gint installed_size;
+} PackageInfo;
+
+#include "ubuntu-unity-launcher-proxy.h"
+
+struct GsPluginPrivate {
+ GMutex mutex;
+ gboolean loaded;
+ GHashTable *package_info;
+ GHashTable *installed_files;
+ GList *installed_packages;
+ GList *updatable_packages;
+};
+
+const gchar *
+gs_plugin_get_name (void)
+{
+ return "apt";
+}
+
+const gchar **
+gs_plugin_order_after (GsPlugin *plugin)
+{
+ static const gchar *deps[] = {
+ "appstream", /* need pkgname */
+ NULL };
+ return deps;
+}
+
+/**
+ * gs_plugin_get_conflicts:
+ */
+const gchar **
+gs_plugin_get_conflicts (GsPlugin *plugin)
+{
+
+ static const gchar *deps[] = {
+ "packagekit",
+ "packagekit-history",
+ "packagekit-offline",
+ "packagekit-origin",
+ "packagekit-proxy",
+ "packagekit-refine",
+ "packagekit-refresh",
+ "systemd-updates",
+ NULL };
+ return deps;
+}
+
+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)
+{
+ plugin->priv = GS_PLUGIN_GET_PRIVATE (GsPluginPrivate);
+ plugin->priv->package_info = g_hash_table_new_full (g_str_hash,
+ g_str_equal,
+ NULL,
+ free_package_info);
+
+ plugin->priv->installed_files = g_hash_table_new_full (g_str_hash,
+ g_str_equal,
+ g_free,
+ g_free);
+
+ g_mutex_init (&plugin->priv->mutex);
+
+ pkgInitConfig (*_config);
+ pkgInitSystem (*_config, _system);
+}
+
+void
+gs_plugin_destroy (GsPlugin *plugin)
+{
+ g_mutex_lock (&plugin->priv->mutex);
+ plugin->priv->loaded = FALSE;
+ g_clear_pointer (&plugin->priv->package_info, g_hash_table_unref);
+ g_clear_pointer (&plugin->priv->installed_files, g_hash_table_unref);
+ g_clear_pointer (&plugin->priv->installed_packages, g_list_free);
+ g_clear_pointer (&plugin->priv->updatable_packages, g_list_free);
+ g_mutex_unlock (&plugin->priv->mutex);
+ g_mutex_clear (&plugin->priv->mutex);
+}
+
+
+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;
+ 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;
+ gchar *line;
+
+ file = G_FILE (object);
+ data = (ReadListData *) user_data;
+ 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 (data->plugin->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)
+{
+ 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.FileList ())
+ return TRUE;
+
+ name = g_strdup (P.Name ());
+ info = (PackageInfo *) g_hash_table_lookup (plugin->priv->package_info, name);
+ if (info == NULL) {
+ info = g_new0 (PackageInfo, 1);
+ info->name = name;
+ g_hash_table_insert (plugin->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 (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 = 0;
+
+ if (current)
+ 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) {
+ plugin->priv->installed_packages =
+ g_list_append (plugin->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)
+ plugin->priv->updatable_packages =
+ g_list_append (plugin->priv->updatable_packages, info);
+
+ return TRUE;
+}
+
+static gboolean
+load_apt_db (GsPlugin *plugin, GError **error)
+{
+ pkgSourceList *list;
+ pkgPolicy *policy;
+ pkgCacheFile cachefile;
+ pkgCache *cache;
+ pkgCache::PkgIterator P;
+ g_autoptr(GMutexLocker) locker = g_mutex_locker_new (&plugin->priv->mutex);
+
+ if (plugin->priv->loaded)
+ return TRUE;
+
+ _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 plugin->priv->installed_files */
+ look_for_files (plugin);
+
+ plugin->priv->loaded = TRUE;
+ return TRUE;
+}
+
+static void
+unload_apt_db (GsPlugin *plugin)
+{
+ g_autoptr(GMutexLocker) locker = g_mutex_locker_new (&plugin->priv->mutex);
+
+ plugin->priv->loaded = FALSE;
+ g_hash_table_remove_all (plugin->priv->package_info);
+ g_hash_table_remove_all (plugin->priv->installed_files);
+ g_clear_pointer (&plugin->priv->installed_packages, g_list_free);
+ g_clear_pointer (&plugin->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 (plugin->soup_session, 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_official (PackageInfo *info)
+{
+ return g_strcmp0 (info->origin, "Ubuntu") == 0;
+}
+
+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_refine (GsPlugin *plugin,
+ GList **list,
+ GsPluginRefineFlags flags,
+ GCancellable *cancellable,
+ GError **error)
+{
+ GList *link;
+ GsApp *app;
+ PackageInfo *info;
+ const gchar *tmp;
+ g_autoptr(GMutexLocker) locker = NULL;
+
+ if (!load_apt_db (plugin, error))
+ return FALSE;
+
+ locker = g_mutex_locker_new (&plugin->priv->mutex);
+
+ for (link = *list; link; link = link->next) {
+ app = (GsApp *) link->data;
+ g_autofree gchar *fn = NULL;
+ g_autofree gchar *origin = NULL;
+ gchar *package = NULL;
+
+ 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 (!g_file_test (fn, G_FILE_TEST_EXISTS)) {
+ g_debug ("ignoring %s as does not exist", fn);
+ } else {
+ package = (gchar *) g_hash_table_lookup (plugin->priv->installed_files,
+ fn);
+ if (package != NULL) {
+ gs_app_add_source (app, package);
+ gs_app_set_management_plugin (app, "apt");
+ }
+ }
+ }
+
+ if (gs_app_get_source_default (app) == NULL)
+ continue;
+
+ info = (PackageInfo *) g_hash_table_lookup (plugin->priv->package_info,
gs_app_get_source_default (app));
+ if (info == NULL)
+ continue;
+
+ origin = get_origin (info);
+ gs_app_set_origin (app, origin);
+ gs_app_set_origin_ui (app, info->origin);
+
+ 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);
+ gs_app_set_origin_ui (app, info->origin);
+ }
+ if ((flags & GS_PLUGIN_REFINE_FLAGS_REQUIRE_SIZE) != 0 && gs_app_get_size (app) == 0) {
+ gs_app_set_size (app, info->installed_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_LICENCE) != 0 && is_open_source(info)) {
++ 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_UPDATE_DETAILS) != 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,
+ GList **list,
+ GCancellable *cancellable,
+ GError **error)
+{
+ GList *link;
+ g_autoptr(GMutexLocker) locker = NULL;
+
+ if (!load_apt_db (plugin, error))
+ return FALSE;
+
+ locker = g_mutex_locker_new (&plugin->priv->mutex);
+
+ for (link = plugin->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_origin (app, origin);
+ gs_app_set_origin_ui (app, info->origin);
+ gs_app_set_state (app, AS_APP_STATE_INSTALLED);
+ gs_plugin_add_app (list, app);
+ }
+
+ return TRUE;
+}
+
+typedef struct {
+ GsPlugin *plugin;
+ GMainLoop *loop;
+ GsApp *app;
+ GList *apps;
+ gchar **result;
+} TransactionData;
+
+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;
+ GList *i;
+ 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_plugin_progress_update (data->plugin, data->app, g_variant_get_int32
(value));
+ for (i = data->apps; i != NULL; i = i->next)
+ gs_plugin_progress_update (data->plugin, GS_APP (i->data),
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)
+{
+ UbuntuUnityLauncher *launcher = NULL;
+
+ g_return_if_fail (GS_IS_APP (app));
+ g_return_if_fail (transaction_path);
+
+ launcher = ubuntu_unity_launcher_proxy_new_for_bus_sync (G_BUS_TYPE_SESSION,
+ G_DBUS_PROXY_FLAGS_NONE,
+ "com.canonical.Unity.Launcher",
+ "/com/canonical/Unity/Launcher",
+ NULL, NULL);
+
+ g_return_if_fail (launcher);
+
+ ubuntu_unity_launcher_call_add_launcher_item (launcher,
+ gs_app_get_id (app),
+ transaction_path,
+ NULL, NULL, NULL);
+
+ g_object_unref (launcher);
+}
+
+static gboolean
+aptd_transaction (GsPlugin *plugin,
+ const gchar *method,
+ GsApp *app,
+ GList *apps,
+ GVariant *parameters,
+ GError **error)
+{
+ g_autoptr(GDBusConnection) conn = NULL;
+ g_autoptr(GVariant) result = NULL;
+ g_autofree gchar *transaction_path = NULL, *transaction_result = NULL;
+ g_autoptr(GMainLoop) loop = NULL;
+ guint property_signal, finished_signal;
+ TransactionData data;
+
+ conn = g_bus_get_sync (G_BUS_TYPE_SYSTEM, NULL, 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,
+ NULL,
+ error);
+ if (result == NULL)
+ return FALSE;
+ g_variant_get (result, "(s)", &transaction_path);
+ g_variant_unref (result);
+
+ if (!g_strcmp0(method, "InstallPackages"))
+ notify_unity_launcher (app, transaction_path);
+
+ loop = g_main_loop_new (NULL, FALSE);
+
+ data.plugin = plugin;
+ data.app = app;
+ data.apps = apps;
+ data.loop = loop;
+ data.result = &transaction_result;
+ 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,
+ NULL,
+ error);
+ if (result != NULL)
+ g_main_loop_run (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 (transaction_result, "exit-success") != 0) {
+ g_set_error (error,
+ GS_PLUGIN_ERROR,
+ GS_PLUGIN_ERROR_FAILED,
+ "apt transaction returned result %s", transaction_result);
+ return FALSE;
+ }
+
+ return TRUE;
+}
+
+static gboolean
+app_is_ours (GsApp *app)
+{
+ const gchar *management_plugin = gs_app_get_management_plugin (app);
+
+ // FIXME: Since appstream marks all packages as owned by PackageKit and
+ // we are replacing PackageKit we need to accept those packages
+ const gchar *our_management_plugins[] = { "PackageKit", "apt", NULL };
+
+ return g_strv_contains (our_management_plugins, management_plugin);
+}
+
+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, 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),
+ 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, 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_UPDATES) == 0)
+ return TRUE;
+
+ if (!aptd_transaction (plugin, "UpdateCache", NULL, NULL, NULL, error))
+ return FALSE;
+
+ unload_apt_db (plugin);
+
+ gs_plugin_updates_changed (plugin);
+
+ return TRUE;
+}
+
+gboolean
+gs_plugin_add_updates (GsPlugin *plugin,
+ GList **list,
+ GCancellable *cancellable,
+ GError **error)
+{
+ GList *link;
+ g_autoptr(GMutexLocker) locker = NULL;
+
+ if (!load_apt_db (plugin, error))
+ return FALSE;
+
+ locker = g_mutex_locker_new (&plugin->priv->mutex);
+
+ for (link = plugin->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_add_source (app, info->name);
+ gs_plugin_add_app (list, app);
+ }
+
+ return TRUE;
+}
+
+static void
+set_list_state (GList *apps,
+ AsAppState state)
+{
+ GList *i;
+ guint j;
+ GsApp *app_i;
+ GsApp *app_j;
+ GPtrArray *related;
+
+ for (i = apps; i != NULL; i = i->next) {
+ app_i = GS_APP (i->data);
+ 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,
+ GList *apps,
+ GCancellable *cancellable,
+ GError **error)
+{
+ GList *i;
+ GsApp *app_i;
+
+ for (i = apps; i != NULL; i = i->next) {
+ app_i = GS_APP (i->data);
+
+ if (g_strcmp0 (gs_app_get_id (app_i), "os-update.virtual") == 0) {
+ set_list_state (apps, AS_APP_STATE_INSTALLING);
+
+ if (aptd_transaction (plugin, "UpgradeSystem", NULL, apps, g_variant_new_parsed
("(false,)"), 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), "os-update.virtual") == 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),
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, 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 --cc src/plugins/gs-plugin-dummy.c
index 213c17f,059e590..4ec457b
--- a/src/plugins/gs-plugin-dummy.c
+++ b/src/plugins/gs-plugin-dummy.c
@@@ -140,34 -284,64 +284,64 @@@ gs_plugin_app_install (GsPlugin *plugin
}
/**
- * gs_plugin_refine:
- * gs_plugin_app_update:
++ * gs_plugin_update_app:
*/
gboolean
- gs_plugin_refine (GsPlugin *plugin,
- GList **list,
- GsPluginRefineFlags flags,
- GCancellable *cancellable,
- GError **error)
-gs_plugin_app_update (GsPlugin *plugin,
++gs_plugin_update_app (GsPlugin *plugin,
+ GsApp *app,
+ GCancellable *cancellable,
+ GError **error)
{
- GsApp *app;
- GList *l;
-
- for (l = *list; l != NULL; l = l->next) {
- app = GS_APP (l->data);
- if (gs_app_get_name (app) == NULL) {
- if (g_strcmp0 (gs_app_get_id (app), "gnome-boxes") == 0) {
- gs_app_set_license (app, GS_APP_QUALITY_NORMAL,
- "GPL-2.0+");
- gs_app_set_name (app, GS_APP_QUALITY_NORMAL,
- "Boxes");
- gs_app_set_url (app, AS_URL_KIND_HOMEPAGE,
- "http://www.gimp.org/");
- gs_app_set_summary (app, GS_APP_QUALITY_NORMAL,
- "A simple GNOME 3 application "
- "to access remote or virtual systems");
- gs_app_set_description (app, GS_APP_QUALITY_NORMAL,
- "<p>long description!</p>");
- }
+ /* only process this app if was created by this plugin */
+ if (g_strcmp0 (gs_app_get_management_plugin (app), plugin->name) != 0)
+ return TRUE;
+
+ /* always fail */
+ g_set_error_literal (error,
+ GS_PLUGIN_ERROR,
+ GS_PLUGIN_ERROR_NO_NETWORK,
+ "no network connection is available");
+ return FALSE;
+ }
+
+ /**
+ * gs_plugin_refine_app:
+ */
+ gboolean
+ gs_plugin_refine_app (GsPlugin *plugin,
+ GsApp *app,
+ GsPluginRefineFlags flags,
+ GCancellable *cancellable,
+ GError **error)
+ {
+ /* default */
+ if (g_strcmp0 (gs_app_get_id (app), "chiron.desktop") == 0 ||
+ g_strcmp0 (gs_app_get_id (app), "mate-spell.desktop") == 0 ||
+ g_strcmp0 (gs_app_get_id (app), "zeus.desktop") == 0) {
+ if (gs_app_get_state (app) == AS_APP_STATE_UNKNOWN)
+ gs_app_set_state (app, AS_APP_STATE_INSTALLED);
+ }
+
+ /* license */
+ if (flags & GS_PLUGIN_REFINE_FLAGS_REQUIRE_LICENSE) {
+ if (g_strcmp0 (gs_app_get_id (app), "chiron.desktop") == 0 ||
+ g_strcmp0 (gs_app_get_id (app), "zeus.desktop") == 0)
+ gs_app_set_license (app, GS_APP_QUALITY_NORMAL, "GPL-2.0+");
+ }
+
+ /* homepage */
+ if (flags & GS_PLUGIN_REFINE_FLAGS_REQUIRE_URL) {
+ if (g_strcmp0 (gs_app_get_id (app), "chiron.desktop") == 0) {
+ gs_app_set_url (app, AS_URL_KIND_HOMEPAGE,
+ "http://www.test.org/");
+ }
+ }
+
+ /* description */
+ if (flags & GS_PLUGIN_REFINE_FLAGS_REQUIRE_DESCRIPTION) {
+ if (g_strcmp0 (gs_app_get_id (app), "chiron.desktop") == 0) {
+ gs_app_set_description (app, GS_APP_QUALITY_NORMAL,
+ "long description!");
}
}
@@@ -268,6 -462,75 +462,65 @@@ gs_plugin_add_distro_upgrades (GsPlugi
}
/**
+ * gs_plugin_refresh:
+ */
+ gboolean
+ gs_plugin_refresh (GsPlugin *plugin,
+ guint cache_age,
+ GsPluginRefreshFlags flags,
+ GCancellable *cancellable,
+ GError **error)
+ {
+ guint delay_ms = 100;
+ g_autoptr(GsApp) app = gs_app_new (NULL);
+
+ /* each one takes more time */
+ if (flags & GS_PLUGIN_REFRESH_FLAGS_METADATA)
+ delay_ms += 3000;
+ if (flags & GS_PLUGIN_REFRESH_FLAGS_PAYLOAD)
+ delay_ms += 5000;
+
+ /* do delay */
+ return gs_plugin_dummy_delay (plugin, app, delay_ms, cancellable, error);
+ }
+
+ /**
+ * gs_plugin_app_upgrade_download:
+ */
+ gboolean
+ gs_plugin_app_upgrade_download (GsPlugin *plugin, GsApp *app,
+ GCancellable *cancellable, GError **error)
+ {
+ /* only process this app if was created by this plugin */
+ if (g_strcmp0 (gs_app_get_management_plugin (app), plugin->name) != 0)
+ return TRUE;
+
+ g_debug ("starting download");
+ gs_app_set_state (app, AS_APP_STATE_INSTALLING);
+ if (!gs_plugin_dummy_delay (plugin, app, 5000, cancellable, error)) {
+ gs_app_set_state_recover (app);
+ return FALSE;
+ }
+ gs_app_set_state (app, AS_APP_STATE_UPDATABLE);
+ return TRUE;
+ }
+
+ /**
+ * gs_plugin_app_upgrade_trigger:
+ */
+ gboolean
+ gs_plugin_app_upgrade_trigger (GsPlugin *plugin, GsApp *app,
+ GCancellable *cancellable, GError **error)
+ {
+ /* only process this app if was created by this plugin */
+ if (g_strcmp0 (gs_app_get_management_plugin (app), plugin->name) != 0)
+ return TRUE;
+
+ /* NOP */
+ return TRUE;
+ }
+
+ /**
- * gs_plugin_offline_update_cancel:
- */
-gboolean
-gs_plugin_offline_update_cancel (GsPlugin *plugin, GsApp *app,
- GCancellable *cancellable, GError **error)
-{
- return TRUE;
-}
-
-/**
* gs_plugin_review_submit:
*/
gboolean
diff --cc src/plugins/gs-plugin-fwupd.c
index 38294d9,b968005..05ef7ac
--- a/src/plugins/gs-plugin-fwupd.c
+++ b/src/plugins/gs-plugin-fwupd.c
@@@ -940,11 -907,11 +916,11 @@@ gs_plugin_fwupd_install (GsPlugin *plug
gboolean offline = FALSE;
/* only process this app if was created by this plugin */
- if (g_strcmp0 (gs_app_get_management_plugin (app), "fwupd") != 0)
+ if (g_strcmp0 (gs_app_get_management_plugin (app), plugin->name) != 0)
return TRUE;
- filename = gs_app_get_source_id_default (app);
- if (filename == NULL) {
+ /* not set */
+ if (gs_app_get_local_file (app) == NULL) {
g_set_error (error,
GS_PLUGIN_ERROR,
GS_PLUGIN_ERROR_FAILED,
diff --cc src/plugins/gs-plugin-packagekit.c
index 2ce848d,189e1f1..555d1b3
--- a/src/plugins/gs-plugin-packagekit.c
+++ b/src/plugins/gs-plugin-packagekit.c
@@@ -325,11 -323,9 +323,11 @@@ gs_plugin_app_install (GsPlugin *plugin
ProgressData data;
const gchar *package_id;
guint i, j;
+ g_autoptr(PkError) error_code = NULL;
+ g_autofree gchar *local_filename = NULL;
+ g_auto(GStrv) package_ids = NULL;
- g_autoptr(GPtrArray) array_package_ids = NULL;
g_autoptr(PkResults) results = NULL;
+ g_autoptr(GPtrArray) array_package_ids = NULL;
- g_auto(GStrv) package_ids = NULL;
data.app = app;
data.plugin = plugin;
@@@ -431,11 -430,19 +432,18 @@@
cancellable,
gs_plugin_packagekit_progress_cb, &data,
error);
- if (results == NULL)
+ if (results == NULL) {
+ gs_plugin_packagekit_convert_gerror (error);
+ gs_app_set_state_recover (app);
return FALSE;
+ }
+
+ /* state is known */
+ gs_app_set_state (app, AS_APP_STATE_INSTALLED);
+
break;
case AS_APP_STATE_AVAILABLE_LOCAL:
- package_id = gs_app_get_metadata_item (app, "packagekit::local-filename");
- if (package_id == NULL) {
+ if (gs_app_get_local_file (app) == NULL) {
g_set_error_literal (error,
GS_PLUGIN_ERROR,
GS_PLUGIN_ERROR_NOT_SUPPORTED,
@@@ -450,11 -456,17 +458,17 @@@
cancellable,
gs_plugin_packagekit_progress_cb, &data,
error);
- if (results == NULL)
+ if (results == NULL) {
+ gs_plugin_packagekit_convert_gerror (error);
+ gs_app_set_state_recover (app);
return FALSE;
+ }
+
+ /* state is known */
+ gs_app_set_state (app, AS_APP_STATE_INSTALLED);
/* get the new icon from the package */
- gs_app_set_metadata (app, "packagekit::local-filename", NULL);
+ gs_app_set_local_file (app, NULL);
gs_app_set_icon (app, NULL);
gs_app_set_pixbuf (app, NULL);
break;
diff --cc src/plugins/gs-plugin-provenance.c
index bb360d3,dd424a4..a692345
--- a/src/plugins/gs-plugin-provenance.c
+++ b/src/plugins/gs-plugin-provenance.c
@@@ -136,11 -157,9 +158,11 @@@ gs_plugin_refine_app (GsPlugin *plugin
/* simple case */
origin = gs_app_get_origin (app);
+ g_debug ("prov: considering %s", gs_app_get_id (app));
if (origin != NULL && gs_utils_strv_fnmatch (sources, origin)) {
gs_app_add_quirk (app, AS_APP_QUIRK_PROVENANCE);
+ g_debug ("prov: %s", gs_app_to_string (app));
- return;
+ return TRUE;
}
/* this only works for packages */
diff --cc src/plugins/gs-plugin-systemd-updates.c
index ab14388,e433ca2..eb1b4ec
--- a/src/plugins/gs-plugin-systemd-updates.c
+++ b/src/plugins/gs-plugin-systemd-updates.c
@@@ -172,14 -164,17 +164,17 @@@ gs_plugin_update (GsPlugin *plugin
}
/**
- * gs_plugin_offline_update_cancel:
+ * gs_plugin_update_cancel:
*/
gboolean
-gs_plugin_offline_update_cancel (GsPlugin *plugin,
- GsApp *app,
- GCancellable *cancellable,
- GError **error)
+gs_plugin_update_cancel (GsPlugin *plugin,
+ GsApp *app,
+ GCancellable *cancellable,
+ GError **error)
{
+ /* only process this app if was created by this plugin */
+ if (g_strcmp0 (gs_app_get_management_plugin (app), "packagekit") != 0)
+ return TRUE;
return pk_offline_cancel (NULL, error);
}
diff --cc src/plugins/gs-plugin-ubuntu-reviews.c
index 6f40572,21a9e1f..11cd1af
--- a/src/plugins/gs-plugin-ubuntu-reviews.c
+++ b/src/plugins/gs-plugin-ubuntu-reviews.c
@@@ -89,9 -80,7 +89,10 @@@ gs_plugin_initialize (GsPlugin *plugin
const gchar **
gs_plugin_order_after (GsPlugin *plugin)
{
- static const gchar *deps[] = { NULL };
+ static const gchar *deps[] = {
+ "appstream",
++ "odrs",
+ NULL };
return deps;
}
@@@ -829,367 -665,21 +830,360 @@@ refine_reviews (GsPlugin *plugin, GsAp
}
gboolean
- gs_plugin_refine (GsPlugin *plugin,
- GList **list,
- GsPluginRefineFlags flags,
- GCancellable *cancellable,
- GError **error)
- {
- GList *l;
- gboolean ret = TRUE;
-
- for (l = *list; l != NULL; l = l->next) {
- GsApp *app = GS_APP (l->data);
-
- if ((flags & (GS_PLUGIN_REFINE_FLAGS_REQUIRE_RATING |
GS_PLUGIN_REFINE_FLAGS_REQUIRE_REVIEW_RATINGS)) != 0) {
- if (!refine_rating (plugin, app, cancellable, error))
- return FALSE;
- }
- if ((flags & GS_PLUGIN_REFINE_FLAGS_REQUIRE_REVIEWS) != 0) {
- if (!refine_reviews (plugin, app, cancellable, error))
- return FALSE;
- }
+ gs_plugin_refine_app (GsPlugin *plugin,
+ GsApp *app,
+ GsPluginRefineFlags flags,
+ GCancellable *cancellable,
+ GError **error)
+ {
+ if ((flags & (GS_PLUGIN_REFINE_FLAGS_REQUIRE_RATING | GS_PLUGIN_REFINE_FLAGS_REQUIRE_REVIEW_RATINGS))
!= 0) {
- if (!refine_rating (plugin, app, error))
++ if (!refine_rating (plugin, app, cancellable, error))
+ return FALSE;
+ }
+ if ((flags & GS_PLUGIN_REFINE_FLAGS_REQUIRE_REVIEWS) != 0) {
- if (!refine_reviews (plugin, app, error))
++ if (!refine_reviews (plugin, app, cancellable, error))
+ return FALSE;
}
- return ret;
+ return TRUE;
}
+static void
+add_string_member (JsonBuilder *builder, const gchar *name, const gchar *value)
+{
+ json_builder_set_member_name (builder, name);
+ json_builder_add_string_value (builder, value);
+}
+
+static void
+add_int_member (JsonBuilder *builder, const gchar *name, gint64 value)
+{
+ json_builder_set_member_name (builder, name);
+ json_builder_add_int_value (builder, value);
+}
+
+gboolean
+gs_plugin_review_submit (GsPlugin *plugin,
+ GsApp *app,
+ GsReview *review,
+ GCancellable *cancellable,
+ GError **error)
+{
+ GsPluginPrivate *priv = plugin->priv;
+ gint rating;
+ gint n_stars;
+ g_autofree gchar *os_id = NULL, *os_ubuntu_codename = NULL, *language = NULL, *architecture = NULL;
+ g_autoptr(JsonBuilder) request = NULL;
+ guint status_code;
+ g_autoptr(JsonParser) result = NULL;
+
+ /* Load database once */
+ if (g_once_init_enter (&plugin->priv->db_loaded)) {
+ gboolean ret = load_database (plugin, cancellable, error);
+ g_once_init_leave (&plugin->priv->db_loaded, TRUE);
+ if (!ret)
+ return FALSE;
+ }
+
+ if (!get_ubuntuone_credentials (plugin, TRUE, error))
+ return FALSE;
+
+ /* Ubuntu reviews require a summary and description - just make one up for now */
+ rating = gs_review_get_rating (review);
+ if (rating > 80)
+ n_stars = 5;
+ else if (rating > 60)
+ n_stars = 4;
+ else if (rating > 40)
+ n_stars = 3;
+ else if (rating > 20)
+ n_stars = 2;
+ else
+ n_stars = 1;
+
+ os_id = gs_os_release_get_id (error);
+ if (os_id == NULL)
+ return FALSE;
+ os_ubuntu_codename = gs_os_release_get ("UBUNTU_CODENAME", error);
+ if (os_ubuntu_codename == NULL)
+ return FALSE;
+
+ language = get_language (plugin);
+
+ // FIXME: Need to get Apt::Architecture configuration value from APT
+ architecture = g_strdup ("amd64");
+
+ /* Create message for reviews.ubuntu.com */
+ request = json_builder_new ();
+ json_builder_begin_object (request);
+ add_string_member (request, "package_name", gs_app_get_source_default (app));
+ add_string_member (request, "summary", gs_review_get_summary (review));
+ add_string_member (request, "review_text", gs_review_get_text (review));
+ add_string_member (request, "language", language);
+ add_string_member (request, "origin", os_id);
+ add_string_member (request, "distroseries", os_ubuntu_codename);
+ add_string_member (request, "version", gs_review_get_version (review));
+ add_int_member (request, "rating", n_stars);
+ add_string_member (request, "arch_tag", architecture);
+ json_builder_end_object (request);
+
+ if (!send_review_request (plugin, SOUP_METHOD_POST, "/api/1.0/reviews/",
+ request, TRUE,
+ &status_code, &result, cancellable, error))
+ return FALSE;
+
+ if (status_code != SOUP_STATUS_OK) {
+ g_set_error (error,
+ GS_PLUGIN_ERROR,
+ GS_PLUGIN_ERROR_FAILED,
+ "Failed to submit review, server returned status code %d",
+ status_code);
+ return FALSE;
+ }
+
+ // Extract new fields from posted review
+ parse_review (review, priv->consumer_key, json_parser_get_root (result));
+
+ return TRUE;
+}
+
+gboolean
+gs_plugin_review_report (GsPlugin *plugin,
+ GsApp *app,
+ GsReview *review,
+ GCancellable *cancellable,
+ GError **error)
+{
+ const gchar *review_id;
+ g_autofree gchar *reason = NULL;
+ g_autofree gchar *text = NULL;
+ g_autofree gchar *path = NULL;
+ guint status_code;
+
+ /* Can only modify Ubuntu reviews */
+ review_id = gs_review_get_metadata_item (review, "ubuntu-id");
+ if (review_id == NULL)
+ return TRUE;
+
+ if (!get_ubuntuone_credentials (plugin, TRUE, error))
+ return FALSE;
+
+ /* Create message for reviews.ubuntu.com */
+ reason = soup_uri_encode ("FIXME: gnome-software", NULL);
+ text = soup_uri_encode ("FIXME: gnome-software", NULL);
+ path = g_strdup_printf ("/api/1.0/reviews/%s/recommendations/?reason=%s&text=%s",
+ review_id, reason, text);
+ if (!send_review_request (plugin, SOUP_METHOD_POST, path,
+ NULL, TRUE,
+ &status_code, NULL, cancellable, error))
+ return FALSE;
+
+ if (status_code != SOUP_STATUS_CREATED) {
+ g_set_error (error,
+ GS_PLUGIN_ERROR,
+ GS_PLUGIN_ERROR_FAILED,
+ "Failed to report review, server returned status code %d",
+ status_code);
+ return FALSE;
+ }
+
+ gs_review_add_flags (review, GS_REVIEW_FLAG_VOTED);
+ return TRUE;
+}
+
+static gboolean
+set_review_usefulness (GsPlugin *plugin,
+ const gchar *review_id,
+ gboolean is_useful,
+ GCancellable *cancellable,
+ GError **error)
+{
+ g_autofree gchar *path = NULL;
+ guint status_code;
+
+ if (!get_ubuntuone_credentials (plugin, TRUE, error))
+ return FALSE;
+
+ /* Create message for reviews.ubuntu.com */
+ path = g_strdup_printf ("/api/1.0/reviews/%s/recommendations/?useful=%s",
+ review_id, is_useful ? "True" : "False");
+ if (!send_review_request (plugin, SOUP_METHOD_POST, path,
+ NULL, TRUE,
+ &status_code, NULL, cancellable, error))
+ return FALSE;
+
+ if (status_code != SOUP_STATUS_CREATED) {
+ g_set_error (error,
+ GS_PLUGIN_ERROR,
+ GS_PLUGIN_ERROR_FAILED,
+ "Got status code %d from reviews.ubuntu.com",
+ status_code);
+ return FALSE;
+ }
+
+ return TRUE;
+}
+
+gboolean
+gs_plugin_review_upvote (GsPlugin *plugin,
+ GsApp *app,
+ GsReview *review,
+ GCancellable *cancellable,
+ GError **error)
+{
+ const gchar *review_id;
+
+ /* Can only modify Ubuntu reviews */
+ review_id = gs_review_get_metadata_item (review, "ubuntu-id");
+ if (review_id == NULL)
+ return TRUE;
+
+ if (!set_review_usefulness (plugin, review_id, TRUE, cancellable, error))
+ return FALSE;
+
+ gs_review_add_flags (review, GS_REVIEW_FLAG_VOTED);
+ return TRUE;
+}
+
+gboolean
+gs_plugin_review_downvote (GsPlugin *plugin,
+ GsApp *app,
+ GsReview *review,
+ GCancellable *cancellable,
+ GError **error)
+{
+ const gchar *review_id;
+
+ /* Can only modify Ubuntu reviews */
+ review_id = gs_review_get_metadata_item (review, "ubuntu-id");
+ if (review_id == NULL)
+ return TRUE;
+
+ if (!set_review_usefulness (plugin, review_id, FALSE, cancellable, error))
+ return FALSE;
+
+ gs_review_add_flags (review, GS_REVIEW_FLAG_VOTED);
+ return TRUE;
+}
+
+gboolean
+gs_plugin_review_remove (GsPlugin *plugin,
+ GsApp *app,
+ GsReview *review,
+ GCancellable *cancellable,
+ GError **error)
+{
+ const gchar *review_id;
+ g_autofree gchar *path = NULL;
+ guint status_code;
+
+ /* Can only modify Ubuntu reviews */
+ review_id = gs_review_get_metadata_item (review, "ubuntu-id");
+ if (review_id == NULL)
+ return TRUE;
+
+ /* Create message for reviews.ubuntu.com */
+ path = g_strdup_printf ("/api/1.0/reviews/delete/%s/", review_id);
+ if (!send_review_request (plugin, SOUP_METHOD_POST, path,
+ NULL, TRUE,
+ &status_code, NULL, cancellable, error))
+ return FALSE;
+
+ if (status_code != SOUP_STATUS_OK) {
+ g_set_error (error,
+ GS_PLUGIN_ERROR,
+ GS_PLUGIN_ERROR_FAILED,
+ "Failed to remove review, server returned status code %d",
+ status_code);
+ return FALSE;
+ }
+
+ return TRUE;
+}
+
+typedef struct {
+ gchar *package_name;
+ gint rating;
+} PopularEntry;
+
+static gint
+popular_sqlite_cb (void *data,
+ gint argc,
+ gchar **argv,
+ gchar **col_name)
+{
+ GList **list = data;
+ PopularEntry *entry;
+
+ entry = g_slice_new (PopularEntry);
+ entry->package_name = g_strdup (argv[0]);
+ entry->rating = get_rating (g_ascii_strtoll (argv[1], NULL, 10), g_ascii_strtoll (argv[2], NULL, 10),
g_ascii_strtoll (argv[3], NULL, 10), g_ascii_strtoll (argv[4], NULL, 10), g_ascii_strtoll (argv[5], NULL,
10));
+ *list = g_list_prepend (*list, entry);
+
+ return 0;
+}
+
+static gint
+compare_popular_entry (gconstpointer a, gconstpointer b)
+{
+ PopularEntry *ea = a, *eb = b;
+ return eb->rating - ea->rating;
+}
+
+static void
+free_popular_entry (gpointer data)
+{
+ PopularEntry *entry = data;
+ g_free (entry->package_name);
+ g_slice_free (PopularEntry, entry);
+}
+
+gboolean
+gs_plugin_add_popular (GsPlugin *plugin,
+ GList **list,
+ GCancellable *cancellable,
+ GError **error)
+{
+ gint result;
+ GList *entries = NULL, *link;
+ char *error_msg = NULL;
+
+ /* Load database once */
+ if (g_once_init_enter (&plugin->priv->db_loaded)) {
+ gboolean ret = load_database (plugin, cancellable, error);
+ g_once_init_leave (&plugin->priv->db_loaded, TRUE);
+ if (!ret)
+ return FALSE;
+ }
+
+ result = sqlite3_exec (plugin->priv->db,
+ "SELECT package_name, one_star_count, two_star_count, three_star_count,
four_star_count, five_star_count FROM review_stats",
+ popular_sqlite_cb,
+ &entries,
+ &error_msg);
+ if (result != SQLITE_OK) {
+ g_set_error (error,
+ GS_PLUGIN_ERROR,
+ GS_PLUGIN_ERROR_FAILED,
+ "SQL error: %s", error_msg);
+ sqlite3_free (error_msg);
+ return FALSE;
+ }
+
+ entries = g_list_sort (entries, compare_popular_entry);
+ for (link = entries; link; link = link->next) {
+ PopularEntry *entry = link->data;
+ g_autoptr(GsApp) app = NULL;
+
+ /* Need four stars to show */
+ if (entry->rating < 80)
+ break;
+
+ app = gs_app_new (NULL);
+ gs_app_add_source (app, entry->package_name);
+ gs_plugin_add_app (list, app);
+ }
+ g_list_free_full (entries, free_popular_entry);
+
+ return TRUE;
+}
[
Date Prev][
Date Next] [
Thread Prev][
Thread Next]
[
Thread Index]
[
Date Index]
[
Author Index]