/*
- * Geeqie
- * (C) 2006 John Ellis
- * Copyright (C) 2008 - 2012 The Geeqie Team
+ * Copyright (C) 2006 John Ellis
+ * Copyright (C) 2008 - 2016 The Geeqie Team
*
* Author: John Ellis
*
- * This software is released under the GNU General Public License (GNU GPL).
- * Please read the included file COPYING for more information.
- * This software comes with no warranty of any kind, use at your own risk!
+ * 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, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
-
#include "main.h"
#include "filedata.h"
#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;
/* what I would like to use is printf("%'d", size)
* BUT: not supported on every libc :(
*/
- if (size > G_MAXUINT)
+ if (size > G_MAXINT)
{
/* the %lld conversion is not valid in all libcs, so use a simple work-around */
a = g_strdup_printf("%d%09d", (guint)(size / 1000000000), (guint)(size % 1000000000));
/*
*-----------------------------------------------------------------------------
- * changed files detection and notification
+ * changed files detection and notification
*-----------------------------------------------------------------------------
*/
{
fd->version++;
fd->valid_marks = 0;
- if (fd->parent)
+ if (fd->parent)
{
fd->parent->version++;
fd->parent->valid_marks = 0;
{
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;
{
gboolean ret = FALSE;
GList *work;
-
+
ret = file_data_check_changed_single_file(fd, st);
work = fd->sidecar_files;
{
gboolean ret = FALSE;
struct stat st;
-
+
if (fd->parent) fd = fd->parent;
if (!stat_utf8(fd->path, &st))
ret = TRUE;
fd->size = 0;
fd->date = 0;
-
+
/* file_data_disconnect_sidecar_file might delete the file,
we have to keep the reference to prevent this */
sidecars = filelist_copy(fd->sidecar_files);
{
sfd = work->data;
work = work->next;
-
+
file_data_disconnect_sidecar_file(fd, sfd);
}
file_data_check_sidecars(sidecars); /* this will group the sidecars back together */
/*
*-----------------------------------------------------------------------------
- * file name, extension, sorting, ...
+ * file name, extension, sorting, ...
*-----------------------------------------------------------------------------
*/
g_free(fd->collate_key_name);
g_free(fd->collate_key_name_nocase);
-#if 0 && GLIB_CHECK_VERSION(2, 8, 0)
- fd->collate_key_name = g_utf8_collate_key_for_filename(valid_name, -1);
- fd->collate_key_name_nocase = g_utf8_collate_key_for_filename(caseless_name, -1);
+#if GTK_CHECK_VERSION(2, 8, 0)
+ if (options->file_sort.natural)
+ {
+ fd->collate_key_name = g_utf8_collate_key_for_filename(fd->name, -1);
+ fd->collate_key_name_nocase = g_utf8_collate_key_for_filename(caseless_name, -1);
+ }
+ else
+ {
+ fd->collate_key_name = g_utf8_collate_key(valid_name, -1);
+ fd->collate_key_name_nocase = g_utf8_collate_key(caseless_name, -1);
+ }
#else
fd->collate_key_name = g_utf8_collate_key(valid_name, -1);
fd->collate_key_name_nocase = g_utf8_collate_key(caseless_name, -1);
#endif
+
g_free(valid_name);
g_free(caseless_name);
}
{
fd->extension = fd->name + strlen(fd->name);
}
-
+
fd->sidecar_priority = sidecar_file_priority(fd->extension);
file_data_set_collate_keys(fd);
}
DEBUG_2("file_data_new: '%s' %d", path_utf8, disable_sidecars);
- if (S_ISDIR(st->st_mode)) disable_sidecars = TRUE;
+ if (S_ISDIR(st->st_mode)) disable_sidecars = TRUE;
if (!file_data_pool)
file_data_pool = g_hash_table_new(g_str_hash, g_str_equal);
{
file_data_ref(fd);
}
-
+
if (!fd && file_data_planned_change_hash)
{
fd = g_hash_table_lookup(file_data_planned_change_hash, path_utf8);
if (fd)
{
DEBUG_1("planned change: using %s -> %s", path_utf8, fd->path);
- file_data_ref(fd);
- file_data_apply_ci(fd);
+ if (!isfile(fd->path))
+ {
+ file_data_ref(fd);
+ file_data_apply_ci(fd);
+ }
+ else
+ {
+ fd = NULL;
+ }
}
}
-
+
if (fd)
{
gboolean changed;
-
+
if (disable_sidecars) file_data_disable_grouping(fd, TRUE);
-
-
+
+
changed = file_data_check_changed_single_file(fd, st);
DEBUG_2("file_data_pool hit: '%s' %s", fd->path, changed ? "(changed)" : "");
-
+
return fd;
}
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 = 0x12345678;
-
+ fd->magick = FD_MAGICK;
+ fd->exifdate = 0;
+
if (disable_sidecars) fd->disable_grouping = TRUE;
file_data_set_path(fd, path_utf8); /* set path, name, collate_key_*, original_path */
return ret;
}
-void init_exif_time_data(GList *files)
+FileData *file_data_new_simple(const gchar *path_utf8)
{
- FileData *file;
- DEBUG_1("%s init_exif_time_data: ...", get_exec_time());
- while (files)
- {
- file = files->data;
+ struct stat st;
+ FileData *fd;
- if (file)
- file->exifdate = 0;
+ if (!stat_utf8(path_utf8, &st))
+ {
+ st.st_size = 0;
+ st.st_mtime = 0;
+ }
- files = files->next;
+ 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 read_exif_time_data(FileData *file)
DEBUG_1("%s set_exif_time_data: Already exists for %s", get_exec_time(), file->path);
return;
}
-
+
file->exif = exif_read_fd(file);
if (file->exif)
{
struct tm time_str;
uint year, month, day, hour, min, sec;
-
+
sscanf(tmp, "%4d:%2d:%2d %2d:%2d:%2d", &year, &month, &day, &hour, &min, &sec);
time_str.tm_year = year - 1900;
time_str.tm_mon = month - 1;
time_str.tm_min = min;
time_str.tm_sec = sec;
time_str.tm_isdst = 0;
-
+
file->exifdate = mktime(&time_str);
g_free(tmp);
}
void set_exif_time_data(GList *files)
{
DEBUG_1("%s set_exif_time_data: ...", get_exec_time());
-
+
while (files)
{
FileData *file = files->data;
-
+
read_exif_time_data(file);
files = files->next;
}
else
/* dir or non-existing yet */
g_assert(S_ISDIR(st.st_mode));
-
+
return file_data_new(path_utf8, &st, TRUE);
}
#endif
{
if (fd == NULL) return NULL;
+ if (fd->magick != FD_MAGICK)
#ifdef DEBUG_FILEDATA
- if (fd->magick != 0x12345678)
- DEBUG_0("fd magick mismatch at %s:%d", file, line);
+ DEBUG_0("fd magick mismatch @ %s:%d fd=%p", file, line, fd);
+#else
+ DEBUG_0("fd magick mismatch fd=%p", fd);
#endif
- g_assert(fd->magick == 0x12345678);
+ g_assert(fd->magick == FD_MAGICK);
fd->ref++;
#ifdef DEBUG_FILEDATA
- DEBUG_2("file_data_ref (%d): '%s' @ %s:%d", fd->ref, fd->path, file, line);
+ DEBUG_2("file_data_ref fd=%p (%d): '%s' @ %s:%d", fd, fd->ref, fd->path, file, line);
#else
- DEBUG_2("file_data_ref (%d): '%s'", fd->ref, fd->path);
+ DEBUG_2("file_data_ref fd=%p (%d): '%s'", fd, fd->ref, fd->path);
#endif
return fd;
}
static void file_data_free(FileData *fd)
{
- g_assert(fd->magick == 0x12345678);
+ 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->original_path);
g_free(fd->collate_key_name);
g_free(fd->collate_key_name_nocase);
+ g_free(fd->extended_extension);
if (fd->thumb_pixbuf) g_object_unref(fd->thumb_pixbuf);
histmap_free(fd->histmap);
-
+
g_assert(fd->sidecar_files == NULL); /* sidecar files must be freed before calling this */
file_data_change_info_free(NULL, 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
#endif
{
if (fd == NULL) return;
+ if (fd->magick != FD_MAGICK)
#ifdef DEBUG_FILEDATA
- if (fd->magick != 0x12345678)
- DEBUG_0("fd magick mismatch @ %s:%d", file, line);
+ DEBUG_0("fd magick mismatch @ %s:%d fd=%p", file, line, fd);
+#else
+ DEBUG_0("fd magick mismatch fd=%p", fd);
#endif
- g_assert(fd->magick == 0x12345678);
-
+ g_assert(fd->magick == FD_MAGICK);
+
fd->ref--;
#ifdef DEBUG_FILEDATA
- DEBUG_2("file_data_unref (%d): '%s' @ %s:%d", 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 (%d): '%s'", 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;
- work = parent->sidecar_files;
- while (work)
- {
- FileData *sfd = work->data;
- if (sfd->ref > 0) return;
- work = work->next;
- }
+ // Free FileData if it's no longer ref'd
+ file_data_consider_free(fd);
+}
+
+/**
+ * \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;
- g_list_free(parent->sidecar_files);
- parent->sidecar_files = NULL;
+ // Free FileData if it's no longer ref'd
+ file_data_consider_free(fd);
+}
+
+/**
+ * \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);
+ }
+}
/*
*-----------------------------------------------------------------------------
{
const FileData *fda = a;
const FileData *fdb = b;
-
+
if (fda->sidecar_priority < fdb->sidecar_priority) return -1;
if (fda->sidecar_priority > fdb->sidecar_priority) return 1;
-
+
return strcmp(fdb->extension, fda->extension);
}
while (work) {
gchar *ext = work->data;
-
+
work = work->next;
if (g_ascii_strcasecmp(extension, ext) == 0) return i;
i++;
static void file_data_check_sidecars(const GList *basename_list)
{
/* basename_list contains the new group - first is the parent, then sorted sidecars */
- /* all files in the list have ref count > 0 */
+ /* all files in the list have ref count > 0 */
const GList *work;
GList *s_work, *new_sidecars;
{
FileData *fd = work->data;
work = work->next;
- g_assert(fd->magick == 0x12345678);
+ g_assert(fd->magick == FD_MAGICK);
DEBUG_2("basename: %p %s", fd, fd->name);
- if (fd->parent)
+ if (fd->parent)
{
- g_assert(fd->parent->magick == 0x12345678);
+ g_assert(fd->parent->magick == FD_MAGICK);
DEBUG_2(" parent: %p", fd->parent);
}
s_work = fd->sidecar_files;
{
FileData *sfd = s_work->data;
s_work = s_work->next;
- g_assert(sfd->magick == 0x12345678);
+ g_assert(sfd->magick == FD_MAGICK);
DEBUG_2(" sidecar: %p %s", sfd, sfd->name);
}
-
+
g_assert(fd->parent == NULL || fd->sidecar_files == NULL);
}
as sidecars of the first entry (parent_fd) */
work = basename_list->next;
s_work = parent_fd->sidecar_files;
-
+
while (work && s_work)
{
if (work->data != s_work->data) break;
work = work->next;
s_work = s_work->next;
}
-
- if (!work && !s_work)
+
+ if (!work && !s_work)
{
DEBUG_2("basename no change");
return; /* no change in grouping */
}
-
+
/* we have to regroup it */
-
+
/* first, disconnect everything and send notification*/
work = basename_list;
FileData *fd = work->data;
work = work->next;
g_assert(fd->parent == NULL || fd->sidecar_files == NULL);
-
+
if (fd->parent)
{
FileData *old_parent = fd->parent;
file_data_send_notification(old_parent, NOTIFY_REREAD);
file_data_unref(old_parent);
}
-
+
while (fd->sidecar_files)
{
FileData *sfd = fd->sidecar_files->data;
file_data_unref(sfd);
}
file_data_send_notification(fd, NOTIFY_GROUPING);
-
+
g_assert(fd->parent == NULL && fd->sidecar_files == NULL);
}
while (work)
{
FileData *sfd = work->data;
- g_assert(sfd->magick == 0x12345678);
+ g_assert(sfd->magick == FD_MAGICK);
g_assert(sfd->parent == NULL && sfd->sidecar_files == NULL);
sfd->parent = parent_fd;
new_sidecars = g_list_prepend(new_sidecars, sfd);
static void file_data_disconnect_sidecar_file(FileData *target, FileData *sfd)
{
- g_assert(target->magick == 0x12345678);
- g_assert(sfd->magick == 0x12345678);
+ g_assert(target->magick == FD_MAGICK);
+ g_assert(sfd->magick == FD_MAGICK);
g_assert(g_list_find(target->sidecar_files, sfd));
file_data_ref(target);
file_data_ref(sfd);
g_assert(sfd->parent == target);
-
+
file_data_increment_version(sfd); /* increments both sfd and target */
target->sidecar_files = g_list_remove(target->sidecar_files, sfd);
sfd->parent = NULL;
+ g_free(sfd->extended_extension);
+ sfd->extended_extension = NULL;
file_data_unref(target);
file_data_unref(sfd);
void file_data_disable_grouping(FileData *fd, gboolean disable)
{
if (!fd->disable_grouping == !disable) return;
-
+
fd->disable_grouping = !!disable;
-
+
if (disable)
{
if (fd->parent)
void file_data_disable_grouping_list(GList *fd_list, gboolean disable)
{
GList *work;
-
+
work = fd_list;
while (work)
{
FileData *fd = work->data;
-
+
file_data_disable_grouping(fd, disable);
work = work->next;
}
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;
ret = strcmp(fa->collate_key_name_nocase, fb->collate_key_name_nocase);
if (ret != 0) return ret;
-
- /* do not return 0 unless the files are really the same
- file_data_pool ensures that original_path is unique
+
+ /* do not return 0 unless the files are really the same
+ file_data_pool ensures that original_path is unique
*/
return strcmp(fa->original_path, fb->original_path);
}
gchar *basename = g_strndup(fd->path, fd->extension - fd->path);
list = g_hash_table_lookup(basename_hash, basename);
-
+
+ if (!list)
+ {
+ DEBUG_1("TG: basename_hash not found for %s",fd->path);
+ const gchar *parent_extension = registered_extension_from_path(basename);
+
+ if (parent_extension)
+ {
+ DEBUG_1("TG: parent extension %s",parent_extension);
+ gchar *parent_basename = g_strndup(basename, parent_extension - basename);
+ DEBUG_1("TG: parent basename %s",parent_basename);
+ FileData *parent_fd = g_hash_table_lookup(file_data_pool, basename);
+ if (parent_fd)
+ {
+ DEBUG_1("TG: parent fd found");
+ list = g_hash_table_lookup(basename_hash, parent_basename);
+ if (!g_list_find(list, parent_fd))
+ {
+ DEBUG_1("TG: parent fd doesn't fit");
+ g_free(parent_basename);
+ list = NULL;
+ }
+ else
+ {
+ g_free(basename);
+ basename = parent_basename;
+ fd->extended_extension = g_strconcat(parent_extension, fd->extension, NULL);
+ }
+ }
+ }
+ }
+
if (!g_list_find(list, fd))
{
list = g_list_insert_sorted(list, file_data_ref(fd), file_data_sort_by_ext);
g_hash_table_insert(basename_hash, basename, list);
}
- else
+ else
{
g_free(basename);
}
return list;
}
-#if 0
-static void file_data_basename_hash_remove(GHashTable *basename_hash, FileData *fd)
+static void file_data_basename_hash_insert_cb(gpointer fd, gpointer basename_hash)
{
- GList *list;
- gchar *basename = g_strndup(fd->path, fd->extension - fd->path);
-
- list = g_hash_table_lookup(basename_hash, basename);
-
- if (!g_list_find(list, fd)) return;
-
- list = g_list_remove(list, fd);
- file_data_unref(fd);
-
- if (list)
- {
- g_hash_table_insert(basename_hash, basename, list);
- }
- else
- {
- g_hash_table_remove(basename_hash, basename);
- g_free(basename);
- }
+ file_data_basename_hash_insert((GHashTable *)basename_hash, (FileData *)fd);
}
-#endif
static void file_data_basename_hash_remove_list(gpointer key, gpointer value, gpointer data)
{
static void file_data_basename_hash_free(GHashTable *basename_hash)
{
- g_hash_table_foreach(basename_hash, file_data_basename_hash_remove_list, NULL);
+ g_hash_table_foreach(basename_hash, file_data_basename_hash_remove_list, NULL);
g_hash_table_destroy(basename_hash);
}
while (work)
{
FileData *fd = work->data;
-
+
work = work->next;
if (fd->parent) /* remove fd's that are children */
file_data_unref(fd);
gchar *pathl;
GList *dlist = NULL;
GList *flist = NULL;
+ GList *xmp_files = NULL;
gint (*stat_func)(const gchar *path, struct stat *buf);
GHashTable *basename_hash = NULL;
{
FileData *fd = file_data_new_local(filepath, &ent_sbuf, FALSE);
flist = g_list_prepend(flist, fd);
- if (fd->sidecar_priority && !fd->disable_grouping)
+ if (fd->sidecar_priority && !fd->disable_grouping)
{
- file_data_basename_hash_insert(basename_hash, fd);
+ if (strcmp(fd->extension, ".xmp") != 0)
+ file_data_basename_hash_insert(basename_hash, fd);
+ else
+ xmp_files = g_list_append(xmp_files, fd);
}
}
}
}
closedir(dp);
-
+
g_free(pathl);
+ if (xmp_files)
+ {
+ g_list_foreach(xmp_files,file_data_basename_hash_insert_cb,basename_hash);
+ g_list_free(xmp_files);
+ }
+
if (dirs) *dirs = dlist;
- if (files)
+ if (files)
{
- g_hash_table_foreach(basename_hash, file_data_basename_hash_to_sidecars, NULL);
+ g_hash_table_foreach(basename_hash, file_data_basename_hash_to_sidecars, NULL);
*files = filelist_filter_out_sidecars(flist);
}
if (basename_hash) file_data_basename_hash_free(basename_hash);
- // Call a separate function to initialize the exif datestamps for the found files..
- if (files) init_exif_time_data(*files);
-
return TRUE;
}
if (S_ISDIR(st.st_mode))
return file_data_new(path_utf8, &st, TRUE);
-
+
dir = remove_level_from_path(path_utf8);
-
+
filelist_read_real(dir, &files, NULL, TRUE);
-
+
fd = g_hash_table_lookup(file_data_pool, path_utf8);
- g_assert(fd);
- file_data_ref(fd);
-
+ if (!fd) fd = file_data_new(path_utf8, &st, TRUE);
+ if (fd)
+ {
+ file_data_ref(fd);
+ }
+
filelist_free(files);
g_free(dir);
return fd;
strcmp(name, GQ_CACHE_LOCAL_METADATA) == 0)) )
{
GList *link = work;
-
+
list = g_list_remove_link(list, link);
file_data_unref(fd);
g_list_free(link);
}
-
+
work = work->next;
}
{
gchar *sidecar_path = NULL;
GList *work;
-
+
if (!file_data_can_write_sidecar(fd)) return NULL;
-
+
work = fd->parent ? fd->parent->sidecar_files : fd->sidecar_files;
+ gchar *extended_extension = g_strconcat(fd->parent ? fd->parent->extension : fd->extension, ".xmp", NULL);
while (work)
{
FileData *sfd = work->data;
work = work->next;
- if (g_ascii_strcasecmp(sfd->extension, ".xmp") == 0)
+ if (g_ascii_strcasecmp(sfd->extension, ".xmp") == 0 || g_ascii_strcasecmp(sfd->extension, extended_extension) == 0)
{
sidecar_path = g_strdup(sfd->path);
break;
}
}
-
+ g_free(extended_extension);
+
if (!existing_only && !sidecar_path)
{
- gchar *base = g_strndup(fd->path, fd->extension - fd->path);
- sidecar_path = g_strconcat(base, ".xmp", NULL);
- g_free(base);
+ if (options->metadata.sidecar_extended_name)
+ sidecar_path = g_strconcat(fd->path, ".xmp", NULL);
+ else
+ {
+ gchar *base = g_strndup(fd->path, fd->extension - fd->path);
+ sidecar_path = g_strconcat(base, ".xmp", NULL);
+ g_free(base);
+ }
}
return sidecar_path;
gboolean file_data_get_mark(FileData *fd, gint n)
{
gboolean valid = (fd->valid_marks & (1 << n));
-
- if (file_data_get_mark_func[n] && !valid)
+
+ if (file_data_get_mark_func[n] && !valid)
{
guint old = fd->marks;
gboolean value = (file_data_get_mark_func[n])(fd, n, file_data_mark_func_data[n]);
-
- if (!value != !(fd->marks & (1 << n)))
+
+ if (!value != !(fd->marks & (1 << n)))
{
fd->marks = fd->marks ^ (1 << n);
}
-
+
fd->valid_marks |= (1 << n);
if (old && !fd->marks) /* keep files with non-zero marks in memory */
{
{
guint old;
if (!value == !file_data_get_mark(fd, n)) return;
-
- if (file_data_set_mark_func[n])
+
+ if (file_data_set_mark_func[n])
{
(file_data_set_mark_func[n])(fd, n, value, file_data_mark_func_data[n]);
}
-
+
old = fd->marks;
fd->marks = fd->marks ^ (1 << n);
-
+
if (old && !fd->marks) /* keep files with non-zero marks in memory */
{
file_data_unref(fd);
{
file_data_ref(fd);
}
-
+
file_data_increment_version(fd);
file_data_send_notification(fd, NOTIFY_MARKS);
}
gboolean file_data_register_mark_func(gint n, FileDataGetMarkFunc get_mark_func, FileDataSetMarkFunc set_mark_func, gpointer data, GDestroyNotify notify)
{
if (n < 0 || n >= FILEDATA_MARKS_SIZE) return FALSE;
-
+
if (file_data_destroy_mark_func[n]) (file_data_destroy_mark_func[n])(file_data_mark_func_data[n]);
-
+
file_data_get_mark_func[n] = get_mark_func;
file_data_set_mark_func[n] = set_mark_func;
file_data_mark_func_data[n] = data;
file_data_destroy_mark_func[n] = notify;
- if (get_mark_func)
+ if (get_mark_func && file_data_pool)
{
/* this effectively changes all known files */
g_hash_table_foreach(file_data_pool, file_data_notify_mark_func, NULL);
fdci->dest = g_strdup(dest);
fd->change = fdci;
-
+
return TRUE;
}
if (!fdci) return;
file_data_planned_change_remove(fd);
-
+
if (fdci->regroup_when_finished) file_data_disable_grouping(fd, FALSE);
g_free(fdci->source);
GList *work;
if (fd->parent) fd = fd->parent;
-
+
if (fd->change) return FALSE;
-
+
work = fd->sidecar_files;
while (work)
{
FileData *sfd = work->data;
-
+
if (sfd->change) return FALSE;
work = work->next;
}
file_data_add_ci(fd, type, NULL, NULL);
-
+
work = fd->sidecar_files;
while (work)
{
FileData *sfd = work->data;
-
+
file_data_add_ci(sfd, type, NULL, NULL);
work = work->next;
}
-
+
return TRUE;
}
static gboolean file_data_sc_check_ci(FileData *fd, FileDataChangeType type)
{
GList *work;
-
+
if (fd->parent) fd = fd->parent;
-
+
if (!fd->change || fd->change->type != type) return FALSE;
-
+
work = fd->sidecar_files;
while (work)
{
GList *work;
if (fd->parent) fd = fd->parent;
-
+
file_data_free_ci(fd);
-
+
work = fd->sidecar_files;
while (work)
{
FileData *sfd = work->data;
-
+
file_data_free_ci(sfd);
work = work->next;
}
while (work)
{
FileData *fd = work->data;
-
+
if (!file_data_sc_add_ci_delete(fd)) ret = FALSE;
work = work->next;
}
static void file_data_sc_revert_ci_list(GList *fd_list)
{
GList *work;
-
+
work = fd_list;
while (work)
{
FileData *fd = work->data;
-
+
file_data_sc_free_ci(fd);
work = work->prev;
}
static gboolean file_data_sc_add_ci_list_call_func(GList *fd_list, const gchar *dest, gboolean (*func)(FileData *, const gchar *))
{
GList *work;
-
+
work = fd_list;
while (work)
{
FileData *fd = work->data;
-
+
if (!func(fd, dest))
{
file_data_sc_revert_ci_list(work->prev);
}
work = work->next;
}
-
+
return TRUE;
}
while (work)
{
FileData *fd = work->data;
-
+
if (!file_data_add_ci_write_metadata(fd)) ret = FALSE;
work = work->next;
}
void file_data_free_ci_list(GList *fd_list)
{
GList *work;
-
+
work = fd_list;
while (work)
{
FileData *fd = work->data;
-
+
file_data_free_ci(fd);
work = work->next;
}
void file_data_sc_free_ci_list(GList *fd_list)
{
GList *work;
-
+
work = fd_list;
while (work)
{
FileData *fd = work->data;
-
+
file_data_sc_free_ci(fd);
work = work->next;
}
static void file_data_update_planned_change_hash(FileData *fd, const gchar *old_path, gchar *new_path)
{
FileDataChangeType type = fd->change->type;
-
+
if (type == FILEDATA_CHANGE_MOVE || type == FILEDATA_CHANGE_RENAME)
{
FileData *ofd;
-
+
if (!file_data_planned_change_hash)
file_data_planned_change_hash = g_hash_table_new(g_str_hash, g_str_equal);
-
+
if (old_path && g_hash_table_lookup(file_data_planned_change_hash, old_path) == fd)
{
DEBUG_1("planned change: removing %s -> %s", old_path, fd->path);
g_hash_table_remove(file_data_planned_change_hash, new_path);
file_data_unref(ofd);
}
-
+
DEBUG_1("planned change: inserting %s -> %s", new_path, fd->path);
file_data_ref(fd);
g_hash_table_insert(file_data_planned_change_hash, new_path, fd);
static void file_data_update_ci_dest_preserve_ext(FileData *fd, const gchar *dest_path)
{
- const gchar *extension = extension_from_path(fd->change->source);
+ const gchar *extension = registered_extension_from_path(fd->change->source);
gchar *base = remove_extension_from_path(dest_path);
gchar *old_path = fd->change->dest;
-
- fd->change->dest = g_strconcat(base, extension, NULL);
+
+ fd->change->dest = g_strconcat(base, fd->extended_extension ? fd->extended_extension : extension, NULL);
file_data_update_planned_change_hash(fd, old_path, fd->change->dest);
-
+
g_free(old_path);
g_free(base);
}
{
GList *work;
gchar *dest_path_full = NULL;
-
+
if (fd->parent) fd = fd->parent;
-
+
if (!dest_path)
{
dest_path = fd->path;
else if (!strchr(dest_path, G_DIR_SEPARATOR)) /* we got only filename, not a full path */
{
gchar *dir = remove_level_from_path(fd->path);
-
+
dest_path_full = g_build_filename(dir, dest_path, NULL);
g_free(dir);
dest_path = dest_path_full;
dest_path_full = g_build_filename(dest_path, fd->name, NULL);
dest_path = dest_path_full;
}
-
+
file_data_update_ci_dest(fd, dest_path);
-
+
work = fd->sidecar_files;
while (work)
{
FileData *sfd = work->data;
-
+
file_data_update_ci_dest_preserve_ext(sfd, dest_path);
work = work->next;
}
-
+
g_free(dest_path_full);
}
{
return file_data_sc_check_update_ci(fd, dest_path, FILEDATA_CHANGE_COPY);
}
-
+
gboolean file_data_sc_update_ci_move(FileData *fd, const gchar *dest_path)
{
return file_data_sc_check_update_ci(fd, dest_path, FILEDATA_CHANGE_MOVE);
{
GList *work;
gboolean ret = TRUE;
-
+
work = fd_list;
while (work)
{
FileData *fd = work->data;
-
+
if (!func(fd, dest)) ret = FALSE;
work = work->next;
}
-
+
return ret;
}
* 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)
{
DEBUG_1("Change checked: no change info: %s", fd->path);
DEBUG_1("Change checked: file does not exist: %s", fd->path);
return ret;
}
-
+
dir = remove_level_from_path(fd->path);
-
+
if (fd->change->type != FILEDATA_CHANGE_DELETE &&
fd->change->type != FILEDATA_CHANGE_MOVE && /* the unsaved metadata should survive move and rename operations */
fd->change->type != FILEDATA_CHANGE_RENAME &&
ret |= CHANGE_WARN_UNSAVED_META;
DEBUG_1("Change checked: unsaved metadata: %s", fd->path);
}
-
+
if (fd->change->type != FILEDATA_CHANGE_DELETE &&
fd->change->type != FILEDATA_CHANGE_WRITE_METADATA &&
!access_file(fd->path, R_OK))
/* determine destination file */
gboolean have_dest = FALSE;
gchar *dest_dir = NULL;
-
+
if (options->metadata.save_in_image_file)
{
- if (file_data_can_write_directly(fd))
+ if (file_data_can_write_directly(fd))
{
/* we can write the file directly */
if (access_file(fd->path, W_OK))
}
}
}
- else if (file_data_can_write_sidecar(fd))
+ else if (file_data_can_write_sidecar(fd))
{
/* we can write sidecar */
gchar *sidecar = file_data_get_sidecar_path(fd, FALSE);
g_free(sidecar);
}
}
-
+
if (!have_dest)
{
/* write private metadata file under ~/.geeqie */
metadata_path = cache_find_location(CACHE_TYPE_XMP_METADATA, fd->path);
#endif
if (!metadata_path) metadata_path = cache_find_location(CACHE_TYPE_METADATA, fd->path);
-
+
if (metadata_path && !access_file(metadata_path, W_OK))
{
g_free(metadata_path);
if (recursive_mkdir_if_not_exists(dest_dir, mode))
{
gchar *filename = g_strconcat(fd->name, options->metadata.save_legacy_format ? GQ_CACHE_EXT_METADATA : GQ_CACHE_EXT_XMP_METADATA, NULL);
-
+
metadata_path = g_build_filename(dest_dir, filename, NULL);
g_free(filename);
}
}
g_free(dest_dir);
}
-
+
if (fd->change->dest && fd->change->type != FILEDATA_CHANGE_WRITE_METADATA)
{
gboolean same;
gchar *dest_dir;
-
+
same = (strcmp(fd->path, fd->change->dest) == 0);
if (!same)
{
- const gchar *dest_ext = extension_from_path(fd->change->dest);
+ const gchar *dest_ext = registered_extension_from_path(fd->change->dest);
if (!dest_ext) dest_ext = "";
-
- if (g_ascii_strcasecmp(fd->extension, dest_ext) != 0)
+ if (!options->file_filter.disable_file_extension_checks)
{
- ret |= CHANGE_WARN_CHANGED_EXT;
- DEBUG_1("Change checked: source and destination have different extensions: %s -> %s", fd->path, fd->change->dest);
+ if (g_ascii_strcasecmp(fd->extension, dest_ext) != 0)
+ {
+ ret |= CHANGE_WARN_CHANGED_EXT;
+ DEBUG_1("Change checked: source and destination have different extensions: %s -> %s", fd->path, fd->change->dest);
+ }
}
}
else
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 ||
+ fd->change->type == FILEDATA_CHANGE_COPY ||
+ fd->change->type == FILEDATA_CHANGE_MOVE)
+ {
+ 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;
}
if (result->len > 0) g_string_append(result, ", ");
g_string_append(result, _("destination already exists and will be overwritten"));
}
-
+
if (error & CHANGE_WARN_SAME)
{
if (result->len > 0) g_string_append(result, ", ");
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);
}
gint num;
gint *errors;
gint i;
-
+
if (!list) return 0;
-
+
num = g_list_length(list);
errors = g_new(int, num);
work = list;
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;
-
+
errors[i] = error;
-
+
i++;
}
-
+
if (desc && all_errors)
{
GList *work;
GString *result = g_string_new("");
-
+
if (common_errors)
{
gchar *str = file_data_get_error_string(common_errors);
g_string_append(result, "\n");
g_free(str);
}
-
+
work = list;
i = 0;
while (work)
fd = work->data;
work = work->next;
-
+
error = errors[i] & ~common_errors;
-
+
if (error)
{
gchar *str = file_data_get_error_string(error);
GList *work;
gboolean ret = TRUE;
FileDataChangeType type = fd->change->type;
-
+
if (!file_data_sc_check_ci(fd, type)) return FALSE;
work = fd->sidecar_files;
while (work)
{
FileData *sfd = work->data;
-
+
if (!file_data_perform_ci(sfd)) ret = FALSE;
work = work->next;
}
-
+
if (!file_data_perform_ci(fd)) ret = FALSE;
-
+
return ret;
}
{
DEBUG_1("planned change: applying %s -> %s", fd->change->dest, fd->path);
file_data_planned_change_remove(fd);
-
+
if (g_hash_table_lookup(file_data_pool, fd->change->dest))
{
/* this change overwrites another file which is already known to other modules
}
file_data_increment_version(fd);
file_data_send_notification(fd, NOTIFY_CHANGE);
-
+
return TRUE;
}
{
GList *work;
FileDataChangeType type = fd->change->type;
-
+
if (!file_data_sc_check_ci(fd, type)) return FALSE;
work = fd->sidecar_files;
while (work)
{
FileData *sfd = work->data;
-
+
file_data_apply_ci(sfd);
work = work->next;
}
-
+
file_data_apply_ci(fd);
-
+
return TRUE;
}
GList *work;
if (fd->parent) fd = fd->parent;
if (!g_list_find(list, fd)) return FALSE;
-
+
work = fd->sidecar_files;
while (work)
{
return TRUE;
}
-#if 0
-static gboolean file_data_list_dump(GList *list)
-{
- GList *work, *work2;
-
- work = list;
- while (work)
- {
- FileData *fd = work->data;
- printf("%s\n", fd->name);
- work2 = fd->sidecar_files;
- while (work2)
- {
- FileData *fd = work2->data;
- printf(" %s\n", fd->name);
- work2 = work2->next;
- }
- work = work->next;
- }
- return TRUE;
-}
-#endif
-
GList *file_data_process_groups_in_selection(GList *list, gboolean ungroup, GList **ungrouped_list)
{
GList *out = NULL;
{
FileData *fd = work->data;
work = work->next;
-
- if (!file_data_list_contains_whole_group(list, fd))
+
+ if (!file_data_list_contains_whole_group(list, fd))
{
file_data_disable_grouping(fd, TRUE);
- if (ungrouped_list)
+ if (ungrouped_list)
{
*ungrouped_list = g_list_prepend(*ungrouped_list, file_data_ref(fd));
}
}
}
}
-
- /* remove sidecars from the list,
+
+ /* remove sidecars from the list,
they can be still acessed via main_fd->sidecar_files */
work = list;
while (work)
{
FileData *fd = work->data;
work = work->next;
-
+
if (!fd->parent ||
(!ungroup && !file_data_list_contains_whole_group(list, fd)))
{
out = g_list_prepend(out, file_data_ref(fd));
}
}
-
+
filelist_free(list);
out = g_list_reverse(out);
{
NotifyData *nd;
GList *work = notify_func_list;
-
+
while (work)
{
NotifyData *nd = (NotifyData *)work->data;
-
+
if (nd->func == func && nd->data == data)
{
g_warning("Notify func already registered");
}
work = work->next;
}
-
+
nd = g_new(NotifyData, 1);
nd->func = func;
nd->data = data;
notify_func_list = g_list_insert_sorted(notify_func_list, nd, file_data_notify_sort);
DEBUG_2("Notify func registered: %p", nd);
-
+
return TRUE;
}
gboolean file_data_unregister_notify_func(FileDataNotifyFunc func, gpointer data)
{
GList *work = notify_func_list;
-
+
while (work)
{
NotifyData *nd = (NotifyData *)work->data;
-
+
if (nd->func == func && nd->data == data)
{
notify_func_list = g_list_delete_link(notify_func_list, work);
while (work)
{
NotifyData *nd = (NotifyData *)work->data;
-
+
nd->func(nid->fd, nid->type, nd->data);
work = work->next;
}
void file_data_send_notification(FileData *fd, NotifyType type)
{
+ GList *work = notify_func_list;
+
+ while (work)
+ {
+ NotifyData *nd = (NotifyData *)work->data;
+
+ nd->func(fd, type, nd->data);
+ work = work->next;
+ }
+ /*
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;
FileData *fd = key;
file_data_check_changed_files(fd);
-
+
DEBUG_1("monitor %s", fd->path);
}
gboolean file_data_register_real_time_monitor(FileData *fd)
{
gint count;
-
+
file_data_ref(fd);
-
+
if (!file_data_monitor_pool)
file_data_monitor_pool = g_hash_table_new(g_direct_hash, g_direct_equal);
-
+
count = GPOINTER_TO_INT(g_hash_table_lookup(file_data_monitor_pool, fd));
DEBUG_1("Register realtime %d %s", count, fd->path);
-
+
count++;
g_hash_table_insert(file_data_monitor_pool, fd, GINT_TO_POINTER(count));
-
+
if (!realtime_monitor_id)
{
realtime_monitor_id = g_timeout_add(5000, realtime_monitor_cb, NULL);
}
-
+
return TRUE;
}
gint count;
g_assert(file_data_monitor_pool);
-
+
count = GPOINTER_TO_INT(g_hash_table_lookup(file_data_monitor_pool, fd));
-
+
DEBUG_1("Unregister realtime %d %s", count, fd->path);
-
+
g_assert(count > 0);
-
+
count--;
-
+
if (count == 0)
g_hash_table_remove(file_data_monitor_pool, fd);
else
g_hash_table_insert(file_data_monitor_pool, fd, GINT_TO_POINTER(count));
file_data_unref(fd);
-
+
if (g_hash_table_size(file_data_monitor_pool) == 0)
{
g_source_remove(realtime_monitor_id);
realtime_monitor_id = 0;
return FALSE;
}
-
+
return TRUE;
}
/* vim: set shiftwidth=8 softtabstop=0 cindent cinoptions={1s: */