/*
* Geeqie
* (C) 2006 John Ellis
- * Copyright (C) 2008 The Geeqie Team
+ * Copyright (C) 2008 - 2012 The Geeqie Team
*
* Author: John Ellis
*
#include "filefilter.h"
#include "cache.h"
-#include "rcfile.h"
-#include "secure_save.h"
#include "thumb_standard.h"
#include "ui_fileops.h"
+#include "metadata.h"
+#include "trash.h"
+#include "histogram.h"
+#include "exif.h"
+
+#include <errno.h>
static GHashTable *file_data_pool = NULL;
static GHashTable *file_data_planned_change_hash = NULL;
-static gint sidecar_file_priority(const gchar *path);
-static void file_data_apply_ci(FileData *fd);
+static gint sidecar_file_priority(const gchar *extension);
+static void file_data_check_sidecars(const GList *basename_list);
+static void file_data_disconnect_sidecar_file(FileData *target, FileData *sfd);
+
+static SortType filelist_sort_method = SORT_NONE;
+static gboolean filelist_sort_ascend = TRUE;
/*
*-----------------------------------------------------------------------------
}
if (size < (gint64)1048576)
{
- return g_strdup_printf(_("%.1f K"), (double)size / 1024.0);
+ return g_strdup_printf(_("%.1f K"), (gdouble)size / 1024.0);
}
if (size < (gint64)1073741824)
{
- return g_strdup_printf(_("%.1f MB"), (double)size / 1048576.0);
+ return g_strdup_printf(_("%.1f MB"), (gdouble)size / 1048576.0);
}
- /* to avoid overflowing the double, do division in two steps */
+ /* to avoid overflowing the gdouble, do division in two steps */
size /= 1048576;
- return g_strdup_printf(_("%.1f GB"), (double)size / 1024.0);
+ return g_strdup_printf(_("%.1f GB"), (gdouble)size / 1024.0);
}
/* note: returned string is valid until next call to text_from_time() */
btime = localtime(&t);
/* the %x warning about 2 digit years is not an error */
- buflen = strftime(buf, sizeof(buf), "%x %H:%M", btime);
+ buflen = strftime(buf, sizeof(buf), "%x %X", btime);
if (buflen < 1) return "";
g_free(ret);
/*
*-----------------------------------------------------------------------------
- * file info struct
+ * changed files detection and notification
*-----------------------------------------------------------------------------
*/
-FileData *file_data_merge_sidecar_files(FileData *target, FileData *source);
-static void file_data_check_sidecars(FileData *fd);
-FileData *file_data_disconnect_sidecar_file(FileData *target, FileData *sfd);
-
-
void file_data_increment_version(FileData *fd)
{
fd->version++;
- if (fd->parent) fd->parent->version++;
-}
-
-static void file_data_set_collate_keys(FileData *fd)
-{
- gchar *caseless_name;
-
- g_assert(g_utf8_validate(fd->name, -1, NULL));
-
- caseless_name = g_utf8_casefold(fd->name, -1);
-
- g_free(fd->collate_key_name);
- g_free(fd->collate_key_name_nocase);
-
-#if GLIB_CHECK_VERSION(2, 8, 0)
- 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(fd->name, -1);
- fd->collate_key_name_nocase = g_utf8_collate_key(caseless_name, -1);
-#endif
- g_free(caseless_name);
-}
-
-static void file_data_set_path(FileData *fd, const gchar *path)
-{
- g_assert(path /* && *path*/); /* view_dir_tree uses FileData with zero length path */
- g_assert(file_data_pool);
-
- g_free(fd->path);
-
- if (fd->original_path)
+ fd->valid_marks = 0;
+ if (fd->parent)
{
- g_hash_table_remove(file_data_pool, fd->original_path);
- g_free(fd->original_path);
+ fd->parent->version++;
+ fd->parent->valid_marks = 0;
}
-
- g_assert(!g_hash_table_lookup(file_data_pool, path));
-
- fd->original_path = g_strdup(path);
- g_hash_table_insert(file_data_pool, fd->original_path, fd);
-
- if (strcmp(path, G_DIR_SEPARATOR_S) == 0)
- {
- fd->path = g_strdup(path);
- fd->name = fd->path;
- fd->extension = fd->name + 1;
- file_data_set_collate_keys(fd);
- return;
- }
-
- fd->path = g_strdup(path);
- fd->name = filename_from_path(fd->path);
-
- if (strcmp(fd->name, "..") == 0)
- {
- gchar *dir = remove_level_from_path(path);
- g_free(fd->path);
- fd->path = remove_level_from_path(dir);
- g_free(dir);
- fd->name = "..";
- fd->extension = fd->name + 2;
- file_data_set_collate_keys(fd);
- return;
- }
- else if (strcmp(fd->name, ".") == 0)
- {
- g_free(fd->path);
- fd->path = remove_level_from_path(path);
- fd->name = ".";
- fd->extension = fd->name + 1;
- file_data_set_collate_keys(fd);
- return;
- }
-
- fd->extension = extension_from_path(fd->path);
- if (fd->extension == NULL)
- fd->extension = fd->name + strlen(fd->name);
-
- file_data_set_collate_keys(fd);
}
-static gboolean file_data_check_changed_files_recursive(FileData *fd, struct stat *st)
+static gboolean file_data_check_changed_single_file(FileData *fd, struct stat *st)
{
- gboolean ret = FALSE;
- GList *work;
-
if (fd->size != st->st_size ||
fd->date != st->st_mtime)
{
if (fd->thumb_pixbuf) g_object_unref(fd->thumb_pixbuf);
fd->thumb_pixbuf = NULL;
file_data_increment_version(fd);
- file_data_send_notification(fd, NOTIFY_TYPE_REREAD);
- ret = TRUE;
+ file_data_send_notification(fd, NOTIFY_REREAD);
+ return TRUE;
}
+ return FALSE;
+}
+
+static gboolean file_data_check_changed_files_recursive(FileData *fd, struct stat *st)
+{
+ gboolean ret = FALSE;
+ GList *work;
+
+ ret = file_data_check_changed_single_file(fd, st);
work = fd->sidecar_files;
while (work)
{
fd->size = 0;
fd->date = 0;
+ file_data_ref(sfd);
file_data_disconnect_sidecar_file(fd, sfd);
ret = TRUE;
+ file_data_increment_version(sfd);
+ file_data_send_notification(sfd, NOTIFY_REREAD);
+ file_data_unref(sfd);
continue;
}
if (!stat_utf8(fd->path, &st))
{
+ GList *sidecars;
GList *work;
FileData *sfd = NULL;
fd->size = 0;
fd->date = 0;
- work = fd->sidecar_files;
+ /* file_data_disconnect_sidecar_file might delete the file,
+ we have to keep the reference to prevent this */
+ sidecars = filelist_copy(fd->sidecar_files);
+ file_data_ref(fd);
+ work = sidecars;
while (work)
{
sfd = work->data;
file_data_disconnect_sidecar_file(fd, sfd);
}
- if (sfd) file_data_check_sidecars(sfd); /* this will group the sidecars back together */
- file_data_send_notification(fd, NOTIFY_TYPE_REREAD);
+ file_data_check_sidecars(sidecars); /* this will group the sidecars back together */
+ /* now we can release the sidecars */
+ filelist_free(sidecars);
+ file_data_increment_version(fd);
+ file_data_send_notification(fd, NOTIFY_REREAD);
+ file_data_unref(fd);
}
else
{
return ret;
}
-static FileData *file_data_new(const gchar *path_utf8, struct stat *st, gboolean check_sidecars)
+/*
+ *-----------------------------------------------------------------------------
+ * file name, extension, sorting, ...
+ *-----------------------------------------------------------------------------
+ */
+
+static void file_data_set_collate_keys(FileData *fd)
+{
+ gchar *caseless_name;
+ gchar *valid_name;
+
+ valid_name = g_filename_display_name(fd->name);
+ caseless_name = g_utf8_casefold(valid_name, -1);
+
+ g_free(fd->collate_key_name);
+ g_free(fd->collate_key_name_nocase);
+
+ fd->collate_key_name = g_utf8_collate_key(valid_name, -1);
+ fd->collate_key_name_nocase = g_utf8_collate_key(caseless_name, -1);
+
+ g_free(valid_name);
+ g_free(caseless_name);
+}
+
+static void file_data_set_path(FileData *fd, const gchar *path)
+{
+ g_assert(path /* && *path*/); /* view_dir_tree uses FileData with zero length path */
+ g_assert(file_data_pool);
+
+ g_free(fd->path);
+
+ if (fd->original_path)
+ {
+ g_hash_table_remove(file_data_pool, fd->original_path);
+ g_free(fd->original_path);
+ }
+
+ g_assert(!g_hash_table_lookup(file_data_pool, path));
+
+ fd->original_path = g_strdup(path);
+ g_hash_table_insert(file_data_pool, fd->original_path, fd);
+
+ if (strcmp(path, G_DIR_SEPARATOR_S) == 0)
+ {
+ fd->path = g_strdup(path);
+ fd->name = fd->path;
+ fd->extension = fd->name + 1;
+ file_data_set_collate_keys(fd);
+ return;
+ }
+
+ fd->path = g_strdup(path);
+ fd->name = filename_from_path(fd->path);
+
+ if (strcmp(fd->name, "..") == 0)
+ {
+ gchar *dir = remove_level_from_path(path);
+ g_free(fd->path);
+ fd->path = remove_level_from_path(dir);
+ g_free(dir);
+ fd->name = "..";
+ fd->extension = fd->name + 2;
+ file_data_set_collate_keys(fd);
+ return;
+ }
+ else if (strcmp(fd->name, ".") == 0)
+ {
+ g_free(fd->path);
+ fd->path = remove_level_from_path(path);
+ fd->name = ".";
+ fd->extension = fd->name + 1;
+ file_data_set_collate_keys(fd);
+ return;
+ }
+
+ fd->extension = registered_extension_from_path(fd->path);
+ if (fd->extension == NULL)
+ {
+ fd->extension = fd->name + strlen(fd->name);
+ }
+
+ fd->sidecar_priority = sidecar_file_priority(fd->extension);
+ file_data_set_collate_keys(fd);
+}
+
+/*
+ *-----------------------------------------------------------------------------
+ * create or reuse Filedata
+ *-----------------------------------------------------------------------------
+ */
+
+static FileData *file_data_new(const gchar *path_utf8, struct stat *st, gboolean disable_sidecars)
{
FileData *fd;
- DEBUG_2("file_data_new: '%s' %d", path_utf8, check_sidecars);
+ DEBUG_2("file_data_new: '%s' %d", path_utf8, disable_sidecars);
+
+ 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);
{
gboolean changed;
- if (fd->parent)
- changed = file_data_check_changed_files(fd);
- else
- changed = file_data_check_changed_files_recursive(fd, st);
- if (changed && check_sidecars && sidecar_file_priority(fd->extension))
- file_data_check_sidecars(fd);
+ 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);
- fd->path = NULL;
- fd->name = NULL;
- fd->collate_key_name = NULL;
- fd->collate_key_name_nocase = NULL;
- fd->original_path = NULL;
-
fd->size = st->st_size;
fd->date = st->st_mtime;
fd->mode = st->st_mode;
- fd->thumb_pixbuf = NULL;
- fd->sidecar_files = NULL;
fd->ref = 1;
- fd->magick = 0x12345678;
+ fd->magick = FD_MAGICK;
+
+ if (disable_sidecars) fd->disable_grouping = TRUE;
file_data_set_path(fd, path_utf8); /* set path, name, collate_key_*, original_path */
- if (check_sidecars)
- file_data_check_sidecars(fd);
-
return fd;
}
-static void file_data_check_sidecars(FileData *fd)
+static FileData *file_data_new_local(const gchar *path, struct stat *st, gboolean disable_sidecars)
{
- int base_len;
- GString *fname;
- FileData *parent_fd = NULL;
- GList *work;
-
- if (fd->disable_grouping || !sidecar_file_priority(fd->extension))
- return;
+ gchar *path_utf8 = path_to_utf8(path);
+ FileData *ret = file_data_new(path_utf8, st, disable_sidecars);
- base_len = fd->extension - fd->path;
- fname = g_string_new_len(fd->path, base_len);
- work = sidecar_ext_get_list();
+ g_free(path_utf8);
+ return ret;
+}
- while (work)
+void init_exif_time_data(GList *files)
+{
+ FileData *file;
+ DEBUG_1("%s init_exif_time_data: ...", get_exec_time());
+ while (files)
{
- /* check for possible sidecar files;
- the sidecar files created here are referenced only via fd->sidecar_files or fd->parent,
- they have fd->ref set to 0 and file_data unref must chack and free them all together
- (using fd->ref would cause loops and leaks)
- */
+ file = files->data;
- FileData *new_fd;
- gchar *ext = work->data;
+ if (file)
+ file->exifdate = 0;
- work = work->next;
+ files = files->next;
+ }
+}
- if (strcmp(ext, fd->extension) == 0)
- {
- new_fd = fd; /* processing the original file */
- }
- else
- {
- struct stat nst;
- g_string_truncate(fname, base_len);
- g_string_append(fname, ext);
+void read_exif_time_data(FileData *file)
+{
+ if (file->exifdate > 0)
+ {
+ DEBUG_1("%s set_exif_time_data: Already exists for %s", get_exec_time(), file->path);
+ return;
+ }
+
+ file->exif = exif_read_fd(file);
- if (!stat_utf8(fname->str, &nst))
- continue;
+ if (file->exif)
+ {
+ gchar *tmp = exif_get_data_as_text(file->exif, "Exif.Photo.DateTimeOriginal");
+ DEBUG_2("%s set_exif_time_data: reading %p %s", get_exec_time(), file, file->path);
- new_fd = file_data_new(fname->str, &nst, FALSE);
-
- if (new_fd->disable_grouping)
- {
- file_data_unref(new_fd);
- continue;
- }
-
- new_fd->ref--; /* do not use ref here */
+ if (tmp)
+ {
+ 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_mday = day;
+ time_str.tm_hour = hour;
+ time_str.tm_min = min;
+ time_str.tm_sec = sec;
+ time_str.tm_isdst = 0;
+
+ file->exifdate = mktime(&time_str);
+ g_free(tmp);
}
-
- if (!parent_fd)
- parent_fd = new_fd; /* parent is the one with the highest prio, found first */
- else
- file_data_merge_sidecar_files(parent_fd, new_fd);
}
- g_string_free(fname, TRUE);
}
-
-static FileData *file_data_new_local(const gchar *path, struct stat *st, gboolean check_sidecars)
+void set_exif_time_data(GList *files)
{
- gchar *path_utf8 = path_to_utf8(path);
- FileData *ret = file_data_new(path_utf8, st, check_sidecars);
-
- g_free(path_utf8);
- return ret;
+ DEBUG_1("%s set_exif_time_data: ...", get_exec_time());
+
+ while (files)
+ {
+ FileData *file = files->data;
+
+ read_exif_time_data(file);
+ files = files->next;
+ }
}
-FileData *file_data_new_simple(const gchar *path_utf8)
+FileData *file_data_new_no_grouping(const gchar *path_utf8)
{
struct stat st;
return file_data_new(path_utf8, &st, TRUE);
}
-FileData *file_data_add_sidecar_file(FileData *target, FileData *sfd)
-{
- sfd->parent = target;
- if (!g_list_find(target->sidecar_files, sfd))
- target->sidecar_files = g_list_prepend(target->sidecar_files, sfd);
- file_data_increment_version(sfd); /* increments both sfd and target */
- return target;
-}
-
-
-FileData *file_data_merge_sidecar_files(FileData *target, FileData *source)
+FileData *file_data_new_dir(const gchar *path_utf8)
{
- GList *work;
-
- file_data_add_sidecar_file(target, source);
+ struct stat st;
- work = source->sidecar_files;
- while (work)
+ if (!stat_utf8(path_utf8, &st))
{
- FileData *sfd = work->data;
- file_data_add_sidecar_file(target, sfd);
- work = work->next;
+ st.st_size = 0;
+ st.st_mtime = 0;
}
-
- g_list_free(source->sidecar_files);
- source->sidecar_files = NULL;
-
- target->sidecar_files = filelist_sort(target->sidecar_files, SORT_NAME, TRUE);
-
- return target;
+ else
+ /* dir or non-existing yet */
+ g_assert(S_ISDIR(st.st_mode));
+
+ return file_data_new(path_utf8, &st, TRUE);
}
+/*
+ *-----------------------------------------------------------------------------
+ * reference counting
+ *-----------------------------------------------------------------------------
+ */
+
#ifdef DEBUG_FILEDATA
FileData *file_data_ref_debug(const gchar *file, gint line, FileData *fd)
#else
#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);
+ metadata_cache_free(fd);
g_hash_table_remove(file_data_pool, fd->original_path);
g_free(fd->path);
g_free(fd->collate_key_name);
g_free(fd->collate_key_name_nocase);
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);
#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): '%s' @ %s:%d", fd, fd->ref, fd->path, file, line);
#else
- DEBUG_2("file_data_unref (%d): '%s'", fd->ref, fd->path);
+ DEBUG_2("file_data_unref fd=%p (%d): '%s'", fd, fd->ref, fd->path);
#endif
if (fd->ref == 0)
{
GList *work;
FileData *parent = fd->parent ? fd->parent : fd;
- if (parent->ref > 0)
- return;
+ if (parent->ref > 0) return;
work = parent->sidecar_files;
while (work)
{
FileData *sfd = work->data;
- if (sfd->ref > 0)
- return;
+ if (sfd->ref > 0) return;
work = work->next;
}
}
}
-FileData *file_data_disconnect_sidecar_file(FileData *target, FileData *sfd)
+
+
+/*
+ *-----------------------------------------------------------------------------
+ * sidecar file info struct
+ *-----------------------------------------------------------------------------
+ */
+
+static gint file_data_sort_by_ext(gconstpointer a, gconstpointer b)
{
- sfd->parent = target;
+ 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);
+}
+
+
+static gint sidecar_file_priority(const gchar *extension)
+{
+ gint i = 1;
+ GList *work;
+
+ if (extension == NULL)
+ return 0;
+
+ work = sidecar_ext_get_list();
+
+ while (work) {
+ gchar *ext = work->data;
+
+ work = work->next;
+ if (g_ascii_strcasecmp(extension, ext) == 0) return i;
+ i++;
+ }
+ return 0;
+}
+
+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 */
+
+ const GList *work;
+ GList *s_work, *new_sidecars;
+ FileData *parent_fd;
+
+ if (!basename_list) return;
+
+
+ DEBUG_2("basename start");
+ work = basename_list;
+ while (work)
+ {
+ FileData *fd = work->data;
+ work = work->next;
+ g_assert(fd->magick == FD_MAGICK);
+ DEBUG_2("basename: %p %s", fd, fd->name);
+ if (fd->parent)
+ {
+ g_assert(fd->parent->magick == FD_MAGICK);
+ DEBUG_2(" parent: %p", fd->parent);
+ }
+ s_work = fd->sidecar_files;
+ while (s_work)
+ {
+ FileData *sfd = s_work->data;
+ s_work = s_work->next;
+ g_assert(sfd->magick == FD_MAGICK);
+ DEBUG_2(" sidecar: %p %s", sfd, sfd->name);
+ }
+
+ g_assert(fd->parent == NULL || fd->sidecar_files == NULL);
+ }
+
+ parent_fd = basename_list->data;
+
+ /* check if the second and next entries of basename_list are already connected
+ 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)
+ {
+ DEBUG_2("basename no change");
+ return; /* no change in grouping */
+ }
+
+ /* we have to regroup it */
+
+ /* first, disconnect everything and send notification*/
+
+ work = basename_list;
+ while (work)
+ {
+ FileData *fd = work->data;
+ work = work->next;
+ g_assert(fd->parent == NULL || fd->sidecar_files == NULL);
+
+ if (fd->parent)
+ {
+ FileData *old_parent = fd->parent;
+ g_assert(old_parent->parent == NULL || old_parent->sidecar_files == NULL);
+ file_data_ref(old_parent);
+ file_data_disconnect_sidecar_file(old_parent, fd);
+ file_data_send_notification(old_parent, NOTIFY_REREAD);
+ file_data_unref(old_parent);
+ }
+
+ while (fd->sidecar_files)
+ {
+ FileData *sfd = fd->sidecar_files->data;
+ g_assert(sfd->parent == NULL || sfd->sidecar_files == NULL);
+ file_data_ref(sfd);
+ file_data_disconnect_sidecar_file(fd, sfd);
+ file_data_send_notification(sfd, NOTIFY_REREAD);
+ file_data_unref(sfd);
+ }
+ file_data_send_notification(fd, NOTIFY_GROUPING);
+
+ g_assert(fd->parent == NULL && fd->sidecar_files == NULL);
+ }
+
+ /* now we can form the new group */
+ work = basename_list->next;
+ new_sidecars = NULL;
+ while (work)
+ {
+ FileData *sfd = work->data;
+ 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);
+ work = work->next;
+ }
+ g_assert(parent_fd->sidecar_files == NULL);
+ parent_fd->sidecar_files = g_list_reverse(new_sidecars);
+ DEBUG_1("basename group changed for %s", parent_fd->path);
+}
+
+
+static void file_data_disconnect_sidecar_file(FileData *target, FileData *sfd)
+{
+ 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;
- if (sfd->ref == 0)
- {
- file_data_free(sfd);
- return NULL;
- }
-
- return sfd;
+ file_data_unref(target);
+ file_data_unref(sfd);
}
/* disables / enables grouping for particular file, sends UPDATE notification */
void file_data_disable_grouping(FileData *fd, gboolean disable)
{
if (!fd->disable_grouping == !disable) return;
+
fd->disable_grouping = !!disable;
if (disable)
{
FileData *parent = file_data_ref(fd->parent);
file_data_disconnect_sidecar_file(parent, fd);
- file_data_send_notification(fd, NOTIFY_TYPE_INTERNAL);
- file_data_send_notification(parent, NOTIFY_TYPE_INTERNAL);
+ file_data_send_notification(parent, NOTIFY_GROUPING);
file_data_unref(parent);
}
else if (fd->sidecar_files)
FileData *sfd = work->data;
work = work->next;
file_data_disconnect_sidecar_file(fd, sfd);
- file_data_send_notification(sfd, NOTIFY_TYPE_INTERNAL);
+ file_data_send_notification(sfd, NOTIFY_GROUPING);
}
- file_data_send_notification(fd, NOTIFY_TYPE_INTERNAL);
- file_data_check_sidecars((FileData *)sidecar_files->data); /* this will group the sidecars back together */
+ file_data_check_sidecars(sidecar_files); /* this will group the sidecars back together */
filelist_free(sidecar_files);
}
+ else
+ {
+ file_data_increment_version(fd); /* the functions called in the cases above increments the version too */
+ }
}
else
{
- file_data_check_sidecars(fd);
- file_data_send_notification(fd, NOTIFY_TYPE_INTERNAL);
+ file_data_increment_version(fd);
+ /* file_data_check_sidecars call is not necessary - the file will be re-grouped on next dir read */
}
+ file_data_send_notification(fd, NOTIFY_GROUPING);
}
-/* compare name without extension */
-gint file_data_compare_name_without_ext(FileData *fd1, FileData *fd2)
-{
- size_t len1 = fd1->extension - fd1->name;
- size_t len2 = fd2->extension - fd2->name;
-
- if (len1 < len2) return -1;
- if (len1 > len2) return 1;
-
- return strncmp(fd1->name, fd2->name, len1); /* FIXME: utf8 */
-}
-
-void file_data_change_info_free(FileDataChangeInfo *fdci, FileData *fd)
-{
- if (!fdci && fd)
- fdci = fd->change;
-
- if (!fdci)
- return;
-
- g_free(fdci->source);
- g_free(fdci->dest);
-
- g_free(fdci);
-
- if (fd)
- fd->change = NULL;
-}
-
-
-
-
-/*
- *-----------------------------------------------------------------------------
- * sidecar file info struct
- *-----------------------------------------------------------------------------
- */
-
-
-
-static gint sidecar_file_priority(const gchar *path)
+void file_data_disable_grouping_list(GList *fd_list, gboolean disable)
{
- const char *extension = extension_from_path(path);
- int i = 1;
GList *work;
-
- if (extension == NULL)
- return 0;
-
- work = sidecar_ext_get_list();
-
- while (work) {
- gchar *ext = work->data;
+
+ work = fd_list;
+ while (work)
+ {
+ FileData *fd = work->data;
+ file_data_disable_grouping(fd, disable);
work = work->next;
- if (strcmp(extension, ext) == 0) return i;
- i++;
- }
- return 0;
+ }
}
+
/*
*-----------------------------------------------------------------------------
- * load file list
+ * filelist sorting
*-----------------------------------------------------------------------------
*/
-static SortType filelist_sort_method = SORT_NONE;
-static gint filelist_sort_ascend = TRUE;
-
gint filelist_sort_compare_filedata(FileData *fa, FileData *fb)
{
+ gint ret;
if (!filelist_sort_ascend)
{
FileData *tmp = fa;
if (fa->date > fb->date) return 1;
/* fall back to name */
break;
+ case SORT_EXIFTIME:
+ if (fa->exifdate < fb->exifdate) return -1;
+ if (fa->exifdate > fb->exifdate) return 1;
+ /* fall back to name */
+ break;
#ifdef HAVE_STRVERSCMP
case SORT_NUMBER:
- return strverscmp(fa->name, fb->name);
+ ret = strverscmp(fa->name, fb->name);
+ if (ret != 0) return ret;
break;
#endif
default:
}
if (options->file_sort.case_sensitive)
- return strcmp(fa->collate_key_name, fb->collate_key_name);
+ ret = strcmp(fa->collate_key_name, fb->collate_key_name);
else
- return strcmp(fa->collate_key_name_nocase, fb->collate_key_name_nocase);
+ 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
+ */
+ return strcmp(fa->original_path, fb->original_path);
}
-gint filelist_sort_compare_filedata_full(FileData *fa, FileData *fb, SortType method, gint ascend)
+gint filelist_sort_compare_filedata_full(FileData *fa, FileData *fb, SortType method, gboolean ascend)
{
filelist_sort_method = method;
filelist_sort_ascend = ascend;
return filelist_sort_compare_filedata(fa, fb);
}
-static gint filelist_sort_file_cb(void *a, void *b)
+static gint filelist_sort_file_cb(gpointer a, gpointer b)
{
return filelist_sort_compare_filedata(a, b);
}
-GList *filelist_sort_full(GList *list, SortType method, gint ascend, GCompareFunc cb)
+GList *filelist_sort_full(GList *list, SortType method, gboolean ascend, GCompareFunc cb)
{
filelist_sort_method = method;
filelist_sort_ascend = ascend;
return g_list_sort(list, cb);
}
-GList *filelist_insert_sort_full(GList *list, void *data, SortType method, gint ascend, GCompareFunc cb)
+GList *filelist_insert_sort_full(GList *list, gpointer data, SortType method, gboolean ascend, GCompareFunc cb)
{
filelist_sort_method = method;
filelist_sort_ascend = ascend;
return g_list_insert_sorted(list, data, cb);
}
-GList *filelist_sort(GList *list, SortType method, gint ascend)
+GList *filelist_sort(GList *list, SortType method, gboolean ascend)
{
+ if (method == SORT_EXIFTIME)
+ {
+ set_exif_time_data(list);
+ }
return filelist_sort_full(list, method, ascend, (GCompareFunc) filelist_sort_file_cb);
}
-GList *filelist_insert_sort(GList *list, FileData *fd, SortType method, gint ascend)
+GList *filelist_insert_sort(GList *list, FileData *fd, SortType method, gboolean ascend)
{
return filelist_insert_sort_full(list, fd, method, ascend, (GCompareFunc) filelist_sort_file_cb);
}
+/*
+ *-----------------------------------------------------------------------------
+ * basename hash - grouping of sidecars in filelist
+ *-----------------------------------------------------------------------------
+ */
+
+
+static GHashTable *file_data_basename_hash_new(void)
+{
+ return g_hash_table_new_full(g_str_hash, g_str_equal, g_free, NULL);
+}
+
+static GList * file_data_basename_hash_insert(GHashTable *basename_hash, FileData *fd)
+{
+ 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))
+ {
+ list = g_list_insert_sorted(list, file_data_ref(fd), file_data_sort_by_ext);
+ g_hash_table_insert(basename_hash, basename, list);
+ }
+ else
+ {
+ g_free(basename);
+ }
+ return list;
+}
+
+static void file_data_basename_hash_remove_list(gpointer key, gpointer value, gpointer data)
+{
+ filelist_free((GList *)value);
+}
+
+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_destroy(basename_hash);
+}
+
+/*
+ *-----------------------------------------------------------------------------
+ * handling sidecars in filelist
+ *-----------------------------------------------------------------------------
+ */
static GList *filelist_filter_out_sidecars(GList *flist)
{
return flist_filtered;
}
-static gint filelist_read_real(FileData *dir_fd, GList **files, GList **dirs, gint follow_symlinks)
+static void file_data_basename_hash_to_sidecars(gpointer key, gpointer value, gpointer data)
+{
+ GList *basename_list = (GList *)value;
+ file_data_check_sidecars(basename_list);
+}
+
+
+static gboolean is_hidden_file(const gchar *name)
+{
+ if (name[0] != '.') return FALSE;
+ if (name[1] == '\0' || (name[1] == '.' && name[2] == '\0')) return FALSE;
+ return TRUE;
+}
+
+/*
+ *-----------------------------------------------------------------------------
+ * the main filelist function
+ *-----------------------------------------------------------------------------
+ */
+
+static gboolean filelist_read_real(const gchar *dir_path, GList **files, GList **dirs, gboolean follow_symlinks)
{
DIR *dp;
struct dirent *dir;
gchar *pathl;
GList *dlist = NULL;
GList *flist = NULL;
- int (*stat_func)(const char *path, struct stat *buf);
+ gint (*stat_func)(const gchar *path, struct stat *buf);
+ GHashTable *basename_hash = NULL;
g_assert(files || dirs);
if (files) *files = NULL;
if (dirs) *dirs = NULL;
- pathl = path_from_utf8(dir_fd->path);
+ pathl = path_from_utf8(dir_path);
if (!pathl) return FALSE;
dp = opendir(pathl);
return FALSE;
}
+ if (files) basename_hash = file_data_basename_hash_new();
+
if (follow_symlinks)
stat_func = stat;
else
const gchar *name = dir->d_name;
gchar *filepath;
- if (!options->file_filter.show_hidden_files && ishidden(name))
+ if (!options->file_filter.show_hidden_files && is_hidden_file(name))
continue;
filepath = g_build_filename(pathl, name, NULL);
strcmp(name, GQ_CACHE_LOCAL_METADATA) != 0 &&
strcmp(name, THUMB_FOLDER_LOCAL) != 0)
{
- dlist = g_list_prepend(dlist, file_data_new_local(filepath, &ent_sbuf, FALSE));
+ dlist = g_list_prepend(dlist, file_data_new_local(filepath, &ent_sbuf, TRUE));
}
}
else
{
if (files && filter_name_exists(name))
{
- flist = g_list_prepend(flist, file_data_new_local(filepath, &ent_sbuf, TRUE));
+ FileData *fd = file_data_new_local(filepath, &ent_sbuf, FALSE);
+ flist = g_list_prepend(flist, fd);
+ if (fd->sidecar_priority && !fd->disable_grouping)
+ {
+ file_data_basename_hash_insert(basename_hash, fd);
+ }
}
}
}
+ else
+ {
+ if (errno == EOVERFLOW)
+ {
+ log_printf("stat(): EOVERFLOW, skip '%s'", filepath);
+ }
+ }
g_free(filepath);
}
g_free(pathl);
if (dirs) *dirs = dlist;
- if (files) *files = filelist_filter_out_sidecars(flist);
+
+ if (files)
+ {
+ 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;
}
-gint filelist_read(FileData *dir_fd, GList **files, GList **dirs)
-{
- return filelist_read_real(dir_fd, files, dirs, TRUE);
+gboolean filelist_read(FileData *dir_fd, GList **files, GList **dirs)
+{
+ return filelist_read_real(dir_fd->path, files, dirs, TRUE);
+}
+
+gboolean filelist_read_lstat(FileData *dir_fd, GList **files, GList **dirs)
+{
+ return filelist_read_real(dir_fd->path, files, dirs, FALSE);
+}
+
+FileData *file_data_new_group(const gchar *path_utf8)
+{
+ gchar *dir;
+ struct stat st;
+ FileData *fd;
+ GList *files;
+
+ if (!stat_utf8(path_utf8, &st))
+ {
+ st.st_size = 0;
+ st.st_mtime = 0;
+ }
+
+ 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);
+
+ filelist_free(files);
+ g_free(dir);
+ return fd;
}
-gint filelist_read_lstat(FileData *dir_fd, GList **files, GList **dirs)
-{
- return filelist_read_real(dir_fd, files, dirs, FALSE);
-}
void filelist_free(GList *list)
{
path = work->data;
work = work->next;
- new_list = g_list_prepend(new_list, file_data_new_simple(path));
+ new_list = g_list_prepend(new_list, file_data_new_group(path));
}
return g_list_reverse(new_list);
return g_list_reverse(new_list);
}
-GList *filelist_filter(GList *list, gint is_dir_list)
+GList *filelist_filter(GList *list, gboolean is_dir_list)
{
GList *work;
FileData *fd = (FileData *)(work->data);
const gchar *name = fd->name;
- if ((!options->file_filter.show_hidden_files && ishidden(name)) ||
+ if ((!options->file_filter.show_hidden_files && is_hidden_file(name)) ||
(!is_dir_list && !filter_name_exists(name)) ||
(is_dir_list && name[0] == '.' && (strcmp(name, GQ_CACHE_LOCAL_THUMB) == 0 ||
strcmp(name, GQ_CACHE_LOCAL_METADATA) == 0)) )
return list;
}
+/*
+ *-----------------------------------------------------------------------------
+ * file modification support
+ *-----------------------------------------------------------------------------
+ */
+
+
+void file_data_change_info_free(FileDataChangeInfo *fdci, FileData *fd)
+{
+ if (!fdci && fd) fdci = fd->change;
+
+ if (!fdci) return;
+
+ g_free(fdci->source);
+ g_free(fdci->dest);
+
+ g_free(fdci);
+
+ if (fd) fd->change = NULL;
+}
+
+static gboolean file_data_can_write_directly(FileData *fd)
+{
+ return filter_name_is_writable(fd->extension);
+}
+
+static gboolean file_data_can_write_sidecar(FileData *fd)
+{
+ return filter_name_allow_sidecar(fd->extension) && !filter_name_is_writable(fd->extension);
+}
+
+gchar *file_data_get_sidecar_path(FileData *fd, gboolean existing_only)
+{
+ 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;
+ while (work)
+ {
+ FileData *sfd = work->data;
+ work = work->next;
+ if (g_ascii_strcasecmp(sfd->extension, ".xmp") == 0)
+ {
+ sidecar_path = g_strdup(sfd->path);
+ break;
+ }
+ }
+
+ 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);
+ }
+
+ return sidecar_path;
+}
/*
* marks and orientation
*/
-
-
+
+static FileDataGetMarkFunc file_data_get_mark_func[FILEDATA_MARKS_SIZE];
+static FileDataSetMarkFunc file_data_set_mark_func[FILEDATA_MARKS_SIZE];
+static gpointer file_data_mark_func_data[FILEDATA_MARKS_SIZE];
+static GDestroyNotify file_data_destroy_mark_func[FILEDATA_MARKS_SIZE];
+
gboolean file_data_get_mark(FileData *fd, gint n)
{
+ gboolean valid = (fd->valid_marks & (1 << n));
+
+ 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)))
+ {
+ fd->marks = fd->marks ^ (1 << n);
+ }
+
+ fd->valid_marks |= (1 << n);
+ if (old && !fd->marks) /* keep files with non-zero marks in memory */
+ {
+ file_data_unref(fd);
+ }
+ else if (!old && fd->marks)
+ {
+ file_data_ref(fd);
+ }
+ }
+
return !!(fd->marks & (1 << n));
}
+guint file_data_get_marks(FileData *fd)
+{
+ gint i;
+ for (i = 0; i < FILEDATA_MARKS_SIZE; i++) file_data_get_mark(fd, i);
+ return fd->marks;
+}
+
void file_data_set_mark(FileData *fd, gint n, gboolean value)
{
- guint old = fd->marks;
- if (!value == !(fd->marks & (1 << n))) return;
+ guint old;
+ if (!value == !file_data_get_mark(fd, n)) return;
+
+ 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);
}
file_data_increment_version(fd);
- file_data_send_notification(fd, NOTIFY_TYPE_INTERNAL);
+ file_data_send_notification(fd, NOTIFY_MARKS);
}
gboolean file_data_filter_marks(FileData *fd, guint filter)
{
+ gint i;
+ for (i = 0; i < FILEDATA_MARKS_SIZE; i++) if (filter & (1 << i)) file_data_get_mark(fd, i);
return ((fd->marks & filter) == filter);
}
return list;
}
+static void file_data_notify_mark_func(gpointer key, gpointer value, gpointer user_data)
+{
+ FileData *fd = value;
+ 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)
+ {
+ /* this effectively changes all known files */
+ g_hash_table_foreach(file_data_pool, file_data_notify_mark_func, NULL);
+ }
+
+ return TRUE;
+}
+
+void file_data_get_registered_mark_func(gint n, FileDataGetMarkFunc *get_mark_func, FileDataSetMarkFunc *set_mark_func, gpointer *data)
+{
+ if (get_mark_func) *get_mark_func = file_data_get_mark_func[n];
+ if (set_mark_func) *set_mark_func = file_data_set_mark_func[n];
+ if (data) *data = file_data_mark_func_data[n];
+}
+
gint file_data_get_user_orientation(FileData *fd)
{
return fd->user_orientation;
fd->user_orientation = value;
file_data_increment_version(fd);
- file_data_send_notification(fd, NOTIFY_TYPE_INTERNAL);
+ file_data_send_notification(fd, NOTIFY_ORIENTATION);
}
-
/*
* file_data - operates on the given fd
* file_data_sc - operates on the given fd + sidecars - all fds linked via fd->sidecar_files or fd->parent
}
-
-/*
- * add FileDataChangeInfo (see typedefs.h) for the given operation
+
+/*
+ * add FileDataChangeInfo (see typedefs.h) for the given operation
* uses file_data_add_change_info
*
* fails if the fd->change already exists - change operations can't run in parallel
* fd->change_info works as a lock
*
* dest can be NULL - in this case the current name is used for now, it will
- * be changed later
+ * be changed later
*/
/*
extension should remain (FIXME should we allow editing extension? it will make problems wth grouping)
sidecar names are changed too, extensions are not changed
DELETE
- UPDATE - file size, date or grouping has been changed
+ UPDATE - file size, date or grouping has been changed
*/
gboolean file_data_add_ci(FileData *fd, FileDataChangeType type, const gchar *src, const gchar *dest)
static void file_data_planned_change_remove(FileData *fd)
{
- if (file_data_planned_change_hash &&
+ if (file_data_planned_change_hash &&
(fd->change->type == FILEDATA_CHANGE_MOVE || fd->change->type == FILEDATA_CHANGE_RENAME))
{
if (g_hash_table_lookup(file_data_planned_change_hash, fd->change->dest) == fd)
}
}
}
-
+
void file_data_free_ci(FileData *fd)
{
FileDataChangeInfo *fdci = fd->change;
- if (!fdci)
- return;
+ if (!fdci) return;
file_data_planned_change_remove(fd);
+
+ if (fdci->regroup_when_finished) file_data_disable_grouping(fd, FALSE);
g_free(fdci->source);
g_free(fdci->dest);
fd->change = NULL;
}
-
+void file_data_set_regroup_when_finished(FileData *fd, gboolean enable)
+{
+ FileDataChangeInfo *fdci = fd->change;
+ if (!fdci) return;
+ fdci->regroup_when_finished = enable;
+}
+
static gboolean file_data_sc_add_ci(FileData *fd, FileDataChangeType type)
{
GList *work;
work = work->next;
}
- return TRUE;
+ return TRUE;
}
static gboolean file_data_sc_check_ci(FileData *fd, FileDataChangeType type)
return TRUE;
}
+gboolean file_data_add_ci_write_metadata(FileData *fd)
+{
+ return file_data_add_ci(fd, FILEDATA_CHANGE_WRITE_METADATA, NULL, NULL);
+}
+
void file_data_sc_free_ci(FileData *fd)
{
GList *work;
{
FileData *fd = work->data;
- if (!func(fd, dest))
+ if (!func(fd, dest))
{
file_data_sc_revert_ci_list(work->prev);
return FALSE;
return file_data_sc_add_ci_list_call_func(fd_list, dest, file_data_sc_add_ci_unspecified);
}
+gboolean file_data_add_ci_write_metadata_list(GList *fd_list)
+{
+ GList *work;
+ gboolean ret = TRUE;
+
+ work = fd_list;
+ while (work)
+ {
+ FileData *fd = work->data;
+
+ if (!file_data_add_ci_write_metadata(fd)) ret = FALSE;
+ work = work->next;
+ }
+
+ return ret;
+}
+
+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;
}
}
-/*
+/*
* update existing fd->change, it will be used from dialog callbacks for interactive editing
* fails if fd->change does not exist or the change type does not match
*/
static void file_data_update_ci_dest_preserve_ext(FileData *fd, const gchar *dest_path)
{
- const char *extension = extension_from_path(fd->change->source);
+ const gchar *extension = extension_from_path(fd->change->source);
gchar *base = remove_extension_from_path(dest_path);
gchar *old_path = fd->change->dest;
if (fd->parent) fd = fd->parent;
- if (!dest_path)
+ if (!dest_path)
{
dest_path = fd->path;
}
g_free(dest_path_full);
}
-static gint file_data_sc_check_update_ci(FileData *fd, const gchar *dest_path, FileDataChangeType type)
+static gboolean file_data_sc_check_update_ci(FileData *fd, const gchar *dest_path, FileDataChangeType type)
{
if (!file_data_sc_check_ci(fd, type)) return FALSE;
file_data_sc_update_ci(fd, dest_path);
return TRUE;
}
-gint file_data_sc_update_ci_copy(FileData *fd, const gchar *dest_path)
+gboolean file_data_sc_update_ci_copy(FileData *fd, const gchar *dest_path)
{
return file_data_sc_check_update_ci(fd, dest_path, FILEDATA_CHANGE_COPY);
}
-gint file_data_sc_update_ci_move(FileData *fd, const gchar *dest_path)
+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);
}
-gint file_data_sc_update_ci_rename(FileData *fd, const gchar *dest_path)
+gboolean file_data_sc_update_ci_rename(FileData *fd, const gchar *dest_path)
{
return file_data_sc_check_update_ci(fd, dest_path, FILEDATA_CHANGE_RENAME);
}
-gint file_data_sc_update_ci_unspecified(FileData *fd, const gchar *dest_path)
+gboolean file_data_sc_update_ci_unspecified(FileData *fd, const gchar *dest_path)
{
return file_data_sc_check_update_ci(fd, dest_path, FILEDATA_CHANGE_UNSPECIFIED);
}
if (!fd->change)
{
DEBUG_1("Change checked: no change info: %s", fd->path);
- return ret;
+ return ret;
}
if (!isname(fd->path))
/* this probably should not happen */
ret |= CHANGE_NO_SRC;
DEBUG_1("Change checked: file does not exist: %s", fd->path);
- return ret;
+ return ret;
}
dir = remove_level_from_path(fd->path);
- if (fd->change->type != FILEDATA_CHANGE_DELETE &&
+ 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 &&
+ fd->change->type != FILEDATA_CHANGE_WRITE_METADATA &&
+ fd->modified_xmp)
+ {
+ 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))
{
ret |= CHANGE_NO_READ_PERM;
DEBUG_1("Change checked: no read permission: %s", fd->path);
}
else if ((fd->change->type == FILEDATA_CHANGE_DELETE || fd->change->type == FILEDATA_CHANGE_MOVE) &&
- !access_file(dir, W_OK))
+ !access_file(dir, W_OK))
{
ret |= CHANGE_NO_WRITE_PERM_DIR;
DEBUG_1("Change checked: source dir is readonly: %s", fd->path);
}
- else if (fd->change->type != FILEDATA_CHANGE_COPY &&
- fd->change->type != FILEDATA_CHANGE_UNSPECIFIED &&
- !access_file(fd->path, W_OK))
+ else if (fd->change->type != FILEDATA_CHANGE_COPY &&
+ fd->change->type != FILEDATA_CHANGE_UNSPECIFIED &&
+ fd->change->type != FILEDATA_CHANGE_WRITE_METADATA &&
+ !access_file(fd->path, W_OK))
{
ret |= CHANGE_WARN_NO_WRITE_PERM;
DEBUG_1("Change checked: no write permission: %s", fd->path);
}
+ /* WRITE_METADATA is special because it can be configured to silently write to ~/.geeqie/...
+ - that means that there are no hard errors and warnings can be disabled
+ - the destination is determined during the check
+ */
+ else if (fd->change->type == FILEDATA_CHANGE_WRITE_METADATA)
+ {
+ /* 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))
+ {
+ /* we can write the file directly */
+ if (access_file(fd->path, W_OK))
+ {
+ have_dest = TRUE;
+ }
+ else
+ {
+ if (options->metadata.warn_on_write_problems)
+ {
+ ret |= CHANGE_WARN_NO_WRITE_PERM;
+ DEBUG_1("Change checked: file is not writable: %s", fd->path);
+ }
+ }
+ }
+ else if (file_data_can_write_sidecar(fd))
+ {
+ /* we can write sidecar */
+ gchar *sidecar = file_data_get_sidecar_path(fd, FALSE);
+ if (access_file(sidecar, W_OK) || (!isname(sidecar) && access_file(dir, W_OK)))
+ {
+ file_data_update_ci_dest(fd, sidecar);
+ have_dest = TRUE;
+ }
+ else
+ {
+ if (options->metadata.warn_on_write_problems)
+ {
+ ret |= CHANGE_WARN_NO_WRITE_PERM;
+ DEBUG_1("Change checked: file is not writable: %s", sidecar);
+ }
+ }
+ g_free(sidecar);
+ }
+ }
+
+ if (!have_dest)
+ {
+ /* write private metadata file under ~/.geeqie */
+
+ /* If an existing metadata file exists, we will try writing to
+ * it's location regardless of the user's preference.
+ */
+ gchar *metadata_path = NULL;
+#ifdef HAVE_EXIV2
+ /* but ignore XMP if we are not able to write it */
+ 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);
+ metadata_path = NULL;
+ }
+
+ if (!metadata_path)
+ {
+ mode_t mode = 0755;
- if (fd->change->dest)
+ dest_dir = cache_get_location(CACHE_TYPE_METADATA, fd->path, FALSE, &mode);
+ 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);
+ }
+ }
+ if (access_file(metadata_path, W_OK) || (!isname(metadata_path) && access_file(dest_dir, W_OK)))
+ {
+ file_data_update_ci_dest(fd, metadata_path);
+ have_dest = TRUE;
+ }
+ else
+ {
+ ret |= CHANGE_NO_WRITE_PERM_DEST;
+ DEBUG_1("Change checked: file is not writable: %s", metadata_path);
+ }
+ g_free(metadata_path);
+ }
+ g_free(dest_dir);
+ }
+
+ if (fd->change->dest && fd->change->type != FILEDATA_CHANGE_WRITE_METADATA)
{
gboolean same;
gchar *dest_dir;
const gchar *dest_ext = extension_from_path(fd->change->dest);
if (!dest_ext) dest_ext = "";
- if (strcasecmp(fd->extension, dest_ext) != 0)
+ 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);
dest_dir = remove_level_from_path(fd->change->dest);
- if (!isdir(dest_dir))
+ if (!isdir(dest_dir))
{
ret |= CHANGE_NO_DEST_DIR;
DEBUG_1("Change checked: destination dir does not exist: %s -> %s", fd->path, fd->change->dest);
}
else if (!access_file(dest_dir, W_OK))
{
- ret |= CHANGE_NO_WRITE_PERM_DEST_DIR;
+ ret |= CHANGE_WARN_NO_WRITE_PERM_DEST_DIR;
DEBUG_1("Change checked: destination dir is readonly: %s -> %s", fd->path, fd->change->dest);
}
else if (!same)
return ret;
}
-
+
gint file_data_sc_verify_ci(FileData *fd)
{
GList *work;
g_string_append(result, _("destination can't be overwritten"));
}
- if (error & CHANGE_NO_WRITE_PERM_DEST_DIR)
+ if (error & CHANGE_WARN_NO_WRITE_PERM_DEST_DIR)
{
if (result->len > 0) g_string_append(result, ", ");
g_string_append(result, _("destination directory is not writable"));
g_string_append(result, _("source and destination have different extension"));
}
+ if (error & CHANGE_WARN_UNSAVED_META)
+ {
+ if (result->len > 0) g_string_append(result, ", ");
+ g_string_append(result, _("there are unsaved metadata changes for the file"));
+ }
+
return g_string_free(result, FALSE);
}
-gint file_data_sc_verify_ci_list(GList *list, gchar **desc)
+gint file_data_verify_ci_list(GList *list, gchar **desc, gboolean with_sidecars)
{
GList *work;
gint all_errors = 0;
- gint common_errors = ~0;
+ gint common_errors = ~0;
gint num;
gint *errors;
gint i;
fd = work->data;
work = work->next;
- error = file_data_sc_verify_ci(fd);
+ error = with_sidecars ? file_data_sc_verify_ci(fd) : file_data_verify_ci(fd);
all_errors |= error;
common_errors &= error;
/*
* perform the change described by FileFataChangeInfo
- * it is used for internal operations,
+ * it is used for internal operations,
* this function actually operates with files on the filesystem
* it should implement safe delete
*/
-
+
static gboolean file_data_perform_move(FileData *fd)
{
g_assert(!strcmp(fd->change->source, fd->path));
if (isdir(fd->path) && !islink(fd->path))
return rmdir_utf8(fd->path);
else
- return unlink_file(fd->path);
+ if (options->file_ops.safe_delete_enable)
+ return file_util_safe_unlink(fd->path);
+ else
+ return unlink_file(fd->path);
}
-static gboolean file_data_perform_ci(FileData *fd)
+gboolean file_data_perform_ci(FileData *fd)
{
FileDataChangeType type = fd->change->type;
+
switch (type)
{
case FILEDATA_CHANGE_MOVE:
return file_data_perform_move(fd); /* the same as move */
case FILEDATA_CHANGE_DELETE:
return file_data_perform_delete(fd);
+ case FILEDATA_CHANGE_WRITE_METADATA:
+ return metadata_write_perform(fd);
case FILEDATA_CHANGE_UNSPECIFIED:
/* nothing to do here */
break;
* updates FileData structure according to FileDataChangeInfo
*/
-static void file_data_apply_ci(FileData *fd)
+gboolean file_data_apply_ci(FileData *fd)
{
FileDataChangeType type = fd->change->type;
-
+
/* FIXME delete ?*/
if (type == FILEDATA_CHANGE_MOVE || type == FILEDATA_CHANGE_RENAME)
{
}
}
file_data_increment_version(fd);
- file_data_send_notification(fd, NOTIFY_TYPE_CHANGE);
+ file_data_send_notification(fd, NOTIFY_CHANGE);
+
+ return TRUE;
}
-gint file_data_sc_apply_ci(FileData *fd)
+gboolean file_data_sc_apply_ci(FileData *fd)
{
GList *work;
FileDataChangeType type = fd->change->type;
return TRUE;
}
+static gboolean file_data_list_contains_whole_group(GList *list, FileData *fd)
+{
+ GList *work;
+ if (fd->parent) fd = fd->parent;
+ if (!g_list_find(list, fd)) return FALSE;
+
+ work = fd->sidecar_files;
+ while (work)
+ {
+ if (!g_list_find(list, work->data)) return FALSE;
+ work = work->next;
+ }
+ return TRUE;
+}
+
+GList *file_data_process_groups_in_selection(GList *list, gboolean ungroup, GList **ungrouped_list)
+{
+ GList *out = NULL;
+ GList *work = list;
+
+ /* change partial groups to independent files */
+ if (ungroup)
+ {
+ while (work)
+ {
+ FileData *fd = work->data;
+ work = work->next;
+
+ if (!file_data_list_contains_whole_group(list, fd))
+ {
+ file_data_disable_grouping(fd, TRUE);
+ if (ungrouped_list)
+ {
+ *ungrouped_list = g_list_prepend(*ungrouped_list, file_data_ref(fd));
+ }
+ }
+ }
+ }
+
+ /* 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);
+
+ return out;
+}
+
+
+
+
+
/*
- * notify other modules about the change described by FileFataChangeInfo
+ * notify other modules about the change described by FileDataChangeInfo
*/
-
+
/* might use file_maint_ functions for now, later it should be changed to a system of callbacks
FIXME do we need the ignore_list? It looks like a workaround for ineffective
implementation in view_file_list.c */
+typedef struct _NotifyIdleData NotifyIdleData;
+
+struct _NotifyIdleData {
+ FileData *fd;
+ NotifyType type;
+};
typedef struct _NotifyData NotifyData;
FileDataNotifyFunc func;
gpointer data;
NotifyPriority priority;
- };
+};
static GList *notify_func_list = NULL;
return 0;
}
-gint file_data_register_notify_func(FileDataNotifyFunc func, gpointer data, NotifyPriority priority)
+gboolean file_data_register_notify_func(FileDataNotifyFunc func, gpointer data, NotifyPriority priority)
{
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");
+ return FALSE;
+ }
+ work = work->next;
+ }
nd = g_new(NotifyData, 1);
nd->func = func;
nd->priority = priority;
notify_func_list = g_list_insert_sorted(notify_func_list, nd, file_data_notify_sort);
- DEBUG_1("Notify func registered: %p", nd);
+ DEBUG_2("Notify func registered: %p", nd);
return TRUE;
}
-gint file_data_unregister_notify_func(FileDataNotifyFunc func, gpointer data)
+gboolean file_data_unregister_notify_func(FileDataNotifyFunc func, gpointer data)
{
GList *work = notify_func_list;
{
notify_func_list = g_list_delete_link(notify_func_list, work);
g_free(nd);
- DEBUG_1("Notify func unregistered: %p", nd);
+ DEBUG_2("Notify func unregistered: %p", nd);
return TRUE;
}
work = work->next;
}
+ g_warning("Notify func not found");
return FALSE;
}
-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;
- DEBUG_1("Notify func calling: %p %s", nd, fd->path);
- 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;
-static gint realtime_monitor_id = -1;
+static guint realtime_monitor_id = 0; /* event source id */
static void realtime_monitor_check_cb(gpointer key, gpointer value, gpointer data)
{
return TRUE;
}
-gint file_data_register_real_time_monitor(FileData *fd)
+gboolean file_data_register_real_time_monitor(FileData *fd)
{
gint count;
count++;
g_hash_table_insert(file_data_monitor_pool, fd, GINT_TO_POINTER(count));
- if (realtime_monitor_id == -1)
+ if (!realtime_monitor_id)
{
realtime_monitor_id = g_timeout_add(5000, realtime_monitor_cb, NULL);
}
return TRUE;
}
-gint file_data_unregister_real_time_monitor(FileData *fd)
+gboolean file_data_unregister_real_time_monitor(FileData *fd)
{
gint count;
if (g_hash_table_size(file_data_monitor_pool) == 0)
{
g_source_remove(realtime_monitor_id);
- realtime_monitor_id = -1;
+ realtime_monitor_id = 0;
return FALSE;
}
return TRUE;
}
+/* vim: set shiftwidth=8 softtabstop=0 cindent cinoptions={1s: */