static gboolean cra_plugin_nm_app (CraApp *app, const gchar *filename, GError **error) { gboolean ret; _cleanup_free_ gchar *data_err = NULL; _cleanup_free_ gchar *data_out = NULL; const gchar *argv[] = { "/usr/bin/nm", "--dynamic", "--no-sort", "--undefined-only", filename, NULL }; ret = g_spawn_sync (NULL, (gchar **) argv, NULL, #if GLIB_CHECK_VERSION(2,40,0) G_SPAWN_CLOEXEC_PIPES, #else G_SPAWN_DEFAULT, #endif NULL, NULL, &data_out, &data_err, NULL, error); if (!ret) return FALSE; if (g_strstr_len (data_out, -1, "gtk_application_new") != NULL) as_app_add_metadata (AS_APP (app), "X-Kudo-GTK3", "", -1); if (g_strstr_len (data_out, -1, "gtk_application_set_app_menu") != NULL) as_app_add_metadata (AS_APP (app), "X-Kudo-UsesAppMenu", "", -1); return TRUE; }
static void as_app_parse_file_metadata (AsApp *app, GKeyFile *kf, const gchar *key) { guint i; g_autofree gchar *value = NULL; const gchar *blacklist[] = { "X-AppInstall-*", "X-Desktop-File-Install-Version", "X-Geoclue-Reason*", "X-GNOME-Bugzilla-*", "X-GNOME-FullName*", "X-GNOME-Gettext-Domain", "X-GNOME-UsesNotifications", NULL }; if (!g_str_has_prefix (key, "X-")) return; /* anything blacklisted */ for (i = 0; blacklist[i] != NULL; i++) { if (fnmatch (blacklist[i], key, 0) == 0) return; } value = g_key_file_get_string (kf, G_KEY_FILE_DESKTOP_GROUP, key, NULL); as_app_add_metadata (app, key, value); }
/** * asb_plugin_process_filename: */ static gboolean asb_plugin_process_filename (const gchar *filename, AsbApp *app, GError **error) { g_autofree gchar *app_runtime = NULL; g_autofree gchar *app_sdk = NULL; g_autoptr(GKeyFile) kf = NULL; kf = g_key_file_new (); if (!g_key_file_load_from_file (kf, filename, G_KEY_FILE_NONE, error)) return FALSE; app_runtime = g_key_file_get_string (kf, "Application", "runtime", NULL); if (app_runtime != NULL) as_app_add_metadata (AS_APP (app), "BundleRuntime", app_runtime); app_sdk = g_key_file_get_string (kf, "Application", "sdk", NULL); if (app_sdk != NULL) as_app_add_metadata (AS_APP (app), "BundleSDK", app_sdk); if (g_key_file_get_string (kf, "Environment", "network", NULL)) as_app_add_permission (AS_APP (app), "network"); return TRUE; }
/** * asb_context_add_app_ignore: **/ void asb_context_add_app_ignore (AsbContext *ctx, AsbPackage *pkg) { AsApp *app_tmp; AsbContextPrivate *priv = GET_PRIVATE (ctx); g_autofree gchar *name_arch = NULL; g_autoptr(AsApp) app = NULL; g_autoptr(GPtrArray) apps = NULL; /* only do this when we are using a cache-id */ if ((priv->flags & ASB_CONTEXT_FLAG_ADD_CACHE_ID) == 0) return; /* check not already added a dummy application for this package */ apps = as_store_get_apps_by_metadata (priv->store_ignore, "X-CacheID", asb_package_get_basename (pkg)); if (apps->len > 0) { g_debug ("already found CacheID of %s", asb_package_get_basename (pkg)); return; } /* package name already exists, but with a different CacheID */ name_arch = g_strdup_printf ("%s.%s", asb_package_get_name (pkg), asb_package_get_arch (pkg)); app_tmp = as_store_get_app_by_id (priv->store_ignore, name_arch); if (app_tmp != NULL) { as_app_add_metadata (AS_APP (app_tmp), "X-CacheID", asb_package_get_basename (pkg)); return; } /* never encountered before, so add */ app = as_app_new (); as_app_set_id (app, name_arch); as_app_add_pkgname (app, asb_package_get_name (pkg)); as_app_add_metadata (app, "X-CacheID", asb_package_get_basename (pkg)); as_store_add_app (priv->store_ignore, app); }
static void gs_design_dialog_buffer_changed_cb (GtkTextBuffer *buffer, GsEditor *self) { GtkTextIter iter_end; GtkTextIter iter_start; g_autofree gchar *css = NULL; /* ignore, self change */ if (self->is_in_refresh) return; gtk_text_buffer_get_bounds (buffer, &iter_start, &iter_end); css = gtk_text_buffer_get_text (buffer, &iter_start, &iter_end, FALSE); g_debug ("CSS now '%s'", css); if (as_app_get_kind (self->selected_item) == AS_APP_KIND_OS_UPGRADE) { as_app_add_metadata (self->selected_item, "GnomeSoftware::UpgradeBanner-css", NULL); as_app_add_metadata (self->selected_item, "GnomeSoftware::UpgradeBanner-css", css); } else { as_app_add_metadata (self->selected_item, "GnomeSoftware::FeatureTile-css", NULL); as_app_add_metadata (self->selected_item, "GnomeSoftware::FeatureTile-css", css); } self->pending_changes = TRUE; gs_design_dialog_refresh_details_delayed (self); }
static void gs_editor_button_new_os_upgrade_clicked_cb (GtkApplication *application, GsEditor *self) { g_autoptr(AsApp) item = as_app_new (); const gchar *css = "border: 1px solid #808080;\nbackground: #fffeee;\ncolor: #000;"; /* add new app */ as_app_set_kind (item, AS_APP_KIND_OS_UPGRADE); as_app_set_state (item, AS_APP_STATE_AVAILABLE); as_app_set_id (item, "org.gnome.release"); as_app_set_name (item, NULL, "GNOME"); as_app_set_comment (item, NULL, "A major upgrade, with new features and added polish."); as_app_add_metadata (item, "GnomeSoftware::UpgradeBanner-css", css); as_store_add_app (self->store, item); g_set_object (&self->selected_item, item); self->pending_changes = TRUE; gs_editor_refresh_choice (self); gs_editor_refresh_details (self); gs_editor_set_page (self, "details"); }
/** * asb_plugin_absorb_parent_for_pkgname: */ static void asb_plugin_absorb_parent_for_pkgname (GList *list, AsApp *parent, const gchar *pkgname) { AsApp *app; GList *l; for (l = list; l != NULL; l = l->next) { app = AS_APP (l->data); if (as_app_get_id_kind (app) != AS_ID_KIND_ADDON) continue; if (g_strcmp0 (as_app_get_pkgname_default (app), pkgname) != 0) continue; g_debug ("Adding X-Merge-With-Parent on %s as %s depends on %s", as_app_get_id_full (app), as_app_get_pkgname_default (parent), as_app_get_pkgname_default (app)); as_app_add_metadata (app, "X-Merge-With-Parent", as_app_get_id_full (parent), -1); } }
static void gs_editor_button_new_feature_clicked_cb (GtkApplication *application, GsEditor *self) { g_autofree gchar *id = NULL; g_autoptr(AsApp) item = as_app_new (); const gchar *css = "border: 1px solid #808080;\nbackground: #eee;\ncolor: #000;"; /* add new app */ as_app_set_kind (item, AS_APP_KIND_DESKTOP); id = g_strdup_printf ("example-%04x.desktop", (guint) g_random_int_range (0x0000, 0xffff)); as_app_set_id (item, id); as_app_add_metadata (item, "GnomeSoftware::FeatureTile-css", css); as_app_add_kudo (item, "GnomeSoftware::popular"); as_app_add_category (item, "Featured"); as_store_add_app (self->store, item); g_set_object (&self->selected_item, item); self->pending_changes = TRUE; gs_editor_refresh_choice (self); gs_editor_refresh_details (self); gs_editor_set_page (self, "details"); }
/** * asb_plugin_hardcoded_add_screenshots: */ static gboolean asb_plugin_hardcoded_add_screenshots (AsbApp *app, const gchar *location, GError **error) { const gchar *tmp; gboolean ret = TRUE; GList *l; GList *list = NULL; _cleanup_dir_close_ GDir *dir = NULL; /* scan for files */ dir = g_dir_open (location, 0, error); if (dir == NULL) { ret = FALSE; goto out; } while ((tmp = g_dir_read_name (dir)) != NULL) { if (!g_str_has_suffix (tmp, ".png")) continue; list = g_list_prepend (list, g_build_filename (location, tmp, NULL)); } list = g_list_sort (list, asb_plugin_hardcoded_sort_screenshots_cb); for (l = list; l != NULL; l = l->next) { tmp = l->data; asb_package_log (asb_app_get_package (app), ASB_PACKAGE_LOG_LEVEL_DEBUG, "Adding extra screenshot: %s", tmp); ret = asb_app_add_screenshot_source (app, tmp, error); if (!ret) goto out; } as_app_add_metadata (AS_APP (app), "DistroScreenshots", NULL, -1); out: g_list_free_full (list, g_free); return ret; }
/** * cra_plugin_process_app: */ gboolean cra_plugin_process_app (CraPlugin *plugin, CraPackage *pkg, CraApp *app, const gchar *tmpdir, GError **error) { const gchar *tmp; AsRelease *release; gchar **deps; gchar **filelist; GPtrArray *releases; guint i; guint secs; guint days; /* add extra categories */ tmp = as_app_get_id (AS_APP (app)); if (g_strcmp0 (tmp, "0install") == 0) as_app_add_category (AS_APP (app), "System", -1); if (g_strcmp0 (tmp, "alacarte") == 0) as_app_add_category (AS_APP (app), "System", -1); if (g_strcmp0 (tmp, "deja-dup") == 0) as_app_add_category (AS_APP (app), "Utility", -1); if (g_strcmp0 (tmp, "gddccontrol") == 0) as_app_add_category (AS_APP (app), "System", -1); if (g_strcmp0 (tmp, "nautilus") == 0) as_app_add_category (AS_APP (app), "System", -1); if (g_strcmp0 (tmp, "pessulus") == 0) as_app_add_category (AS_APP (app), "System", -1); if (g_strcmp0 (tmp, "pmdefaults") == 0) as_app_add_category (AS_APP (app), "System", -1); if (g_strcmp0 (tmp, "fwfstab") == 0) as_app_add_category (AS_APP (app), "System", -1); /* add extra project groups */ if (g_strcmp0 (tmp, "nemo") == 0) as_app_set_project_group (AS_APP (app), "Cinnamon", -1); if (g_strcmp0 (tmp, "xfdashboard") == 0) as_app_set_project_group (AS_APP (app), "XFCE", -1); /* use the URL to guess the project group */ tmp = cra_package_get_url (pkg); if (as_app_get_project_group (AS_APP (app)) == NULL && tmp != NULL) { tmp = cra_glob_value_search (plugin->priv->project_groups, tmp); if (tmp != NULL) as_app_set_project_group (AS_APP (app), tmp, -1); } /* use summary to guess the project group */ tmp = as_app_get_comment (AS_APP (app), NULL); if (tmp != NULL && g_strstr_len (tmp, -1, "for KDE") != NULL) as_app_set_project_group (AS_APP (app), "KDE", -1); /* look for any installed docs */ filelist = cra_package_get_filelist (pkg); for (i = 0; filelist[i] != NULL; i++) { if (g_str_has_prefix (filelist[i], "/usr/share/help/")) { as_app_add_metadata (AS_APP (app), "X-Kudo-InstallsUserDocs", "", -1); break; } } /* look for a shell search provider */ for (i = 0; filelist[i] != NULL; i++) { if (g_str_has_prefix (filelist[i], "/usr/share/gnome-shell/search-providers/")) { as_app_add_metadata (AS_APP (app), "X-Kudo-SearchProvider", "", -1); break; } } /* look for a modern toolkit */ deps = cra_package_get_deps (pkg); for (i = 0; deps != NULL && deps[i] != NULL; i++) { if (g_strcmp0 (deps[i], "libgtk-3.so.0") == 0) { as_app_add_metadata (AS_APP (app), "X-Kudo-GTK3", "", -1); break; } if (g_strcmp0 (deps[i], "libQt5Core.so.5") == 0) { as_app_add_metadata (AS_APP (app), "X-Kudo-QT5", "", -1); break; } } /* look for ancient toolkits */ for (i = 0; deps != NULL && deps[i] != NULL; i++) { if (g_strcmp0 (deps[i], "libgtk-1.2.so.0") == 0) { cra_app_add_veto (app, "Uses obsolete GTK1 toolkit"); break; } if (g_strcmp0 (deps[i], "libqt-mt.so.3") == 0) { cra_app_add_veto (app, "Uses obsolete QT3 toolkit"); break; } if (g_strcmp0 (deps[i], "liblcms.so.1") == 0) { cra_app_add_veto (app, "Uses obsolete LCMS library"); break; } if (g_strcmp0 (deps[i], "libelektra.so.4") == 0) { cra_app_add_veto (app, "Uses obsolete Elektra library"); break; } if (g_strcmp0 (deps[i], "libXt.so.6") == 0) { cra_app_add_requires_appdata (app, "Uses obsolete X11 toolkit"); break; } if (g_strcmp0 (deps[i], "wine-core") == 0) { cra_app_add_requires_appdata (app, "Uses wine"); break; } } /* has the application been updated in the last year */ releases = as_app_get_releases (AS_APP (app)); for (i = 0; i < releases->len; i++) { release = g_ptr_array_index (releases, i); secs = (g_get_real_time () / G_USEC_PER_SEC) - as_release_get_timestamp (release); days = secs / (60 * 60 * 24); if (days < 365) { as_app_add_metadata (AS_APP (app), "X-Kudo-RecentRelease", "", -1); break; } } /* has there been no upstream version recently */ if (releases->len > 0 && as_app_get_id_kind (AS_APP (app)) == AS_ID_KIND_DESKTOP) { release = g_ptr_array_index (releases, 0); secs = (g_get_real_time () / G_USEC_PER_SEC) - as_release_get_timestamp (release); days = secs / (60 * 60 * 24); /* this is just too old for us to care about */ if (days > 365 * 10) { cra_app_add_veto (app, "Dead upstream for %i years", secs / (60 * 60 * 24 * 365)); } /* we need AppData if the app needs saving */ else if (days > 365 * 5) { cra_app_add_requires_appdata (app, "Dead upstream for > %i years", 5); } } /* do any extra screenshots exist */ tmp = cra_package_get_config (pkg, "ScreenshotsExtra"); if (tmp != NULL) { _cleanup_free_ gchar *dirname = NULL; dirname = g_build_filename (tmp, as_app_get_id (AS_APP (app)), NULL); if (g_file_test (dirname, G_FILE_TEST_EXISTS)) { if (!cra_plugin_hardcoded_add_screenshots (app, dirname, error)) return FALSE; } } /* a ConsoleOnly category means we require AppData */ if (as_app_has_category (AS_APP(app), "ConsoleOnly")) cra_app_add_requires_appdata (app, "ConsoleOnly"); /* no categories means we require AppData */ if (as_app_get_categories(AS_APP(app))->len == 0) cra_app_add_requires_appdata (app, "no Categories"); return TRUE; }
/** * asb_task_process: * @task: A #AsbTask * @error_not_used: A #GError or %NULL * * Processes the task. * * Returns: %TRUE for success, %FALSE otherwise * * Since: 0.1.0 **/ gboolean asb_task_process (AsbTask *task, GError **error_not_used) { AsRelease *release; AsbApp *app; AsbPlugin *plugin = NULL; AsbTaskPrivate *priv = GET_PRIVATE (task); GList *apps = NULL; GList *l; GPtrArray *array; gboolean ret; gchar *cache_id; guint i; guint nr_added = 0; g_autoptr(GError) error = NULL; g_autofree gchar *basename = NULL; /* reset the profile timer */ asb_package_log_start (priv->pkg); /* ensure nevra read */ if (!asb_package_ensure (priv->pkg, ASB_PACKAGE_ENSURE_NEVRA, error_not_used)) return FALSE; g_debug ("starting: %s", asb_package_get_name (priv->pkg)); /* treat archive as a special case */ if (g_str_has_suffix (priv->filename, ".cab")) { AsApp *app_tmp; GPtrArray *apps_tmp; g_autoptr(AsStore) store = as_store_new (); g_autoptr(GFile) file = g_file_new_for_path (priv->filename); if (!as_store_from_file (store, file, NULL, NULL, &error)) { asb_package_log (priv->pkg, ASB_PACKAGE_LOG_LEVEL_WARNING, "Failed to parse %s: %s", asb_package_get_filename (priv->pkg), error->message); return TRUE; } apps_tmp = as_store_get_apps (store); for (i = 0; i < apps_tmp->len; i++) { g_autoptr(AsbApp) app2 = NULL; app_tmp = AS_APP (g_ptr_array_index (apps_tmp, i)); app2 = asb_app_new (priv->pkg, as_app_get_id (app_tmp)); as_app_subsume (AS_APP (app2), app_tmp); asb_context_add_app (priv->ctx, app2); /* set cache-id in case we want to use the metadata directly */ if (asb_context_get_flag (priv->ctx, ASB_CONTEXT_FLAG_ADD_CACHE_ID)) { cache_id = asb_utils_get_cache_id_for_filename (priv->filename); as_app_add_metadata (AS_APP (app2), "X-CacheID", cache_id); g_free (cache_id); } nr_added++; } g_debug ("added %i apps from archive", apps_tmp->len); goto skip; } /* ensure file list read */ if (!asb_package_ensure (priv->pkg, ASB_PACKAGE_ENSURE_FILES, error_not_used)) return FALSE; /* did we get a file match on any plugin */ basename = g_path_get_basename (priv->filename); asb_package_log (priv->pkg, ASB_PACKAGE_LOG_LEVEL_DEBUG, "Getting filename match for %s", basename); asb_task_add_suitable_plugins (task); if (priv->plugins_to_run->len == 0) { asb_context_add_app_ignore (priv->ctx, priv->pkg); goto out; } /* delete old tree if it exists */ ret = asb_utils_ensure_exists_and_empty (priv->tmpdir, &error); if (!ret) { asb_package_log (priv->pkg, ASB_PACKAGE_LOG_LEVEL_WARNING, "Failed to clear: %s", error->message); goto out; } /* explode tree */ g_debug ("decompressing files: %s", asb_package_get_name (priv->pkg)); asb_package_log (priv->pkg, ASB_PACKAGE_LOG_LEVEL_DEBUG, "Exploding tree for %s", asb_package_get_name (priv->pkg)); ret = asb_package_explode (priv->pkg, priv->tmpdir, asb_context_get_file_globs (priv->ctx), &error); if (!ret) { asb_package_log (priv->pkg, ASB_PACKAGE_LOG_LEVEL_WARNING, "Failed to explode: %s", error->message); g_clear_error (&error); goto skip; } /* add extra packages */ if (!asb_package_ensure (priv->pkg, ASB_PACKAGE_ENSURE_DEPS | ASB_PACKAGE_ENSURE_SOURCE, error_not_used)) return FALSE; ret = asb_task_explode_extra_packages (task, &error); if (!ret) { asb_package_log (priv->pkg, ASB_PACKAGE_LOG_LEVEL_WARNING, "Failed to explode extra file: %s", error->message); goto skip; } /* run plugins */ g_debug ("examining: %s", asb_package_get_name (priv->pkg)); for (i = 0; i < priv->plugins_to_run->len; i++) { GList *apps_tmp = NULL; plugin = g_ptr_array_index (priv->plugins_to_run, i); asb_package_log (priv->pkg, ASB_PACKAGE_LOG_LEVEL_DEBUG, "Processing %s with %s", basename, plugin->name); apps_tmp = asb_plugin_process (plugin, priv->pkg, priv->tmpdir, &error); if (apps_tmp == NULL) { asb_package_log (priv->pkg, ASB_PACKAGE_LOG_LEVEL_WARNING, "Failed to run process '%s': %s", plugin->name, error->message); g_clear_error (&error); } for (l = apps_tmp; l != NULL; l = l->next) { app = ASB_APP (l->data); asb_plugin_add_app (&apps, AS_APP (app)); } g_list_free_full (apps_tmp, g_object_unref); } if (apps == NULL) goto skip; /* print */ g_debug ("processing: %s", asb_package_get_name (priv->pkg)); for (l = apps; l != NULL; l = l->next) { app = l->data; /* never set */ if (as_app_get_id (AS_APP (app)) == NULL) { asb_package_log (priv->pkg, ASB_PACKAGE_LOG_LEVEL_INFO, "app id not set for %s", asb_package_get_name (priv->pkg)); continue; } /* copy data from pkg into app */ if (!asb_package_ensure (priv->pkg, ASB_PACKAGE_ENSURE_LICENSE | ASB_PACKAGE_ENSURE_RELEASES | ASB_PACKAGE_ENSURE_VCS | ASB_PACKAGE_ENSURE_URL, error_not_used)) return FALSE; if (asb_package_get_url (priv->pkg) != NULL && as_app_get_url_item (AS_APP (app), AS_URL_KIND_HOMEPAGE) == NULL) { as_app_add_url (AS_APP (app), AS_URL_KIND_HOMEPAGE, asb_package_get_url (priv->pkg)); } if (asb_package_get_license (priv->pkg) != NULL && as_app_get_project_license (AS_APP (app)) == NULL) { as_app_set_project_license (AS_APP (app), asb_package_get_license (priv->pkg)); } /* add the source name so we can suggest these together */ if (g_strcmp0 (asb_package_get_source_pkgname (priv->pkg), asb_package_get_name (priv->pkg)) != 0) { as_app_set_source_pkgname (AS_APP (app), asb_package_get_source_pkgname (priv->pkg)); } /* set all the releases on the app */ array = asb_package_get_releases (priv->pkg); for (i = 0; i < array->len; i++) { release = g_ptr_array_index (array, i); as_app_add_release (AS_APP (app), release); } /* run each refine plugin on each app */ ret = asb_plugin_loader_process_app (asb_context_get_plugin_loader (priv->ctx), priv->pkg, app, priv->tmpdir, &error); if (!ret) { asb_package_log (priv->pkg, ASB_PACKAGE_LOG_LEVEL_WARNING, "Failed to run process on %s: %s", as_app_get_id (AS_APP (app)), error->message); g_clear_error (&error); goto skip; } /* set cache-id in case we want to use the metadata directly */ if (asb_context_get_flag (priv->ctx, ASB_CONTEXT_FLAG_ADD_CACHE_ID)) { cache_id = asb_utils_get_cache_id_for_filename (priv->filename); as_app_add_metadata (AS_APP (app), "X-CacheID", cache_id); g_free (cache_id); } /* set the VCS information into the metadata */ if (asb_package_get_vcs (priv->pkg) != NULL) { as_app_add_metadata (AS_APP (app), "VersionControlSystem", asb_package_get_vcs (priv->pkg)); } /* save any screenshots early */ if (array->len == 0) { if (!asb_app_save_resources (ASB_APP (app), ASB_APP_SAVE_FLAG_SCREENSHOTS, error_not_used)) return FALSE; } /* all okay */ asb_context_add_app (priv->ctx, app); nr_added++; } skip: /* add a dummy element to the AppStream metadata so that we don't keep * parsing this every time */ if (asb_context_get_flag (priv->ctx, ASB_CONTEXT_FLAG_ADD_CACHE_ID) && nr_added == 0) asb_context_add_app_ignore (priv->ctx, priv->pkg); /* delete tree */ g_debug ("deleting temp files: %s", asb_package_get_name (priv->pkg)); if (!asb_utils_rmtree (priv->tmpdir, &error)) { asb_package_log (priv->pkg, ASB_PACKAGE_LOG_LEVEL_WARNING, "Failed to delete tree: %s", error->message); goto out; } /* write log */ g_debug ("writing log: %s", asb_package_get_name (priv->pkg)); if (!asb_package_log_flush (priv->pkg, &error)) { asb_package_log (priv->pkg, ASB_PACKAGE_LOG_LEVEL_WARNING, "Failed to write package log: %s", error->message); goto out; } out: /* clear loaded resources */ asb_package_close (priv->pkg, NULL); asb_package_clear (priv->pkg, ASB_PACKAGE_ENSURE_DEPS | ASB_PACKAGE_ENSURE_FILES); g_list_free_full (apps, (GDestroyNotify) g_object_unref); return TRUE; }
/** * asb_plugin_process_filename: */ static gboolean asb_plugin_process_filename (AsbPlugin *plugin, AsbPackage *pkg, const gchar *filename, GList **apps, const gchar *tmpdir, GError **error) { gboolean ret = TRUE; gchar *error_msg = 0; gchar *filename_tmp; gint rc; guint i; sqlite3 *db = NULL; _cleanup_free_ gchar *basename = NULL; _cleanup_free_ gchar *description = NULL; _cleanup_free_ gchar *language_string = NULL; _cleanup_free_ gchar *name = NULL; _cleanup_free_ gchar *symbol = NULL; _cleanup_object_unref_ AsbApp *app = NULL; _cleanup_object_unref_ AsIcon *icon = NULL; _cleanup_strv_free_ gchar **languages = NULL; /* open IME database */ filename_tmp = g_build_filename (tmpdir, filename, NULL); rc = sqlite3_open (filename_tmp, &db); if (rc) { ret = FALSE; g_set_error (error, ASB_PLUGIN_ERROR, ASB_PLUGIN_ERROR_FAILED, "Can't open database: %s", sqlite3_errmsg (db)); goto out; } /* get name */ rc = sqlite3_exec(db, "SELECT * FROM ime WHERE attr = 'name' LIMIT 1;", asb_plugin_sqlite_callback_cb, &name, &error_msg); if (rc != SQLITE_OK) { ret = FALSE; g_set_error (error, ASB_PLUGIN_ERROR, ASB_PLUGIN_ERROR_FAILED, "Can't get IME name from %s: %s", filename, error_msg); sqlite3_free(error_msg); goto out; } /* get symbol */ rc = sqlite3_exec(db, "SELECT * FROM ime WHERE attr = 'symbol' LIMIT 1;", asb_plugin_sqlite_callback_cb, &symbol, &error_msg); if (rc != SQLITE_OK) { ret = FALSE; g_set_error (error, ASB_PLUGIN_ERROR, ASB_PLUGIN_ERROR_FAILED, "Can't get IME symbol from %s: %s", filename, error_msg); sqlite3_free(error_msg); goto out; } /* get languages */ rc = sqlite3_exec(db, "SELECT * FROM ime WHERE attr = 'languages' LIMIT 1;", asb_plugin_sqlite_callback_cb, &language_string, &error_msg); if (rc != SQLITE_OK) { ret = FALSE; g_set_error (error, ASB_PLUGIN_ERROR, ASB_PLUGIN_ERROR_FAILED, "Can't get IME languages from %s: %s", filename, error_msg); sqlite3_free(error_msg); goto out; } /* get description */ rc = sqlite3_exec(db, "SELECT * FROM ime WHERE attr = 'description' LIMIT 1;", asb_plugin_sqlite_callback_cb, &description, &error_msg); if (rc != SQLITE_OK) { ret = FALSE; g_set_error (error, ASB_PLUGIN_ERROR, ASB_PLUGIN_ERROR_FAILED, "Can't get IME name from %s: %s", filename, error_msg); sqlite3_free(error_msg); goto out; } /* this is _required_ */ if (name == NULL || description == NULL) { ret = FALSE; g_set_error (error, ASB_PLUGIN_ERROR, ASB_PLUGIN_ERROR_FAILED, "No 'name' and 'description' in %s", filename); goto out; } /* create new app */ basename = g_path_get_basename (filename); app = asb_app_new (pkg, basename); as_app_set_id_kind (AS_APP (app), AS_ID_KIND_INPUT_METHOD); as_app_add_category (AS_APP (app), "Addons"); as_app_add_category (AS_APP (app), "InputSources"); as_app_set_name (AS_APP (app), "C", name); as_app_set_comment (AS_APP (app), "C", description); if (symbol != NULL && symbol[0] != '\0') as_app_add_metadata (AS_APP (app), "X-IBus-Symbol", symbol); if (language_string != NULL) { languages = g_strsplit (language_string, ",", -1); for (i = 0; languages[i] != NULL; i++) { if (g_strcmp0 (languages[i], "other") == 0) continue; as_app_add_language (AS_APP (app), 100, languages[i]); } } asb_app_set_requires_appdata (app, TRUE); asb_app_set_hidpi_enabled (app, asb_context_get_flag (plugin->ctx, ASB_CONTEXT_FLAG_HIDPI_ICONS)); /* add icon */ icon = as_icon_new (); as_icon_set_kind (icon, AS_ICON_KIND_STOCK); as_icon_set_name (icon, "system-run-symbolic"); as_app_add_icon (AS_APP (app), icon); asb_plugin_add_app (apps, AS_APP (app)); out: if (db != NULL) sqlite3_close (db); return ret; }
/** * cra_plugin_process_filename: */ static gboolean cra_plugin_process_filename (CraPlugin *plugin, CraPackage *pkg, const gchar *filename, GList **apps, const gchar *tmpdir, GError **error) { GNode *root = NULL; GString *valid_xml; const gchar *tmp; const GNode *n; gboolean found_header = FALSE; gboolean ret; guint i; _cleanup_free_ gchar *basename = NULL; _cleanup_free_ gchar *data = NULL; _cleanup_free_ gchar *filename_tmp; _cleanup_object_unref_ CraApp *app = NULL; _cleanup_strv_free_ gchar **languages = NULL; _cleanup_strv_free_ gchar **lines = NULL; /* open file */ filename_tmp = g_build_filename (tmpdir, filename, NULL); ret = g_file_get_contents (filename_tmp, &data, NULL, error); if (!ret) goto out; /* some components start with a comment (invalid XML) and some * don't even have '<?xml' -- try to fix up best we can */ valid_xml = g_string_new (""); lines = g_strsplit (data, "\n", -1); for (i = 0; lines[i] != NULL; i++) { if (g_str_has_prefix (lines[i], "<?xml") || g_str_has_prefix (lines[i], "<component>")) found_header = TRUE; if (found_header) g_string_append_printf (valid_xml, "%s\n", lines[i]); } /* parse contents */ root = as_node_from_xml (valid_xml->str, -1, AS_NODE_FROM_XML_FLAG_NONE, error); if (!ret) goto out; /* create new app */ basename = g_path_get_basename (filename); app = cra_app_new (pkg, basename); as_app_set_id_kind (AS_APP (app), AS_ID_KIND_INPUT_METHOD); as_app_add_category (AS_APP (app), "Addons", -1); as_app_add_category (AS_APP (app), "InputSources", -1); as_app_set_icon (AS_APP (app), "system-run-symbolic", -1); as_app_set_icon_kind (AS_APP (app), AS_ICON_KIND_STOCK); cra_app_set_requires_appdata (app, TRUE); /* read the component header which all input methods have */ n = as_node_find (root, "component/description"); if (n != NULL) { as_app_set_name (AS_APP (app), "C", as_node_get_data (n), -1); as_app_set_comment (AS_APP (app), "C", as_node_get_data (n), -1); } n = as_node_find (root, "component/homepage"); if (n != NULL) { as_app_add_url (AS_APP (app), AS_URL_KIND_HOMEPAGE, as_node_get_data (n), -1); } /* do we have a engine section we can use? */ n = as_node_find (root, "component/engines/engine/longname"); if (n != NULL) as_app_set_name (AS_APP (app), "C", as_node_get_data (n), -1); n = as_node_find (root, "component/engines/engine/description"); if (n != NULL) as_app_set_comment (AS_APP (app), "C", as_node_get_data (n), -1); n = as_node_find (root, "component/engines/engine/symbol"); if (n != NULL) { tmp = as_node_get_data (n); if (tmp != NULL && tmp[0] != '\0') { as_app_add_metadata (AS_APP (app), "X-IBus-Symbol", tmp, -1); } } n = as_node_find (root, "component/engines/engine/language"); if (n != NULL) { tmp = as_node_get_data (n); if (tmp != NULL) { languages = g_strsplit (tmp, ",", -1); for (i = 0; languages[i] != NULL; i++) { if (g_strcmp0 (languages[i], "other") == 0) continue; as_app_add_language (AS_APP (app), 100, languages[i], -1); } } } /* add */ cra_plugin_add_app (apps, app); out: if (root != NULL) as_node_unref (root); return ret; }
static gboolean as_app_parse_shell_extension_data (AsbPlugin *plugin, AsApp *app, const gchar *data, gsize len, GError **error) { JsonArray *json_array; JsonNode *json_root; JsonObject *json_obj; const gchar *tmp; g_autoptr(JsonParser) json_parser = NULL; /* parse the data */ json_parser = json_parser_new (); if (!json_parser_load_from_data (json_parser, data, (gssize) len, error)) return FALSE; json_root = json_parser_get_root (json_parser); if (json_root == NULL) { g_set_error_literal (error, ASB_PLUGIN_ERROR, ASB_PLUGIN_ERROR_FAILED, "no root"); return FALSE; } json_obj = json_node_get_object (json_root); if (json_obj == NULL) { g_set_error_literal (error, ASB_PLUGIN_ERROR, ASB_PLUGIN_ERROR_FAILED, "no object"); return FALSE; } as_app_set_kind (app, AS_APP_KIND_SHELL_EXTENSION); as_app_set_comment (app, NULL, "GNOME Shell Extension"); if (asb_context_get_flag (plugin->ctx, ASB_CONTEXT_FLAG_ADD_DEFAULT_ICONS)) { as_app_add_category (AS_APP (app), "Addons"); as_app_add_category (AS_APP (app), "ShellExtensions"); } tmp = json_object_get_string_member (json_obj, "uuid"); if (tmp != NULL) { g_autofree gchar *id = NULL; id = as_utils_appstream_id_build (tmp); as_app_set_id (app, id); as_app_add_metadata (AS_APP (app), "shell-extensions::uuid", tmp); } if (json_object_has_member (json_obj, "gettext-domain")) { tmp = json_object_get_string_member (json_obj, "gettext-domain"); if (tmp != NULL) { g_autoptr(AsTranslation) transaction = NULL; transaction = as_translation_new (); as_translation_set_kind (transaction, AS_TRANSLATION_KIND_GETTEXT); as_translation_set_id (transaction, tmp); as_app_add_translation (app, transaction); } } if (json_object_has_member (json_obj, "name")) { tmp = json_object_get_string_member (json_obj, "name"); if (tmp != NULL) as_app_set_name (app, NULL, tmp); } if (json_object_has_member (json_obj, "description")) { tmp = json_object_get_string_member (json_obj, "description"); if (tmp != NULL) { g_autofree gchar *desc = NULL; desc = as_markup_import (tmp, AS_MARKUP_CONVERT_FORMAT_SIMPLE, error); if (desc == NULL) return FALSE; as_app_set_description (app, NULL, desc); } } if (json_object_has_member (json_obj, "url")) { tmp = json_object_get_string_member (json_obj, "url"); if (tmp != NULL) as_app_add_url (app, AS_URL_KIND_HOMEPAGE, tmp); } if (json_object_has_member (json_obj, "original-authors")) { json_array = json_object_get_array_member (json_obj, "original-authors"); if (json_array != NULL) { tmp = json_array_get_string_element (json_array, 0); as_app_set_developer_name (app, NULL, tmp); } } if (as_app_get_release_default (app) == NULL && json_object_has_member (json_obj, "shell-version")) { json_array = json_object_get_array_member (json_obj, "shell-version"); if (json_array != NULL) { g_autoptr(AsRelease) release = NULL; tmp = json_array_get_string_element (json_array, 0); release = as_release_new (); as_release_set_state (release, AS_RELEASE_STATE_INSTALLED); as_release_set_version (release, tmp); as_app_add_release (app, release); } } /* use a stock icon */ if (asb_context_get_flag (plugin->ctx, ASB_CONTEXT_FLAG_ADD_DEFAULT_ICONS)) { g_autoptr(AsIcon) ic = as_icon_new (); as_icon_set_kind (ic, AS_ICON_KIND_STOCK); as_icon_set_name (ic, "application-x-addon-symbolic"); as_app_add_icon (app, ic); } return TRUE; }
/** * asb_plugin_process_app: */ gboolean asb_plugin_process_app (AsbPlugin *plugin, AsbPackage *pkg, AsbApp *app, const gchar *tmpdir, GError **error) { const gchar *tmp; AsRelease *release; GPtrArray *deps; gchar **filelist; GPtrArray *releases; guint i; gint64 secs; guint days; /* add extra categories */ tmp = as_app_get_id (AS_APP (app)); if (g_strcmp0 (tmp, "0install.desktop") == 0) as_app_add_category (AS_APP (app), "System", -1); if (g_strcmp0 (tmp, "alacarte.desktop") == 0) as_app_add_category (AS_APP (app), "System", -1); if (g_strcmp0 (tmp, "deja-dup.desktop") == 0) as_app_add_category (AS_APP (app), "Utility", -1); if (g_strcmp0 (tmp, "gddccontrol.desktop") == 0) as_app_add_category (AS_APP (app), "System", -1); if (g_strcmp0 (tmp, "nautilus.desktop") == 0) as_app_add_category (AS_APP (app), "System", -1); if (g_strcmp0 (tmp, "pessulus.desktop") == 0) as_app_add_category (AS_APP (app), "System", -1); if (g_strcmp0 (tmp, "pmdefaults.desktop") == 0) as_app_add_category (AS_APP (app), "System", -1); if (g_strcmp0 (tmp, "fwfstab.desktop") == 0) as_app_add_category (AS_APP (app), "System", -1); if (g_strcmp0 (tmp, "bmpanel2cfg.desktop") == 0) as_app_add_category (AS_APP (app), "System", -1); if (g_strcmp0 (tmp, "wallpapoz.desktop") == 0) as_app_add_category (AS_APP (app), "System", -1); if (g_strcmp0 (tmp, "superkaramba.desktop") == 0) as_app_add_category (AS_APP (app), "System", -1); /* add extra project groups */ if (g_strcmp0 (tmp, "nemo.desktop") == 0) as_app_set_project_group (AS_APP (app), "Cinnamon", -1); if (g_strcmp0 (tmp, "xfdashboard.desktop") == 0) as_app_set_project_group (AS_APP (app), "XFCE", -1); /* use the URL to guess the project group */ tmp = asb_package_get_url (pkg); if (as_app_get_project_group (AS_APP (app)) == NULL && tmp != NULL) { tmp = asb_glob_value_search (plugin->priv->project_groups, tmp); if (tmp != NULL) as_app_set_project_group (AS_APP (app), tmp, -1); } /* use summary to guess the project group */ tmp = as_app_get_comment (AS_APP (app), NULL); if (tmp != NULL && g_strstr_len (tmp, -1, "for KDE") != NULL) as_app_set_project_group (AS_APP (app), "KDE", -1); /* look for any installed docs */ filelist = asb_package_get_filelist (pkg); for (i = 0; filelist[i] != NULL; i++) { if (asb_plugin_match_glob ("/usr/share/help/*", filelist[i])) { as_app_add_kudo_kind (AS_APP (app), AS_KUDO_KIND_USER_DOCS); break; } } /* look for a shell search provider */ for (i = 0; filelist[i] != NULL; i++) { if (asb_plugin_match_glob ("/usr/share/gnome-shell/search-providers/*", filelist[i])) { as_app_add_kudo_kind (AS_APP (app), AS_KUDO_KIND_SEARCH_PROVIDER); break; } } /* look for a high contrast icon */ for (i = 0; filelist[i] != NULL; i++) { if (asb_plugin_match_glob ("/usr/share/icons/HighContrast/*", filelist[i])) { as_app_add_kudo_kind (AS_APP (app), AS_KUDO_KIND_HIGH_CONTRAST); break; } if (asb_plugin_match_glob ("/usr/share/icons/hicolor/symbolic/apps/*.svg", filelist[i])) { as_app_add_kudo_kind (AS_APP (app), AS_KUDO_KIND_HIGH_CONTRAST); break; } } /* look for a modern toolkit */ deps = asb_package_get_deps (pkg); for (i = 0; i < deps->len; i++) { tmp = g_ptr_array_index (deps, i); if (g_strcmp0 (tmp, "libgtk-3.so.0") == 0 || g_strcmp0 (tmp, "libQt5Core.so.5") == 0) { as_app_add_kudo_kind (AS_APP (app), AS_KUDO_KIND_MODERN_TOOLKIT); break; } } /* look for ancient toolkits */ for (i = 0; i < deps->len; i++) { tmp = g_ptr_array_index (deps, i); if (g_strcmp0 (tmp, "libgtk-1.2.so.0") == 0) { as_app_add_veto (AS_APP (app), "Uses obsolete GTK1 toolkit"); break; } if (g_strcmp0 (tmp, "libglib-1.2.so.0") == 0) { as_app_add_veto (AS_APP (app), "Uses obsolete GLib library"); break; } if (g_strcmp0 (tmp, "libqt-mt.so.3") == 0) { as_app_add_veto (AS_APP (app), "Uses obsolete QT3 toolkit"); break; } if (g_strcmp0 (tmp, "liblcms.so.1") == 0) { as_app_add_veto (AS_APP (app), "Uses obsolete LCMS library"); break; } if (g_strcmp0 (tmp, "libelektra.so.4") == 0) { as_app_add_veto (AS_APP (app), "Uses obsolete Elektra library"); break; } if (g_strcmp0 (tmp, "libXt.so.6") == 0) { asb_app_add_requires_appdata (app, "Uses obsolete X11 toolkit"); break; } if (g_strcmp0 (tmp, "Xvfb") == 0) { asb_app_add_requires_appdata (app, "Uses obsolete Xvfb"); break; } if (g_strcmp0 (tmp, "wine-core") == 0) { asb_app_add_requires_appdata (app, "Uses wine"); break; } } /* has the application been updated in the last year */ releases = as_app_get_releases (AS_APP (app)); if (asb_context_get_api_version (plugin->ctx) < 0.8) { for (i = 0; i < releases->len; i++) { release = g_ptr_array_index (releases, i); secs = (g_get_real_time () / G_USEC_PER_SEC) - as_release_get_timestamp (release); days = secs / (60 * 60 * 24); if (secs > 0 && days < 365) { as_app_add_metadata (AS_APP (app), "X-Kudo-RecentRelease", "", -1); break; } } } /* has there been no upstream version recently */ if (releases->len > 0 && as_app_get_id_kind (AS_APP (app)) == AS_ID_KIND_DESKTOP) { release = g_ptr_array_index (releases, 0); secs = (g_get_real_time () / G_USEC_PER_SEC) - as_release_get_timestamp (release); days = secs / (60 * 60 * 24); /* we need AppData if the app needs saving */ if (secs > 0 && days > 365 * 5) { asb_app_add_requires_appdata (app, "Dead upstream for > %i years", 5); } } /* a ConsoleOnly category means we require AppData */ if (as_app_has_category (AS_APP(app), "ConsoleOnly")) asb_app_add_requires_appdata (app, "ConsoleOnly"); /* no categories means we require AppData */ if (as_app_get_id_kind (AS_APP (app)) == AS_ID_KIND_DESKTOP && as_app_get_categories(AS_APP(app))->len == 0) asb_app_add_requires_appdata (app, "no Categories"); return TRUE; }
static gboolean gs_plugin_steam_update_store_app (GsPlugin *plugin, AsStore *store, GHashTable *app, GError **error) { const gchar *name; GVariant *tmp; guint32 gameid; gchar *app_id; g_autofree gchar *cache_basename = NULL; g_autofree gchar *cache_fn = NULL; g_autofree gchar *gameid_str = NULL; g_autofree gchar *html = NULL; g_autofree gchar *uri = NULL; g_autoptr(AsApp) item = NULL; /* this is the key */ tmp = g_hash_table_lookup (app, "gameid"); if (tmp == NULL) return TRUE; gameid = g_variant_get_uint32 (tmp); /* valve use the name as the application ID, not the gameid */ tmp = g_hash_table_lookup (app, "name"); if (tmp == NULL) return TRUE; name = g_variant_get_string (tmp, NULL); app_id = g_strdup_printf ("%s.desktop", name); /* already exists */ if (as_store_get_app_by_id (store, app_id) != NULL) { g_debug ("already exists %" G_GUINT32_FORMAT ", skipping", gameid); return TRUE; } /* create application with the gameid as the key */ g_debug ("parsing steam %" G_GUINT32_FORMAT, gameid); item = as_app_new (); as_app_set_kind (item, AS_APP_KIND_DESKTOP); as_app_set_project_license (item, "Steam"); as_app_set_id (item, app_id); as_app_set_name (item, NULL, name); as_app_add_category (item, "Game"); as_app_add_kudo_kind (item, AS_KUDO_KIND_MODERN_TOOLKIT); as_app_set_comment (item, NULL, "Available on Steam"); /* this is for the GNOME Software plugin */ gameid_str = g_strdup_printf ("%" G_GUINT32_FORMAT, gameid); as_app_add_metadata (item, "X-Steam-GameID", gameid_str); as_app_add_metadata (item, "GnomeSoftware::Plugin", "steam"); /* ban certains apps based on the name */ if (g_strstr_len (name, -1, "Dedicated Server") != NULL) as_app_add_veto (item, "Dedicated Server"); /* oslist */ tmp = g_hash_table_lookup (app, "oslist"); if (tmp == NULL) { as_app_add_veto (item, "No operating systems listed"); } else if (g_strstr_len (g_variant_get_string (tmp, NULL), -1, "linux") == NULL) { as_app_add_veto (item, "No Linux support"); } /* url: homepage */ tmp = g_hash_table_lookup (app, "homepage"); if (tmp != NULL) as_app_add_url (item, AS_URL_KIND_HOMEPAGE, g_variant_get_string (tmp, NULL)); /* developer name */ tmp = g_hash_table_lookup (app, "developer"); if (tmp != NULL) as_app_set_developer_name (item, NULL, g_variant_get_string (tmp, NULL)); /* type */ tmp = g_hash_table_lookup (app, "type"); if (tmp != NULL) { const gchar *kind = g_variant_get_string (tmp, NULL); if (g_strcmp0 (kind, "DLC") == 0 || g_strcmp0 (kind, "Config") == 0 || g_strcmp0 (kind, "Tool") == 0) as_app_add_veto (item, "type is %s", kind); } /* don't bother saving apps with failures */ if (as_app_get_vetos(item)->len > 0) return TRUE; /* icons */ tmp = g_hash_table_lookup (app, "clienticns"); if (tmp != NULL) { g_autoptr(GError) error_local = NULL; g_autofree gchar *ic_uri = NULL; ic_uri = g_strdup_printf ("https://steamcdn-a.akamaihd.net/steamcommunity/public/images/apps/%" G_GUINT32_FORMAT "/%s.icns", gameid, g_variant_get_string (tmp, NULL)); if (!gs_plugin_steam_download_icon (plugin, item, ic_uri, &error_local)) { g_warning ("Failed to parse clienticns: %s", error_local->message); } } /* try clienticon */ if (as_app_get_icons(item)->len == 0) { tmp = g_hash_table_lookup (app, "clienticon"); if (tmp != NULL) { g_autoptr(GError) error_local = NULL; g_autofree gchar *ic_uri = NULL; ic_uri = g_strdup_printf ("http://cdn.akamai.steamstatic.com/steamcommunity/public/images/apps/%" G_GUINT32_FORMAT "/%s.ico", gameid, g_variant_get_string (tmp, NULL)); if (!gs_plugin_steam_download_icon (plugin, item, ic_uri, &error_local)) { g_warning ("Failed to parse clienticon: %s", error_local->message); } } } /* fall back to a resized logo */ if (as_app_get_icons(item)->len == 0) { tmp = g_hash_table_lookup (app, "logo"); if (tmp != NULL) { AsIcon *icon = NULL; g_autofree gchar *ic_uri = NULL; ic_uri = g_strdup_printf ("http://cdn.akamai.steamstatic.com/steamcommunity/public/images/apps/%" G_GUINT32_FORMAT "/%s.jpg", gameid, g_variant_get_string (tmp, NULL)); icon = as_icon_new (); as_icon_set_kind (icon, AS_ICON_KIND_REMOTE); as_icon_set_url (icon, ic_uri); as_app_add_icon (item, icon); } } /* size */ tmp = g_hash_table_lookup (app, "maxsize"); if (tmp != NULL) { /* string when over 16Gb... :/ */ if (g_strcmp0 (g_variant_get_type_string (tmp), "u") == 0) { g_autofree gchar *val = NULL; val = g_strdup_printf ("%" G_GUINT32_FORMAT, g_variant_get_uint32 (tmp)); as_app_add_metadata (item, "X-Steam-Size", val); } else { as_app_add_metadata (item, "X-Steam-Size", g_variant_get_string (tmp, NULL)); } } /* download page from the store */ cache_basename = g_strdup_printf ("%s.html", gameid_str); cache_fn = gs_utils_get_cache_filename ("steam", cache_basename, GS_UTILS_CACHE_FLAG_WRITEABLE, error); if (cache_fn == NULL) return FALSE; if (!g_file_test (cache_fn, G_FILE_TEST_EXISTS)) { g_autoptr(GsApp) app_dl = gs_app_new (gs_plugin_get_name (plugin)); uri = g_strdup_printf ("http://store.steampowered.com/app/%s/", gameid_str); if (!gs_plugin_download_file (plugin, app_dl, uri, cache_fn, NULL, /* GCancellable */ error)) return FALSE; } /* get screenshots and descriptions */ if (!g_file_get_contents (cache_fn, &html, NULL, error)) { gs_utils_error_convert_gio (error); return FALSE; } if (!gs_plugin_steam_update_screenshots (item, html, error)) return FALSE; if (!gs_plugin_steam_update_description (item, html, error)) return FALSE; /* add */ as_store_add_app (store, item); return TRUE; }