Include a Other Software section in Help file
[geeqie.git] / src / collect-io.c
index 669dabf..464f997 100644 (file)
@@ -1,16 +1,24 @@
 /*
- * Geeqie
- * (C) 2004 John Ellis
- * Copyright (C) 2008 - 2009 The Geeqie Team
+ * Copyright (C) 2004 John Ellis
+ * Copyright (C) 2008 - 2016 The Geeqie Team
  *
  * Author: John Ellis
  *
- * This software is released under the GNU General Public License (GNU GPL).
- * Please read the included file COPYING for more information.
- * This software comes with no warranty of any kind, use at your own risk!
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
  */
 
-
 #include "main.h"
 #include "collect-io.h"
 
@@ -31,7 +39,7 @@
 typedef struct _CollectManagerEntry CollectManagerEntry;
 
 static void collection_load_thumb_step(CollectionData *cd);
-static gint collection_save_private(CollectionData *cd, const gchar *path);
+static gboolean collection_save_private(CollectionData *cd, const gchar *path);
 
 static CollectManagerEntry *collect_manager_get_entry(const gchar *path);
 static void collect_manager_entry_reset(CollectManagerEntry *entry);
@@ -70,6 +78,9 @@ static gboolean collection_load_private(CollectionData *cd, const gchar *path, C
        guint flush = !!(flags & COLLECTION_LOAD_FLUSH);
        guint append = !!(flags & COLLECTION_LOAD_APPEND);
        guint only_geometry = !!(flags & COLLECTION_LOAD_GEOMETRY);
+       gboolean reading_extended_filename = FALSE;
+       GString *extended_filename_buffer = g_string_new(NULL);
+       gchar *buffer2;
 
        if (!only_geometry)
                {
@@ -91,11 +102,12 @@ static gboolean collection_load_private(CollectionData *cd, const gchar *path, C
 
        if (!path) path = cd->path;
 
+       pathl = path_from_utf8(path);
+
        DEBUG_1("collection load: append=%d flush=%d only_geometry=%d path=%s",
-                         append, flush, only_geometry, path);
+                         append, flush, only_geometry, pathl);
 
        /* load it */
-       pathl = path_from_utf8(path);
        f = fopen(pathl, "r");
        g_free(pathl);
        if (!f)
@@ -109,59 +121,104 @@ static gboolean collection_load_private(CollectionData *cd, const gchar *path, C
                gchar *buf;
                gchar *p = s_buf;
 
-               /* Skip whitespaces and empty lines */
-               while (*p && g_ascii_isspace(*p)) p++;
-               if (*p == '\n' || *p == '\r') continue;
+               if (!reading_extended_filename)
+                       {
+                       /* Skip whitespaces and empty lines */
+                       while (*p && g_ascii_isspace(*p)) p++;
+                       if (*p == '\n' || *p == '\r') continue;
 
-               /* Parse comments */
-               if (*p == '#')
+                       /* Parse comments */
+                       if (*p == '#')
+                               {
+                               if (!need_header) continue;
+                               if (g_ascii_strncasecmp(p, GQ_COLLECTION_MARKER, strlen(GQ_COLLECTION_MARKER)) == 0)
+                                       {
+                                       /* Looks like an official collection, allow unchecked input.
+                                        * All this does is allow adding files that may not exist,
+                                        * which is needed for the collection manager to work.
+                                        * Also unofficial files abort after too many invalid entries.
+                                        */
+                                       has_official_header = TRUE;
+                                       limit_failures = FALSE;
+                                       }
+                               else if (strncmp(p, "#geometry:", 10 ) == 0 &&
+                                        scan_geometry(p + 10, &cd->window_x, &cd->window_y, &cd->window_w, &cd->window_h))
+                                       {
+                                       has_geometry_header = TRUE;
+                                       cd->window_read = TRUE;
+                                       if (only_geometry) break;
+                                       }
+                               else if (g_ascii_strncasecmp(p, "#GQview collection", strlen("#GQview collection")) == 0)
+                                       {
+                                       /* As 2008/04/15 there is no difference between our collection file format
+                                        * and GQview 2.1.5 collection file format so ignore failures as well. */
+                                       has_gqview_header = TRUE;
+                                       limit_failures = FALSE;
+                                       }
+                               need_header = (!has_official_header && !has_gqview_header) || !has_geometry_header;
+                               continue;
+                               }
+
+                       if (only_geometry) continue;
+                       }
+
+               /* Read filenames */
+               /** @todo This is not safe! */
+               /* Updated: anything within double quotes is considered a filename */
+               if (!reading_extended_filename)
                        {
-                       if (!need_header) continue;
-                       if (g_ascii_strncasecmp(p, GQ_COLLECTION_MARKER, strlen(GQ_COLLECTION_MARKER)) == 0)
+                       /* not yet reading filename */
+                       while (*p && *p != '"') p++;
+                       if (*p) p++;
+                       /* start of filename read */
+                       buf = p;
+                       while (*p && *p != '"') p++;
+                       if (p[0] != '"')
                                {
-                               /* Looks like an official collection, allow unchecked input.
-                                * All this does is allow adding files that may not exist,
-                                * which is needed for the collection manager to work.
-                                * Also unofficial files abort after too many invalid entries.
-                                */
-                               has_official_header = TRUE;
-                               limit_failures = FALSE;
+                               /* first part of extended filename */
+                               g_string_append(extended_filename_buffer, buf);
+                               reading_extended_filename = TRUE;
+                               continue;
                                }
-                       else if (strncmp(p, "#geometry:", 10 ) == 0 &&
-                                scan_geometry(p + 10, &cd->window_x, &cd->window_y, &cd->window_w, &cd->window_h))
+                       }
+               else
+                       {
+                       buf = p;
+                       while (*p && *p != '"') p++;
+                       if (p[0] != '"')
                                {
-                               has_geometry_header = TRUE;
-                               cd->window_read = TRUE;
-                               if (only_geometry) break;
+                               /* end of extended filename still not found */
+                               g_string_append(extended_filename_buffer, buf);
+                               continue;
                                }
-                       else if (g_ascii_strncasecmp(p, "#GQview collection", strlen("#GQview collection")) == 0)
+                       else
                                {
-                               /* As 2008/04/15 there is no difference between our collection file format
-                                * and GQview 2.1.5 collection file format so ignore failures as well. */
-                               has_gqview_header = TRUE;
-                               limit_failures = FALSE;
+                               /* end of extended filename found */
+                               g_string_append_len(extended_filename_buffer, buf, p - buf);
+                               reading_extended_filename = FALSE;
                                }
-                       need_header = (!has_official_header && !has_gqview_header) || !has_geometry_header;
-                       continue;
                        }
 
-               if (only_geometry) continue;
+               if (strlen(extended_filename_buffer->str) > 0)
+                       {
+                       buffer2 = g_strdup(extended_filename_buffer->str);
+                       g_string_erase(extended_filename_buffer, 0, -1);
+                       }
+               else
+                       {
+                       *p = 0;
+                       buffer2 = g_strdup(buf);
+                       }
 
-               /* Read filenames */
-               while (*p && *p != '"') p++;
-               if (*p) p++;
-               buf = p;
-               while (*p && *p != '"') p++;
-               *p = 0;
-               if (*buf)
+               if (*buffer2)
                        {
-                       gint valid;
+                       gboolean valid;
 
                        if (!flush)
-                               changed |= collect_manager_process_action(entry, &buf);
+                               changed |= collect_manager_process_action(entry, &buffer2);
 
-                       valid = (buf[0] == G_DIR_SEPARATOR && collection_add_check(cd, file_data_new_simple(buf), FALSE, TRUE));
-                       if (!valid) DEBUG_1("collection invalid file: %s", buf);
+                       valid = (buffer2[0] == G_DIR_SEPARATOR && collection_add_check(cd, file_data_new_simple(buffer2), FALSE, TRUE));
+                       if (!valid) DEBUG_1("collection invalid file: %s", buffer2);
 
                        total++;
                        if (!valid)
@@ -177,8 +234,11 @@ static gboolean collection_load_private(CollectionData *cd, const gchar *path, C
                                        }
                                }
                        }
+               g_free(buffer2);
                }
 
+       g_string_free(extended_filename_buffer, TRUE);
+
        DEBUG_1("collection files: total = %d fail = %d official=%d gqview=%d geometry=%d",
                          total, fail, has_official_header, has_gqview_header, has_geometry_header);
 
@@ -190,7 +250,7 @@ static gboolean collection_load_private(CollectionData *cd, const gchar *path, C
                gchar *buf = NULL;
                while (collect_manager_process_action(entry, &buf))
                        {
-                       collection_add_check(cd, file_data_new_simple(buf), FALSE, TRUE);
+                       collection_add_check(cd, file_data_new_group(buf), FALSE, TRUE);
                        changed = TRUE;
                        g_free(buf);
                        buf = NULL;
@@ -447,7 +507,7 @@ struct _CollectManagerAction
 static GList *collection_manager_entry_list = NULL;
 static GList *collection_manager_action_list = NULL;
 static GList *collection_manager_action_tail = NULL;
-static gint collection_manager_timer_id = -1;
+static guint collection_manager_timer_id = 0; /* event source id */
 
 
 static CollectManagerAction *collect_manager_action_new(const gchar *oldpath, const gchar *newpath,
@@ -497,8 +557,14 @@ static void collect_manager_entry_free_data(CollectManagerEntry *entry)
                collect_manager_action_unref(action);
                }
        g_list_free(entry->add_list);
-       g_hash_table_destroy(entry->oldpath_hash);
-       g_hash_table_destroy(entry->newpath_hash);
+       if (g_hash_table_size(entry->oldpath_hash) > 0)
+               g_hash_table_destroy(entry->oldpath_hash);
+       else
+               g_hash_table_unref(entry->oldpath_hash);
+       if (g_hash_table_size(entry->newpath_hash) > 0)
+               g_hash_table_destroy(entry->newpath_hash);
+       else
+               g_hash_table_unref(entry->newpath_hash);
 }
 
 static void collect_manager_entry_init_data(CollectManagerEntry *entry)
@@ -658,9 +724,7 @@ static gboolean collect_manager_process_action(CollectManagerEntry *entry, gchar
 
        if (action)
                {
-               g_free(path);
-               path = g_strdup(action->newpath);
-               *path_ptr = path;
+               strcpy(*path_ptr, action->newpath);
                return TRUE;
                }
 
@@ -673,7 +737,7 @@ static void collect_manager_refresh(void)
        GList *work;
        FileData *dir_fd;
 
-       dir_fd = file_data_new_simple(get_collections_dir());
+       dir_fd = file_data_new_dir(get_collections_dir());
        filelist_read(dir_fd, &list, NULL);
        file_data_unref(dir_fd);
 
@@ -704,6 +768,8 @@ static void collect_manager_refresh(void)
                        else
                                {
                                collect_manager_entry_free(entry);
+
+                               entry = NULL;
                                }
                        }
                }
@@ -725,7 +791,7 @@ static void collect_manager_refresh(void)
 static void collect_manager_process_actions(gint max)
 {
        if (collection_manager_action_list) DEBUG_1("collection manager processing actions");
-       
+
        while (collection_manager_action_list != NULL && max > 0)
                {
                CollectManagerAction *action;
@@ -784,12 +850,11 @@ static void collect_manager_process_actions(gint max)
 static gboolean collect_manager_process_entry(CollectManagerEntry *entry)
 {
        CollectionData *cd;
-       gboolean success;
 
        if (entry->empty) return FALSE;
 
        cd = collection_new(entry->path);
-       success = collection_load_private(cd, entry->path, COLLECTION_LOAD_NONE);
+       (void) collection_load_private(cd, entry->path, COLLECTION_LOAD_NONE);
 
        collection_unref(cd);
 
@@ -833,18 +898,18 @@ static gboolean collect_manager_timer_cb(gpointer data)
 
        g_idle_add_full(G_PRIORITY_LOW, collect_manager_process_cb, NULL, NULL);
 
-       collection_manager_timer_id = -1;
+       collection_manager_timer_id = 0;
        return FALSE;
 }
 
 static void collect_manager_timer_push(gint stop)
 {
-       if (collection_manager_timer_id != -1)
+       if (collection_manager_timer_id)
                {
                if (!stop) return;
 
                g_source_remove(collection_manager_timer_id);
-               collection_manager_timer_id = -1;
+               collection_manager_timer_id = 0;
                }
 
        if (!stop)
@@ -935,7 +1000,8 @@ void collect_manager_flush(void)
 void collect_manager_notify_cb(FileData *fd, NotifyType type, gpointer data)
 {
        if (!(type & NOTIFY_CHANGE) || !fd->change) return;
-       
+
+       DEBUG_1("Notify collect_manager: %s %04x", fd->path, type);
        switch (fd->change->type)
                {
                case FILEDATA_CHANGE_MOVE:
@@ -951,6 +1017,75 @@ void collect_manager_notify_cb(FileData *fd, NotifyType type, gpointer data)
                case FILEDATA_CHANGE_WRITE_METADATA:
                        break;
                }
+}
+
+static gint collection_manager_sort_cb(gconstpointer a, gconstpointer b)
+{
+       const gchar *char_a = a;
+       const gchar *char_b = b;
+
+       return g_strcmp0(char_a, char_b);
+}
+/**
+ * @brief Creates sorted list of collections
+ * @param[out] names_exc sorted list of collections names excluding extension
+ * @param[out] names_inc sorted list of collections names including extension
+ * @param[out] paths sorted list of collection paths
+ * 
+ * Lists of type gchar.
+ * Used lists must be freed with string_list_free()
+ */
+void collect_manager_list(GList **names_exc, GList **names_inc, GList **paths)
+{
+       FileData *dir_fd;
+       GList *list = NULL;
+       gchar *name;
+       FileData *fd;
+       gchar *filename;
+
+       if (names_exc == NULL && names_inc == NULL && paths == NULL)
+               {
+               return;
+               }
+
+       dir_fd = file_data_new_dir((get_collections_dir()));
 
+       filelist_read(dir_fd, &list, NULL);
+
+       while (list)
+               {
+               fd = list->data;
+               filename = g_strdup(filename_from_path((gchar *)fd->path));
+
+               if (file_extension_match(filename, GQ_COLLECTION_EXT))
+                       {
+                       name = remove_extension_from_path(filename);
+
+                       if (names_exc != NULL)
+                               {
+                               *names_exc = g_list_insert_sorted(*names_exc, g_strdup(name),
+                                                                                       collection_manager_sort_cb);
+                               *names_exc = g_list_first(*names_exc);
+                               }
+                       if (names_inc != NULL)
+                               {
+                               *names_inc = g_list_insert_sorted(*names_inc,filename,
+                                                                                       collection_manager_sort_cb);
+                               *names_inc = g_list_first(*names_inc);
+                               }
+                       if (paths != NULL)
+                               {
+                               *paths = g_list_insert_sorted(*paths,fd->path,
+                                                                                       collection_manager_sort_cb);
+                               *paths = g_list_first(*paths);
+                               }
+                       g_free(name);
+                       }
+               list = list->next;
+               g_free(filename);
+               }
+
+       filelist_free(list);
 }
 /* vim: set shiftwidth=8 softtabstop=0 cindent cinoptions={1s: */