/** * asb_plugin_merge: */ void asb_plugin_merge (AsbPlugin *plugin, GList *list) { AsApp *app; AsApp *found; GList *l; const gchar *tmp; _cleanup_hashtable_unref_ GHashTable *hash = NULL; /* add all packages to the hash */ hash = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, (GDestroyNotify) g_object_unref); for (l = list; l != NULL; l = l->next) { app = AS_APP (l->data); if (as_app_get_vetos (app)->len > 0) continue; tmp = as_app_get_pkgname_default (app); if (tmp == NULL) continue; found = g_hash_table_lookup (hash, tmp); if (found != NULL) { /* ignore duplicates */ if (g_strcmp0 (as_app_get_id (app), as_app_get_id (found)) == 0) { continue; } asb_plugin_composite_app (app, found); continue; } g_hash_table_insert (hash, g_strdup (as_app_get_pkgname_default (app)), g_object_ref (app)); } }
/** * asb_context_detect_pkgname_dups: **/ static gboolean asb_context_detect_pkgname_dups (AsbContext *ctx, GError **error) { AsApp *app; AsApp *found; AsbContextPrivate *priv = GET_PRIVATE (ctx); GList *l; const gchar *pkgname; g_autoptr(GHashTable) hash = NULL; hash = g_hash_table_new (g_str_hash, g_str_equal); for (l = priv->apps; l != NULL; l = l->next) { app = AS_APP (l->data); pkgname = as_app_get_pkgname_default (app); if (pkgname == NULL) continue; if (ASB_IS_APP (app) && as_app_get_vetos(app)->len > 0) continue; found = g_hash_table_lookup (hash, pkgname); if (found != NULL) { g_print ("WARNING: %s and %s share the package '%s'\n", as_app_get_id (app), as_app_get_id (found), pkgname); continue; } g_hash_table_insert (hash, (gpointer) pkgname, app); } return TRUE; }
/** * asb_context_detect_missing_parents: **/ static gboolean asb_context_detect_missing_parents (AsbContext *ctx, GError **error) { AsApp *app; AsApp *found; AsbContextPrivate *priv = GET_PRIVATE (ctx); GList *l; const gchar *tmp; g_autoptr(GHashTable) hash = NULL; /* add all desktop apps to the hash */ hash = g_hash_table_new (g_str_hash, g_str_equal); for (l = priv->apps; l != NULL; l = l->next) { app = AS_APP (l->data); if (!ASB_IS_APP (app)) continue; if (as_app_get_pkgname_default (app) == NULL) continue; if (as_app_get_id_kind (app) != AS_ID_KIND_DESKTOP) continue; g_hash_table_insert (hash, (gpointer) as_app_get_id (app), app); } /* look for the thing that an addon extends */ for (l = priv->apps; l != NULL; l = l->next) { app = AS_APP (l->data); if (!ASB_IS_APP (app)) continue; if (as_app_get_pkgname_default (app) == NULL) continue; if (as_app_get_id_kind (app) != AS_ID_KIND_ADDON) continue; if (as_app_get_extends(app)->len == 0) continue; tmp = g_ptr_array_index (as_app_get_extends(app), 0); found = g_hash_table_lookup (hash, tmp); if (found != NULL) continue; found = as_store_get_app_by_id (priv->store_old, tmp); if (found != NULL) continue; /* do not add the addon */ as_app_add_veto (app, "%s has no parent of '%s'", as_app_get_id (app), tmp); g_print ("WARNING: %s has no parent of '%s'\n", as_app_get_id (app), tmp); } return TRUE; }
/** * asb_plugin_process_app: */ gboolean asb_plugin_process_app (AsbPlugin *plugin, AsbPackage *pkg, AsbApp *app, const gchar *tmpdir, GError **error) { guint i; guint j; for (i = 0; data[i].path != NULL; i++) { g_auto(GStrv) split = NULL; if (!asb_utils_is_file_in_tmpdir (tmpdir, data[i].path)) continue; split = g_strsplit (data[i].text, "|", -1); for (j = 0; split[j] != NULL; j++) { as_app_add_keyword (AS_APP (app), NULL, split[j]); asb_package_log (pkg, ASB_PACKAGE_LOG_LEVEL_DEBUG, "Auto-adding keyword %s for %s", split[j], as_app_get_id (AS_APP (app))); } } return TRUE; }
/** * asb_plugin_process_app: */ gboolean asb_plugin_process_app (AsbPlugin *plugin, AsbPackage *pkg, AsbApp *app, const gchar *tmpdir, GError **error) { gchar **filelist; guint i; /* already set */ if (as_app_has_kudo_kind (AS_APP (app), AS_KUDO_KIND_NOTIFICATIONS)) return TRUE; /* look for a shell search provider */ filelist = asb_package_get_filelist (pkg); for (i = 0; filelist[i] != NULL; i++) { if (!asb_plugin_match_glob ("/usr/share/kde4/apps/*/*.notifyrc", filelist[i])) continue; asb_package_log (pkg, ASB_PACKAGE_LOG_LEVEL_DEBUG, "Auto-adding kudo Notifications for %s", as_app_get_id (AS_APP (app))); as_app_add_kudo_kind (AS_APP (app), AS_KUDO_KIND_NOTIFICATIONS); break; } return TRUE; }
/** * gs_appstream_refine_add_addons: */ static void gs_appstream_refine_add_addons (GsPlugin *plugin, GsApp *app, AsApp *item) { GPtrArray *addons; guint i; addons = as_app_get_addons (item); if (addons == NULL) return; for (i = 0; i < addons->len; i++) { AsApp *as_addon = g_ptr_array_index (addons, i); g_autoptr(GError) error = NULL; g_autoptr(GsApp) addon = NULL; addon = gs_app_new (as_app_get_id (as_addon)); /* add all the data we can */ if (!gs_appstream_refine_app (plugin, addon, as_addon, &error)) { g_warning ("failed to refine addon: %s", error->message); continue; } gs_app_add_addon (app, addon); } }
/** * asb_plugin_process_filename: */ static gboolean asb_plugin_process_filename (AsbPlugin *plugin, AsbPackage *pkg, const gchar *filename, GList **apps, GError **error) { _cleanup_object_unref_ AsbApp *app = NULL; app = asb_app_new (pkg, NULL); if (!as_app_parse_file (AS_APP (app), filename, AS_APP_PARSE_FLAG_APPEND_DATA, error)) return FALSE; if (as_app_get_id_kind (AS_APP (app)) != AS_ID_KIND_ADDON && as_app_get_id_kind (AS_APP (app)) != AS_ID_KIND_FONT) { g_set_error (error, ASB_PLUGIN_ERROR, ASB_PLUGIN_ERROR_FAILED, "%s is not an addon or font", as_app_get_id (AS_APP (app))); return FALSE; } asb_app_set_requires_appdata (app, FALSE); asb_app_set_hidpi_enabled (app, asb_context_get_flag (plugin->ctx, ASB_CONTEXT_FLAG_HIDPI_ICONS)); asb_plugin_add_app (apps, AS_APP (app)); return TRUE; }
static AsApp * load_appdata (const gchar *prefix, const gchar *app_name, GError **error) { g_autofree gchar *appdata_basename = NULL; g_autofree gchar *appdata_path = NULL; g_autoptr(AsApp) app = NULL; g_autoptr(GPtrArray) problems = NULL; AsProblemKind problem_kind; AsProblem *problem; guint i; appdata_basename = g_strconcat (app_name, ".appdata.xml", NULL); appdata_path = g_build_filename (prefix, "share", "appdata", appdata_basename, NULL); g_debug ("Looking for %s", appdata_path); app = as_app_new (); if (!as_app_parse_file (app, appdata_path, AS_APP_PARSE_FLAG_USE_HEURISTICS, error)) return NULL; if (as_app_get_kind (app) == AS_APP_KIND_UNKNOWN) { g_set_error (error, AS_APP_ERROR, AS_APP_ERROR_FAILED, "%s has no recognised type", as_app_get_id (AS_APP (app))); return NULL; } problems = as_app_validate (app, AS_APP_VALIDATE_FLAG_NO_NETWORK | AS_APP_VALIDATE_FLAG_RELAX, error); if (problems == NULL) return NULL; for (i = 0; i < problems->len; i++) { problem = g_ptr_array_index (problems, i); problem_kind = as_problem_get_kind (problem); as_compose_app_log (app, "AppData problem: %s : %s", as_problem_kind_to_string (problem_kind), as_problem_get_message (problem)); } if (problems->len > 0) { g_set_error (error, AS_APP_ERROR, AS_APP_ERROR_FAILED, "AppData file %s was not valid", appdata_path); return NULL; } return g_steal_pointer (&app); }
static gboolean as_app_builder_search_path (AsApp *app, const gchar *prefix, const gchar *path, AsAppBuilderFlags flags) { const gchar *tmp; g_autofree gchar *fn_prefix = NULL; g_autoptr(GDir) dir = NULL; /* find dir */ fn_prefix = g_build_filename (prefix, path, NULL); if (!g_file_test (fn_prefix, G_FILE_TEST_IS_DIR)) return FALSE; dir = g_dir_open (fn_prefix, 0, NULL); if (dir == NULL) return FALSE; /* find any file with the app-id prefix */ while ((tmp = g_dir_read_name (dir)) != NULL) { if (g_str_has_prefix (tmp, as_app_get_id (app))) return TRUE; } /* just anything */ if (flags & AS_APP_BUILDER_FLAG_USE_FALLBACKS) return TRUE; return FALSE; }
static gboolean as_app_builder_search_dbus (AsApp *app, const gchar *prefix, const gchar *path, AsProvideKind provide_kind, AsAppBuilderFlags flags, GError **error) { const gchar *tmp; g_autofree gchar *fn_prefix = NULL; g_autoptr(GDir) dir = NULL; /* find dir */ fn_prefix = g_build_filename (prefix, path, NULL); if (!g_file_test (fn_prefix, G_FILE_TEST_IS_DIR)) return TRUE; dir = g_dir_open (fn_prefix, 0, error); if (dir == NULL) return FALSE; /* find any file with the app-id prefix */ while ((tmp = g_dir_read_name (dir)) != NULL) { g_autofree gchar *fn = NULL; if ((flags & AS_APP_BUILDER_FLAG_USE_FALLBACKS) == 0) { if (!g_str_has_prefix (tmp, as_app_get_id (app))) continue; } fn = g_build_filename (fn_prefix, tmp, NULL); if (!as_app_builder_search_dbus_file (app, fn, provide_kind, error)) return FALSE; } return TRUE; }
gboolean asb_plugin_process_app (AsbPlugin *plugin, AsbPackage *pkg, AsbApp *app, const gchar *tmpdir, GError **error) { guint i; const gchar *app_dirs[] = { "/usr/share/applications", "/usr/share/applications/kde4", NULL }; /* use the .desktop file to refine the application */ for (i = 0; app_dirs[i] != NULL; i++) { g_autofree gchar *fn = NULL; fn = g_build_filename (tmpdir, app_dirs[i], as_app_get_id (AS_APP (app)), NULL); if (g_file_test (fn, G_FILE_TEST_EXISTS)) { if (!asb_plugin_desktop_refine (plugin, pkg, fn, app, tmpdir, error)) return FALSE; } } return TRUE; }
static gboolean asb_plugin_gresource_app (AsbApp *app, const gchar *filename, GError **error) { gboolean ret; g_autofree gchar *data_err = NULL; g_autofree gchar *data_out = NULL; const gchar *argv[] = { "/usr/bin/gresource", "list", 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/menus.ui") != NULL) { asb_package_log (asb_app_get_package (app), ASB_PACKAGE_LOG_LEVEL_DEBUG, "Auto-adding kudo AppMenu for %s", as_app_get_id (AS_APP (app))); as_app_add_kudo_kind (AS_APP (app), AS_KUDO_KIND_APP_MENU); } return TRUE; }
static AsApp * load_desktop (const gchar *prefix, const gchar *icons_dir, guint min_icon_size, const gchar *app_name, const gchar *appdata_id, GError **error) { AsIcon *icon; g_autofree gchar *desktop_basename = NULL; g_autofree gchar *desktop_path = NULL; g_autoptr(AsApp) app = NULL; if (appdata_id != NULL) desktop_basename = g_strdup (appdata_id); else desktop_basename = g_strconcat (app_name, ".desktop", NULL); desktop_path = g_build_filename (prefix, "share/applications", desktop_basename, NULL); app = as_app_new (); if (!as_app_parse_file (app, desktop_path, AS_APP_PARSE_FLAG_USE_HEURISTICS | AS_APP_PARSE_FLAG_ALLOW_VETO, error)) return NULL; if (as_app_get_kind (app) == AS_APP_KIND_UNKNOWN) { g_set_error (error, AS_APP_ERROR, AS_APP_ERROR_FAILED, "%s has no recognised type", as_app_get_id (AS_APP (app))); return NULL; } icon = as_app_get_icon_default (AS_APP (app)); if (icon != NULL) { g_autofree gchar *key = NULL; key = g_strdup (as_icon_get_name (icon)); if (as_icon_get_kind (icon) == AS_ICON_KIND_STOCK) { as_compose_app_log (app, "using stock icon %s", key); } else { g_autoptr(GError) error_local = NULL; gboolean ret; g_ptr_array_set_size (as_app_get_icons (AS_APP (app)), 0); ret = add_icons (app, icons_dir, min_icon_size, prefix, key, error); if (!ret) return NULL; } } return g_steal_pointer (&app); }
/** * asb_plugin_merge: */ void asb_plugin_merge (AsbPlugin *plugin, GList *list) { AsApp *app; AsApp *found; GList *l; const gchar *tmp; _cleanup_hashtable_unref_ GHashTable *hash = NULL; /* add X-Merge-With-Parent on any metainfo files that are in a package * required by a desktop package */ asb_plugin_merge_prepare_deps (list); /* add all packages to the hash */ hash = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, (GDestroyNotify) g_object_unref); for (l = list; l != NULL; l = l->next) { app = AS_APP (l->data); g_hash_table_insert (hash, g_strdup (as_app_get_id (app)), g_object_ref (app)); } /* absorb some apps into their parent */ for (l = list; l != NULL; l = l->next) { app = AS_APP (l->data); /* no absorb metadata */ tmp = as_app_get_metadata_item (app, "X-Merge-With-Parent"); if (tmp == NULL) continue; /* find the parent app */ found = g_hash_table_lookup (hash, tmp); if (found == NULL) { as_app_add_veto (app, "No referenced '%s'", tmp); continue; } /* partially absorb */ as_app_add_veto (app, "partially absorbing %s into %s", as_app_get_id (app), as_app_get_id (found)); as_app_subsume_full (found, app, AS_APP_SUBSUME_FLAG_PARTIAL); } }
static gboolean gs_plugin_steam_update_description (AsApp *app, const gchar *html, GError **error) { guint i = 0; g_autofree gchar *desc = NULL; g_autofree gchar *subsect = NULL; g_autoptr(GError) error_local = NULL; g_autoptr(GString) subsect_str = NULL; /* get the game description div section */ subsect = gs_plugin_steam_capture (html, "<div id=\"game_area_description\" class=\"game_area_description\">", "</div>", &i); /* fall back gracefully */ if (subsect == NULL) { subsect = gs_plugin_steam_capture (html, "<meta name=\"Description\" content=\"", "\">", &i); } if (subsect == NULL) { g_warning ("Failed to get description for %s [%s]", as_app_get_name (app, NULL), as_app_get_id (app)); return TRUE; } subsect_str = g_string_new (subsect); as_utils_string_replace (subsect_str, "About This Game", ""); desc = as_markup_import (subsect_str->str, AS_MARKUP_CONVERT_FORMAT_HTML, &error_local); if (desc == NULL) { g_warning ("Failed to parse description for %s [%s]: %s", as_app_get_name (app, NULL), as_app_get_id (app), error_local->message); return TRUE; } as_app_set_description (app, NULL, desc); return TRUE; }
/** * asb_plugin_merge: */ void asb_plugin_merge (AsbPlugin *plugin, GList *list) { AsApp *app; AsApp *found; GList *l; _cleanup_hashtable_unref_ GHashTable *hash = NULL; /* make a hash table of ID->AsApp */ hash = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, (GDestroyNotify) g_object_unref); for (l = list; l != NULL; l = l->next) { app = AS_APP (l->data); if (as_app_get_id_kind (app) != AS_ID_KIND_DESKTOP) continue; g_hash_table_insert (hash, g_strdup (as_app_get_id (app)), g_object_ref (app)); } /* add addons where the pkgname is different from the * main package */ for (l = list; l != NULL; l = l->next) { if (!ASB_IS_APP (l->data)) continue; app = AS_APP (l->data); if (as_app_get_id_kind (app) != AS_ID_KIND_ADDON) continue; found = g_hash_table_lookup (hash, as_app_get_id (app)); if (found == NULL) continue; if (g_strcmp0 (as_app_get_pkgname_default (app), as_app_get_pkgname_default (found)) != 0) continue; as_app_add_veto (app, "absorbing addon %s shipped in " "main package %s", as_app_get_id (app), as_app_get_pkgname_default (app)); as_app_subsume_full (found, app, AS_APP_SUBSUME_FLAG_PARTIAL); } }
static void gs_editor_button_remove_clicked_cb (GtkWidget *widget, GsEditor *self) { const gchar *name; g_autofree gchar *msg = NULL; if (self->selected_item == NULL) return; /* send notification */ name = as_app_get_name (self->selected_item, NULL); if (name == NULL) { AsApp *item_global = as_store_get_app_by_id (self->store_global, as_app_get_id (self->selected_item)); if (item_global != NULL) name = as_app_get_name (item_global, NULL); } if (name != NULL) { g_autofree gchar *name_markup = NULL; name_markup = g_strdup_printf ("<b>%s</b>", name); /* TRANSLATORS, the %s is the app name, e.g. 'Inkscape' */ msg = g_strdup_printf (_("%s banner design deleted."), name_markup); } else { /* TRANSLATORS, this is a notification */ msg = g_strdup (_("Banner design deleted.")); } gs_editor_show_notification (self, msg); /* save this so we can undo */ g_set_object (&self->deleted_item, self->selected_item); as_store_remove_app_by_id (self->store, as_app_get_id (self->selected_item)); self->pending_changes = TRUE; gs_editor_refresh_choice (self); /* set the appropriate page */ gs_editor_set_page (self, as_store_get_size (self->store) == 0 ? "none" : "choice"); }
/** * 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; if (as_app_get_vetos(app)->len > 0) continue; g_debug ("Adding X-Merge-With-Parent on %s as %s depends on %s", as_app_get_id (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 (parent)); } }
static void gs_editor_load_completion_model (GsEditor *self) { GPtrArray *apps; GtkListStore *store; GtkTreeIter iter; store = GTK_LIST_STORE (gtk_builder_get_object (self->builder, "liststore_ids")); apps = as_store_get_apps (self->store_global); for (guint i = 0; i < apps->len; i++) { AsApp *item = g_ptr_array_index (apps, i); gtk_list_store_append (store, &iter); gtk_list_store_set (store, &iter, 0, as_app_get_id (item), -1); } }
/** * asb_plugin_process_gir: */ static gboolean asb_plugin_process_gir (AsbApp *app, const gchar *tmpdir, const gchar *filename, GError **error) { GNode *l; GNode *node = NULL; const gchar *name; const gchar *version; gboolean ret = TRUE; g_autofree gchar *filename_full = NULL; g_autoptr(GFile) file = NULL; /* load file */ filename_full = g_build_filename (tmpdir, filename, NULL); file = g_file_new_for_path (filename_full); node = as_node_from_file (file, AS_NODE_FROM_XML_FLAG_NONE, NULL, error); if (node == NULL) { ret = FALSE; goto out; } /* look for includes */ l = as_node_find (node, "repository"); if (l == NULL) goto out; for (l = l->children; l != NULL; l = l->next) { if (g_strcmp0 (as_node_get_name (l), "include") != 0) continue; name = as_node_get_attribute (l, "name"); version = as_node_get_attribute (l, "version"); if (g_strcmp0 (name, "Gtk") == 0 && g_strcmp0 (version, "3.0") == 0) { asb_package_log (asb_app_get_package (app), ASB_PACKAGE_LOG_LEVEL_DEBUG, "Auto-adding kudo ModernToolkit for %s", as_app_get_id (AS_APP (app))); as_app_add_kudo_kind (AS_APP (app), AS_KUDO_KIND_MODERN_TOOLKIT); } } out: if (node != NULL) as_node_unref (node); return ret; }
/** * asb_context_write_xml_fail: **/ static gboolean asb_context_write_xml_fail (AsbContext *ctx, GError **error) { AsApp *app; AsbContextPrivate *priv = GET_PRIVATE (ctx); GList *l; g_autofree gchar *basename_failed = NULL; g_autofree gchar *filename = NULL; g_autoptr(GFile) file = NULL; /* no need to create */ if ((priv->flags & ASB_CONTEXT_FLAG_INCLUDE_FAILED) == 0) return TRUE; for (l = priv->apps; l != NULL; l = l->next) { app = AS_APP (l->data); if (!ASB_IS_APP (app)) continue; if (as_app_get_vetos(app)->len == 0) continue; if (as_store_get_app_by_id (priv->store_failed, as_app_get_id (app)) != NULL) continue; as_store_add_app (priv->store_failed, app); } filename = g_strdup_printf ("%s/%s-failed.xml.gz", priv->output_dir, priv->basename); file = g_file_new_for_path (filename); g_print ("Writing %s...\n", filename); basename_failed = g_strdup_printf ("%s-failed", priv->origin); as_store_set_origin (priv->store_failed, basename_failed); as_store_set_api_version (priv->store_failed, priv->api_version); if (priv->flags & ASB_CONTEXT_FLAG_ADD_CACHE_ID) { g_autofree gchar *builder_id = asb_utils_get_builder_id (); as_store_set_builder_id (priv->store_failed, builder_id); } return as_store_to_file (priv->store_failed, file, AS_NODE_TO_XML_FLAG_ADD_HEADER | AS_NODE_TO_XML_FLAG_FORMAT_INDENT | AS_NODE_TO_XML_FLAG_FORMAT_MULTILINE, NULL, error); }
static void as_compose_app_log (AsApp *app, const gchar *fmt, ...) { const gchar *id; guint i; va_list args; g_autofree gchar *tmp = NULL; va_start (args, fmt); tmp = g_strdup_vprintf (fmt, args); va_end (args); /* print status */ id = as_app_get_id (app); g_print ("%s: ", id); for (i = strlen (id) + 2; i < 35; i++) g_print (" "); g_print ("%s\n", tmp); }
/** * asb_plugin_process_filename: */ static gboolean asb_plugin_process_filename (const gchar *filename, AsbApp *app, const gchar *tmpdir, GError **error) { g_autofree gchar *types = 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; types = g_key_file_get_string (kf, G_KEY_FILE_DESKTOP_GROUP, "X-KDE-ServiceTypes", NULL); if (types == NULL) return TRUE; if (g_strcmp0 (types, "Plasma/Runner") != 0) return TRUE; asb_package_log (asb_app_get_package (app), ASB_PACKAGE_LOG_LEVEL_DEBUG, "Auto-adding kudo SearchProvider for %s", as_app_get_id (AS_APP (app))); as_app_add_kudo_kind (AS_APP (app), AS_KUDO_KIND_SEARCH_PROVIDER); return TRUE; }
/** * cra_plugin_process_filename: */ static gboolean cra_plugin_process_filename (CraPlugin *plugin, CraPackage *pkg, const gchar *filename, GList **apps, const gchar *tmpdir, GError **error) { const gchar *key; gboolean ret; _cleanup_free_ gchar *app_id = NULL; _cleanup_free_ gchar *full_filename = NULL; _cleanup_free_ gchar *icon_filename = NULL; _cleanup_object_unref_ CraApp *app = NULL; _cleanup_object_unref_ GdkPixbuf *pixbuf = NULL; /* create app */ app_id = g_path_get_basename (filename); app = cra_app_new (pkg, app_id); full_filename = g_build_filename (tmpdir, filename, NULL); ret = as_app_parse_file (AS_APP (app), full_filename, AS_APP_PARSE_FLAG_USE_HEURISTICS, error); if (!ret) return FALSE; /* NoDisplay requires AppData */ if (as_app_get_metadata_item (AS_APP (app), "NoDisplay") != NULL) cra_app_add_requires_appdata (app, "NoDisplay=true"); /* Settings or DesktopSettings requires AppData */ if (as_app_has_category (AS_APP (app), "Settings")) cra_app_add_requires_appdata (app, "Category=Settings"); if (as_app_has_category (AS_APP (app), "DesktopSettings")) cra_app_add_requires_appdata (app, "Category=DesktopSettings"); /* is the icon a stock-icon-name? */ key = as_app_get_icon (AS_APP (app)); if (key != NULL) { if (as_app_get_icon_kind (AS_APP (app)) == AS_ICON_KIND_STOCK) { cra_package_log (pkg, CRA_PACKAGE_LOG_LEVEL_DEBUG, "using stock icon %s", key); } else { /* is icon XPM or GIF */ if (g_str_has_suffix (key, ".xpm")) cra_app_add_veto (app, "Uses XPM icon: %s", key); else if (g_str_has_suffix (key, ".gif")) cra_app_add_veto (app, "Uses GIF icon: %s", key); else if (g_str_has_suffix (key, ".ico")) cra_app_add_veto (app, "Uses ICO icon: %s", key); /* find icon */ pixbuf = cra_app_find_icon (tmpdir, key, error); if (pixbuf == NULL) return FALSE; /* save in target directory */ icon_filename = g_strdup_printf ("%s.png", as_app_get_id (AS_APP (app))); as_app_set_icon (AS_APP (app), icon_filename, -1); as_app_set_icon_kind (AS_APP (app), AS_ICON_KIND_CACHED); cra_app_set_pixbuf (app, pixbuf); } } /* add */ cra_plugin_add_app (apps, app); return TRUE; }
/** * 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_loader_merge: * @plugin_loader: A #AsbPluginLoader * @apps: (element-type AsbApp): a list of applications that need merging * * Merge the list of applications using the plugins. * * Since: 0.2.5 **/ void asb_plugin_loader_merge (AsbPluginLoader *plugin_loader, GList *apps) { AsbApp *app; AsbApp *found; AsbPluginLoaderPrivate *priv = GET_PRIVATE (plugin_loader); AsbPluginMergeFunc plugin_func = NULL; AsbPlugin *plugin; GList *l; const gchar *key; const gchar *tmp; gboolean ret; guint i; g_autoptr(GHashTable) hash = NULL; /* run each plugin */ for (i = 0; i < priv->plugins->len; i++) { plugin = g_ptr_array_index (priv->plugins, i); ret = g_module_symbol (plugin->module, "asb_plugin_merge", (gpointer *) &plugin_func); if (!ret) continue; plugin_func (plugin, apps); } /* FIXME: move to font plugin */ for (l = apps; l != NULL; l = l->next) { if (!ASB_IS_APP (l->data)) continue; app = ASB_APP (l->data); as_app_remove_metadata (AS_APP (app), "FontFamily"); as_app_remove_metadata (AS_APP (app), "FontFullName"); as_app_remove_metadata (AS_APP (app), "FontIconText"); as_app_remove_metadata (AS_APP (app), "FontParent"); as_app_remove_metadata (AS_APP (app), "FontSampleText"); as_app_remove_metadata (AS_APP (app), "FontSubFamily"); as_app_remove_metadata (AS_APP (app), "FontClassifier"); } /* deduplicate */ hash = g_hash_table_new (g_str_hash, g_str_equal); for (l = apps; l != NULL; l = l->next) { if (!ASB_IS_APP (l->data)) continue; app = ASB_APP (l->data); if (as_app_get_vetos(AS_APP(app))->len > 0) continue; key = as_app_get_id (AS_APP (app)); found = g_hash_table_lookup (hash, key); if (found == NULL) { g_hash_table_insert (hash, (gpointer) key, (gpointer) app); continue; } if (as_app_get_kind (AS_APP (app)) == AS_APP_KIND_FIRMWARE) { as_app_subsume_full (AS_APP (found), AS_APP (app), AS_APP_SUBSUME_FLAG_MERGE); } tmp = asb_package_get_nevr (asb_app_get_package (found)); as_app_add_veto (AS_APP (app), "duplicate of %s", tmp); asb_package_log (asb_app_get_package (app), ASB_PACKAGE_LOG_LEVEL_WARNING, "duplicate %s not included as added from %s", key, tmp); } }
/** * gs_refine_item_pixbuf: */ static void gs_refine_item_pixbuf (GsPlugin *plugin, GsApp *app, AsApp *item) { AsIcon *icon; gboolean ret; g_autoptr(GError) error = NULL; g_autofree gchar *fn = NULL; g_autofree gchar *cachedir = NULL; icon = as_app_get_icon_default (item); switch (as_icon_get_kind (icon)) { case AS_ICON_KIND_REMOTE: gs_app_set_icon (app, icon); if (as_icon_get_filename (icon) == NULL) { cachedir = gs_utils_get_cachedir ("icons", NULL); fn = g_build_filename (cachedir, as_icon_get_name (icon), NULL); as_icon_set_filename (icon, fn); as_icon_set_prefix (icon, cachedir); } if (g_file_test (fn, G_FILE_TEST_EXISTS)) { as_icon_set_kind (icon, AS_ICON_KIND_LOCAL); ret = gs_app_load_icon (app, plugin->scale, &error); if (!ret) { g_warning ("failed to load icon %s: %s", as_icon_get_name (icon), error->message); return; } } break; case AS_ICON_KIND_STOCK: case AS_ICON_KIND_LOCAL: gs_app_set_icon (app, icon); /* does not exist, so try to find using the icon theme */ if (as_icon_get_kind (icon) == AS_ICON_KIND_LOCAL && as_icon_get_filename (icon) == NULL) as_icon_set_kind (icon, AS_ICON_KIND_STOCK); /* load */ ret = gs_app_load_icon (app, plugin->scale, &error); if (!ret) { g_warning ("failed to load %s icon %s: %s", as_icon_kind_to_string (as_icon_get_kind (icon)), as_icon_get_name (icon), error->message); return; } break; case AS_ICON_KIND_CACHED: if (plugin->scale == 2) icon = as_app_get_icon_for_size (item, 128, 128); if (icon == NULL) icon = as_app_get_icon_for_size (item, 64, 64); if (icon == NULL) { g_warning ("failed to find cached icon %s", as_icon_get_name (icon)); return; } if (!as_icon_load (icon, AS_ICON_LOAD_FLAG_SEARCH_SIZE, &error)) { g_warning ("failed to load cached icon %s: %s", as_icon_get_name (icon), error->message); return; } gs_app_set_pixbuf (app, as_icon_get_pixbuf (icon)); break; default: g_warning ("icon kind unknown for %s", as_app_get_id (item)); break; } }
/** * gs_appstream_refine_app: */ gboolean gs_appstream_refine_app (GsPlugin *plugin, GsApp *app, AsApp *item, GError **error) { AsRelease *rel; GHashTable *urls; GPtrArray *pkgnames; GPtrArray *kudos; const gchar *tmp; guint i; /* set the kind to be more precise */ if (gs_app_get_kind (app) == AS_APP_KIND_UNKNOWN || gs_app_get_kind (app) == AS_APP_KIND_GENERIC) { gs_app_set_kind (app, as_app_get_kind (item)); } /* is installed already */ if (gs_app_get_state (app) == AS_APP_STATE_UNKNOWN) { switch (as_app_get_source_kind (item)) { case AS_APP_SOURCE_KIND_APPDATA: case AS_APP_SOURCE_KIND_DESKTOP: gs_app_set_kind (app, AS_APP_KIND_DESKTOP); gs_app_set_state (app, AS_APP_STATE_INSTALLED); break; case AS_APP_SOURCE_KIND_METAINFO: gs_app_set_state (app, AS_APP_STATE_INSTALLED); break; case AS_APP_SOURCE_KIND_APPSTREAM: gs_app_set_state (app, as_app_get_state (item)); break; default: break; } } /* set management plugin automatically */ gs_refine_item_management_plugin (app, item); /* set id */ if (as_app_get_id (item) != NULL && gs_app_get_id (app) == NULL) gs_app_set_id (app, as_app_get_id (item)); /* set name */ tmp = as_app_get_name (item, NULL); if (tmp != NULL) { if (g_str_has_prefix (tmp, "(Nightly) ")) { tmp += 10; if (gs_app_get_metadata_item (app, "X-XdgApp-Tags") == NULL) gs_app_set_metadata (app, "X-XdgApp-Tags", "nightly"); } gs_app_set_name (app, GS_APP_QUALITY_HIGHEST, tmp); } /* set summary */ tmp = as_app_get_comment (item, NULL); if (tmp != NULL) { gs_app_set_summary (app, GS_APP_QUALITY_HIGHEST, tmp); } /* add urls */ urls = as_app_get_urls (item); if (g_hash_table_size (urls) > 0 && gs_app_get_url (app, AS_URL_KIND_HOMEPAGE) == NULL) { GList *l; g_autoptr(GList) keys = NULL; keys = g_hash_table_get_keys (urls); for (l = keys; l != NULL; l = l->next) { gs_app_set_url (app, as_url_kind_from_string (l->data), g_hash_table_lookup (urls, l->data)); } } /* set licence */ if (as_app_get_project_license (item) != NULL && gs_app_get_license (app) == NULL) gs_app_set_license (app, GS_APP_QUALITY_HIGHEST, as_app_get_project_license (item)); /* set keywords */ if (as_app_get_keywords (item, NULL) != NULL && gs_app_get_keywords (app) == NULL) { gs_app_set_keywords (app, as_app_get_keywords (item, NULL)); gs_app_add_kudo (app, GS_APP_KUDO_HAS_KEYWORDS); } /* set origin */ if (as_app_get_origin (item) != NULL && gs_app_get_origin (app) == NULL) { gs_app_set_origin (app, as_app_get_origin (item)); } /* set description */ tmp = as_app_get_description (item, NULL); if (tmp != NULL) { g_autofree gchar *from_xml = NULL; from_xml = as_markup_convert_simple (tmp, error); if (from_xml == NULL) { g_prefix_error (error, "trying to parse '%s': ", tmp); return FALSE; } gs_app_set_description (app, GS_APP_QUALITY_HIGHEST, from_xml); } /* set icon */ if (as_app_get_icon_default (item) != NULL && gs_app_get_pixbuf (app) == NULL) gs_refine_item_pixbuf (plugin, app, item); /* set categories */ if (as_app_get_categories (item) != NULL && gs_app_get_categories (app)->len == 0) gs_app_set_categories (app, as_app_get_categories (item)); /* set project group */ if (as_app_get_project_group (item) != NULL && gs_app_get_project_group (app) == NULL) gs_app_set_project_group (app, as_app_get_project_group (item)); /* this is a core application for the desktop and cannot be removed */ if (_as_app_has_compulsory_for_desktop (item, "GNOME") && gs_app_get_kind (app) == AS_APP_KIND_DESKTOP) gs_app_add_quirk (app, AS_APP_QUIRK_COMPULSORY); /* set id kind */ if (gs_app_get_kind (app) == AS_APP_KIND_UNKNOWN) gs_app_set_kind (app, as_app_get_kind (item)); /* copy all the metadata */ gs_appstream_copy_metadata (app, item); /* set package names */ pkgnames = as_app_get_pkgnames (item); if (pkgnames->len > 0 && gs_app_get_sources(app)->len == 0) gs_app_set_sources (app, pkgnames); /* set addons */ gs_appstream_refine_add_addons (plugin, app, item); /* set screenshots */ gs_appstream_refine_add_screenshots (app, item); /* are the screenshots perfect */ if (gs_appstream_are_screenshots_perfect (item)) gs_app_add_kudo (app, GS_APP_KUDO_PERFECT_SCREENSHOTS); /* was this application released recently */ if (gs_appstream_is_recent_release (item)) gs_app_add_kudo (app, GS_APP_KUDO_RECENT_RELEASE); /* add kudos */ if (as_app_get_language (item, plugin->locale) > 50) gs_app_add_kudo (app, GS_APP_KUDO_MY_LANGUAGE); /* add new-style kudos */ kudos = as_app_get_kudos (item); for (i = 0; i < kudos->len; i++) { tmp = g_ptr_array_index (kudos, i); switch (as_kudo_kind_from_string (tmp)) { case AS_KUDO_KIND_SEARCH_PROVIDER: gs_app_add_kudo (app, GS_APP_KUDO_SEARCH_PROVIDER); break; case AS_KUDO_KIND_USER_DOCS: gs_app_add_kudo (app, GS_APP_KUDO_INSTALLS_USER_DOCS); break; case AS_KUDO_KIND_APP_MENU: gs_app_add_kudo (app, GS_APP_KUDO_USES_APP_MENU); break; case AS_KUDO_KIND_MODERN_TOOLKIT: gs_app_add_kudo (app, GS_APP_KUDO_MODERN_TOOLKIT); break; case AS_KUDO_KIND_NOTIFICATIONS: gs_app_add_kudo (app, GS_APP_KUDO_USES_NOTIFICATIONS); break; case AS_KUDO_KIND_HIGH_CONTRAST: gs_app_add_kudo (app, GS_APP_KUDO_HIGH_CONTRAST); break; case AS_KUDO_KIND_HI_DPI_ICON: gs_app_add_kudo (app, GS_APP_KUDO_HI_DPI_ICON); break; default: g_debug ("no idea how to handle kudo '%s'", tmp); break; } } /* is there any update information */ rel = as_app_get_release_default (item); if (rel != NULL) { tmp = as_release_get_description (rel, NULL); if (tmp != NULL) { g_autofree gchar *desc = NULL; desc = as_markup_convert (tmp, AS_MARKUP_CONVERT_FORMAT_MARKDOWN, error); if (desc == NULL) return FALSE; gs_app_set_update_details (app, desc); } gs_app_set_update_urgency (app, as_release_get_urgency (rel)); gs_app_set_update_version (app, as_release_get_version (rel)); } return TRUE; }
/** * as_app_validate: * @app: a #AsApp instance. * @flags: the #AsAppValidateFlags to use, e.g. %AS_APP_VALIDATE_FLAG_NONE * @error: A #GError or %NULL. * * Validates data in the instance for style and consistency. * * Returns: (transfer container) (element-type AsProblem): A list of problems, or %NULL * * Since: 0.1.4 **/ GPtrArray * as_app_validate (AsApp *app, guint32 flags, GError **error) { AsAppProblems problems; AsFormat *format; GError *error_local = NULL; GHashTable *urls; GList *l; const gchar *description; const gchar *key; const gchar *license; const gchar *name; const gchar *summary; const gchar *tmp; const gchar *update_contact; gboolean deprecated_failure = FALSE; gboolean require_appstream_spec_only = FALSE; gboolean require_contactdetails = FALSE; gboolean require_copyright = FALSE; gboolean require_description = FALSE; gboolean require_project_license = FALSE; gboolean require_sentence_case = FALSE; gboolean require_translations = FALSE; gboolean require_url = TRUE; gboolean require_content_license = TRUE; gboolean require_name = TRUE; gboolean require_translation = FALSE; gboolean require_content_rating = FALSE; gboolean require_name_shorter_than_summary = FALSE; gboolean validate_license = TRUE; gboolean ret; guint length_name_max = 60; guint length_name_min = 3; guint length_summary_max = 200; guint length_summary_min = 8; guint number_para_max = 10; guint number_para_min = 1; guint str_len; g_autoptr(GList) keys = NULL; g_autoptr(AsAppValidateHelper) helper = g_new0 (AsAppValidateHelper, 1); /* has to be set */ format = as_app_get_format_default (app); if (format == NULL) { g_set_error_literal (error, AS_APP_ERROR, AS_APP_ERROR_FAILED, "cannot validate without at least one format"); return NULL; } /* only for desktop and console apps */ if (as_app_get_kind (app) == AS_APP_KIND_DESKTOP || as_app_get_kind (app) == AS_APP_KIND_CONSOLE) { require_content_rating = TRUE; require_description = TRUE; } /* relax the requirements a bit */ if ((flags & AS_APP_VALIDATE_FLAG_RELAX) > 0) { length_name_max = 100; length_summary_max = 200; require_content_license = FALSE; validate_license = FALSE; require_url = FALSE; number_para_max = 20; number_para_min = 1; require_sentence_case = FALSE; require_content_rating = FALSE; switch (as_format_get_kind (format)) { case AS_FORMAT_KIND_METAINFO: case AS_FORMAT_KIND_APPDATA: require_name = FALSE; break; default: break; } } /* make the requirements more strict */ if ((flags & AS_APP_VALIDATE_FLAG_STRICT) > 0) { deprecated_failure = TRUE; require_copyright = TRUE; require_translations = TRUE; require_project_license = TRUE; require_content_license = TRUE; require_appstream_spec_only = TRUE; require_sentence_case = TRUE; require_name_shorter_than_summary = TRUE; require_contactdetails = TRUE; require_translation = TRUE; number_para_min = 2; number_para_max = 4; } /* addons don't need such a long description */ switch (as_format_get_kind (format)) { case AS_FORMAT_KIND_METAINFO: case AS_FORMAT_KIND_APPDATA: number_para_min = 1; break; default: break; } /* set up networking */ helper->app = app; helper->probs = g_ptr_array_new_with_free_func ((GDestroyNotify) g_object_unref); helper->screenshot_urls = g_ptr_array_new_with_free_func (g_free); helper->flags = flags; if (!as_app_validate_setup_networking (helper, error)) return NULL; /* invalid component type */ if (as_app_get_kind (app) == AS_APP_KIND_UNKNOWN) { ai_app_validate_add (helper, AS_PROBLEM_KIND_ATTRIBUTE_INVALID, "<component> has invalid type attribute"); } as_app_validate_check_id (helper, as_app_get_id (app)); /* metadata_license */ license = as_app_get_metadata_license (app); if (license != NULL) { if (require_content_license && !as_app_validate_is_content_license (license)) { ai_app_validate_add (helper, AS_PROBLEM_KIND_TAG_INVALID, "<metadata_license> is not valid [%s]", license); } else if (validate_license) { if (!as_app_validate_license (license, &error_local)) { g_prefix_error (&error_local, "<metadata_license> is not valid [%s]", license); ai_app_validate_add (helper, AS_PROBLEM_KIND_TAG_INVALID, "%s", error_local->message); g_clear_error (&error_local); } } } if (license == NULL) { switch (as_format_get_kind (format)) { case AS_FORMAT_KIND_APPDATA: case AS_FORMAT_KIND_METAINFO: ai_app_validate_add (helper, AS_PROBLEM_KIND_TAG_MISSING, "<metadata_license> is not present"); break; default: break; } } /* project_license */ license = as_app_get_project_license (app); if (license != NULL && validate_license) { if (!as_app_validate_license (license, &error_local)) { g_prefix_error (&error_local, "<project_license> is not valid [%s]", license); ai_app_validate_add (helper, AS_PROBLEM_KIND_TAG_INVALID, "%s", error_local->message); g_clear_error (&error_local); } } if (require_project_license && license == NULL) { switch (as_format_get_kind (format)) { case AS_FORMAT_KIND_APPDATA: case AS_FORMAT_KIND_METAINFO: ai_app_validate_add (helper, AS_PROBLEM_KIND_TAG_MISSING, "<project_license> is not present"); break; default: break; } } /* categories */ if (as_format_get_kind (format) == AS_FORMAT_KIND_APPSTREAM && as_app_get_kind (app) == AS_APP_KIND_DESKTOP) { GPtrArray *categories = as_app_get_categories (app); guint nr_toplevel_cats = 0; const gchar *cats[] = { "AudioVideo", "Development", "Education", "Game", "Graphics", "Network", "Office", "Science", "Settings", "System", "Utility", NULL }; for (guint i = 0; i < categories->len; i++) { const gchar *cat = g_ptr_array_index (categories, i); for (guint j = 0; cats[j] != NULL; j++) { if (g_strcmp0 (cats[j], cat) == 0) nr_toplevel_cats++; } } if (nr_toplevel_cats == 0) { ai_app_validate_add (helper, AS_PROBLEM_KIND_TAG_MISSING, "<category> must include main categories " "from the desktop entry spec"); } else if (nr_toplevel_cats > 3) { ai_app_validate_add (helper, AS_PROBLEM_KIND_TAG_MISSING, "too many main <category> types: %u", nr_toplevel_cats); } } /* translation */ if (require_translation && as_format_get_kind (format) == AS_FORMAT_KIND_APPDATA && as_app_get_translations (app)->len == 0) { ai_app_validate_add (helper, AS_PROBLEM_KIND_TAG_MISSING, "<translation> not specified"); } /* pkgname */ if (as_app_get_pkgname_default (app) != NULL && as_format_get_kind (format) == AS_FORMAT_KIND_METAINFO) { ai_app_validate_add (helper, AS_PROBLEM_KIND_TAG_INVALID, "<pkgname> not allowed in metainfo"); } /* appdata */ if (as_format_get_kind (format) == AS_FORMAT_KIND_APPDATA && as_app_get_kind (app) == AS_APP_KIND_DESKTOP) { AsIcon *icon = as_app_get_icon_default (app); if (icon != NULL && as_icon_get_kind (icon) != AS_ICON_KIND_REMOTE) { ai_app_validate_add (helper, AS_PROBLEM_KIND_TAG_INVALID, "<icon> not allowed in desktop appdata"); } } /* extends */ if (as_app_get_extends(app)->len == 0 && as_app_get_kind (app) == AS_APP_KIND_ADDON && as_format_get_kind (format) == AS_FORMAT_KIND_METAINFO) { ai_app_validate_add (helper, AS_PROBLEM_KIND_TAG_MISSING, "<extends> is not present"); } /* update_contact */ update_contact = as_app_get_update_contact (app); if (g_strcmp0 (update_contact, "someone_who_cares@upstream_project.org") == 0) { ai_app_validate_add (helper, AS_PROBLEM_KIND_TAG_INVALID, "<update_contact> is still set to a dummy value"); } if (update_contact != NULL && strlen (update_contact) < 6) { ai_app_validate_add (helper, AS_PROBLEM_KIND_STYLE_INCORRECT, "<update_contact> is too short [%s]", update_contact); } if (require_contactdetails && update_contact == NULL) { switch (as_format_get_kind (format)) { case AS_FORMAT_KIND_APPDATA: case AS_FORMAT_KIND_METAINFO: ai_app_validate_add (helper, AS_PROBLEM_KIND_TAG_MISSING, "<update_contact> is not present"); break; default: break; } } /* only found for files */ problems = as_app_get_problems (app); if (as_format_get_kind (format) == AS_FORMAT_KIND_APPDATA || as_format_get_kind (format) == AS_FORMAT_KIND_METAINFO) { if ((problems & AS_APP_PROBLEM_NO_XML_HEADER) > 0) { ai_app_validate_add (helper, AS_PROBLEM_KIND_MARKUP_INVALID, "<?xml> header not found"); } if (require_copyright && (problems & AS_APP_PROBLEM_NO_COPYRIGHT_INFO) > 0) { ai_app_validate_add (helper, AS_PROBLEM_KIND_VALUE_MISSING, "<!-- Copyright [year] [name] --> is not present"); } if (deprecated_failure && (problems & AS_APP_PROBLEM_UPDATECONTACT_FALLBACK) > 0) { ai_app_validate_add (helper, AS_PROBLEM_KIND_TAG_INVALID, "<updatecontact> should be <update_contact>"); } } /* check invalid values */ if ((problems & AS_APP_PROBLEM_INVALID_PROJECT_GROUP) > 0) { ai_app_validate_add (helper, AS_PROBLEM_KIND_TAG_INVALID, "<project_group> is not valid"); } /* only allow XML in the specification */ if (require_appstream_spec_only && (problems & AS_APP_PROBLEM_INVALID_XML_TAG) > 0) { ai_app_validate_add (helper, AS_PROBLEM_KIND_TAG_INVALID, "XML data contains unknown tag"); } /* only allow XML in the specification */ if (problems & AS_APP_PROBLEM_EXPECTED_CHILDREN) { ai_app_validate_add (helper, AS_PROBLEM_KIND_TAG_INVALID, "Expected children for tag"); } /* only allow XML in the specification */ if (problems & AS_APP_PROBLEM_INVALID_KEYWORDS) { ai_app_validate_add (helper, AS_PROBLEM_KIND_TAG_INVALID, "<keyword> invalid contents"); } /* releases all have to have unique versions */ if (problems & AS_APP_PROBLEM_DUPLICATE_RELEASE) { ai_app_validate_add (helper, AS_PROBLEM_KIND_TAG_INVALID, "<release> version was duplicated"); } if (problems & AS_APP_PROBLEM_DUPLICATE_SCREENSHOT) { ai_app_validate_add (helper, AS_PROBLEM_KIND_TAG_INVALID, "<screenshot> content was duplicated"); } if (problems & AS_APP_PROBLEM_DUPLICATE_CONTENT_RATING) { ai_app_validate_add (helper, AS_PROBLEM_KIND_TAG_INVALID, "<content_rating> was duplicated"); } /* check for things that have to exist */ if (as_app_get_id (app) == NULL) { ai_app_validate_add (helper, AS_PROBLEM_KIND_TAG_MISSING, "<id> is not present"); } /* games require a content rating */ if (require_content_rating) { GPtrArray *ratings = as_app_get_content_ratings (app); if (ratings->len == 0) { ai_app_validate_add (helper, AS_PROBLEM_KIND_TAG_MISSING, "<content_rating> required " "[use https://odrs.gnome.org/oars]"); } } /* url */ urls = as_app_get_urls (app); keys = g_hash_table_get_keys (urls); for (l = keys; l != NULL; l = l->next) { key = l->data; if (g_strcmp0 (key, "unknown") == 0) { ai_app_validate_add (helper, AS_PROBLEM_KIND_TAG_INVALID, "<url> type invalid [%s]", key); } tmp = g_hash_table_lookup (urls, key); if (tmp == NULL || tmp[0] == '\0') continue; if (!g_str_has_prefix (tmp, "http://") && !g_str_has_prefix (tmp, "https://")) { ai_app_validate_add (helper, AS_PROBLEM_KIND_TAG_INVALID, "<url> does not start with 'http://' [%s]", tmp); } } /* screenshots */ as_app_validate_screenshots (app, helper); /* icons */ as_app_validate_icons (app, helper); /* releases */ if (!as_app_validate_releases (app, helper, error)) return NULL; /* kudos */ if (!as_app_validate_kudos (app, helper, error)) return NULL; /* name */ name = as_app_get_name (app, "C"); if (name != NULL) { str_len = (guint) strlen (name); if (str_len < length_name_min) { ai_app_validate_add (helper, AS_PROBLEM_KIND_STYLE_INCORRECT, "<name> is too short [%s] minimum is %u chars", name, length_name_min); } if (str_len > length_name_max) { ai_app_validate_add (helper, AS_PROBLEM_KIND_STYLE_INCORRECT, "<name> is too long [%s] maximum is %u chars", name, length_name_max); } if (ai_app_validate_fullstop_ending (name)) { ai_app_validate_add (helper, AS_PROBLEM_KIND_STYLE_INCORRECT, "<name> cannot end in '.' [%s]", name); } if (as_app_validate_has_hyperlink (name)) { ai_app_validate_add (helper, AS_PROBLEM_KIND_STYLE_INCORRECT, "<name> cannot contain a hyperlink [%s]", name); } if (require_sentence_case && !as_app_validate_has_first_word_capital (helper, name)) { ai_app_validate_add (helper, AS_PROBLEM_KIND_STYLE_INCORRECT, "<name> requires sentence case [%s]", name); } } else if (require_name) { ai_app_validate_add (helper, AS_PROBLEM_KIND_TAG_MISSING, "<name> is not present"); } /* comment */ summary = as_app_get_comment (app, "C"); if (summary != NULL) { str_len = (guint) strlen (summary); if (str_len < length_summary_min) { ai_app_validate_add (helper, AS_PROBLEM_KIND_STYLE_INCORRECT, "<summary> is too short [%s] minimum is %u chars", summary, length_summary_min); } if (str_len > length_summary_max) { ai_app_validate_add (helper, AS_PROBLEM_KIND_STYLE_INCORRECT, "<summary> is too long [%s] maximum is %u chars", summary, length_summary_max); } if (require_sentence_case && ai_app_validate_fullstop_ending (summary)) { ai_app_validate_add (helper, AS_PROBLEM_KIND_STYLE_INCORRECT, "<summary> cannot end in '.' [%s]", summary); } if (as_app_validate_has_hyperlink (summary)) { ai_app_validate_add (helper, AS_PROBLEM_KIND_STYLE_INCORRECT, "<summary> cannot contain a hyperlink [%s]", summary); } if (require_sentence_case && !as_app_validate_has_first_word_capital (helper, summary)) { ai_app_validate_add (helper, AS_PROBLEM_KIND_STYLE_INCORRECT, "<summary> requires sentence case [%s]", summary); } } else if (require_name) { ai_app_validate_add (helper, AS_PROBLEM_KIND_TAG_MISSING, "<summary> is not present"); } if (require_name_shorter_than_summary && summary != NULL && name != NULL && strlen (summary) < strlen (name)) { ai_app_validate_add (helper, AS_PROBLEM_KIND_STYLE_INCORRECT, "<summary> is shorter than <name>"); } description = as_app_get_description (app, "C"); if (description == NULL) { if (require_description) { ai_app_validate_add (helper, AS_PROBLEM_KIND_TAG_MISSING, "<description> required"); } } else { ret = as_app_validate_description (description, helper, number_para_min, number_para_max, FALSE, &error_local); if (!ret) { ai_app_validate_add (helper, AS_PROBLEM_KIND_MARKUP_INVALID, "%s", error_local->message); g_error_free (error_local); } } if (require_translations) { if (name != NULL && as_app_get_name_size (app) == 1 && (problems & AS_APP_PROBLEM_INTLTOOL_NAME) == 0) { ai_app_validate_add (helper, AS_PROBLEM_KIND_TRANSLATIONS_REQUIRED, "<name> has no translations"); } if (summary != NULL && as_app_get_comment_size (app) == 1 && (problems & AS_APP_PROBLEM_INTLTOOL_SUMMARY) == 0) { ai_app_validate_add (helper, AS_PROBLEM_KIND_TRANSLATIONS_REQUIRED, "<summary> has no translations"); } if (description != NULL && as_app_get_description_size (app) == 1 && (problems & AS_APP_PROBLEM_INTLTOOL_DESCRIPTION) == 0) { ai_app_validate_add (helper, AS_PROBLEM_KIND_TRANSLATIONS_REQUIRED, "<description> has no translations"); } } /* developer_name */ name = as_app_get_developer_name (app, NULL); if (name != NULL) { str_len = (guint) strlen (name); if (str_len < length_name_min) { ai_app_validate_add (helper, AS_PROBLEM_KIND_STYLE_INCORRECT, "<developer_name> is too short [%s] minimum is %u chars", name, length_name_min); } if (str_len > length_name_max) { ai_app_validate_add (helper, AS_PROBLEM_KIND_STYLE_INCORRECT, "<developer_name> is too long [%s] maximum is %u chars", name, length_name_max); } if (as_app_validate_has_hyperlink (name)) { ai_app_validate_add (helper, AS_PROBLEM_KIND_STYLE_INCORRECT, "<developer_name> cannot contain a hyperlink [%s]", name); } if (as_app_validate_has_email (name)) { ai_app_validate_add (helper, AS_PROBLEM_KIND_STYLE_INCORRECT, "<developer_name> cannot contain an email address [%s]", name); } } /* using deprecated names */ if (deprecated_failure && (problems & AS_APP_PROBLEM_DEPRECATED_LICENCE) > 0) { ai_app_validate_add (helper, AS_PROBLEM_KIND_ATTRIBUTE_INVALID, "<licence> is deprecated, use " "<metadata_license> instead"); } if ((problems & AS_APP_PROBLEM_MULTIPLE_ENTRIES) > 0) { ai_app_validate_add (helper, AS_PROBLEM_KIND_MARKUP_INVALID, "<application> used more than once"); } /* require homepage */ if (require_url && as_app_get_url_item (app, AS_URL_KIND_HOMEPAGE) == NULL) { switch (as_format_get_kind (format)) { case AS_FORMAT_KIND_APPDATA: case AS_FORMAT_KIND_METAINFO: ai_app_validate_add (helper, AS_PROBLEM_KIND_TAG_MISSING, "<url> is not present"); break; default: break; } } return helper->probs; }