/* * Copyright (C) 2008 Michael J. Chudobiak * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, see . * */ #include "config.h" #include #include #include #include #include "gnome-settings-profile.h" #include "gsd-housekeeping-manager.h" #include "gsd-disk-space.h" #include "gsd-systemd-notify.h" /* General */ #define INTERVAL_ONCE_A_DAY 24*60*60 #define INTERVAL_TWO_MINUTES 2*60 /* Thumbnail cleaner */ #define THUMB_PREFIX "org.gnome.desktop.thumbnail-cache" #define THUMB_AGE_KEY "maximum-age" #define THUMB_SIZE_KEY "maximum-size" #define GSD_HOUSEKEEPING_DBUS_PATH "/org/gnome/SettingsDaemon/Housekeeping" static const gchar introspection_xml[] = "" " " " " " " " " ""; struct _GsdHousekeepingManager { GObject parent; GSettings *settings; guint long_term_cb; guint short_term_cb; GDBusNodeInfo *introspection_data; GDBusConnection *connection; GCancellable *bus_cancellable; guint name_id; GsdSystemdNotify *systemd_notify; }; static void gsd_housekeeping_manager_class_init (GsdHousekeepingManagerClass *klass); static void gsd_housekeeping_manager_init (GsdHousekeepingManager *housekeeping_manager); G_DEFINE_TYPE (GsdHousekeepingManager, gsd_housekeeping_manager, G_TYPE_OBJECT) static gpointer manager_object = NULL; typedef struct { glong now; glong max_age; goffset total_size; goffset max_size; } PurgeData; typedef struct { gint64 time; char *path; glong size; } ThumbData; static void thumb_data_free (gpointer data) { ThumbData *info = data; if (info) { g_free (info->path); g_free (info); } } static GList * read_dir_for_purge (const char *path, GList *files) { GFile *read_path; GFileEnumerator *enum_dir; int cannot_get_time = 0; read_path = g_file_new_for_path (path); enum_dir = g_file_enumerate_children (read_path, G_FILE_ATTRIBUTE_STANDARD_NAME "," G_FILE_ATTRIBUTE_TIME_ACCESS "," G_FILE_ATTRIBUTE_TIME_MODIFIED "," G_FILE_ATTRIBUTE_STANDARD_SIZE, G_FILE_QUERY_INFO_NONE, NULL, NULL); if (enum_dir != NULL) { GFileInfo *info; while ((info = g_file_enumerator_next_file (enum_dir, NULL, NULL)) != NULL) { const char *name; name = g_file_info_get_name (info); if (strlen (name) == 36 && strcmp (name + 32, ".png") == 0) { ThumbData *td; GFile *entry; char *entry_path; gint64 time; entry = g_file_get_child (read_path, name); entry_path = g_file_get_path (entry); g_object_unref (entry); // If atime is available, it should be no worse than mtime. // - Even if the file system is mounted with noatime, the atime and // mtime will be set to the same value on file creation. // - Since the thumbnailer never edits thumbnails, and instead swaps // in newly created temp files, atime will always be >= mtime. if (g_file_info_has_attribute (info, G_FILE_ATTRIBUTE_TIME_ACCESS)) { time = g_file_info_get_attribute_uint64 (info, G_FILE_ATTRIBUTE_TIME_ACCESS); } else if (g_file_info_has_attribute (info, G_FILE_ATTRIBUTE_TIME_MODIFIED)) { time = g_file_info_get_attribute_uint64 (info, G_FILE_ATTRIBUTE_TIME_MODIFIED); } else { // Unlikely to get here, but be defensive cannot_get_time += 1; time = G_MAXINT64; } td = g_new0 (ThumbData, 1); td->path = entry_path; td->time = time; td->size = g_file_info_get_size (info); files = g_list_prepend (files, td); } g_object_unref (info); } g_object_unref (enum_dir); if (cannot_get_time > 0) { g_warning ("Could not read atime or mtime on %d files in %s", cannot_get_time, path); } } g_object_unref (read_path); return files; } static void purge_old_thumbnails (ThumbData *info, PurgeData *purge_data) { if ((purge_data->now - info->time) > purge_data->max_age) { g_unlink (info->path); info->size = 0; } else { purge_data->total_size += info->size; } } static int sort_file_time (ThumbData *file1, ThumbData *file2) { return file1->time - file2->time; } static char ** get_thumbnail_dirs (void) { GPtrArray *array; char *path; array = g_ptr_array_new (); /* check new XDG cache */ path = g_build_filename (g_get_user_cache_dir (), "thumbnails", "normal", NULL); g_ptr_array_add (array, path); path = g_build_filename (g_get_user_cache_dir (), "thumbnails", "large", NULL); g_ptr_array_add (array, path); path = g_build_filename (g_get_user_cache_dir (), "thumbnails", "x-large", NULL); g_ptr_array_add (array, path); path = g_build_filename (g_get_user_cache_dir (), "thumbnails", "xx-large", NULL); g_ptr_array_add (array, path); path = g_build_filename (g_get_user_cache_dir (), "thumbnails", "fail", "gnome-thumbnail-factory", NULL); g_ptr_array_add (array, path); /* cleanup obsolete locations too */ path = g_build_filename (g_get_home_dir (), ".thumbnails", "normal", NULL); g_ptr_array_add (array, path); path = g_build_filename (g_get_home_dir (), ".thumbnails", "large", NULL); g_ptr_array_add (array, path); path = g_build_filename (g_get_home_dir (), ".thumbnails", "fail", "gnome-thumbnail-factory", NULL); g_ptr_array_add (array, path); g_ptr_array_add (array, NULL); return (char **) g_ptr_array_free (array, FALSE); } static void purge_thumbnail_cache (GsdHousekeepingManager *manager) { char **paths; GList *files; PurgeData purge_data; GTimeVal current_time; guint i; g_debug ("housekeeping: checking thumbnail cache size and freshness"); purge_data.max_age = (glong) g_settings_get_int (manager->settings, THUMB_AGE_KEY) * 24 * 60 * 60; purge_data.max_size = (goffset) g_settings_get_int (manager->settings, THUMB_SIZE_KEY) * 1024 * 1024; /* if both are set to -1, we don't need to read anything */ if ((purge_data.max_age < 0) && (purge_data.max_size < 0)) return; paths = get_thumbnail_dirs (); files = NULL; for (i = 0; paths[i] != NULL; i++) files = read_dir_for_purge (paths[i], files); g_strfreev (paths); g_get_current_time (¤t_time); purge_data.now = current_time.tv_sec; purge_data.total_size = 0; if (purge_data.max_age >= 0) g_list_foreach (files, (GFunc) purge_old_thumbnails, &purge_data); if ((purge_data.total_size > purge_data.max_size) && (purge_data.max_size >= 0)) { GList *scan; files = g_list_sort (files, (GCompareFunc) sort_file_time); for (scan = files; scan && (purge_data.total_size > purge_data.max_size); scan = scan->next) { ThumbData *info = scan->data; g_unlink (info->path); purge_data.total_size -= info->size; } } g_list_foreach (files, (GFunc) thumb_data_free, NULL); g_list_free (files); } static gboolean do_cleanup (GsdHousekeepingManager *manager) { purge_thumbnail_cache (manager); return TRUE; } static gboolean do_cleanup_once (GsdHousekeepingManager *manager) { do_cleanup (manager); manager->short_term_cb = 0; return FALSE; } static void do_cleanup_soon (GsdHousekeepingManager *manager) { if (manager->short_term_cb == 0) { g_debug ("housekeeping: will tidy up in 2 minutes"); manager->short_term_cb = g_timeout_add_seconds (INTERVAL_TWO_MINUTES, (GSourceFunc) do_cleanup_once, manager); g_source_set_name_by_id (manager->short_term_cb, "[gnome-settings-daemon] do_cleanup_once"); } } static void settings_changed_callback (GSettings *settings, const char *key, GsdHousekeepingManager *manager) { do_cleanup_soon (manager); } static void handle_method_call (GDBusConnection *connection, const gchar *sender, const gchar *object_path, const gchar *interface_name, const gchar *method_name, GVariant *parameters, GDBusMethodInvocation *invocation, gpointer user_data) { GDateTime *now; now = g_date_time_new_now_local (); if (g_strcmp0 (method_name, "EmptyTrash") == 0) { gsd_ldsm_purge_trash (now); g_dbus_method_invocation_return_value (invocation, NULL); } else if (g_strcmp0 (method_name, "RemoveTempFiles") == 0) { gsd_ldsm_purge_temp_files (now); g_dbus_method_invocation_return_value (invocation, NULL); } g_date_time_unref (now); } static const GDBusInterfaceVTable interface_vtable = { handle_method_call, NULL, /* Get Property */ NULL, /* Set Property */ }; static void on_bus_gotten (GObject *source_object, GAsyncResult *res, GsdHousekeepingManager *manager) { GDBusConnection *connection; GError *error = NULL; GDBusInterfaceInfo **infos; int i; connection = g_bus_get_finish (res, &error); if (connection == NULL) { if (!g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED)) g_warning ("Could not get session bus: %s", error->message); g_error_free (error); return; } manager->connection = connection; infos = manager->introspection_data->interfaces; for (i = 0; infos[i] != NULL; i++) { g_dbus_connection_register_object (connection, GSD_HOUSEKEEPING_DBUS_PATH, infos[i], &interface_vtable, manager, NULL, NULL); } manager->name_id = g_bus_own_name_on_connection (connection, "org.gnome.SettingsDaemon.Housekeeping", G_BUS_NAME_OWNER_FLAGS_NONE, NULL, NULL, NULL, NULL); } static void register_manager_dbus (GsdHousekeepingManager *manager) { manager->introspection_data = g_dbus_node_info_new_for_xml (introspection_xml, NULL); g_assert (manager->introspection_data != NULL); manager->bus_cancellable = g_cancellable_new (); g_bus_get (G_BUS_TYPE_SESSION, manager->bus_cancellable, (GAsyncReadyCallback) on_bus_gotten, manager); } gboolean gsd_housekeeping_manager_start (GsdHousekeepingManager *manager, GError **error) { gchar *dir; g_debug ("Starting housekeeping manager"); gnome_settings_profile_start (NULL); /* Create ~/.local/ as early as possible */ (void) g_mkdir_with_parents(g_get_user_data_dir (), 0700); /* Create ~/.local/share/applications/, see * https://bugzilla.gnome.org/show_bug.cgi?id=703048 */ dir = g_build_filename (g_get_user_data_dir (), "applications", NULL); (void) g_mkdir (dir, 0700); g_free (dir); gsd_ldsm_setup (FALSE); manager->settings = g_settings_new (THUMB_PREFIX); g_signal_connect (G_OBJECT (manager->settings), "changed", G_CALLBACK (settings_changed_callback), manager); /* Clean once, a few minutes after start-up */ do_cleanup_soon (manager); /* Clean periodically, on a daily basis. */ manager->long_term_cb = g_timeout_add_seconds (INTERVAL_ONCE_A_DAY, (GSourceFunc) do_cleanup, manager); g_source_set_name_by_id (manager->long_term_cb, "[gnome-settings-daemon] do_cleanup"); manager->systemd_notify = g_object_new (GSD_TYPE_SYSTEMD_NOTIFY, NULL); gnome_settings_profile_end (NULL); return TRUE; } void gsd_housekeeping_manager_stop (GsdHousekeepingManager *manager) { g_debug ("Stopping housekeeping manager"); if (manager->name_id != 0) { g_bus_unown_name (manager->name_id); manager->name_id = 0; } g_clear_object (&manager->bus_cancellable); g_clear_pointer (&manager->introspection_data, g_dbus_node_info_unref); g_clear_object (&manager->connection); g_clear_object (&manager->systemd_notify); if (manager->short_term_cb) { g_source_remove (manager->short_term_cb); manager->short_term_cb = 0; } if (manager->long_term_cb) { g_source_remove (manager->long_term_cb); manager->long_term_cb = 0; /* Do a clean-up on shutdown if and only if the size or age limits have been set to paranoid levels (zero) */ if ((g_settings_get_int (manager->settings, THUMB_AGE_KEY) == 0) || (g_settings_get_int (manager->settings, THUMB_SIZE_KEY) == 0)) { do_cleanup (manager); } } g_clear_object (&manager->settings); gsd_ldsm_clean (); } static void gsd_housekeeping_manager_finalize (GObject *object) { gsd_housekeeping_manager_stop (GSD_HOUSEKEEPING_MANAGER (object)); G_OBJECT_CLASS (gsd_housekeeping_manager_parent_class)->finalize (object); } static void gsd_housekeeping_manager_class_init (GsdHousekeepingManagerClass *klass) { GObjectClass *object_class = G_OBJECT_CLASS (klass); object_class->finalize = gsd_housekeeping_manager_finalize; notify_init ("gnome-settings-daemon"); } static void gsd_housekeeping_manager_init (GsdHousekeepingManager *manager) { } GsdHousekeepingManager * gsd_housekeeping_manager_new (void) { if (manager_object != NULL) { g_object_ref (manager_object); } else { manager_object = g_object_new (GSD_TYPE_HOUSEKEEPING_MANAGER, NULL); g_object_add_weak_pointer (manager_object, (gpointer *) &manager_object); register_manager_dbus (manager_object); } return GSD_HOUSEKEEPING_MANAGER (manager_object); }