[evolution-data-server] Bug 752197 - Teach cache-reaper of 3rd-party private directories ][
- From: Milan Crha <mcrha src gnome org>
- To: commits-list gnome org
- Cc:
- Subject: [evolution-data-server] Bug 752197 - Teach cache-reaper of 3rd-party private directories ][
- Date: Thu, 13 Aug 2015 09:35:44 +0000 (UTC)
commit 6668200ca90db865ca610037e4a9aa1e81d46787
Author: Milan Crha <mcrha redhat com>
Date: Thu Aug 13 11:20:59 2015 +0200
Bug 752197 - Teach cache-reaper of 3rd-party private directories ][
Make the ECacheReaper more public accessible, thus the modules can
link to it easily.
libebackend/Makefile.am | 4 +
.../e-cache-reaper-utils.c | 0
.../e-cache-reaper-utils.h | 0
libebackend/e-cache-reaper.c | 722 ++++++++++++++++++++
.../cache-reaper => libebackend}/e-cache-reaper.h | 4 +-
libebackend/libebackend.h | 1 +
modules/cache-reaper/Makefile.am | 6 -
modules/cache-reaper/module-cache-reaper.c | 702 +-------------------
8 files changed, 734 insertions(+), 705 deletions(-)
---
diff --git a/libebackend/Makefile.am b/libebackend/Makefile.am
index 30a2cf5..fab3049 100644
--- a/libebackend/Makefile.am
+++ b/libebackend/Makefile.am
@@ -57,6 +57,9 @@ libebackend_1_2_la_SOURCES = \
$(BUILT_SOURCES) \
e-backend.c \
e-backend-factory.c \
+ e-cache-reaper.c \
+ e-cache-reaper-utils.c \
+ e-cache-reaper-utils.h \
e-collection-backend.c \
e-collection-backend-factory.c \
e-data-factory.c \
@@ -100,6 +103,7 @@ libebackendinclude_HEADERS = \
e-backend-enums.h \
e-backend-enumtypes.h \
e-backend-factory.h \
+ e-cache-reaper.h \
e-collection-backend.h \
e-collection-backend-factory.h \
e-data-factory.h \
diff --git a/modules/cache-reaper/e-cache-reaper-utils.c b/libebackend/e-cache-reaper-utils.c
similarity index 100%
rename from modules/cache-reaper/e-cache-reaper-utils.c
rename to libebackend/e-cache-reaper-utils.c
diff --git a/modules/cache-reaper/e-cache-reaper-utils.h b/libebackend/e-cache-reaper-utils.h
similarity index 100%
rename from modules/cache-reaper/e-cache-reaper-utils.h
rename to libebackend/e-cache-reaper-utils.h
diff --git a/libebackend/e-cache-reaper.c b/libebackend/e-cache-reaper.c
new file mode 100644
index 0000000..ec8af5c
--- /dev/null
+++ b/libebackend/e-cache-reaper.c
@@ -0,0 +1,722 @@
+/*
+ * e-cache-reaper.c
+ *
+ * This library is free software: you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation.
+ *
+ * This library 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 Lesser General Public License
+ * for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this library. If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#include <errno.h>
+#include <time.h>
+#include <glib/gstdio.h>
+
+#include <libebackend/libebackend.h>
+
+#include "e-cache-reaper.h"
+#include "e-cache-reaper-utils.h"
+
+/* Where abandoned directories go to die. */
+#define TRASH_DIRECTORY_NAME "trash"
+
+/* XXX These intervals are rather arbitrary and prone to bikeshedding.
+ * It's just what I decided on. On startup we wait an hour to reap
+ * abandoned directories, and thereafter repeat every 24 hours. */
+#define INITIAL_INTERVAL_SECONDS ( 1 * (60 * 60))
+#define REGULAR_INTERVAL_SECONDS (24 * (60 * 60))
+
+/* XXX Similarly, these expiry times are rather arbitrary and prone to
+ * bikeshedding. Most importantly, the expiry for data directories
+ * should be far more conservative (longer) than cache directories.
+ * Cache directories are disposable, data directories are not, so
+ * we want to let abandoned data directories linger longer. */
+
+/* Minimum days for a data directory
+ * to live in trash before reaping it. */
+#define DATA_EXPIRY_IN_DAYS 28
+
+/* Minimum days for a cache directory
+ * to live in trash before reaping it. */
+#define CACHE_EXPIRY_IN_DAYS 7
+
+struct _ECacheReaper {
+ EExtension parent;
+
+ guint n_data_directories;
+ GFile **data_directories;
+ GFile **data_trash_directories;
+
+ guint n_cache_directories;
+ GFile **cache_directories;
+ GFile **cache_trash_directories;
+
+ guint reaping_timeout_id;
+
+ GSList *private_directories;
+};
+
+struct _ECacheReaperClass {
+ EExtensionClass parent_class;
+};
+
+G_DEFINE_DYNAMIC_TYPE_EXTENDED (ECacheReaper, e_cache_reaper, E_TYPE_EXTENSION, 0,
+ G_IMPLEMENT_INTERFACE_DYNAMIC (E_TYPE_EXTENSIBLE, NULL))
+
+static ESourceRegistryServer *
+cache_reaper_get_server (ECacheReaper *extension)
+{
+ EExtensible *extensible;
+
+ extensible = e_extension_get_extensible (E_EXTENSION (extension));
+
+ return E_SOURCE_REGISTRY_SERVER (extensible);
+}
+
+static gboolean
+cache_reaper_make_directory_and_parents (GFile *directory,
+ GCancellable *cancellable,
+ GError **error)
+{
+ gboolean success;
+ GError *local_error = NULL;
+
+ /* XXX Maybe add some function like this to libedataserver.
+ * It's annoying to always have to check for and clear
+ * G_IO_ERROR_EXISTS when ensuring a directory exists. */
+
+ success = g_file_make_directory_with_parents (
+ directory, cancellable, &local_error);
+
+ if (g_error_matches (local_error, G_IO_ERROR, G_IO_ERROR_EXISTS))
+ g_clear_error (&local_error);
+
+ if (local_error != NULL) {
+ gchar *path;
+
+ g_propagate_error (error, local_error);
+
+ path = g_file_get_path (directory);
+ g_prefix_error (
+ error, "Failed to make directory '%s': ", path);
+ g_free (path);
+ }
+
+ return success;
+}
+
+static void
+cache_reaper_trash_directory_reaped (GObject *source_object,
+ GAsyncResult *result,
+ gpointer unused)
+{
+ GFile *trash_directory;
+ GError *error = NULL;
+
+ trash_directory = G_FILE (source_object);
+
+ e_reap_trash_directory_finish (trash_directory, result, &error);
+
+ /* Ignore cancellations. */
+ if (g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED)) {
+ /* do nothing */
+
+ } else if (error != NULL) {
+ gchar *path;
+
+ path = g_file_get_path (trash_directory);
+ g_warning ("Failed to reap '%s': %s", path, error->message);
+ g_free (path);
+ }
+
+ g_clear_error (&error);
+}
+
+static gboolean
+cache_reaper_reap_trash_directories (gpointer user_data)
+{
+ ECacheReaper *extension = E_CACHE_REAPER (user_data);
+ guint ii;
+
+ g_debug ("Reaping abandoned data directories");
+
+ for (ii = 0; ii < extension->n_data_directories; ii++)
+ e_reap_trash_directory (
+ extension->data_trash_directories[ii],
+ DATA_EXPIRY_IN_DAYS,
+ G_PRIORITY_LOW, NULL,
+ cache_reaper_trash_directory_reaped,
+ NULL);
+
+ g_debug ("Reaping abandoned cache directories");
+
+ for (ii = 0; ii < extension->n_cache_directories; ii++)
+ e_reap_trash_directory (
+ extension->cache_trash_directories[ii],
+ CACHE_EXPIRY_IN_DAYS,
+ G_PRIORITY_LOW, NULL,
+ cache_reaper_trash_directory_reaped,
+ NULL);
+
+ /* Always explicitly reschedule since the initial
+ * interval is different than the regular interval. */
+ extension->reaping_timeout_id =
+ e_named_timeout_add_seconds (
+ REGULAR_INTERVAL_SECONDS,
+ cache_reaper_reap_trash_directories,
+ extension);
+
+ return FALSE;
+}
+
+static void
+cache_reaper_move_directory (GFile *source_directory,
+ GFile *target_directory)
+{
+ GFileType file_type;
+ GError *error = NULL;
+
+ /* Make sure the source directory is really a directory. */
+
+ file_type = g_file_query_file_type (
+ source_directory,
+ G_FILE_QUERY_INFO_NOFOLLOW_SYMLINKS, NULL);
+
+ if (file_type == G_FILE_TYPE_DIRECTORY) {
+ g_file_move (
+ source_directory,
+ target_directory,
+ G_FILE_COPY_NOFOLLOW_SYMLINKS,
+ NULL, NULL, NULL, &error);
+
+ /* Update the target directory's modification time.
+ * This step is not critical, do not set the GError. */
+ if (error == NULL) {
+ time_t now = time (NULL);
+
+ g_file_set_attribute (
+ target_directory,
+ G_FILE_ATTRIBUTE_TIME_MODIFIED,
+ G_FILE_ATTRIBUTE_TYPE_UINT64,
+ &now, G_FILE_QUERY_INFO_NONE,
+ NULL, NULL);
+ }
+ }
+
+ if (error != NULL) {
+ gchar *path;
+
+ path = g_file_get_path (source_directory);
+ g_warning ("Failed to move '%s': %s", path, error->message);
+ g_free (path);
+
+ g_error_free (error);
+ }
+}
+
+static gboolean
+cache_reaper_skip_directory (ECacheReaper *cache_reaper,
+ const gchar *name)
+{
+ GSList *link;
+
+ /* Skip the trash directory, obviously. */
+ if (g_strcmp0 (name, TRASH_DIRECTORY_NAME) == 0)
+ return TRUE;
+
+ /* Also skip directories named "system". For backward
+ * compatibility, data directories for built-in sources
+ * are named "system" instead of "system-address-book"
+ * or "system-calendar" or what have you. */
+ if (g_strcmp0 (name, "system") == 0)
+ return TRUE;
+
+ for (link = cache_reaper->private_directories; link; link = g_slist_next (link)) {
+ if (g_strcmp0 (name, link->data) == 0) {
+ return TRUE;
+ }
+ }
+
+ return FALSE;
+}
+
+static void
+cache_reaper_scan_directory (ECacheReaper *extension,
+ GFile *base_directory,
+ GFile *trash_directory)
+{
+ GFileEnumerator *file_enumerator;
+ ESourceRegistryServer *server;
+ GFileInfo *file_info;
+ GError *error = NULL;
+
+ server = cache_reaper_get_server (extension);
+
+ file_enumerator = g_file_enumerate_children (
+ base_directory,
+ G_FILE_ATTRIBUTE_STANDARD_NAME,
+ G_FILE_QUERY_INFO_NOFOLLOW_SYMLINKS,
+ NULL, &error);
+
+ if (error != NULL) {
+ g_warn_if_fail (file_enumerator == NULL);
+ goto exit;
+ }
+
+ g_return_if_fail (G_IS_FILE_ENUMERATOR (file_enumerator));
+
+ file_info = g_file_enumerator_next_file (
+ file_enumerator, NULL, &error);
+
+ while (file_info != NULL) {
+ ESource *source;
+ const gchar *name;
+
+ name = g_file_info_get_name (file_info);
+
+ if (cache_reaper_skip_directory (extension, name))
+ goto next;
+
+ source = e_source_registry_server_ref_source (server, name);
+
+ if (source == NULL) {
+ GFile *source_directory;
+ GFile *target_directory;
+
+ source_directory = g_file_get_child (
+ base_directory, name);
+ target_directory = g_file_get_child (
+ trash_directory, name);
+
+ cache_reaper_move_directory (
+ source_directory, target_directory);
+
+ g_object_unref (source_directory);
+ g_object_unref (target_directory);
+ } else {
+ g_object_unref (source);
+ }
+
+next:
+ g_object_unref (file_info);
+
+ file_info = g_file_enumerator_next_file (
+ file_enumerator, NULL, &error);
+ }
+
+ g_object_unref (file_enumerator);
+
+exit:
+ if (error != NULL) {
+ gchar *path;
+
+ path = g_file_get_path (base_directory);
+ g_warning ("Failed to scan '%s': %s", path, error->message);
+ g_free (path);
+
+ g_error_free (error);
+ }
+}
+
+static void
+cache_reaper_scan_data_directories (ECacheReaper *extension)
+{
+ guint ii;
+
+ /* Scan the base data directories for unrecognized subdirectories.
+ * The subdirectories are named after data source UIDs, so compare
+ * their names to registered data sources and move any unrecognized
+ * subdirectories to the "trash" subdirectory to be reaped later. */
+
+ g_debug ("Scanning data directories");
+
+ for (ii = 0; ii < extension->n_data_directories; ii++)
+ cache_reaper_scan_directory (
+ extension,
+ extension->data_directories[ii],
+ extension->data_trash_directories[ii]);
+}
+
+static void
+cache_reaper_scan_cache_directories (ECacheReaper *extension)
+{
+ guint ii;
+
+ /* Scan the base cache directories for unrecognized subdirectories.
+ * The subdirectories are named after data source UIDs, so compare
+ * their names to registered data sources and move any unrecognized
+ * subdirectories to the "trash" subdirectory to be reaped later. */
+
+ g_debug ("Scanning cache directories");
+
+ for (ii = 0; ii < extension->n_cache_directories; ii++)
+ cache_reaper_scan_directory (
+ extension,
+ extension->cache_directories[ii],
+ extension->cache_trash_directories[ii]);
+}
+
+static void
+cache_reaper_move_to_trash (ECacheReaper *extension,
+ ESource *source,
+ GFile *base_directory,
+ GFile *trash_directory)
+{
+ GFile *source_directory;
+ GFile *target_directory;
+ const gchar *uid;
+
+ uid = e_source_get_uid (source);
+
+ source_directory = g_file_get_child (base_directory, uid);
+ target_directory = g_file_get_child (trash_directory, uid);
+
+ /* This is a no-op if the source directory does not exist. */
+ cache_reaper_move_directory (source_directory, target_directory);
+
+ g_object_unref (source_directory);
+ g_object_unref (target_directory);
+}
+
+static void
+cache_reaper_recover_from_trash (ECacheReaper *extension,
+ ESource *source,
+ GFile *base_directory,
+ GFile *trash_directory)
+{
+ GFile *source_directory;
+ GFile *target_directory;
+ const gchar *uid;
+
+ uid = e_source_get_uid (source);
+
+ source_directory = g_file_get_child (trash_directory, uid);
+ target_directory = g_file_get_child (base_directory, uid);
+
+ /* This is a no-op if the source directory does not exist. */
+ cache_reaper_move_directory (source_directory, target_directory);
+
+ g_object_unref (source_directory);
+ g_object_unref (target_directory);
+}
+
+static void
+cache_reaper_files_loaded_cb (ESourceRegistryServer *server,
+ ECacheReaper *extension)
+{
+ cache_reaper_scan_data_directories (extension);
+ cache_reaper_scan_cache_directories (extension);
+
+ /* Schedule the initial reaping. */
+ if (extension->reaping_timeout_id == 0) {
+ extension->reaping_timeout_id =
+ e_named_timeout_add_seconds (
+ INITIAL_INTERVAL_SECONDS,
+ cache_reaper_reap_trash_directories,
+ extension);
+ }
+}
+
+static void
+cache_reaper_source_added_cb (ESourceRegistryServer *server,
+ ESource *source,
+ ECacheReaper *extension)
+{
+ guint ii;
+
+ /* The Cache Reaper is not too proud to dig through the
+ * trash on the off chance the newly-added source has a
+ * recoverable data or cache directory. */
+
+ for (ii = 0; ii < extension->n_data_directories; ii++)
+ cache_reaper_recover_from_trash (
+ extension, source,
+ extension->data_directories[ii],
+ extension->data_trash_directories[ii]);
+
+ for (ii = 0; ii < extension->n_cache_directories; ii++)
+ cache_reaper_recover_from_trash (
+ extension, source,
+ extension->cache_directories[ii],
+ extension->cache_trash_directories[ii]);
+}
+
+static void
+cache_reaper_source_removed_cb (ESourceRegistryServer *server,
+ ESource *source,
+ ECacheReaper *extension)
+{
+ guint ii;
+
+ /* Stage the removed source's cache directory for reaping
+ * by moving it to the "trash" directory.
+ *
+ * Do NOT do this for data directories. Cache directories
+ * are disposable and can be regenerated from the canonical
+ * data source, but data directories ARE the canonical data
+ * source so we want to be more conservative with them. If
+ * the removed source has a data directory, we will move it
+ * to the "trash" directory on next registry startup, which
+ * may correspond with the next desktop session startup. */
+
+ for (ii = 0; ii < extension->n_cache_directories; ii++)
+ cache_reaper_move_to_trash (
+ extension, source,
+ extension->cache_directories[ii],
+ extension->cache_trash_directories[ii]);
+}
+
+static void
+cache_reaper_finalize (GObject *object)
+{
+ ECacheReaper *extension;
+ guint ii;
+
+ extension = E_CACHE_REAPER (object);
+
+ for (ii = 0; ii < extension->n_data_directories; ii++) {
+ g_object_unref (extension->data_directories[ii]);
+ g_object_unref (extension->data_trash_directories[ii]);
+ }
+
+ g_free (extension->data_directories);
+ g_free (extension->data_trash_directories);
+
+ for (ii = 0; ii < extension->n_cache_directories; ii++) {
+ g_object_unref (extension->cache_directories[ii]);
+ g_object_unref (extension->cache_trash_directories[ii]);
+ }
+
+ g_free (extension->cache_directories);
+ g_free (extension->cache_trash_directories);
+
+ if (extension->reaping_timeout_id > 0)
+ g_source_remove (extension->reaping_timeout_id);
+
+ g_slist_free_full (extension->private_directories, g_free);
+ extension->private_directories = NULL;
+
+ /* Chain up to parent's finalize() method. */
+ G_OBJECT_CLASS (e_cache_reaper_parent_class)->finalize (object);
+}
+
+static void
+cache_reaper_constructed (GObject *object)
+{
+ EExtension *extension;
+ EExtensible *extensible;
+
+ extension = E_EXTENSION (object);
+ extensible = e_extension_get_extensible (extension);
+
+ g_signal_connect (
+ extensible, "files-loaded",
+ G_CALLBACK (cache_reaper_files_loaded_cb), extension);
+
+ g_signal_connect (
+ extensible, "source-added",
+ G_CALLBACK (cache_reaper_source_added_cb), extension);
+
+ g_signal_connect (
+ extensible, "source-removed",
+ G_CALLBACK (cache_reaper_source_removed_cb), extension);
+
+ e_extensible_load_extensions (E_EXTENSIBLE (object));
+
+ /* Chain up to parent's constructed() method. */
+ G_OBJECT_CLASS (e_cache_reaper_parent_class)->constructed (object);
+}
+
+static void
+e_cache_reaper_class_init (ECacheReaperClass *class)
+{
+ GObjectClass *object_class;
+ EExtensionClass *extension_class;
+
+ object_class = G_OBJECT_CLASS (class);
+ object_class->finalize = cache_reaper_finalize;
+ object_class->constructed = cache_reaper_constructed;
+
+ extension_class = E_EXTENSION_CLASS (class);
+ extension_class->extensible_type = E_TYPE_SOURCE_REGISTRY_SERVER;
+}
+
+static void
+e_cache_reaper_class_finalize (ECacheReaperClass *class)
+{
+}
+
+static void
+e_cache_reaper_init (ECacheReaper *extension)
+{
+ GFile *base_directory;
+ const gchar *user_data_dir;
+ const gchar *user_cache_dir;
+ guint n_directories, ii;
+
+ /* These are component names from which
+ * the data directory arrays are built. */
+ const gchar *data_component_names[] = {
+ "addressbook",
+ "calendar",
+ "mail",
+ "memos",
+ "tasks"
+ };
+
+ /* These are component names from which
+ * the cache directory arrays are built. */
+ const gchar *cache_component_names[] = {
+ "addressbook",
+ "calendar",
+ "mail",
+ "memos",
+ "sources",
+ "tasks"
+ };
+
+ extension->private_directories = NULL;
+
+ /* Setup base directories for data. */
+
+ n_directories = G_N_ELEMENTS (data_component_names);
+
+ extension->n_data_directories = n_directories;
+ extension->data_directories = g_new0 (GFile *, n_directories);
+ extension->data_trash_directories = g_new0 (GFile *, n_directories);
+
+ user_data_dir = e_get_user_data_dir ();
+ base_directory = g_file_new_for_path (user_data_dir);
+
+ for (ii = 0; ii < n_directories; ii++) {
+ GFile *data_directory;
+ GFile *trash_directory;
+ GError *error = NULL;
+
+ data_directory = g_file_get_child (
+ base_directory, data_component_names[ii]);
+ trash_directory = g_file_get_child (
+ data_directory, TRASH_DIRECTORY_NAME);
+
+ /* Data directory is a parent of the trash
+ * directory so this is sufficient for both. */
+ cache_reaper_make_directory_and_parents (
+ trash_directory, NULL, &error);
+
+ if (error != NULL) {
+ g_warning ("%s: %s", G_STRFUNC, error->message);
+ g_error_free (error);
+ }
+
+ extension->data_directories[ii] = data_directory;
+ extension->data_trash_directories[ii] = trash_directory;
+ }
+
+ g_object_unref (base_directory);
+
+ /* Setup base directories for cache. */
+
+ n_directories = G_N_ELEMENTS (cache_component_names);
+
+ extension->n_cache_directories = n_directories;
+ extension->cache_directories = g_new0 (GFile *, n_directories);
+ extension->cache_trash_directories = g_new0 (GFile *, n_directories);
+
+ user_cache_dir = e_get_user_cache_dir ();
+ base_directory = g_file_new_for_path (user_cache_dir);
+
+ for (ii = 0; ii < n_directories; ii++) {
+ GFile *cache_directory;
+ GFile *trash_directory;
+ GError *error = NULL;
+
+ cache_directory = g_file_get_child (
+ base_directory, cache_component_names[ii]);
+ trash_directory = g_file_get_child (
+ cache_directory, TRASH_DIRECTORY_NAME);
+
+ /* Cache directory is a parent of the trash
+ * directory so this is sufficient for both. */
+ cache_reaper_make_directory_and_parents (
+ trash_directory, NULL, &error);
+
+ if (error != NULL) {
+ g_warning ("%s: %s", G_STRFUNC, error->message);
+ g_error_free (error);
+ }
+
+ extension->cache_directories[ii] = cache_directory;
+ extension->cache_trash_directories[ii] = trash_directory;
+ }
+
+ g_object_unref (base_directory);
+}
+
+/**
+ * e_cache_reaper_add_private_directory:
+ * @cache_reaper: an #ECacheReaper
+ * @name: directory name
+ *
+ * Let's the @cache_reaper know about a private directory named @name,
+ * thus it won't delete it from cache or data directories. The @name
+ * is just a directory name, not a path.
+ *
+ * Since 3.18
+ **/
+void
+e_cache_reaper_add_private_directory (ECacheReaper *cache_reaper,
+ const gchar *name)
+{
+ g_return_if_fail (E_IS_CACHE_REAPER (cache_reaper));
+ g_return_if_fail (name != NULL);
+
+ if (g_slist_find_custom (cache_reaper->private_directories, name, (GCompareFunc) g_strcmp0))
+ return;
+
+ cache_reaper->private_directories = g_slist_prepend (cache_reaper->private_directories, g_strdup
(name));
+}
+
+/**
+ * e_cache_reaper_remove_private_directory:
+ * @cache_reaper: an #ECacheReaper
+ * @name: directory name
+ *
+ * Remove private directory named @name from the list of private
+ * directories in the @cache_reaper, previously added with
+ * e_cache_reaper_add_private_directory().
+ *
+ * Since 3.18
+ **/
+void
+e_cache_reaper_remove_private_directory (ECacheReaper *cache_reaper,
+ const gchar *name)
+{
+ GSList *link;
+ gchar *saved_name;
+
+ g_return_if_fail (E_IS_CACHE_REAPER (cache_reaper));
+ g_return_if_fail (name != NULL);
+
+ link = g_slist_find_custom (cache_reaper->private_directories, name, (GCompareFunc) g_strcmp0);
+ if (!link)
+ return;
+
+ saved_name = link->data;
+
+ cache_reaper->private_directories = g_slist_remove (cache_reaper->private_directories, saved_name);
+
+ g_free (saved_name);
+}
+
+void
+e_cache_reaper_type_register (GTypeModule *type_module)
+{
+ e_cache_reaper_register_type (type_module);
+}
diff --git a/modules/cache-reaper/e-cache-reaper.h b/libebackend/e-cache-reaper.h
similarity index 94%
rename from modules/cache-reaper/e-cache-reaper.h
rename to libebackend/e-cache-reaper.h
index 3177bda..85d1620 100644
--- a/modules/cache-reaper/e-cache-reaper.h
+++ b/libebackend/e-cache-reaper.h
@@ -18,7 +18,7 @@
#ifndef E_CACHE_REAPER_H
#define E_CACHE_REAPER_H
-#include <libedataserver/libedataserver.h>
+#include <glib.h>
/* Standard GObject macros */
#define E_TYPE_CACHE_REAPER \
@@ -35,6 +35,8 @@ G_BEGIN_DECLS
typedef struct _ECacheReaper ECacheReaper;
typedef struct _ECacheReaperClass ECacheReaperClass;
+void e_cache_reaper_type_register (GTypeModule *type_module);
+
GType e_cache_reaper_get_type (void);
void e_cache_reaper_add_private_directory (ECacheReaper *cache_reaper,
diff --git a/libebackend/libebackend.h b/libebackend/libebackend.h
index cd8316e..c286f18 100644
--- a/libebackend/libebackend.h
+++ b/libebackend/libebackend.h
@@ -26,6 +26,7 @@
#include <libebackend/e-backend-enumtypes.h>
#include <libebackend/e-backend-factory.h>
#include <libebackend/e-backend.h>
+#include <libebackend/e-cache-reaper.h>
#include <libebackend/e-collection-backend-factory.h>
#include <libebackend/e-collection-backend.h>
#include <libebackend/e-data-factory.h>
diff --git a/modules/cache-reaper/Makefile.am b/modules/cache-reaper/Makefile.am
index 1fdc10e..8af088d 100644
--- a/modules/cache-reaper/Makefile.am
+++ b/modules/cache-reaper/Makefile.am
@@ -12,8 +12,6 @@ module_cache_reaper_la_CPPFLAGS = \
module_cache_reaper_la_SOURCES = \
module-cache-reaper.c \
- e-cache-reaper-utils.c \
- e-cache-reaper-utils.h \
$(NULL)
module_cache_reaper_la_LIBADD = \
@@ -27,8 +25,4 @@ module_cache_reaper_la_LDFLAGS = \
-module -avoid-version $(NO_UNDEFINED) \
$(NULL)
-cachereaperincludedir = $(privincludedir)/cache-reaper
-
-cachereaperinclude_HEADERS = e-cache-reaper.h
-
-include $(top_srcdir)/git.mk
diff --git a/modules/cache-reaper/module-cache-reaper.c b/modules/cache-reaper/module-cache-reaper.c
index a6ef49a..2d15b50 100644
--- a/modules/cache-reaper/module-cache-reaper.c
+++ b/modules/cache-reaper/module-cache-reaper.c
@@ -15,714 +15,20 @@
*
*/
-#include <errno.h>
-#include <time.h>
-#include <glib/gstdio.h>
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
#include <libebackend/libebackend.h>
-#include "e-cache-reaper.h"
-#include "e-cache-reaper-utils.h"
-
-/* Where abandoned directories go to die. */
-#define TRASH_DIRECTORY_NAME "trash"
-
-/* XXX These intervals are rather arbitrary and prone to bikeshedding.
- * It's just what I decided on. On startup we wait an hour to reap
- * abandoned directories, and thereafter repeat every 24 hours. */
-#define INITIAL_INTERVAL_SECONDS ( 1 * (60 * 60))
-#define REGULAR_INTERVAL_SECONDS (24 * (60 * 60))
-
-/* XXX Similarly, these expiry times are rather arbitrary and prone to
- * bikeshedding. Most importantly, the expiry for data directories
- * should be far more conservative (longer) than cache directories.
- * Cache directories are disposable, data directories are not, so
- * we want to let abandoned data directories linger longer. */
-
-/* Minimum days for a data directory
- * to live in trash before reaping it. */
-#define DATA_EXPIRY_IN_DAYS 28
-
-/* Minimum days for a cache directory
- * to live in trash before reaping it. */
-#define CACHE_EXPIRY_IN_DAYS 7
-
-struct _ECacheReaper {
- EExtension parent;
-
- guint n_data_directories;
- GFile **data_directories;
- GFile **data_trash_directories;
-
- guint n_cache_directories;
- GFile **cache_directories;
- GFile **cache_trash_directories;
-
- guint reaping_timeout_id;
-
- GSList *private_directories;
-};
-
-struct _ECacheReaperClass {
- EExtensionClass parent_class;
-};
-
/* Module Entry Points */
void e_module_load (GTypeModule *type_module);
void e_module_unload (GTypeModule *type_module);
-G_DEFINE_DYNAMIC_TYPE_EXTENDED (ECacheReaper, e_cache_reaper, E_TYPE_EXTENSION, 0,
- G_IMPLEMENT_INTERFACE_DYNAMIC (E_TYPE_EXTENSIBLE, NULL))
-
-static ESourceRegistryServer *
-cache_reaper_get_server (ECacheReaper *extension)
-{
- EExtensible *extensible;
-
- extensible = e_extension_get_extensible (E_EXTENSION (extension));
-
- return E_SOURCE_REGISTRY_SERVER (extensible);
-}
-
-static gboolean
-cache_reaper_make_directory_and_parents (GFile *directory,
- GCancellable *cancellable,
- GError **error)
-{
- gboolean success;
- GError *local_error = NULL;
-
- /* XXX Maybe add some function like this to libedataserver.
- * It's annoying to always have to check for and clear
- * G_IO_ERROR_EXISTS when ensuring a directory exists. */
-
- success = g_file_make_directory_with_parents (
- directory, cancellable, &local_error);
-
- if (g_error_matches (local_error, G_IO_ERROR, G_IO_ERROR_EXISTS))
- g_clear_error (&local_error);
-
- if (local_error != NULL) {
- gchar *path;
-
- g_propagate_error (error, local_error);
-
- path = g_file_get_path (directory);
- g_prefix_error (
- error, "Failed to make directory '%s': ", path);
- g_free (path);
- }
-
- return success;
-}
-
-static void
-cache_reaper_trash_directory_reaped (GObject *source_object,
- GAsyncResult *result,
- gpointer unused)
-{
- GFile *trash_directory;
- GError *error = NULL;
-
- trash_directory = G_FILE (source_object);
-
- e_reap_trash_directory_finish (trash_directory, result, &error);
-
- /* Ignore cancellations. */
- if (g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED)) {
- /* do nothing */
-
- } else if (error != NULL) {
- gchar *path;
-
- path = g_file_get_path (trash_directory);
- g_warning ("Failed to reap '%s': %s", path, error->message);
- g_free (path);
- }
-
- g_clear_error (&error);
-}
-
-static gboolean
-cache_reaper_reap_trash_directories (gpointer user_data)
-{
- ECacheReaper *extension = E_CACHE_REAPER (user_data);
- guint ii;
-
- g_debug ("Reaping abandoned data directories");
-
- for (ii = 0; ii < extension->n_data_directories; ii++)
- e_reap_trash_directory (
- extension->data_trash_directories[ii],
- DATA_EXPIRY_IN_DAYS,
- G_PRIORITY_LOW, NULL,
- cache_reaper_trash_directory_reaped,
- NULL);
-
- g_debug ("Reaping abandoned cache directories");
-
- for (ii = 0; ii < extension->n_cache_directories; ii++)
- e_reap_trash_directory (
- extension->cache_trash_directories[ii],
- CACHE_EXPIRY_IN_DAYS,
- G_PRIORITY_LOW, NULL,
- cache_reaper_trash_directory_reaped,
- NULL);
-
- /* Always explicitly reschedule since the initial
- * interval is different than the regular interval. */
- extension->reaping_timeout_id =
- e_named_timeout_add_seconds (
- REGULAR_INTERVAL_SECONDS,
- cache_reaper_reap_trash_directories,
- extension);
-
- return FALSE;
-}
-
-static void
-cache_reaper_move_directory (GFile *source_directory,
- GFile *target_directory)
-{
- GFileType file_type;
- GError *error = NULL;
-
- /* Make sure the source directory is really a directory. */
-
- file_type = g_file_query_file_type (
- source_directory,
- G_FILE_QUERY_INFO_NOFOLLOW_SYMLINKS, NULL);
-
- if (file_type == G_FILE_TYPE_DIRECTORY) {
- g_file_move (
- source_directory,
- target_directory,
- G_FILE_COPY_NOFOLLOW_SYMLINKS,
- NULL, NULL, NULL, &error);
-
- /* Update the target directory's modification time.
- * This step is not critical, do not set the GError. */
- if (error == NULL) {
- time_t now = time (NULL);
-
- g_file_set_attribute (
- target_directory,
- G_FILE_ATTRIBUTE_TIME_MODIFIED,
- G_FILE_ATTRIBUTE_TYPE_UINT64,
- &now, G_FILE_QUERY_INFO_NONE,
- NULL, NULL);
- }
- }
-
- if (error != NULL) {
- gchar *path;
-
- path = g_file_get_path (source_directory);
- g_warning ("Failed to move '%s': %s", path, error->message);
- g_free (path);
-
- g_error_free (error);
- }
-}
-
-static gboolean
-cache_reaper_skip_directory (ECacheReaper *cache_reaper,
- const gchar *name)
-{
- GSList *link;
-
- /* Skip the trash directory, obviously. */
- if (g_strcmp0 (name, TRASH_DIRECTORY_NAME) == 0)
- return TRUE;
-
- /* Also skip directories named "system". For backward
- * compatibility, data directories for built-in sources
- * are named "system" instead of "system-address-book"
- * or "system-calendar" or what have you. */
- if (g_strcmp0 (name, "system") == 0)
- return TRUE;
-
- for (link = cache_reaper->private_directories; link; link = g_slist_next (link)) {
- if (g_strcmp0 (name, link->data) == 0) {
- return TRUE;
- }
- }
-
- return FALSE;
-}
-
-static void
-cache_reaper_scan_directory (ECacheReaper *extension,
- GFile *base_directory,
- GFile *trash_directory)
-{
- GFileEnumerator *file_enumerator;
- ESourceRegistryServer *server;
- GFileInfo *file_info;
- GError *error = NULL;
-
- server = cache_reaper_get_server (extension);
-
- file_enumerator = g_file_enumerate_children (
- base_directory,
- G_FILE_ATTRIBUTE_STANDARD_NAME,
- G_FILE_QUERY_INFO_NOFOLLOW_SYMLINKS,
- NULL, &error);
-
- if (error != NULL) {
- g_warn_if_fail (file_enumerator == NULL);
- goto exit;
- }
-
- g_return_if_fail (G_IS_FILE_ENUMERATOR (file_enumerator));
-
- file_info = g_file_enumerator_next_file (
- file_enumerator, NULL, &error);
-
- while (file_info != NULL) {
- ESource *source;
- const gchar *name;
-
- name = g_file_info_get_name (file_info);
-
- if (cache_reaper_skip_directory (extension, name))
- goto next;
-
- source = e_source_registry_server_ref_source (server, name);
-
- if (source == NULL) {
- GFile *source_directory;
- GFile *target_directory;
-
- source_directory = g_file_get_child (
- base_directory, name);
- target_directory = g_file_get_child (
- trash_directory, name);
-
- cache_reaper_move_directory (
- source_directory, target_directory);
-
- g_object_unref (source_directory);
- g_object_unref (target_directory);
- } else {
- g_object_unref (source);
- }
-
-next:
- g_object_unref (file_info);
-
- file_info = g_file_enumerator_next_file (
- file_enumerator, NULL, &error);
- }
-
- g_object_unref (file_enumerator);
-
-exit:
- if (error != NULL) {
- gchar *path;
-
- path = g_file_get_path (base_directory);
- g_warning ("Failed to scan '%s': %s", path, error->message);
- g_free (path);
-
- g_error_free (error);
- }
-}
-
-static void
-cache_reaper_scan_data_directories (ECacheReaper *extension)
-{
- guint ii;
-
- /* Scan the base data directories for unrecognized subdirectories.
- * The subdirectories are named after data source UIDs, so compare
- * their names to registered data sources and move any unrecognized
- * subdirectories to the "trash" subdirectory to be reaped later. */
-
- g_debug ("Scanning data directories");
-
- for (ii = 0; ii < extension->n_data_directories; ii++)
- cache_reaper_scan_directory (
- extension,
- extension->data_directories[ii],
- extension->data_trash_directories[ii]);
-}
-
-static void
-cache_reaper_scan_cache_directories (ECacheReaper *extension)
-{
- guint ii;
-
- /* Scan the base cache directories for unrecognized subdirectories.
- * The subdirectories are named after data source UIDs, so compare
- * their names to registered data sources and move any unrecognized
- * subdirectories to the "trash" subdirectory to be reaped later. */
-
- g_debug ("Scanning cache directories");
-
- for (ii = 0; ii < extension->n_cache_directories; ii++)
- cache_reaper_scan_directory (
- extension,
- extension->cache_directories[ii],
- extension->cache_trash_directories[ii]);
-}
-
-static void
-cache_reaper_move_to_trash (ECacheReaper *extension,
- ESource *source,
- GFile *base_directory,
- GFile *trash_directory)
-{
- GFile *source_directory;
- GFile *target_directory;
- const gchar *uid;
-
- uid = e_source_get_uid (source);
-
- source_directory = g_file_get_child (base_directory, uid);
- target_directory = g_file_get_child (trash_directory, uid);
-
- /* This is a no-op if the source directory does not exist. */
- cache_reaper_move_directory (source_directory, target_directory);
-
- g_object_unref (source_directory);
- g_object_unref (target_directory);
-}
-
-static void
-cache_reaper_recover_from_trash (ECacheReaper *extension,
- ESource *source,
- GFile *base_directory,
- GFile *trash_directory)
-{
- GFile *source_directory;
- GFile *target_directory;
- const gchar *uid;
-
- uid = e_source_get_uid (source);
-
- source_directory = g_file_get_child (trash_directory, uid);
- target_directory = g_file_get_child (base_directory, uid);
-
- /* This is a no-op if the source directory does not exist. */
- cache_reaper_move_directory (source_directory, target_directory);
-
- g_object_unref (source_directory);
- g_object_unref (target_directory);
-}
-
-static void
-cache_reaper_files_loaded_cb (ESourceRegistryServer *server,
- ECacheReaper *extension)
-{
- cache_reaper_scan_data_directories (extension);
- cache_reaper_scan_cache_directories (extension);
-
- /* Schedule the initial reaping. */
- if (extension->reaping_timeout_id == 0) {
- extension->reaping_timeout_id =
- e_named_timeout_add_seconds (
- INITIAL_INTERVAL_SECONDS,
- cache_reaper_reap_trash_directories,
- extension);
- }
-}
-
-static void
-cache_reaper_source_added_cb (ESourceRegistryServer *server,
- ESource *source,
- ECacheReaper *extension)
-{
- guint ii;
-
- /* The Cache Reaper is not too proud to dig through the
- * trash on the off chance the newly-added source has a
- * recoverable data or cache directory. */
-
- for (ii = 0; ii < extension->n_data_directories; ii++)
- cache_reaper_recover_from_trash (
- extension, source,
- extension->data_directories[ii],
- extension->data_trash_directories[ii]);
-
- for (ii = 0; ii < extension->n_cache_directories; ii++)
- cache_reaper_recover_from_trash (
- extension, source,
- extension->cache_directories[ii],
- extension->cache_trash_directories[ii]);
-}
-
-static void
-cache_reaper_source_removed_cb (ESourceRegistryServer *server,
- ESource *source,
- ECacheReaper *extension)
-{
- guint ii;
-
- /* Stage the removed source's cache directory for reaping
- * by moving it to the "trash" directory.
- *
- * Do NOT do this for data directories. Cache directories
- * are disposable and can be regenerated from the canonical
- * data source, but data directories ARE the canonical data
- * source so we want to be more conservative with them. If
- * the removed source has a data directory, we will move it
- * to the "trash" directory on next registry startup, which
- * may correspond with the next desktop session startup. */
-
- for (ii = 0; ii < extension->n_cache_directories; ii++)
- cache_reaper_move_to_trash (
- extension, source,
- extension->cache_directories[ii],
- extension->cache_trash_directories[ii]);
-}
-
-static void
-cache_reaper_finalize (GObject *object)
-{
- ECacheReaper *extension;
- guint ii;
-
- extension = E_CACHE_REAPER (object);
-
- for (ii = 0; ii < extension->n_data_directories; ii++) {
- g_object_unref (extension->data_directories[ii]);
- g_object_unref (extension->data_trash_directories[ii]);
- }
-
- g_free (extension->data_directories);
- g_free (extension->data_trash_directories);
-
- for (ii = 0; ii < extension->n_cache_directories; ii++) {
- g_object_unref (extension->cache_directories[ii]);
- g_object_unref (extension->cache_trash_directories[ii]);
- }
-
- g_free (extension->cache_directories);
- g_free (extension->cache_trash_directories);
-
- if (extension->reaping_timeout_id > 0)
- g_source_remove (extension->reaping_timeout_id);
-
- g_slist_free_full (extension->private_directories, g_free);
- extension->private_directories = NULL;
-
- /* Chain up to parent's finalize() method. */
- G_OBJECT_CLASS (e_cache_reaper_parent_class)->finalize (object);
-}
-
-static void
-cache_reaper_constructed (GObject *object)
-{
- EExtension *extension;
- EExtensible *extensible;
-
- extension = E_EXTENSION (object);
- extensible = e_extension_get_extensible (extension);
-
- g_signal_connect (
- extensible, "files-loaded",
- G_CALLBACK (cache_reaper_files_loaded_cb), extension);
-
- g_signal_connect (
- extensible, "source-added",
- G_CALLBACK (cache_reaper_source_added_cb), extension);
-
- g_signal_connect (
- extensible, "source-removed",
- G_CALLBACK (cache_reaper_source_removed_cb), extension);
-
- e_extensible_load_extensions (E_EXTENSIBLE (object));
-
- /* Chain up to parent's constructed() method. */
- G_OBJECT_CLASS (e_cache_reaper_parent_class)->constructed (object);
-}
-
-static void
-e_cache_reaper_class_init (ECacheReaperClass *class)
-{
- GObjectClass *object_class;
- EExtensionClass *extension_class;
-
- object_class = G_OBJECT_CLASS (class);
- object_class->finalize = cache_reaper_finalize;
- object_class->constructed = cache_reaper_constructed;
-
- extension_class = E_EXTENSION_CLASS (class);
- extension_class->extensible_type = E_TYPE_SOURCE_REGISTRY_SERVER;
-}
-
-static void
-e_cache_reaper_class_finalize (ECacheReaperClass *class)
-{
-}
-
-static void
-e_cache_reaper_init (ECacheReaper *extension)
-{
- GFile *base_directory;
- const gchar *user_data_dir;
- const gchar *user_cache_dir;
- guint n_directories, ii;
-
- /* These are component names from which
- * the data directory arrays are built. */
- const gchar *data_component_names[] = {
- "addressbook",
- "calendar",
- "mail",
- "memos",
- "tasks"
- };
-
- /* These are component names from which
- * the cache directory arrays are built. */
- const gchar *cache_component_names[] = {
- "addressbook",
- "calendar",
- "mail",
- "memos",
- "sources",
- "tasks"
- };
-
- extension->private_directories = NULL;
-
- /* Setup base directories for data. */
-
- n_directories = G_N_ELEMENTS (data_component_names);
-
- extension->n_data_directories = n_directories;
- extension->data_directories = g_new0 (GFile *, n_directories);
- extension->data_trash_directories = g_new0 (GFile *, n_directories);
-
- user_data_dir = e_get_user_data_dir ();
- base_directory = g_file_new_for_path (user_data_dir);
-
- for (ii = 0; ii < n_directories; ii++) {
- GFile *data_directory;
- GFile *trash_directory;
- GError *error = NULL;
-
- data_directory = g_file_get_child (
- base_directory, data_component_names[ii]);
- trash_directory = g_file_get_child (
- data_directory, TRASH_DIRECTORY_NAME);
-
- /* Data directory is a parent of the trash
- * directory so this is sufficient for both. */
- cache_reaper_make_directory_and_parents (
- trash_directory, NULL, &error);
-
- if (error != NULL) {
- g_warning ("%s: %s", G_STRFUNC, error->message);
- g_error_free (error);
- }
-
- extension->data_directories[ii] = data_directory;
- extension->data_trash_directories[ii] = trash_directory;
- }
-
- g_object_unref (base_directory);
-
- /* Setup base directories for cache. */
-
- n_directories = G_N_ELEMENTS (cache_component_names);
-
- extension->n_cache_directories = n_directories;
- extension->cache_directories = g_new0 (GFile *, n_directories);
- extension->cache_trash_directories = g_new0 (GFile *, n_directories);
-
- user_cache_dir = e_get_user_cache_dir ();
- base_directory = g_file_new_for_path (user_cache_dir);
-
- for (ii = 0; ii < n_directories; ii++) {
- GFile *cache_directory;
- GFile *trash_directory;
- GError *error = NULL;
-
- cache_directory = g_file_get_child (
- base_directory, cache_component_names[ii]);
- trash_directory = g_file_get_child (
- cache_directory, TRASH_DIRECTORY_NAME);
-
- /* Cache directory is a parent of the trash
- * directory so this is sufficient for both. */
- cache_reaper_make_directory_and_parents (
- trash_directory, NULL, &error);
-
- if (error != NULL) {
- g_warning ("%s: %s", G_STRFUNC, error->message);
- g_error_free (error);
- }
-
- extension->cache_directories[ii] = cache_directory;
- extension->cache_trash_directories[ii] = trash_directory;
- }
-
- g_object_unref (base_directory);
-}
-
-/**
- * e_cache_reaper_add_private_directory:
- * @cache_reaper: an #ECacheReaper
- * @name: directory name
- *
- * Let's the @cache_reaper know about a private directory named @name,
- * thus it won't delete it from cache or data directories. The @name
- * is just a directory name, not a path.
- *
- * Since 3.18
- **/
-void
-e_cache_reaper_add_private_directory (ECacheReaper *cache_reaper,
- const gchar *name)
-{
- g_return_if_fail (E_IS_CACHE_REAPER (cache_reaper));
- g_return_if_fail (name != NULL);
-
- if (g_slist_find_custom (cache_reaper->private_directories, name, (GCompareFunc) g_strcmp0))
- return;
-
- cache_reaper->private_directories = g_slist_prepend (cache_reaper->private_directories, g_strdup
(name));
-}
-
-/**
- * e_cache_reaper_remove_private_directory:
- * @cache_reaper: an #ECacheReaper
- * @name: directory name
- *
- * Remove private directory named @name from the list of private
- * directories in the @cache_reaper, previously added with
- * e_cache_reaper_add_private_directory().
- *
- * Since 3.18
- **/
-void
-e_cache_reaper_remove_private_directory (ECacheReaper *cache_reaper,
- const gchar *name)
-{
- GSList *link;
- gchar *saved_name;
-
- g_return_if_fail (E_IS_CACHE_REAPER (cache_reaper));
- g_return_if_fail (name != NULL);
-
- link = g_slist_find_custom (cache_reaper->private_directories, name, (GCompareFunc) g_strcmp0);
- if (!link)
- return;
-
- saved_name = link->data;
-
- cache_reaper->private_directories = g_slist_remove (cache_reaper->private_directories, saved_name);
-
- g_free (saved_name);
-}
-
G_MODULE_EXPORT void
e_module_load (GTypeModule *type_module)
{
- e_cache_reaper_register_type (type_module);
+ e_cache_reaper_type_register (type_module);
}
G_MODULE_EXPORT void
[
Date Prev][
Date Next] [
Thread Prev][
Thread Next]
[
Thread Index]
[
Date Index]
[
Author Index]