4 * Copyright (C) 2008 The Geeqie Team
6 * Author: John Ellis, Laurent Monin
8 * This software is released under the GNU General Public License (GNU GPL).
9 * Please read the included file COPYING for more information.
10 * This software comes with no warranty of any kind, use at your own risk!
21 #include "secure_save.h"
22 #include "ui_fileops.h"
25 #include "filefilter.h"
33 static const gchar *group_keys[] = {KEYWORD_KEY, COMMENT_KEY, NULL}; /* tags that will be written to all files in a group */
35 static gboolean metadata_write_queue_idle_cb(gpointer data);
36 static gint metadata_legacy_write(FileData *fd);
37 static void metadata_legacy_delete(FileData *fd, const gchar *except);
42 *-------------------------------------------------------------------
44 *-------------------------------------------------------------------
47 static GList *metadata_write_queue = NULL;
48 static gint metadata_write_idle_id = -1;
50 static void metadata_write_queue_add(FileData *fd)
52 if (!g_list_find(metadata_write_queue, fd))
54 metadata_write_queue = g_list_prepend(metadata_write_queue, fd);
58 if (metadata_write_idle_id != -1)
60 g_source_remove(metadata_write_idle_id);
61 metadata_write_idle_id = -1;
64 if (options->metadata.confirm_timeout > 0)
66 metadata_write_idle_id = g_timeout_add(options->metadata.confirm_timeout * 1000, metadata_write_queue_idle_cb, NULL);
71 gboolean metadata_write_queue_remove(FileData *fd)
73 g_hash_table_destroy(fd->modified_xmp);
74 fd->modified_xmp = NULL;
76 metadata_write_queue = g_list_remove(metadata_write_queue, fd);
78 file_data_increment_version(fd);
79 file_data_send_notification(fd, NOTIFY_TYPE_REREAD);
85 gboolean metadata_write_queue_remove_list(GList *list)
93 FileData *fd = work->data;
95 ret = ret && metadata_write_queue_remove(fd);
101 gboolean metadata_write_queue_confirm(FileUtilDoneFunc done_func, gpointer done_data)
104 GList *to_approve = NULL;
106 work = metadata_write_queue;
109 FileData *fd = work->data;
112 if (fd->change) continue; /* another operation in progress, skip this file for now */
114 to_approve = g_list_prepend(to_approve, file_data_ref(fd));
117 file_util_write_metadata(NULL, to_approve, NULL, done_func, done_data);
119 filelist_free(to_approve);
121 return (metadata_write_queue != NULL);
124 static gboolean metadata_write_queue_idle_cb(gpointer data)
126 metadata_write_queue_confirm(NULL, NULL);
127 metadata_write_idle_id = -1;
131 gboolean metadata_write_perform(FileData *fd)
136 g_assert(fd->change);
138 if (fd->change->dest &&
139 strcmp(extension_from_path(fd->change->dest), GQ_CACHE_EXT_METADATA) == 0)
141 success = metadata_legacy_write(fd);
142 if (success) metadata_legacy_delete(fd, fd->change->dest);
146 /* write via exiv2 */
147 /* we can either use cached metadata which have fd->modified_xmp already applied
148 or read metadata from file and apply fd->modified_xmp
149 metadata are read also if the file was modified meanwhile */
150 exif = exif_read_fd(fd);
151 if (!exif) return FALSE;
153 success = (fd->change->dest) ? exif_write_sidecar(exif, fd->change->dest) : exif_write(exif); /* write modified metadata */
154 exif_free_fd(fd, exif);
156 if (success) metadata_legacy_delete(fd, fd->change->dest);
160 static gboolean metadata_check_key(const gchar *keys[], const gchar *key)
162 const gchar **k = keys;
166 if (strcmp(key, *k) == 0) return TRUE;
172 gboolean metadata_write_list(FileData *fd, const gchar *key, const GList *values)
174 if (!fd->modified_xmp)
176 fd->modified_xmp = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, (GDestroyNotify)string_list_free);
178 g_hash_table_insert(fd->modified_xmp, g_strdup(key), string_list_copy((GList *)values));
181 exif_update_metadata(fd->exif, key, values);
183 metadata_write_queue_add(fd);
184 file_data_increment_version(fd);
185 file_data_send_notification(fd, NOTIFY_TYPE_INTERNAL);
187 if (options->metadata.sync_grouped_files && metadata_check_key(group_keys, key))
189 GList *work = fd->sidecar_files;
193 FileData *sfd = work->data;
196 if (filter_file_class(sfd->extension, FORMAT_CLASS_META)) continue;
198 metadata_write_list(sfd, key, values);
206 gboolean metadata_write_string(FileData *fd, const gchar *key, const char *value)
208 GList *list = g_list_append(NULL, g_strdup(value));
209 gboolean ret = metadata_write_list(fd, key, list);
210 string_list_free(list);
216 *-------------------------------------------------------------------
217 * keyword / comment read/write
218 *-------------------------------------------------------------------
221 static gint metadata_file_write(gchar *path, GHashTable *modified_xmp)
224 GList *keywords = g_hash_table_lookup(modified_xmp, KEYWORD_KEY);
225 GList *comment_l = g_hash_table_lookup(modified_xmp, COMMENT_KEY);
226 gchar *comment = comment_l ? comment_l->data : NULL;
228 ssi = secure_open(path);
229 if (!ssi) return FALSE;
231 secure_fprintf(ssi, "#%s comment (%s)\n\n", GQ_APPNAME, VERSION);
233 secure_fprintf(ssi, "[keywords]\n");
234 while (keywords && secsave_errno == SS_ERR_NONE)
236 const gchar *word = keywords->data;
237 keywords = keywords->next;
239 secure_fprintf(ssi, "%s\n", word);
241 secure_fputc(ssi, '\n');
243 secure_fprintf(ssi, "[comment]\n");
244 secure_fprintf(ssi, "%s\n", (comment) ? comment : "");
246 secure_fprintf(ssi, "#end\n");
248 return (secure_close(ssi) == 0);
251 static gint metadata_legacy_write(FileData *fd)
253 gint success = FALSE;
255 g_assert(fd->change && fd->change->dest);
256 gchar *metadata_pathl;
258 DEBUG_1("Saving comment: %s", fd->change->dest);
260 metadata_pathl = path_from_utf8(fd->change->dest);
262 success = metadata_file_write(metadata_pathl, fd->modified_xmp);
264 g_free(metadata_pathl);
269 static gint metadata_file_read(gchar *path, GList **keywords, gchar **comment)
273 MetadataKey key = MK_NONE;
275 GString *comment_build = NULL;
277 f = fopen(path, "r");
278 if (!f) return FALSE;
280 while (fgets(s_buf, sizeof(s_buf), f))
284 if (*ptr == '#') continue;
285 if (*ptr == '[' && key != MK_COMMENT)
287 gchar *keystr = ++ptr;
290 while (*ptr != ']' && *ptr != '\n' && *ptr != '\0') ptr++;
295 if (g_ascii_strcasecmp(keystr, "keywords") == 0)
297 else if (g_ascii_strcasecmp(keystr, "comment") == 0)
309 while (*ptr != '\n' && *ptr != '\0') ptr++;
311 if (strlen(s_buf) > 0)
313 gchar *kw = utf8_validate_or_convert(s_buf);
315 list = g_list_prepend(list, kw);
320 if (!comment_build) comment_build = g_string_new("");
321 g_string_append(comment_build, s_buf);
330 *keywords = g_list_reverse(list);
334 string_list_free(list);
342 gchar *ptr = comment_build->str;
344 /* strip leading and trailing newlines */
345 while (*ptr == '\n') ptr++;
347 while (len > 0 && ptr[len - 1] == '\n') len--;
348 if (ptr[len] == '\n') len++; /* keep the last one */
351 gchar *text = g_strndup(ptr, len);
353 *comment = utf8_validate_or_convert(text);
357 g_string_free(comment_build, TRUE);
363 static void metadata_legacy_delete(FileData *fd, const gchar *except)
365 gchar *metadata_path;
366 gchar *metadata_pathl;
369 metadata_path = cache_find_location(CACHE_TYPE_METADATA, fd->path);
370 if (metadata_path && (!except || strcmp(metadata_path, except) != 0))
372 metadata_pathl = path_from_utf8(metadata_path);
373 unlink(metadata_pathl);
374 g_free(metadata_pathl);
375 g_free(metadata_path);
377 metadata_path = cache_find_location(CACHE_TYPE_XMP_METADATA, fd->path);
378 if (metadata_path && (!except || strcmp(metadata_path, except) != 0))
380 metadata_pathl = path_from_utf8(metadata_path);
381 unlink(metadata_pathl);
382 g_free(metadata_pathl);
383 g_free(metadata_path);
387 static gint metadata_legacy_read(FileData *fd, GList **keywords, gchar **comment)
389 gchar *metadata_path;
390 gchar *metadata_pathl;
391 gint success = FALSE;
392 if (!fd) return FALSE;
394 metadata_path = cache_find_location(CACHE_TYPE_METADATA, fd->path);
395 if (!metadata_path) return FALSE;
397 metadata_pathl = path_from_utf8(metadata_path);
399 success = metadata_file_read(metadata_pathl, keywords, comment);
401 g_free(metadata_pathl);
402 g_free(metadata_path);
407 static GList *remove_duplicate_strings_from_list(GList *list)
410 GHashTable *hashtable = g_hash_table_new(g_str_hash, g_str_equal);
411 GList *newlist = NULL;
415 gchar *key = work->data;
417 if (g_hash_table_lookup(hashtable, key) == NULL)
419 g_hash_table_insert(hashtable, (gpointer) key, GINT_TO_POINTER(1));
420 newlist = g_list_prepend(newlist, key);
425 g_hash_table_destroy(hashtable);
428 return g_list_reverse(newlist);
431 GList *metadata_read_list(FileData *fd, const gchar *key)
435 if (!fd) return NULL;
437 /* unwritten data overide everything */
438 if (fd->modified_xmp)
440 list = g_hash_table_lookup(fd->modified_xmp, key);
441 if (list) return string_list_copy(list);
445 Legacy metadata file is the primary source if it exists.
446 Merging the lists does not make much sense, because the existence of
447 legacy metadata file indicates that the other metadata sources are not
448 writable and thus it would not be possible to delete the keywords
449 that comes from the image file.
451 if (strcmp(key, KEYWORD_KEY) == 0)
453 if (metadata_legacy_read(fd, &list, NULL)) return list;
456 if (strcmp(key, COMMENT_KEY) == 0)
458 gchar *comment = NULL;
459 if (metadata_legacy_read(fd, NULL, &comment)) return g_list_append(NULL, comment);
462 exif = exif_read_fd(fd); /* this is cached, thus inexpensive */
463 if (!exif) return NULL;
464 list = exif_get_metadata(exif, key);
465 exif_free_fd(fd, exif);
469 gchar *metadata_read_string(FileData *fd, const gchar *key)
471 GList *string_list = metadata_read_list(fd, key);
474 gchar *str = string_list->data;
475 string_list->data = NULL;
476 string_list_free(string_list);
482 gboolean metadata_append_string(FileData *fd, const gchar *key, const char *value)
484 gchar *str = metadata_read_string(fd, key);
488 return metadata_write_string(fd, key, value);
492 gchar *new_string = g_strconcat(str, value, NULL);
493 gboolean ret = metadata_write_string(fd, key, new_string);
500 gboolean metadata_append_list(FileData *fd, const gchar *key, const GList *values)
502 GList *list = metadata_read_list(fd, key);
506 return metadata_write_list(fd, key, values);
511 list = g_list_concat(list, string_list_copy(values));
512 list = remove_duplicate_strings_from_list(list);
514 ret = metadata_write_list(fd, key, list);
515 string_list_free(list);
520 gboolean find_string_in_list(GList *list, const gchar *string)
524 gchar *haystack = list->data;
526 if (haystack && string && strcmp(haystack, string) == 0) return TRUE;
534 #define KEYWORDS_SEPARATOR(c) ((c) == ',' || (c) == ';' || (c) == '\n' || (c) == '\r' || (c) == '\b')
536 GList *string_to_keywords_list(const gchar *text)
539 const gchar *ptr = text;
546 while (KEYWORDS_SEPARATOR(*ptr)) ptr++;
548 while (*ptr != '\0' && !KEYWORDS_SEPARATOR(*ptr))
554 /* trim starting and ending whitespaces */
555 while (l > 0 && g_ascii_isspace(*begin)) begin++, l--;
556 while (l > 0 && g_ascii_isspace(begin[l-1])) l--;
560 gchar *keyword = g_strndup(begin, l);
562 /* only add if not already in the list */
563 if (find_string_in_list(list, keyword) == FALSE)
564 list = g_list_append(list, keyword);
578 gboolean meta_data_get_keyword_mark(FileData *fd, gint n, gpointer data)
581 gboolean found = FALSE;
582 keywords = metadata_read_list(fd, KEYWORD_KEY);
585 GList *work = keywords;
589 gchar *kw = work->data;
592 if (strcmp(kw, data) == 0)
598 string_list_free(keywords);
603 gboolean meta_data_set_keyword_mark(FileData *fd, gint n, gboolean value, gpointer data)
605 GList *keywords = NULL;
606 gboolean found = FALSE;
607 gboolean changed = FALSE;
609 keywords = metadata_read_list(fd, KEYWORD_KEY);
615 gchar *kw = work->data;
617 if (strcmp(kw, data) == 0)
623 keywords = g_list_delete_link(keywords, work);
633 keywords = g_list_append(keywords, g_strdup(data));
636 if (changed) metadata_write_list(fd, KEYWORD_KEY, keywords);
638 string_list_free(keywords);
643 /* vim: set shiftwidth=8 softtabstop=0 cindent cinoptions={1s: */