Trim trailing white spaces.
[geeqie.git] / src / filedata.c
index b7f260d..105d016 100644 (file)
@@ -1,7 +1,7 @@
 /*
  * Geeqie
  * (C) 2006 John Ellis
- * Copyright (C) 2008 - 2010 The Geeqie Team
+ * Copyright (C) 2008 - 2012 The Geeqie Team
  *
  * Author: John Ellis
  *
@@ -22,6 +22,8 @@
 #include "trash.h"
 #include "histogram.h"
 
+#include "exif.h"
+
 #include <errno.h>
 
 static GHashTable *file_data_pool = NULL;
@@ -29,9 +31,11 @@ static GHashTable *file_data_planned_change_hash = NULL;
 
 static gint sidecar_file_priority(const gchar *extension);
 static void file_data_check_sidecars(const GList *basename_list);
-static FileData *file_data_disconnect_sidecar_file(FileData *target, FileData *sfd);
+static void file_data_disconnect_sidecar_file(FileData *target, FileData *sfd);
 
 
+static SortType filelist_sort_method = SORT_NONE;
+static gboolean filelist_sort_ascend = TRUE;
 
 /*
  *-----------------------------------------------------------------------------
@@ -135,7 +139,7 @@ const gchar *text_from_time(time_t t)
 
 /*
  *-----------------------------------------------------------------------------
- * changed files detection and notification 
+ * changed files detection and notification
  *-----------------------------------------------------------------------------
  */
 
@@ -143,7 +147,7 @@ void file_data_increment_version(FileData *fd)
 {
        fd->version++;
        fd->valid_marks = 0;
-       if (fd->parent) 
+       if (fd->parent)
                {
                fd->parent->version++;
                fd->parent->valid_marks = 0;
@@ -185,8 +189,12 @@ static gboolean file_data_check_changed_files_recursive(FileData *fd, struct sta
                        {
                        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;
                        }
 
@@ -217,6 +225,7 @@ gboolean file_data_check_changed_files(FileData *fd)
                /* 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)
                        {
@@ -228,7 +237,9 @@ gboolean file_data_check_changed_files(FileData *fd)
                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
                {
@@ -240,26 +251,25 @@ gboolean file_data_check_changed_files(FileData *fd)
 
 /*
  *-----------------------------------------------------------------------------
- * file name, extension, sorting, ... 
+ * 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 0 && 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);
 }
 
@@ -336,7 +346,7 @@ static FileData *file_data_new(const gchar *path_utf8, struct stat *st, gboolean
 
        DEBUG_2("file_data_new: '%s' %d", path_utf8, disable_sidecars);
 
-       if (S_ISDIR(st->st_mode)) disable_sidecars = TRUE; 
+       if (S_ISDIR(st->st_mode)) disable_sidecars = TRUE;
 
        if (!file_data_pool)
                file_data_pool = g_hash_table_new(g_str_hash, g_str_equal);
@@ -378,7 +388,7 @@ static FileData *file_data_new(const gchar *path_utf8, struct stat *st, gboolean
        fd->date = st->st_mtime;
        fd->mode = st->st_mode;
        fd->ref = 1;
-       fd->magick = 0x12345678;
+       fd->magick = FD_MAGICK;
        
        if (disable_sidecars) fd->disable_grouping = TRUE;
 
@@ -396,6 +406,69 @@ static FileData *file_data_new_local(const gchar *path, struct stat *st, gboolea
        return ret;
 }
 
+void init_exif_time_data(GList *files)
+{
+       FileData *file;
+       DEBUG_1("%s init_exif_time_data: ...", get_exec_time());
+       while (files)
+               {
+               file = files->data;
+
+               if (file)
+                       file->exifdate = 0;
+
+               files = files->next;
+               }
+}
+
+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 (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 (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);
+                       }
+               }
+}
+
+void set_exif_time_data(GList *files)
+{
+       DEBUG_1("%s set_exif_time_data: ...", get_exec_time());
+       
+       while (files)
+               {
+               FileData *file = files->data;
+               
+               read_exif_time_data(file);
+               files = files->next;
+               }
+}
+
 FileData *file_data_new_no_grouping(const gchar *path_utf8)
 {
        struct stat st;
@@ -438,24 +511,26 @@ FileData *file_data_ref(FileData *fd)
 #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);
@@ -481,17 +556,19 @@ void file_data_unref(FileData *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)
                {
@@ -567,86 +644,134 @@ static gint sidecar_file_priority(const gchar *extension)
        return 0;
 }
 
-static FileData *file_data_add_sidecar_file(FileData *target, FileData *sfd)
+static void file_data_check_sidecars(const GList *basename_list)
 {
-       sfd->parent = target;
-       if (!g_list_find(target->sidecar_files, sfd))
-               target->sidecar_files = g_list_insert_sorted(target->sidecar_files, sfd, file_data_sort_by_ext);
-       file_data_increment_version(sfd); /* increments both sfd and target */
-       return target;
-}
+       /* 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;
 
-static FileData *file_data_merge_sidecar_files(FileData *target, FileData *source)
-{
-       GList *work;
-       
-       file_data_add_sidecar_file(target, source);
 
-       work = source->sidecar_files;
+       DEBUG_2("basename start");
+       work = basename_list;
        while (work)
                {
-               FileData *sfd = work->data;
-               file_data_add_sidecar_file(target, sfd);
+               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_list_free(source->sidecar_files);
-       source->sidecar_files = NULL;
-
-       return target;
-}
-
-static void file_data_check_sidecars(const GList *basename_list)
-{
-       GList *work;
-       FileData *parent_fd;
-       if (!basename_list) return;
-       /* process the group list - the first one is the parent file, others are sidecars */
        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;
-       while (work)
+       s_work = parent_fd->sidecar_files;
+       
+       while (work && s_work)
                {
-               FileData *sfd = work->data;
+               if (work->data != s_work->data) break;
                work = work->next;
-
-               file_data_merge_sidecar_files(parent_fd, sfd);
+               s_work = s_work->next;
                }
                
-       /* there may be some sidecars that are already deleted - disconnect them */
-       work = parent_fd->sidecar_files;
+       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 *sfd = work->data;
+               FileData *fd = work->data;
                work = work->next;
+               g_assert(fd->parent == NULL || fd->sidecar_files == NULL);
                
-               if (!g_list_find((GList *)basename_list, sfd)) 
+               if (fd->parent)
                        {
-                       printf("removing unknown %s: %s \n", parent_fd->path, sfd->path);
-                       file_data_disconnect_sidecar_file(parent_fd, sfd);
+                       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_send_notification(parent_fd, 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 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 */
@@ -714,9 +839,6 @@ void file_data_disable_grouping_list(GList *fd_list, gboolean disable)
  *-----------------------------------------------------------------------------
  */
 
-static SortType filelist_sort_method = SORT_NONE;
-static gboolean filelist_sort_ascend = TRUE;
-
 
 gint filelist_sort_compare_filedata(FileData *fa, FileData *fb)
 {
@@ -742,6 +864,11 @@ 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:
                        ret = strverscmp(fa->name, fb->name);
@@ -759,8 +886,8 @@ gint filelist_sort_compare_filedata(FileData *fa, FileData *fb)
 
        if (ret != 0) return ret;
        
-       /* do not return 0 unless the files are really the same 
-          file_data_pool ensures that original_path is unique 
+       /* do not return 0 unless the files are really the same
+          file_data_pool ensures that original_path is unique
        */
        return strcmp(fa->original_path, fb->original_path);
 }
@@ -793,6 +920,10 @@ GList *filelist_insert_sort_full(GList *list, gpointer data, SortType method, gb
 
 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);
 }
 
@@ -825,38 +956,13 @@ static GList * file_data_basename_hash_insert(GHashTable *basename_hash, FileDat
                list = g_list_insert_sorted(list, file_data_ref(fd), file_data_sort_by_ext);
                g_hash_table_insert(basename_hash, basename, list);
                }
-       else 
+       else
                {
                g_free(basename);
                }
        return list;
 }
 
-#if 0
-static void file_data_basename_hash_remove(GHashTable *basename_hash, FileData *fd)
-{
-       GList *list;
-       gchar *basename = g_strndup(fd->path, fd->extension - fd->path);
-       
-       list = g_hash_table_lookup(basename_hash, basename);
-       
-       if (!g_list_find(list, fd)) return;
-       
-       list = g_list_remove(list, fd);
-       file_data_unref(fd);
-       
-       if (list)
-               {
-               g_hash_table_insert(basename_hash, basename, list);
-               }
-       else 
-               {
-               g_hash_table_remove(basename_hash, basename);
-               g_free(basename);
-               }
-}
-#endif
-
 static void file_data_basename_hash_remove_list(gpointer key, gpointer value, gpointer data)
 {
        filelist_free((GList *)value);
@@ -864,7 +970,7 @@ static void file_data_basename_hash_remove_list(gpointer key, gpointer value, gp
 
 static void file_data_basename_hash_free(GHashTable *basename_hash)
 {
-       g_hash_table_foreach(basename_hash, file_data_basename_hash_remove_list, NULL); 
+       g_hash_table_foreach(basename_hash, file_data_basename_hash_remove_list, NULL);
        g_hash_table_destroy(basename_hash);
 }
 
@@ -976,7 +1082,7 @@ static gboolean filelist_read_real(const gchar *dir_path, GList **files, GList *
                                        {
                                        FileData *fd = file_data_new_local(filepath, &ent_sbuf, FALSE);
                                        flist = g_list_prepend(flist, fd);
-                                       if (fd->sidecar_priority && !fd->disable_grouping) 
+                                       if (fd->sidecar_priority && !fd->disable_grouping)
                                                {
                                                file_data_basename_hash_insert(basename_hash, fd);
                                                }
@@ -998,14 +1104,18 @@ static gboolean filelist_read_real(const gchar *dir_path, GList **files, GList *
        g_free(pathl);
 
        if (dirs) *dirs = dlist;
-       if (files) 
+
+       if (files)
                {
-               g_hash_table_foreach(basename_hash, file_data_basename_hash_to_sidecars, NULL); 
+               g_hash_table_foreach(basename_hash, file_data_basename_hash_to_sidecars, NULL);
 
                *files = filelist_filter_out_sidecars(flist);
                }
        if (basename_hash) file_data_basename_hash_free(basename_hash);
 
+       // Call a separate function to initialize the exif datestamps for the found files..
+       if (files) init_exif_time_data(*files);
+
        return TRUE;
 }
 
@@ -1284,12 +1394,12 @@ gboolean file_data_get_mark(FileData *fd, gint n)
 {
        gboolean valid = (fd->valid_marks & (1 << n));
        
-       if (file_data_get_mark_func[n] && !valid) 
+       if (file_data_get_mark_func[n] && !valid)
                {
                guint old = fd->marks;
                gboolean value = (file_data_get_mark_func[n])(fd, n, file_data_mark_func_data[n]);
                
-               if (!value != !(fd->marks & (1 << n))) 
+               if (!value != !(fd->marks & (1 << n)))
                        {
                        fd->marks = fd->marks ^ (1 << n);
                        }
@@ -1320,7 +1430,7 @@ void file_data_set_mark(FileData *fd, gint n, gboolean value)
        guint old;
        if (!value == !file_data_get_mark(fd, n)) return;
        
-       if (file_data_set_mark_func[n]) 
+       if (file_data_set_mark_func[n])
                {
                (file_data_set_mark_func[n])(fd, n, value, file_data_mark_func_data[n]);
                }
@@ -1995,7 +2105,7 @@ gint file_data_verify_ci(FileData *fd)
                
                if (options->metadata.save_in_image_file)
                        {
-                       if (file_data_can_write_directly(fd)) 
+                       if (file_data_can_write_directly(fd))
                                {
                                /* we can write the file directly */
                                if (access_file(fd->path, W_OK))
@@ -2011,7 +2121,7 @@ gint file_data_verify_ci(FileData *fd)
                                                }
                                        }
                                }
-                       else if (file_data_can_write_sidecar(fd)) 
+                       else if (file_data_can_write_sidecar(fd))
                                {
                                /* we can write sidecar */
                                gchar *sidecar = file_data_get_sidecar_path(fd, FALSE);
@@ -2116,7 +2226,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)
@@ -2193,7 +2303,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"));
@@ -2470,29 +2580,6 @@ static gboolean file_data_list_contains_whole_group(GList *list, FileData *fd)
        return TRUE;
 }
 
-#if 0
-static gboolean file_data_list_dump(GList *list)
-{
-       GList *work, *work2;
-
-       work = list;
-       while (work)
-               {
-               FileData *fd = work->data;
-               printf("%s\n", fd->name);
-               work2 = fd->sidecar_files;
-               while (work2)
-                       {
-                       FileData *fd = work2->data;
-                       printf("       %s\n", fd->name);
-                       work2 = work2->next;
-                       }
-               work = work->next;
-               }
-       return TRUE;
-}
-#endif
-
 GList *file_data_process_groups_in_selection(GList *list, gboolean ungroup, GList **ungrouped_list)
 {
        GList *out = NULL;
@@ -2506,10 +2593,10 @@ GList *file_data_process_groups_in_selection(GList *list, gboolean ungroup, GLis
                        FileData *fd = work->data;
                        work = work->next;
                
-                       if (!file_data_list_contains_whole_group(list, fd)) 
+                       if (!file_data_list_contains_whole_group(list, fd))
                                {
                                file_data_disable_grouping(fd, TRUE);
-                               if (ungrouped_list) 
+                               if (ungrouped_list)
                                        {
                                        *ungrouped_list = g_list_prepend(*ungrouped_list, file_data_ref(fd));
                                        }
@@ -2517,7 +2604,7 @@ GList *file_data_process_groups_in_selection(GList *list, gboolean ungroup, GLis
                        }
                }
        
-       /* remove sidecars from the list, 
+       /* remove sidecars from the list,
           they can be still acessed via main_fd->sidecar_files */
        work = list;
        while (work)