From a6936bf996db954498ee50e8d3930598494ac417 Mon Sep 17 00:00:00 2001
From: Matthias Clasen <mclasen@redhat.com>
Date: Thu, 29 Nov 2012 20:46:37 -0500
Subject: [PATCH 4/6] Do the purging asynchronously

---
 plugins/housekeeping/gsd-disk-space.c | 256 ++++++++++++++++++++++++++--------
 1 file changed, 195 insertions(+), 61 deletions(-)

diff --git a/plugins/housekeeping/gsd-disk-space.c b/plugins/housekeeping/gsd-disk-space.c
index e316885..469bcba 100644
--- a/plugins/housekeeping/gsd-disk-space.c
+++ b/plugins/housekeeping/gsd-disk-space.c
@@ -247,21 +247,19 @@ should_purge_file (GFile        *file,
                                   NULL);
 
         date = g_file_info_get_deletion_date (info);
-        if (date == NULL)
-          {
-            guint uid;
-            guint64 ctime;
-
-            uid = g_file_info_get_attribute_uint32 (info, G_FILE_ATTRIBUTE_UNIX_UID);
-            if (uid != getuid ())
-              {
-                should_purge = FALSE;
-                goto out;
-              }
-
-            ctime = g_file_info_get_attribute_uint64 (info, G_FILE_ATTRIBUTE_TIME_CHANGED);
-            date = g_date_time_new_from_unix_local ((gint64) ctime);
-          }
+        if (date == NULL) {
+                guint uid;
+                guint64 ctime;
+
+                uid = g_file_info_get_attribute_uint32 (info, G_FILE_ATTRIBUTE_UNIX_UID);
+                if (uid != getuid ()) {
+                        should_purge = FALSE;
+                        goto out;
+                }
+
+                ctime = g_file_info_get_attribute_uint64 (info, G_FILE_ATTRIBUTE_TIME_CHANGED);
+                date = g_date_time_new_from_unix_local ((gint64) ctime);
+        }
 
         should_purge = g_date_time_difference (old, date) >= 0;
         g_date_time_unref (date);
@@ -272,81 +270,215 @@ out:
         return should_purge;
 }
 
-static void
-delete_recursively_by_age (GFile        *file,
-                           GCancellable *cancellable,
-                           GDateTime    *old,
-                           gboolean      dry_run,
-                           gboolean      trash,
-                           gint          depth)
+typedef struct {
+        gint ref_count;
+        GFile           *file;
+        GCancellable    *cancellable;
+        GDateTime       *old;
+        gboolean         dry_run;
+        gboolean         trash;
+        gchar           *name;
+        gint             depth;
+} DeleteData;
+
+static DeleteData *
+delete_data_new (GFile        *file,
+                 GCancellable *cancellable,
+                 GDateTime    *old,
+                 gboolean      dry_run,
+                 gboolean      trash,
+                 gint          depth)
 {
-        GFileInfo *info;
-        GFile *child;
-        GFileEnumerator *enumerator;
+        DeleteData *data;
+
+        data = g_new (DeleteData, 1);
+        data->ref_count = 1;
+        data->file = g_object_ref (file);
+        data->cancellable = cancellable ? g_object_ref (cancellable) : NULL;
+        data->old = g_date_time_ref (old);
+        data->dry_run = dry_run;
+        data->trash = trash;
+        data->depth = depth;
+        data->name = g_file_get_parse_name (data->file);
+
+        return data;
+}
 
-        if (trash && (depth == 1) && !should_purge_file (file, cancellable, old)) {
-                /* no need to recurse into trashed directories */
+static DeleteData *
+delete_data_ref (DeleteData *data)
+{
+  data->ref_count += 1;
+  return data;
+}
+
+static void
+delete_data_unref (DeleteData *data)
+{
+        data->ref_count -= 1;
+        if (data->ref_count > 0)
                 return;
-        }
 
-        enumerator = g_file_enumerate_children (file,
-                                                G_FILE_ATTRIBUTE_STANDARD_NAME ","
-                                                G_FILE_ATTRIBUTE_STANDARD_TYPE,
-                                                G_FILE_QUERY_INFO_NOFOLLOW_SYMLINKS,
-                                                cancellable,
-                                                NULL);
-        if (enumerator) {
-                while (!g_cancellable_is_cancelled (cancellable) &&
-                       (info = g_file_enumerator_next_file (enumerator, cancellable, NULL)) != NULL) {
-                       child = g_file_get_child (file, g_file_info_get_name (info));
-                       delete_recursively_by_age (child, cancellable, old, dry_run, trash, depth + 1);
-                       g_object_unref (child);
-                       g_object_unref (info);
+        g_object_unref (data->file);
+        if (data->cancellable)
+                g_object_unref (data->cancellable);
+        g_date_time_unref (data->old);
+        g_free (data->name);
+        g_free (data);
+}
+
+static void delete_recursively_by_age (DeleteData *data);
+
+static void
+delete_batch (GObject      *source,
+              GAsyncResult *res,
+              gpointer      user_data)
+{
+        GFileEnumerator *enumerator = G_FILE_ENUMERATOR (source);
+        DeleteData *data = user_data;
+        GList *files, *f;
+        GFile *child_file;
+        DeleteData *child;
+        GFileInfo *info;
+        GError *error = NULL;
+
+        files = g_file_enumerator_next_files_finish (enumerator, res, &error);
+
+        g_debug ("GsdHousekeeping: purging %d children of %s", g_list_length (files), data->name);
+
+        if (files) {
+                for (f = files; f; f = f->next) {
+                        if (g_cancellable_is_cancelled (data->cancellable))
+                                break;
+                        info = f->data;
+
+                        child_file = g_file_get_child (data->file, g_file_info_get_name (info));
+                        child = delete_data_new (child_file,
+                                                 data->cancellable,
+                                                 data->old,
+                                                 data->dry_run,
+                                                 data->trash,
+                                                 data->depth + 1);
+                        delete_recursively_by_age (child);
+                        delete_data_unref (child);
+                        g_object_unref (child_file);
+                }
+                g_list_free_full (files, g_object_unref);
+                if (!g_cancellable_is_cancelled (data->cancellable)) {
+                        g_file_enumerator_next_files_async (enumerator, 20,
+                                                            0,
+                                                            data->cancellable,
+                                                            delete_batch,
+                                                            data);
+                        return;
                 }
-                g_file_enumerator_close (enumerator, cancellable, NULL);
-                g_object_unref (enumerator);
         }
 
-        if (depth > 0 && !g_cancellable_is_cancelled (cancellable)) {
-                if ((trash && depth > 1) || should_purge_file (file, cancellable, old)) {
-                        g_debug ("GsdHousekeeping: purging %s\n", g_file_get_parse_name (file));
-                        if (!dry_run) {
-                                g_file_delete (file, cancellable, NULL);
+        g_file_enumerator_close (enumerator, data->cancellable, NULL);
+        g_object_unref (enumerator);
+
+        if (data->depth > 0 && !g_cancellable_is_cancelled (data->cancellable)) {
+                if ((data->trash && data->depth > 1) ||
+                     should_purge_file (data->file, data->cancellable, data->old)) {
+                        g_debug ("GsdHousekeeping: purging %s\n", data->name);
+                        if (!data->dry_run) {
+                                g_file_delete (data->file, data->cancellable, NULL);
                         }
                 }
         }
+        delete_data_unref (data);
+}
+
+static void
+delete_subdir (GObject      *source,
+               GAsyncResult *res,
+               gpointer      user_data)
+{
+        GFile *file = G_FILE (source);
+        DeleteData *data = user_data;
+        GFileEnumerator *enumerator;
+        GError *error = NULL;
+
+        g_debug ("GsdHousekeeping: purging %s in %s\n",
+                 data->trash ? "trash" : "temporary files", data->name);
+
+        enumerator = g_file_enumerate_children_finish (file, res, &error);
+        if (error) {
+                if (!g_error_matches (error, G_IO_ERROR, G_IO_ERROR_NOT_DIRECTORY))
+                        g_warning ("Failed to enumerate children of %s: %s\n", data->name, error->message);
+                g_error_free (error);
+        }
+        if (enumerator) {
+                g_file_enumerator_next_files_async (enumerator, 20,
+                                                    0,
+                                                    data->cancellable,
+                                                    delete_batch,
+                                                    delete_data_ref (data));
+        }
+        delete_data_unref (data);
+}
+
+static void
+delete_recursively_by_age (DeleteData *data)
+{
+        if (data->trash && (data->depth == 1) &&
+            !should_purge_file (data->file, data->cancellable, data->old)) {
+                /* no need to recurse into trashed directories */
+                return;
+        }
+
+        g_file_enumerate_children_async (data->file,
+                                         G_FILE_ATTRIBUTE_STANDARD_NAME ","
+                                         G_FILE_ATTRIBUTE_STANDARD_TYPE,
+                                         G_FILE_QUERY_INFO_NOFOLLOW_SYMLINKS,
+                                         0,
+                                         data->cancellable,
+                                         delete_subdir,
+                                         delete_data_ref (data));
 }
 
 void
 gsd_ldsm_purge_trash (GDateTime *old)
 {
         GFile *file;
+        DeleteData *data;
 
         file = g_file_new_for_uri ("trash:");
-        delete_recursively_by_age (file, NULL, old, FALSE, TRUE, 0);
+        data = delete_data_new (file, NULL, old, FALSE, TRUE, 0);
+        delete_recursively_by_age (data);
+        delete_data_unref (data);
         g_object_unref (file);
 }
 
 void
 gsd_ldsm_purge_temp_files (GDateTime *old)
 {
+        DeleteData *data;
         GFile *file;
 
         file = g_file_new_for_path (g_get_tmp_dir ());
-        delete_recursively_by_age (file, NULL, old, FALSE, FALSE, 0);
-        g_object_unref (file);
+        data = delete_data_new (file, NULL, old, FALSE, FALSE, 0);
+        delete_recursively_by_age (data);
 
         if (g_strcmp0 (g_get_tmp_dir (), "/var/tmp") != 0) {
-                file = g_file_new_for_path ("/var/tmp");
-                delete_recursively_by_age (file, NULL, old, FALSE, FALSE, 0);
-                g_object_unref (file);
+                g_assert (data->ref_count == 1);
+                g_assert (data->depth == 0);
+                g_object_unref (data->file);
+                data->file = g_file_new_for_path ("/var/tmp");
+                data->depth = 0;
+                delete_recursively_by_age (data);
         }
 
         if (g_strcmp0 (g_get_tmp_dir (), "/tmp") != 0) {
-                file = g_file_new_for_path ("/tmp");
-                delete_recursively_by_age (file, NULL, old, FALSE, FALSE, 0);
-                g_object_unref (file);
+                g_assert (data->ref_count == 1);
+                g_assert (data->depth == 0);
+                g_object_unref (data->file);
+                data->file = g_file_new_for_path ("/tmp");
+                data->depth = 0;
+                delete_recursively_by_age (data);
         }
+
+        delete_data_unref (data);
+        g_object_unref (file);
 }
 
 void
@@ -354,14 +486,16 @@ gsd_ldsm_show_empty_trash (void)
 {
         GFile *file;
         GDateTime *old;
+        DeleteData *data;
 
         old = g_date_time_new_now_local ();
-
         file = g_file_new_for_uri ("trash:");
-        delete_recursively_by_age (file, NULL, old, TRUE, TRUE, 0);
+        data = delete_data_new (file, NULL, old, TRUE, TRUE, 0);
         g_object_unref (file);
-
         g_date_time_unref (old);
+
+        delete_recursively_by_age (data);
+        delete_data_unref (data);
 }
 
 static gboolean
@@ -371,7 +505,6 @@ ldsm_purge_trash_and_temp (gpointer data)
 
         now = g_date_time_new_now_local ();
         old = g_date_time_add_days (now, - purge_after);
-        g_date_time_unref (now);
 
         if (purge_trash) {
                 g_debug ("housekeeping: purge trash older than %u days", purge_after);
@@ -383,6 +516,7 @@ ldsm_purge_trash_and_temp (gpointer data)
         }
 
         g_date_time_unref (old);
+        g_date_time_unref (now);
 
         return G_SOURCE_CONTINUE;
 }
-- 
1.8.0

