Use FileData locks to avoid expensive reloads with marks enabled
[geeqie.git] / src / filedata.c
index 4b7e70e..6adafc2 100644 (file)
@@ -532,6 +532,7 @@ static void file_data_free(FileData *fd)
 {
        g_assert(fd->magick == FD_MAGICK);
        g_assert(fd->ref == 0);
+       g_assert(!fd->locked);
 
        metadata_cache_free(fd);
        g_hash_table_remove(file_data_pool, fd->original_path);
@@ -549,6 +550,57 @@ static void file_data_free(FileData *fd)
        g_free(fd);
 }
 
+/**
+ * \brief Checks if the FileData is referenced
+ *
+ * Checks the refcount and whether the FileData is locked.
+ */
+static gboolean file_data_check_has_ref(FileData *fd)
+{
+       return fd->ref > 0 || fd->locked;
+}
+
+/**
+ * \brief Consider freeing a FileData.
+ *
+ * This function will free a FileData and its children provided that neither its parent nor it has
+ * a positive refcount, and provided that neither is locked.
+ */
+static void file_data_consider_free(FileData *fd)
+{
+       GList *work;
+       FileData *parent = fd->parent ? fd->parent : fd;
+
+       g_assert(fd->magick == FD_MAGICK);
+       if (file_data_check_has_ref(fd)) return;
+       if (file_data_check_has_ref(parent)) return;
+
+       work = parent->sidecar_files;
+       while (work)
+               {
+               FileData *sfd = work->data;
+               if (file_data_check_has_ref(sfd)) return;
+               work = work->next;
+               }
+
+       /* Neither the parent nor the siblings are referenced, so we can free everything */
+       DEBUG_2("file_data_consider_free: deleting '%s', parent '%s'",
+               fd->path, fd->parent ? parent->path : "-");
+
+       work = parent->sidecar_files;
+       while (work)
+               {
+               FileData *sfd = work->data;
+               file_data_free(sfd);
+               work = work->next;
+               }
+
+       g_list_free(parent->sidecar_files);
+       parent->sidecar_files = NULL;
+
+       file_data_free(parent);
+}
+
 #ifdef DEBUG_FILEDATA
 void file_data_unref_debug(const gchar *file, gint line, FileData *fd)
 #else
@@ -566,45 +618,92 @@ void file_data_unref(FileData *fd)
 
        fd->ref--;
 #ifdef DEBUG_FILEDATA
-       DEBUG_2("file_data_unref fd=%p (%d): '%s' @ %s:%d", fd, fd->ref, fd->path, file, line);
+       DEBUG_2("file_data_unref fd=%p (%d:%d): '%s' @ %s:%d", fd, fd->ref, fd->locked, fd->path,
+               file, line);
 #else
-       DEBUG_2("file_data_unref fd=%p (%d): '%s'", fd, fd->ref, fd->path);
+       DEBUG_2("file_data_unref fd=%p (%d:%d): '%s'", fd, fd->ref, fd->locked, fd->path);
 #endif
-       if (fd->ref == 0)
-               {
-               GList *work;
-               FileData *parent = fd->parent ? fd->parent : fd;
 
-               if (parent->ref > 0) return;
+       // Free FileData if it's no longer ref'd
+       file_data_consider_free(fd);
+}
 
-               work = parent->sidecar_files;
-               while (work)
-                       {
-                       FileData *sfd = work->data;
-                       if (sfd->ref > 0) return;
-                       work = work->next;
-                       }
+/**
+ * \brief Lock the FileData in memory.
+ *
+ * This allows the caller to prevent a FileData from being freed, even after its refcount is zero.
+ * This is intended to be used in cases where a FileData _should_ stay in memory as an optimization,
+ * even if the code would continue to function properly even if the FileData were freed.  Code that
+ * _requires_ the FileData to remain in memory should continue to use file_data_(un)ref.
+ * <p />
+ * Note: This differs from file_data_ref in that the behavior is reentrant -- after N calls to
+ * file_data_lock, a single call to file_data_unlock will unlock the FileData.
+ */
+void file_data_lock(FileData *fd)
+{
+       if (fd == NULL) return;
+       if (fd->magick != FD_MAGICK) DEBUG_0("fd magick mismatch fd=%p", fd);
 
-               /* none of parent/children is referenced, we can free everything */
+       g_assert(fd->magick == FD_MAGICK);
+       fd->locked = TRUE;
 
-               DEBUG_2("file_data_unref: deleting '%s', parent '%s'", fd->path, fd->parent ? parent->path : "-");
+       DEBUG_2("file_data_ref fd=%p (%d): '%s'", fd, fd->ref, fd->path);
+}
 
-               work = parent->sidecar_files;
-               while (work)
-                       {
-                       FileData *sfd = work->data;
-                       file_data_free(sfd);
-                       work = work->next;
-                       }
+/**
+ * \brief Reset the maintain-FileData-in-memory lock
+ *
+ * This again allows the FileData to be freed when its refcount drops to zero.  Automatically frees
+ * the FileData if its refcount is already zero (which will happen if the lock is the only thing
+ * keeping it from being freed.
+ */
+void file_data_unlock(FileData *fd)
+{
+       if (fd == NULL) return;
+       if (fd->magick != FD_MAGICK) DEBUG_0("fd magick mismatch fd=%p", fd);
 
-               g_list_free(parent->sidecar_files);
-               parent->sidecar_files = NULL;
+       g_assert(fd->magick == FD_MAGICK);
+       fd->locked = FALSE;
+
+       // Free FileData if it's no longer ref'd
+       file_data_consider_free(fd);
+}
 
-               file_data_free(parent);
+/**
+ * \brief Lock all of the FileDatas in the provided list
+ *
+ * \see file_data_lock(FileData)
+ */
+void file_data_lock_list(GList *list)
+{
+       GList *work;
+
+       work = list;
+       while (work)
+               {
+               FileData *fd = work->data;
+               work = work->next;
+               file_data_lock(fd);
                }
 }
 
+/**
+ * \brief Unlock all of the FileDatas in the provided list
+ *
+ * \see file_data_unlock(FileData)
+ */
+void file_data_unlock_list(GList *list)
+{
+       GList *work;
 
+       work = list;
+       while (work)
+               {
+               FileData *fd = work->data;
+               work = work->next;
+               file_data_unlock(fd);
+               }
+}
 
 /*
  *-----------------------------------------------------------------------------
@@ -2638,6 +2737,12 @@ GList *file_data_process_groups_in_selection(GList *list, gboolean ungroup, GLis
    implementation in view_file_list.c */
 
 
+typedef struct _NotifyIdleData NotifyIdleData;
+
+struct _NotifyIdleData {
+       FileData *fd;
+       NotifyType type;
+};
 
 
 typedef struct _NotifyData NotifyData;
@@ -2711,17 +2816,29 @@ gboolean file_data_unregister_notify_func(FileDataNotifyFunc func, gpointer data
 }
 
 
-void file_data_send_notification(FileData *fd, NotifyType type)
+gboolean file_data_send_notification_idle_cb(gpointer data)
 {
+       NotifyIdleData *nid = (NotifyIdleData *)data;
        GList *work = notify_func_list;
 
        while (work)
                {
                NotifyData *nd = (NotifyData *)work->data;
 
-               nd->func(fd, type, nd->data);
+               nd->func(nid->fd, nid->type, nd->data);
                work = work->next;
                }
+       file_data_unref(nid->fd);
+       g_free(nid);
+       return FALSE;
+}
+
+void file_data_send_notification(FileData *fd, NotifyType type)
+{
+       NotifyIdleData *nid = g_new0(NotifyIdleData, 1);
+       nid->fd = file_data_ref(fd);
+       nid->type = type;
+       g_idle_add_full(G_PRIORITY_HIGH, file_data_send_notification_idle_cb, nid, NULL);
 }
 
 static GHashTable *file_data_monitor_pool = NULL;