summaryrefslogtreecommitdiffstats
path: root/plugins/housekeeping/gsd-housekeeping-manager.c
diff options
context:
space:
mode:
Diffstat (limited to 'plugins/housekeeping/gsd-housekeeping-manager.c')
-rw-r--r--plugins/housekeeping/gsd-housekeeping-manager.c519
1 files changed, 519 insertions, 0 deletions
diff --git a/plugins/housekeeping/gsd-housekeeping-manager.c b/plugins/housekeeping/gsd-housekeeping-manager.c
new file mode 100644
index 0000000..b291667
--- /dev/null
+++ b/plugins/housekeeping/gsd-housekeeping-manager.c
@@ -0,0 +1,519 @@
+/*
+ * Copyright (C) 2008 Michael J. Chudobiak <mjc@avtechpulse.com>
+ *
+ * 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 <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#include "config.h"
+
+#include <gio/gio.h>
+#include <glib/gstdio.h>
+#include <string.h>
+#include <libnotify/notify.h>
+
+#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[] =
+"<node>"
+" <interface name='org.gnome.SettingsDaemon.Housekeeping'>"
+" <method name='EmptyTrash'/>"
+" <method name='RemoveTempFiles'/>"
+" </interface>"
+"</node>";
+
+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",
+ "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 (&current_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);
+}