#include <errno.h>
+#ifdef DEBUG_FILEDATA
+gint global_file_data_count = 0;
+#endif
+
static GHashTable *file_data_pool = NULL;
static GHashTable *file_data_planned_change_hash = NULL;
{
fd->size = st->st_size;
fd->date = st->st_mtime;
+ fd->cdate = st->st_ctime;
fd->mode = st->st_mode;
if (fd->thumb_pixbuf) g_object_unref(fd->thumb_pixbuf);
fd->thumb_pixbuf = NULL;
}
fd = g_new0(FileData, 1);
+#ifdef DEBUG_FILEDATA
+ global_file_data_count++;
+ DEBUG_2("file data count++: %d", global_file_data_count);
+#endif
fd->size = st->st_size;
fd->date = st->st_mtime;
+ fd->cdate = st->st_ctime;
fd->mode = st->st_mode;
fd->ref = 1;
fd->magick = FD_MAGICK;
return ret;
}
+FileData *file_data_new_simple(const gchar *path_utf8)
+{
+ struct stat st;
+ FileData *fd;
+
+ if (!stat_utf8(path_utf8, &st))
+ {
+ st.st_size = 0;
+ st.st_mtime = 0;
+ }
+
+ fd = g_hash_table_lookup(file_data_pool, path_utf8);
+ if (!fd) fd = file_data_new(path_utf8, &st, TRUE);
+ if (fd)
+ {
+ file_data_ref(fd);
+ }
+
+ return fd;
+}
+
void init_exif_time_data(GList *files)
{
FileData *file;
{
g_assert(fd->magick == FD_MAGICK);
g_assert(fd->ref == 0);
+ g_assert(!fd->locked);
+
+#ifdef DEBUG_FILEDATA
+ global_file_data_count--;
+ DEBUG_2("file data count--: %d", global_file_data_count);
+#endif
metadata_cache_free(fd);
g_hash_table_remove(file_data_pool, fd->original_path);
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
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_assert(fd->magick == FD_MAGICK);
+ fd->locked = FALSE;
+
+ // Free FileData if it's no longer ref'd
+ file_data_consider_free(fd);
+}
- g_list_free(parent->sidecar_files);
- parent->sidecar_files = NULL;
+/**
+ * \brief Lock all of the FileDatas in the provided list
+ *
+ * \see file_data_lock(FileData)
+ */
+void file_data_lock_list(GList *list)
+{
+ GList *work;
- file_data_free(parent);
+ 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);
+ }
+}
/*
*-----------------------------------------------------------------------------
if (fa->date > fb->date) return 1;
/* fall back to name */
break;
+ case SORT_CTIME:
+ if (fa->cdate < fb->cdate) return -1;
+ if (fa->cdate > fb->cdate) return 1;
+ /* fall back to name */
+ break;
case SORT_EXIFTIME:
if (fa->exifdate < fb->exifdate) return -1;
if (fa->exifdate > fb->exifdate) return 1;
fd = g_hash_table_lookup(file_data_pool, path_utf8);
if (!fd) fd = file_data_new(path_utf8, &st, TRUE);
- file_data_ref(fd);
+ if (fd)
+ {
+ file_data_ref(fd);
+ }
filelist_free(files);
g_free(dir);
* it should detect all possible problems with the planned operation
*/
-gint file_data_verify_ci(FileData *fd)
+gint file_data_verify_ci(FileData *fd, GList *list)
{
gint ret = CHANGE_OK;
gchar *dir;
+ GList *work = NULL;
+ FileData *fd1 = NULL;
if (!fd->change)
{
g_free(dest_dir);
}
+ /* During a rename operation, check if another planned destination file has
+ * the same filename
+ */
+ if(fd->change->type == FILEDATA_CHANGE_RENAME)
+ {
+ work = list;
+ while (work)
+ {
+ fd1 = work->data;
+ work = work->next;
+ if (fd1 != NULL && fd != fd1 )
+ {
+ if (!strcmp(fd->change->dest, fd1->change->dest))
+ {
+ ret |= CHANGE_DUPLICATE_DEST;
+ }
+ }
+ }
+ }
+
fd->change->error = ret;
if (ret == 0) DEBUG_1("Change checked: OK: %s", fd->path);
}
-gint file_data_sc_verify_ci(FileData *fd)
+gint file_data_sc_verify_ci(FileData *fd, GList *list)
{
GList *work;
gint ret;
- ret = file_data_verify_ci(fd);
+ ret = file_data_verify_ci(fd, list);
work = fd->sidecar_files;
while (work)
{
FileData *sfd = work->data;
- ret |= file_data_verify_ci(sfd);
+ ret |= file_data_verify_ci(sfd, list);
work = work->next;
}
g_string_append(result, _("there are unsaved metadata changes for the file"));
}
+ if (error & CHANGE_DUPLICATE_DEST)
+ {
+ if (result->len > 0) g_string_append(result, ", ");
+ g_string_append(result, _("another destination file has the same filename"));
+ }
+
return g_string_free(result, FALSE);
}
fd = work->data;
work = work->next;
- error = with_sidecars ? file_data_sc_verify_ci(fd) : file_data_verify_ci(fd);
+ error = with_sidecars ? file_data_sc_verify_ci(fd, list) : file_data_verify_ci(fd, list);
all_errors |= error;
common_errors &= error;