/** * as_extract_icon_cache_tarball: */ static void as_extract_icon_cache_tarball (const gchar *asicons_target, const gchar *origin, const gchar *apt_basename, const gchar *apt_lists_dir, const gchar *icons_size) { g_autofree gchar *icons_tarball = NULL; g_autofree gchar *target_dir = NULL; g_autofree gchar *cmd = NULL; g_autofree gchar *stderr_txt = NULL; gint res; g_autoptr(GError) tmp_error = NULL; icons_tarball = g_strdup_printf ("%s/%sicons-%s.tar.gz", apt_lists_dir, apt_basename, icons_size); if (!g_file_test (icons_tarball, G_FILE_TEST_EXISTS)) { /* no icons found, stop here */ return; } target_dir = g_build_filename (asicons_target, origin, icons_size, NULL); if (g_mkdir_with_parents (target_dir, 0755) > 0) { g_debug ("Unable to create '%s': %s", target_dir, g_strerror (errno)); return; } if (!as_utils_is_writable (target_dir)) { g_debug ("Unable to write to '%s': Can't add AppStream icon-cache from APT to the pool.", target_dir); return; } cmd = g_strdup_printf ("/bin/tar -xzf '%s' -C '%s'", icons_tarball, target_dir); g_spawn_command_line_sync (cmd, NULL, &stderr_txt, &res, &tmp_error); if (tmp_error != NULL) { g_debug ("Failed to run tar: %s", tmp_error->message); } if (res != 0) { g_debug ("Running tar failed with exit-code %i: %s", res, stderr_txt); } }
/** * as_pool_scan_apt: * * Scan for additional metadata in 3rd-party directories and move it to the right place. */ void as_pool_scan_apt (AsPool *pool, gboolean force, GError **error) { const gchar *apt_lists_dir = "/var/lib/apt/lists/"; const gchar *appstream_yml_target = "/var/lib/app-info/yaml"; const gchar *appstream_icons_target = "/var/lib/app-info/icons"; g_autoptr(GPtrArray) yml_files = NULL; g_autoptr(GError) tmp_error = NULL; gboolean data_changed = FALSE; guint i; /* skip this step if the APT lists directory doesn't exist */ if (!g_file_test (apt_lists_dir, G_FILE_TEST_IS_DIR)) { g_debug ("APT lists directory (%s) not found!", apt_lists_dir); return; } if (g_file_test (appstream_yml_target, G_FILE_TEST_IS_DIR)) { g_autoptr(GPtrArray) ytfiles = NULL; /* we can't modify the files here if we don't have write access */ if (!as_utils_is_writable (appstream_yml_target)) { g_debug ("Unable to write to '%s': Can't add AppStream data from APT to the pool.", appstream_yml_target); return; } ytfiles = as_utils_find_files_matching (appstream_yml_target, "*", FALSE, &tmp_error); if (tmp_error != NULL) { g_warning ("Could not scan for broken symlinks in DEP-11 target: %s", tmp_error->message); return; } for (i = 0; i < ytfiles->len; i++) { const gchar *fname = (const gchar*) g_ptr_array_index (ytfiles, i); if (!g_file_test (fname, G_FILE_TEST_EXISTS)) { g_remove (fname); data_changed = TRUE; } } } yml_files = as_utils_find_files_matching (apt_lists_dir, "*Components-*.yml.gz", FALSE, &tmp_error); if (tmp_error != NULL) { g_warning ("Could not scan for APT-downloaded DEP-11 files: %s", tmp_error->message); return; } /* no data found? skip scan step */ if (yml_files->len <= 0) { g_debug ("Couldn't find DEP-11 data in APT directories."); return; } /* We have to check if our metadata is in the target directory at all, and - if not - trigger a cache refresh. * This is needed because APT is putting files with the *server* ctime/mtime into it's lists directory, * and that time might be lower than the time the metadata cache was last updated, which may result * in no cache update being triggered at all. */ for (i = 0; i < yml_files->len; i++) { g_autofree gchar *fbasename = NULL; g_autofree gchar *dest_fname = NULL; const gchar *fname = (const gchar*) g_ptr_array_index (yml_files, i); fbasename = g_path_get_basename (fname); dest_fname = g_build_filename (appstream_yml_target, fbasename, NULL); if (!g_file_test (dest_fname, G_FILE_TEST_EXISTS)) { data_changed = TRUE; g_debug ("File '%s' missing, cache update is needed.", dest_fname); break; } } /* get the last time we touched the database */ if (!data_changed) { for (i = 0; i < yml_files->len; i++) { struct stat sb; const gchar *fname = (const gchar*) g_ptr_array_index (yml_files, i); if (stat (fname, &sb) < 0) continue; if (sb.st_ctime > as_pool_get_cache_age (pool)) { /* we need to update the cache */ data_changed = TRUE; break; } } } /* no changes means nothing to do here */ if ((!data_changed) && (!force)) return; /* this is not really great, but we simply can't detect if we should remove an icons folder or not, * or which specific icons we should drop from a folder. * So, we hereby simply "own" the icons directory and all it's contents, anything put in there by 3rd-parties will * be deleted. * (And there should actually be no cases 3rd-parties put icons there on a Debian machine, since metadata in packages * will land in /usr/share/app-info anyway) */ as_utils_delete_dir_recursive (appstream_icons_target); if (g_mkdir_with_parents (appstream_yml_target, 0755) > 0) { g_debug ("Unable to create '%s': %s", appstream_yml_target, g_strerror (errno)); return; } for (i = 0; i < yml_files->len; i++) { g_autofree gchar *fbasename = NULL; g_autofree gchar *dest_fname = NULL; g_autofree gchar *origin = NULL; g_autofree gchar *file_baseprefix = NULL; const gchar *fname = (const gchar*) g_ptr_array_index (yml_files, i); fbasename = g_path_get_basename (fname); dest_fname = g_build_filename (appstream_yml_target, fbasename, NULL); if (!g_file_test (fname, G_FILE_TEST_EXISTS)) { /* broken symlinks in the dest will have been removed earlier */ g_debug ("File %s is a broken symlink, skipping.", fname); continue; } else if (!g_file_test (dest_fname, G_FILE_TEST_EXISTS)) { /* file not found, let's symlink */ if (symlink (fname, dest_fname) != 0) { g_debug ("Unable to set symlink (%s -> %s): %s", fname, dest_fname, g_strerror (errno)); continue; } } else if (!g_file_test (dest_fname, G_FILE_TEST_IS_SYMLINK)) { /* file found, but it isn't a symlink, try to rescue */ g_debug ("Regular file '%s' found, which doesn't belong there. Removing it.", dest_fname); g_remove (dest_fname); continue; } /* get DEP-11 data origin */ origin = as_get_yml_data_origin (dest_fname); if (origin == NULL) { g_warning ("No origin found for file %s", fbasename); continue; } /* get base prefix for this file in the APT download cache */ file_baseprefix = g_strndup (fbasename, strlen (fbasename) - strlen (g_strrstr (fbasename, "_") + 1)); /* extract icons to their destination (if they exist at all */ as_extract_icon_cache_tarball (appstream_icons_target, origin, file_baseprefix, apt_lists_dir, "64x64"); as_extract_icon_cache_tarball (appstream_icons_target, origin, file_baseprefix, apt_lists_dir, "128x128"); } /* ensure the cache-rebuild process notices these changes */ as_touch_location (appstream_yml_target); }
/** * as_pool_refresh_cache: * @pool: An instance of #AsPool. * @force: Enforce refresh, even if source data has not changed. * * Update the AppStream cache. There is normally no need to call this function manually, because cache updates are handled * transparently in the background. * * Returns: %TRUE if the cache was updated, %FALSE on error or if the cache update was not necessary and has been skipped. */ gboolean as_pool_refresh_cache (AsPool *pool, gboolean force, GError **error) { AsPoolPrivate *priv = GET_PRIVATE (pool); gboolean ret = FALSE; gboolean ret_poolupdate; g_autofree gchar *cache_fname = NULL; g_autoptr(GError) tmp_error = NULL; /* try to create cache directory, in case it doesn't exist */ g_mkdir_with_parents (priv->sys_cache_path, 0755); if (!as_utils_is_writable (priv->sys_cache_path)) { g_set_error (error, AS_POOL_ERROR, AS_POOL_ERROR_TARGET_NOT_WRITABLE, _("Cache location '%s' is not writable."), priv->sys_cache_path); return FALSE; } /* collect metadata */ #ifdef HAVE_APT_SUPPORT /* currently, we only do something here if we are running with explicit APT support compiled in */ as_pool_scan_apt (pool, force, &tmp_error); if (tmp_error != NULL) { /* the exact error is not forwarded here, since we might be able to partially update the cache */ g_warning ("Error while collecting metadata: %s", tmp_error->message); g_error_free (tmp_error); tmp_error = NULL; } #endif /* create the filename of our cache */ cache_fname = g_strdup_printf ("%s/%s.gvz", priv->sys_cache_path, priv->locale); /* check if we need to refresh the cache * (which is only necessary if the AppStream data has changed) */ if (!as_pool_metadata_changed (pool)) { g_debug ("Data did not change, no cache refresh needed."); if (force) { g_debug ("Forcing refresh anyway."); } else { return FALSE; } } g_debug ("Refreshing AppStream cache"); /* find them wherever they are */ ret_poolupdate = as_pool_load (pool, NULL, &tmp_error); if (tmp_error != NULL) { /* the exact error is not forwarded here, since we might be able to partially update the cache */ g_warning ("Error while updating the in-memory data pool: %s", tmp_error->message); g_error_free (tmp_error); tmp_error = NULL; } /* save the cache object */ as_pool_save_cache_file (pool, cache_fname, &tmp_error); if (tmp_error != NULL) { /* the exact error is not forwarded here, since we might be able to partially update the cache */ g_warning ("Error while updating the cache: %s", tmp_error->message); g_error_free (tmp_error); tmp_error = NULL; ret = FALSE; } else { ret = TRUE; } if (ret) { if (!ret_poolupdate) { g_set_error (error, AS_POOL_ERROR, AS_POOL_ERROR_INCOMPLETE, _("AppStream data pool was loaded, but some metadata was ignored due to errors.")); } /* update the cache mtime, to not needlessly rebuild it again */ as_touch_location (cache_fname); as_pool_check_cache_ctime (pool); } else { g_set_error (error, AS_POOL_ERROR, AS_POOL_ERROR_FAILED, _("AppStream cache update failed.")); } return TRUE; }