X-Git-Url: http://geeqie.org/cgi-bin/gitweb.cgi?a=blobdiff_plain;f=src%2Ffiledata.c;h=6adafc2464588ad491c16031dacfcf1a8e72e36f;hb=1ea92516e2f91b7f99b1d41d44853f941d98e114;hp=fb461dc434b742085953bbe6ca58cf3e77ee4106;hpb=b3a6877012f4fbc9d6555f28a69067b0fd270ff6;p=geeqie.git diff --git a/src/filedata.c b/src/filedata.c index fb461dc4..6adafc24 100644 --- a/src/filedata.c +++ b/src/filedata.c @@ -1,7 +1,7 @@ /* * Geeqie * (C) 2006 John Ellis - * Copyright (C) 2008 The Geeqie Team + * Copyright (C) 2008 - 2012 The Geeqie Team * * Author: John Ellis * @@ -20,13 +20,22 @@ #include "ui_fileops.h" #include "metadata.h" #include "trash.h" +#include "histogram.h" +#include "exif.h" + +#include static GHashTable *file_data_pool = NULL; static GHashTable *file_data_planned_change_hash = NULL; -static gint sidecar_file_priority(const gchar *path); +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; /* *----------------------------------------------------------------------------- @@ -113,7 +122,7 @@ const gchar *text_from_time(time_t t) 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); @@ -130,37 +139,137 @@ const gchar *text_from_time(time_t t) /* *----------------------------------------------------------------------------- - * 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++; + fd->valid_marks = 0; + if (fd->parent) + { + fd->parent->version++; + fd->parent->valid_marks = 0; + } +} + +static gboolean file_data_check_changed_single_file(FileData *fd, struct stat *st) +{ + if (fd->size != st->st_size || + fd->date != st->st_mtime) + { + fd->size = st->st_size; + fd->date = st->st_mtime; + fd->mode = st->st_mode; + 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_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) + { + FileData *sfd = work->data; + struct stat st; + work = work->next; + + if (!stat_utf8(sfd->path, &st)) + { + 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; + } + + ret |= file_data_check_changed_files_recursive(sfd, &st); + } + return ret; } + +gboolean file_data_check_changed_files(FileData *fd) +{ + gboolean ret = FALSE; + struct stat st; + + if (fd->parent) fd = fd->parent; + + if (!stat_utf8(fd->path, &st)) + { + GList *sidecars; + GList *work; + FileData *sfd = NULL; + + /* parent is missing, we have to rebuild whole group */ + 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); + file_data_ref(fd); + work = sidecars; + while (work) + { + 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 */ + /* 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 + { + ret |= file_data_check_changed_files_recursive(fd, &st); + } + + return ret; +} + +/* + *----------------------------------------------------------------------------- + * file name, extension, sorting, ... + *----------------------------------------------------------------------------- + */ + static void file_data_set_collate_keys(FileData *fd) { gchar *caseless_name; + gchar *valid_name; - caseless_name = g_utf8_casefold(fd->name, -1); + 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); -#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 = 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); } @@ -215,94 +324,29 @@ static void file_data_set_path(FileData *fd, const gchar *path) return; } - fd->extension = extension_from_path(fd->path); + fd->extension = registered_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) -{ - gboolean ret = FALSE; - GList *work; - - if (fd->size != st->st_size || - fd->date != st->st_mtime) { - fd->size = st->st_size; - fd->date = st->st_mtime; - fd->mode = st->st_mode; - 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; + fd->extension = fd->name + strlen(fd->name); } - work = fd->sidecar_files; - while (work) - { - FileData *sfd = work->data; - struct stat st; - work = work->next; - - if (!stat_utf8(sfd->path, &st)) - { - fd->size = 0; - fd->date = 0; - file_data_disconnect_sidecar_file(fd, sfd); - ret = TRUE; - continue; - } - - ret |= file_data_check_changed_files_recursive(sfd, &st); - } - return ret; + fd->sidecar_priority = sidecar_file_priority(fd->extension); + file_data_set_collate_keys(fd); } +/* + *----------------------------------------------------------------------------- + * create or reuse Filedata + *----------------------------------------------------------------------------- + */ -gboolean file_data_check_changed_files(FileData *fd) -{ - gboolean ret = FALSE; - struct stat st; - - if (fd->parent) fd = fd->parent; - - if (!stat_utf8(fd->path, &st)) - { - GList *work; - FileData *sfd = NULL; - - /* parent is missing, we have to rebuild whole group */ - ret = TRUE; - fd->size = 0; - fd->date = 0; - - work = fd->sidecar_files; - while (work) - { - sfd = work->data; - work = work->next; - - 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); - } - else - { - ret |= file_data_check_changed_files_recursive(fd, &st); - } - - return ret; -} - -static FileData *file_data_new(const gchar *path_utf8, struct stat *st, gboolean check_sidecars) +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); @@ -312,7 +356,7 @@ static FileData *file_data_new(const gchar *path_utf8, struct stat *st, gboolean { file_data_ref(fd); } - + if (!fd && file_data_planned_change_hash) { fd = g_hash_table_lookup(file_data_planned_change_hash, path_utf8); @@ -323,116 +367,122 @@ static FileData *file_data_new(const gchar *path_utf8, struct stat *st, gboolean file_data_apply_ci(fd); } } - + if (fd) { 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; - file_data_set_path(fd, path_utf8); /* set path, name, collate_key_*, original_path */ + if (disable_sidecars) fd->disable_grouping = TRUE; - if (check_sidecars) - file_data_check_sidecars(fd); + file_data_set_path(fd, path_utf8); /* set path, name, collate_key_*, original_path */ 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) { - gint base_len; - GString *fname; - FileData *parent_fd = NULL; - GList *work; + gchar *path_utf8 = path_to_utf8(path); + FileData *ret = file_data_new(path_utf8, st, disable_sidecars); - if (fd->disable_grouping || !sidecar_file_priority(fd->extension)) - return; + g_free(path_utf8); + return ret; +} - base_len = fd->extension - fd->path; - fname = g_string_new_len(fd->path, base_len); - work = sidecar_ext_get_list(); +void init_exif_time_data(GList *files) +{ + FileData *file; + DEBUG_1("%s init_exif_time_data: ...", get_exec_time()); + while (files) + { + file = files->data; - while (work) + if (file) + file->exifdate = 0; + + files = files->next; + } +} + +void read_exif_time_data(FileData *file) +{ + if (file->exifdate > 0) { - /* 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) - */ + DEBUG_1("%s set_exif_time_data: Already exists for %s", get_exec_time(), file->path); + return; + } - FileData *new_fd; - gchar *ext = work->data; + file->exif = exif_read_fd(file); - work = work->next; + 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); - if (strcmp(ext, fd->extension) == 0) + if (tmp) { - new_fd = fd; /* processing the original file */ + 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); } - else - { - struct stat nst; - g_string_truncate(fname, base_len); - g_string_append(fname, ext); + } +} - if (!stat_utf8(fname->str, &nst)) - continue; +void set_exif_time_data(GList *files) +{ + DEBUG_1("%s set_exif_time_data: ...", get_exec_time()); - 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 */ - } + while (files) + { + FileData *file = files->data; - 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); + read_exif_time_data(file); + files = files->next; } - g_string_free(fname, TRUE); } - -static FileData *file_data_new_local(const gchar *path, struct stat *st, gboolean check_sidecars) +FileData *file_data_new_no_grouping(const gchar *path_utf8) { - gchar *path_utf8 = path_to_utf8(path); - FileData *ret = file_data_new(path_utf8, st, check_sidecars); + struct stat st; - g_free(path_utf8); - return ret; + if (!stat_utf8(path_utf8, &st)) + { + st.st_size = 0; + st.st_mtime = 0; + } + + return file_data_new(path_utf8, &st, TRUE); } -FileData *file_data_new_simple(const gchar *path_utf8) +FileData *file_data_new_dir(const gchar *path_utf8) { struct stat st; @@ -441,172 +491,402 @@ FileData *file_data_new_simple(const gchar *path_utf8) st.st_size = 0; st.st_mtime = 0; } + else + /* dir or non-existing yet */ + g_assert(S_ISDIR(st.st_mode)); return file_data_new(path_utf8, &st, TRUE); } -FileData *file_data_add_sidecar_file(FileData *target, FileData *sfd) +/* + *----------------------------------------------------------------------------- + * reference counting + *----------------------------------------------------------------------------- + */ + +#ifdef DEBUG_FILEDATA +FileData *file_data_ref_debug(const gchar *file, gint line, FileData *fd) +#else +FileData *file_data_ref(FileData *fd) +#endif { - 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; + if (fd == NULL) return NULL; + if (fd->magick != FD_MAGICK) +#ifdef DEBUG_FILEDATA + 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 == FD_MAGICK); + fd->ref++; + +#ifdef DEBUG_FILEDATA + DEBUG_2("file_data_ref fd=%p (%d): '%s' @ %s:%d", fd, fd->ref, fd->path, file, line); +#else + 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 == FD_MAGICK); + g_assert(fd->ref == 0); + g_assert(!fd->locked); + + metadata_cache_free(fd); + g_hash_table_remove(file_data_pool, fd->original_path); + + g_free(fd->path); + g_free(fd->original_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); + 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; +} -FileData *file_data_merge_sidecar_files(FileData *target, FileData *source) +/** + * \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; - - file_data_add_sidecar_file(target, source); + FileData *parent = fd->parent ? fd->parent : fd; - work = source->sidecar_files; + 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; - file_data_add_sidecar_file(target, sfd); + if (file_data_check_has_ref(sfd)) return; work = work->next; } - g_list_free(source->sidecar_files); - source->sidecar_files = NULL; + /* 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 : "-"); - target->sidecar_files = filelist_sort(target->sidecar_files, SORT_NAME, TRUE); - - return target; + 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 -FileData *file_data_ref_debug(const gchar *file, gint line, FileData *fd) +void file_data_unref_debug(const gchar *file, gint line, FileData *fd) #else -FileData *file_data_ref(FileData *fd) +void file_data_unref(FileData *fd) #endif { - if (fd == NULL) return NULL; + if (fd == NULL) return; + 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); - fd->ref++; + 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_unref fd=%p (%d:%d): '%s' @ %s:%d", fd, fd->ref, fd->locked, fd->path, + file, line); #else - DEBUG_2("file_data_ref (%d): '%s'", fd->ref, fd->path); + DEBUG_2("file_data_unref fd=%p (%d:%d): '%s'", fd, fd->ref, fd->locked, fd->path); #endif - return fd; + + // 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. + *

+ * 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); + + g_assert(fd->magick == FD_MAGICK); + fd->locked = TRUE; + + DEBUG_2("file_data_ref fd=%p (%d): '%s'", fd, fd->ref, fd->path); +} + +/** + * \brief Reset the maintain-FileData-in-memory lock + * + * This again allows the FileData to be freed when its refcount drops to zero. Automatically frees + * the FileData if its refcount is already zero (which will happen if the lock is the only thing + * keeping it from being freed. + */ +void file_data_unlock(FileData *fd) +{ + if (fd == NULL) return; + if (fd->magick != FD_MAGICK) DEBUG_0("fd magick mismatch fd=%p", fd); + + g_assert(fd->magick == FD_MAGICK); + fd->locked = FALSE; + + // Free FileData if it's no longer ref'd + file_data_consider_free(fd); } -static void file_data_free(FileData *fd) -{ - g_assert(fd->magick == 0x12345678); - g_assert(fd->ref == 0); +/** + * \brief Lock all of the FileDatas in the provided list + * + * \see file_data_lock(FileData) + */ +void file_data_lock_list(GList *list) +{ + GList *work; + + work = list; + while (work) + { + FileData *fd = work->data; + work = work->next; + file_data_lock(fd); + } +} + +/** + * \brief Unlock all of the FileDatas in the provided list + * + * \see file_data_unlock(FileData) + */ +void file_data_unlock_list(GList *list) +{ + GList *work; + + work = list; + while (work) + { + FileData *fd = work->data; + work = work->next; + file_data_unlock(fd); + } +} + +/* + *----------------------------------------------------------------------------- + * sidecar file info struct + *----------------------------------------------------------------------------- + */ + +static gint file_data_sort_by_ext(gconstpointer a, gconstpointer b) +{ + 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); + } - g_hash_table_remove(file_data_pool, fd->original_path); + parent_fd = basename_list->data; - g_free(fd->path); - g_free(fd->original_path); - g_free(fd->collate_key_name); - g_free(fd->collate_key_name_nocase); - if (fd->thumb_pixbuf) g_object_unref(fd->thumb_pixbuf); + /* 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; - g_assert(fd->sidecar_files == NULL); /* sidecar files must be freed before calling this */ + while (work && s_work) + { + if (work->data != s_work->data) break; + work = work->next; + s_work = s_work->next; + } - file_data_change_info_free(NULL, fd); - g_free(fd); -} + if (!work && !s_work) + { + DEBUG_2("basename no change"); + return; /* no change in grouping */ + } -#ifdef DEBUG_FILEDATA -void file_data_unref_debug(const gchar *file, gint line, FileData *fd) -#else -void file_data_unref(FileData *fd) -#endif -{ - if (fd == NULL) return; -#ifdef DEBUG_FILEDATA - if (fd->magick != 0x12345678) - DEBUG_0("fd magick mismatch @ %s:%d", file, line); -#endif - g_assert(fd->magick == 0x12345678); - - fd->ref--; -#ifdef DEBUG_FILEDATA - DEBUG_2("file_data_unref (%d): '%s' @ %s:%d", fd->ref, fd->path, file, line); + /* we have to regroup it */ -#else - DEBUG_2("file_data_unref (%d): '%s'", fd->ref, fd->path); -#endif - if (fd->ref == 0) + /* first, disconnect everything and send notification*/ + + work = basename_list; + while (work) { - GList *work; - FileData *parent = fd->parent ? fd->parent : fd; - - if (parent->ref > 0) - return; + FileData *fd = work->data; + work = work->next; + g_assert(fd->parent == NULL || fd->sidecar_files == NULL); - work = parent->sidecar_files; - while (work) + if (fd->parent) { - FileData *sfd = work->data; - if (sfd->ref > 0) - return; - work = work->next; + 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); } - /* none of parent/children is referenced, we can free everything */ - - DEBUG_2("file_data_unref: deleting '%s', parent '%s'", fd->path, fd->parent ? parent->path : "-"); - - work = parent->sidecar_files; - while (work) + while (fd->sidecar_files) { - FileData *sfd = work->data; - file_data_free(sfd); - work = work->next; + 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_list_free(parent->sidecar_files); - parent->sidecar_files = NULL; + g_assert(fd->parent == NULL && fd->sidecar_files == NULL); + } - file_data_free(parent); + /* 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); } -FileData *file_data_disconnect_sidecar_file(FileData *target, FileData *sfd) + +static void file_data_disconnect_sidecar_file(FileData *target, FileData *sfd) { - sfd->parent = target; + 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) { if (fd->parent) { 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) @@ -618,94 +898,50 @@ void file_data_disable_grouping(FileData *fd, gboolean disable) 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) +void file_data_disable_grouping_list(GList *fd_list, gboolean disable) { - 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) -{ - const gchar *extension = extension_from_path(path); - gint i = 1; GList *work; - if (extension == NULL) - return 0; - - work = sidecar_ext_get_list(); + work = fd_list; + while (work) + { + FileData *fd = work->data; - while (work) { - gchar *ext = 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; @@ -727,9 +963,15 @@ gint filelist_sort_compare_filedata(FileData *fa, FileData *fb) 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: @@ -737,12 +979,19 @@ gint filelist_sort_compare_filedata(FileData *fa, FileData *fb) } 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; @@ -754,30 +1003,81 @@ 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, gpointer 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) { @@ -787,7 +1087,7 @@ static GList *filelist_filter_out_sidecars(GList *flist) while (work) { FileData *fd = work->data; - + work = work->next; if (fd->parent) /* remove fd's that are children */ file_data_unref(fd); @@ -799,7 +1099,27 @@ 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; @@ -807,13 +1127,14 @@ static gint filelist_read_real(FileData *dir_fd, GList **files, GList **dirs, gi GList *dlist = NULL; GList *flist = NULL; 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); @@ -823,6 +1144,8 @@ static gint filelist_read_real(FileData *dir_fd, GList **files, GList **dirs, gi return FALSE; } + if (files) basename_hash = file_data_basename_hash_new(); + if (follow_symlinks) stat_func = stat; else @@ -834,7 +1157,7 @@ static gint filelist_read_real(FileData *dir_fd, GList **files, GList **dirs, gi 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); @@ -849,40 +1172,92 @@ static gint filelist_read_real(FileData *dir_fd, GList **files, GList **dirs, gi 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); } closedir(dp); - + 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) +gboolean filelist_read(FileData *dir_fd, GList **files, GList **dirs) { - return filelist_read_real(dir_fd, files, dirs, TRUE); + return filelist_read_real(dir_fd->path, files, dirs, TRUE); } -gint filelist_read_lstat(FileData *dir_fd, GList **files, GList **dirs) +gboolean filelist_read_lstat(FileData *dir_fd, GList **files, GList **dirs) { - return filelist_read_real(dir_fd, files, dirs, FALSE); + 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; } + void filelist_free(GList *list) { GList *work; @@ -930,7 +1305,7 @@ GList *filelist_from_path_list(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); @@ -955,7 +1330,7 @@ GList *filelist_to_path_list(GList *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; @@ -967,18 +1342,18 @@ GList *filelist_filter(GList *list, gint is_dir_list) 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)) ) { GList *link = work; - + list = g_list_remove_link(list, link); file_data_unref(fd); g_list_free(link); } - + work = work->next; } @@ -1023,51 +1398,146 @@ static void filelist_recursive_append(GList **list, GList *dirs) filelist_recursive_append(list, d); filelist_free(d); } - - work = work->next; + + work = work->next; + } +} + +GList *filelist_recursive(FileData *dir_fd) +{ + GList *list; + GList *d; + + if (!filelist_read(dir_fd, &list, &d)) return NULL; + list = filelist_filter(list, FALSE); + list = filelist_sort_path(list); + + d = filelist_filter(d, TRUE); + d = filelist_sort_path(d); + filelist_recursive_append(&list, d); + filelist_free(d); + + 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; + } } -} - -GList *filelist_recursive(FileData *dir_fd) -{ - GList *list; - GList *d; - if (!filelist_read(dir_fd, &list, &d)) return NULL; - list = filelist_filter(list, FALSE); - list = filelist_sort_path(list); - - d = filelist_filter(d, TRUE); - d = filelist_sort_path(d); - filelist_recursive_append(&list, d); - filelist_free(d); + 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 list; + 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); - + if (old && !fd->marks) /* keep files with non-zero marks in memory */ { file_data_unref(fd); @@ -1076,13 +1546,15 @@ void file_data_set_mark(FileData *fd, gint n, gboolean value) { file_data_ref(fd); } - + 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); } @@ -1108,6 +1580,40 @@ GList *file_data_filter_marks_list(GList *list, guint 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; @@ -1119,7 +1625,7 @@ void file_data_set_user_orientation(FileData *fd, gint value) fd->user_orientation = value; file_data_increment_version(fd); - file_data_send_notification(fd, NOTIFY_TYPE_INTERNAL); + file_data_send_notification(fd, NOTIFY_ORIENTATION); } @@ -1192,7 +1698,7 @@ gboolean file_data_add_ci(FileData *fd, FileDataChangeType type, const gchar *sr fdci->dest = g_strdup(dest); fd->change = fdci; - + return TRUE; } @@ -1221,11 +1727,12 @@ 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); @@ -1234,46 +1741,52 @@ void file_data_free_ci(FileData *fd) 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; 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) { @@ -1330,14 +1843,14 @@ void file_data_sc_free_ci(FileData *fd) 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; } @@ -1352,7 +1865,7 @@ gboolean file_data_sc_add_ci_delete_list(GList *fd_list) while (work) { FileData *fd = work->data; - + if (!file_data_sc_add_ci_delete(fd)) ret = FALSE; work = work->next; } @@ -1363,12 +1876,12 @@ gboolean file_data_sc_add_ci_delete_list(GList *fd_list) 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; } @@ -1377,12 +1890,12 @@ static void file_data_sc_revert_ci_list(GList *fd_list) 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); @@ -1390,7 +1903,7 @@ static gboolean file_data_sc_add_ci_list_call_func(GList *fd_list, const gchar * } work = work->next; } - + return TRUE; } @@ -1423,7 +1936,7 @@ gboolean file_data_add_ci_write_metadata_list(GList *fd_list) while (work) { FileData *fd = work->data; - + if (!file_data_add_ci_write_metadata(fd)) ret = FALSE; work = work->next; } @@ -1434,12 +1947,12 @@ gboolean file_data_add_ci_write_metadata_list(GList *fd_list) 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; } @@ -1448,12 +1961,12 @@ void file_data_free_ci_list(GList *fd_list) 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; } @@ -1467,14 +1980,14 @@ void file_data_sc_free_ci_list(GList *fd_list) 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); @@ -1491,7 +2004,7 @@ static void file_data_update_planned_change_hash(FileData *fd, const gchar *old_ 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); @@ -1513,10 +2026,10 @@ static void file_data_update_ci_dest_preserve_ext(FileData *fd, const gchar *des const gchar *extension = 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); file_data_update_planned_change_hash(fd, old_path, fd->change->dest); - + g_free(old_path); g_free(base); } @@ -1525,9 +2038,9 @@ static void file_data_sc_update_ci(FileData *fd, const gchar *dest_path) { GList *work; gchar *dest_path_full = NULL; - + if (fd->parent) fd = fd->parent; - + if (!dest_path) { dest_path = fd->path; @@ -1535,7 +2048,7 @@ static void file_data_sc_update_ci(FileData *fd, const gchar *dest_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; @@ -1545,44 +2058,44 @@ static void file_data_sc_update_ci(FileData *fd, const gchar *dest_path) 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); } -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); } @@ -1593,16 +2106,16 @@ static gboolean file_data_sc_update_ci_list_call_func(GList *fd_list, { 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; } @@ -1631,24 +2144,33 @@ gint file_data_verify_ci(FileData *fd) { gint ret = CHANGE_OK; gchar *dir; - + if (!fd->change) { DEBUG_1("Change checked: no change info: %s", fd->path); return ret; } - if (!isname(fd->path) && - !filter_file_class(fd->extension, FORMAT_CLASS_META)) /* xmp sidecar can be eventually created from scratch */ + 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; } - + 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 && + 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)) @@ -1672,28 +2194,106 @@ gint file_data_verify_ci(FileData *fd) } /* 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 && - options->metadata.save_in_image_file && options->metadata.warn_on_write_problems) + else if (fd->change->type == FILEDATA_CHANGE_WRITE_METADATA) { - if (isname(fd->path) && !access_file(fd->path, W_OK)) + /* determine destination file */ + gboolean have_dest = FALSE; + gchar *dest_dir = NULL; + + if (options->metadata.save_in_image_file) { - ret |= CHANGE_WARN_NO_WRITE_PERM; - DEBUG_1("Change checked: file is readonly: %s", fd->path); + 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); + } } - - else if (!access_file(dir, W_OK)) + + if (!have_dest) { - ret |= CHANGE_WARN_NO_WRITE_PERM; - DEBUG_1("Change checked: dir is readonly: %s", fd->path); + /* 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; + + 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) + + 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) @@ -1701,7 +2301,7 @@ gint file_data_verify_ci(FileData *fd) 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); @@ -1725,7 +2325,7 @@ gint file_data_verify_ci(FileData *fd) } 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) @@ -1752,7 +2352,7 @@ gint file_data_verify_ci(FileData *fd) g_free(dest_dir); } - + fd->change->error = ret; if (ret == 0) DEBUG_1("Change checked: OK: %s", fd->path); @@ -1802,7 +2402,7 @@ gchar *file_data_get_error_string(gint error) 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")); @@ -1837,7 +2437,7 @@ gchar *file_data_get_error_string(gint error) 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, ", "); @@ -1850,6 +2450,12 @@ gchar *file_data_get_error_string(gint error) 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); } @@ -1861,9 +2467,9 @@ gint file_data_verify_ci_list(GList *list, gchar **desc, gboolean with_sidecars) gint num; gint *errors; gint i; - + if (!list) return 0; - + num = g_list_length(list); errors = g_new(int, num); work = list; @@ -1875,21 +2481,21 @@ gint file_data_verify_ci_list(GList *list, gchar **desc, gboolean with_sidecars) fd = work->data; work = work->next; - + error = with_sidecars ? file_data_sc_verify_ci(fd) : file_data_verify_ci(fd); 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); @@ -1897,7 +2503,7 @@ gint file_data_verify_ci_list(GList *list, gchar **desc, gboolean with_sidecars) g_string_append(result, "\n"); g_free(str); } - + work = list; i = 0; while (work) @@ -1907,9 +2513,9 @@ gint file_data_verify_ci_list(GList *list, gchar **desc, gboolean with_sidecars) fd = work->data; work = work->next; - + error = errors[i] & ~common_errors; - + if (error) { gchar *str = file_data_get_error_string(error); @@ -1959,6 +2565,7 @@ static gboolean file_data_perform_delete(FileData *fd) gboolean file_data_perform_ci(FileData *fd) { FileDataChangeType type = fd->change->type; + switch (type) { case FILEDATA_CHANGE_MOVE: @@ -1985,20 +2592,20 @@ gboolean file_data_sc_perform_ci(FileData *fd) 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; } @@ -2006,7 +2613,7 @@ gboolean file_data_sc_perform_ci(FileData *fd) * updates FileData structure according to FileDataChangeInfo */ -gint file_data_apply_ci(FileData *fd) +gboolean file_data_apply_ci(FileData *fd) { FileDataChangeType type = fd->change->type; @@ -2015,7 +2622,7 @@ gint file_data_apply_ci(FileData *fd) { 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 @@ -2031,34 +2638,98 @@ gint file_data_apply_ci(FileData *fd) } } 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; - + 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; +} + +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 @@ -2066,6 +2737,12 @@ gint file_data_sc_apply_ci(FileData *fd) implementation in view_file_list.c */ +typedef struct _NotifyIdleData NotifyIdleData; + +struct _NotifyIdleData { + FileData *fd; + NotifyType type; +}; typedef struct _NotifyData NotifyData; @@ -2074,7 +2751,7 @@ struct _NotifyData { FileDataNotifyFunc func; gpointer data; NotifyPriority priority; - }; +}; static GList *notify_func_list = NULL; @@ -2088,66 +2765,91 @@ static gint file_data_notify_sort(gconstpointer a, gconstpointer b) 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->data = data; 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; - + 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); 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) { FileData *fd = key; file_data_check_changed_files(fd); - + DEBUG_1("monitor %s", fd->path); } @@ -2158,58 +2860,58 @@ static gboolean realtime_monitor_cb(gpointer data) return TRUE; } -gint file_data_register_real_time_monitor(FileData *fd) +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 == -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; 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 = -1; + realtime_monitor_id = 0; return FALSE; } - + return TRUE; } /* vim: set shiftwidth=8 softtabstop=0 cindent cinoptions={1s: */