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"
34 static const gchar *group_keys[] = {KEYWORD_KEY, COMMENT_KEY, NULL}; /* tags that will be written to all files in a group */
36 static gboolean metadata_write_queue_idle_cb(gpointer data);
37 static gint metadata_legacy_write(FileData *fd);
38 static void metadata_legacy_delete(FileData *fd, const gchar *except);
43 *-------------------------------------------------------------------
45 *-------------------------------------------------------------------
48 static GList *metadata_write_queue = NULL;
49 static gint metadata_write_idle_id = -1;
51 static void metadata_write_queue_add(FileData *fd)
53 if (!g_list_find(metadata_write_queue, fd))
55 metadata_write_queue = g_list_prepend(metadata_write_queue, fd);
58 layout_status_update_write_all();
61 if (metadata_write_idle_id != -1)
63 g_source_remove(metadata_write_idle_id);
64 metadata_write_idle_id = -1;
67 if (options->metadata.confirm_after_timeout)
69 metadata_write_idle_id = g_timeout_add(options->metadata.confirm_timeout * 1000, metadata_write_queue_idle_cb, NULL);
74 gboolean metadata_write_queue_remove(FileData *fd)
76 g_hash_table_destroy(fd->modified_xmp);
77 fd->modified_xmp = NULL;
79 metadata_write_queue = g_list_remove(metadata_write_queue, fd);
81 file_data_increment_version(fd);
82 file_data_send_notification(fd, NOTIFY_TYPE_REREAD);
86 layout_status_update_write_all();
90 gboolean metadata_write_queue_remove_list(GList *list)
98 FileData *fd = work->data;
100 ret = ret && metadata_write_queue_remove(fd);
106 gboolean metadata_write_queue_confirm(FileUtilDoneFunc done_func, gpointer done_data)
109 GList *to_approve = NULL;
111 work = metadata_write_queue;
114 FileData *fd = work->data;
117 if (fd->change) continue; /* another operation in progress, skip this file for now */
119 to_approve = g_list_prepend(to_approve, file_data_ref(fd));
122 file_util_write_metadata(NULL, to_approve, NULL, done_func, done_data);
124 filelist_free(to_approve);
126 return (metadata_write_queue != NULL);
129 static gboolean metadata_write_queue_idle_cb(gpointer data)
131 metadata_write_queue_confirm(NULL, NULL);
132 metadata_write_idle_id = -1;
136 gboolean metadata_write_perform(FileData *fd)
141 g_assert(fd->change);
143 if (fd->change->dest &&
144 strcmp(extension_from_path(fd->change->dest), GQ_CACHE_EXT_METADATA) == 0)
146 success = metadata_legacy_write(fd);
147 if (success) metadata_legacy_delete(fd, fd->change->dest);
151 /* write via exiv2 */
152 /* we can either use cached metadata which have fd->modified_xmp already applied
153 or read metadata from file and apply fd->modified_xmp
154 metadata are read also if the file was modified meanwhile */
155 exif = exif_read_fd(fd);
156 if (!exif) return FALSE;
158 success = (fd->change->dest) ? exif_write_sidecar(exif, fd->change->dest) : exif_write(exif); /* write modified metadata */
159 exif_free_fd(fd, exif);
161 if (success) metadata_legacy_delete(fd, fd->change->dest);
165 gint metadata_queue_length(void)
167 return g_list_length(metadata_write_queue);
170 static gboolean metadata_check_key(const gchar *keys[], const gchar *key)
172 const gchar **k = keys;
176 if (strcmp(key, *k) == 0) return TRUE;
182 gboolean metadata_write_list(FileData *fd, const gchar *key, const GList *values)
184 if (!fd->modified_xmp)
186 fd->modified_xmp = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, (GDestroyNotify)string_list_free);
188 g_hash_table_insert(fd->modified_xmp, g_strdup(key), string_list_copy((GList *)values));
191 exif_update_metadata(fd->exif, key, values);
193 metadata_write_queue_add(fd);
194 file_data_increment_version(fd);
195 file_data_send_notification(fd, NOTIFY_TYPE_INTERNAL);
197 if (options->metadata.sync_grouped_files && metadata_check_key(group_keys, key))
199 GList *work = fd->sidecar_files;
203 FileData *sfd = work->data;
206 if (filter_file_class(sfd->extension, FORMAT_CLASS_META)) continue;
208 metadata_write_list(sfd, key, values);
216 gboolean metadata_write_string(FileData *fd, const gchar *key, const char *value)
218 GList *list = g_list_append(NULL, g_strdup(value));
219 gboolean ret = metadata_write_list(fd, key, list);
220 string_list_free(list);
226 *-------------------------------------------------------------------
227 * keyword / comment read/write
228 *-------------------------------------------------------------------
231 static gint metadata_file_write(gchar *path, GHashTable *modified_xmp)
234 GList *keywords = g_hash_table_lookup(modified_xmp, KEYWORD_KEY);
235 GList *comment_l = g_hash_table_lookup(modified_xmp, COMMENT_KEY);
236 gchar *comment = comment_l ? comment_l->data : NULL;
238 ssi = secure_open(path);
239 if (!ssi) return FALSE;
241 secure_fprintf(ssi, "#%s comment (%s)\n\n", GQ_APPNAME, VERSION);
243 secure_fprintf(ssi, "[keywords]\n");
244 while (keywords && secsave_errno == SS_ERR_NONE)
246 const gchar *word = keywords->data;
247 keywords = keywords->next;
249 secure_fprintf(ssi, "%s\n", word);
251 secure_fputc(ssi, '\n');
253 secure_fprintf(ssi, "[comment]\n");
254 secure_fprintf(ssi, "%s\n", (comment) ? comment : "");
256 secure_fprintf(ssi, "#end\n");
258 return (secure_close(ssi) == 0);
261 static gint metadata_legacy_write(FileData *fd)
263 gint success = FALSE;
265 g_assert(fd->change && fd->change->dest);
266 gchar *metadata_pathl;
268 DEBUG_1("Saving comment: %s", fd->change->dest);
270 metadata_pathl = path_from_utf8(fd->change->dest);
272 success = metadata_file_write(metadata_pathl, fd->modified_xmp);
274 g_free(metadata_pathl);
279 static gint metadata_file_read(gchar *path, GList **keywords, gchar **comment)
283 MetadataKey key = MK_NONE;
285 GString *comment_build = NULL;
287 f = fopen(path, "r");
288 if (!f) return FALSE;
290 while (fgets(s_buf, sizeof(s_buf), f))
294 if (*ptr == '#') continue;
295 if (*ptr == '[' && key != MK_COMMENT)
297 gchar *keystr = ++ptr;
300 while (*ptr != ']' && *ptr != '\n' && *ptr != '\0') ptr++;
305 if (g_ascii_strcasecmp(keystr, "keywords") == 0)
307 else if (g_ascii_strcasecmp(keystr, "comment") == 0)
319 while (*ptr != '\n' && *ptr != '\0') ptr++;
321 if (strlen(s_buf) > 0)
323 gchar *kw = utf8_validate_or_convert(s_buf);
325 list = g_list_prepend(list, kw);
330 if (!comment_build) comment_build = g_string_new("");
331 g_string_append(comment_build, s_buf);
340 *keywords = g_list_reverse(list);
344 string_list_free(list);
352 gchar *ptr = comment_build->str;
354 /* strip leading and trailing newlines */
355 while (*ptr == '\n') ptr++;
357 while (len > 0 && ptr[len - 1] == '\n') len--;
358 if (ptr[len] == '\n') len++; /* keep the last one */
361 gchar *text = g_strndup(ptr, len);
363 *comment = utf8_validate_or_convert(text);
367 g_string_free(comment_build, TRUE);
373 static void metadata_legacy_delete(FileData *fd, const gchar *except)
375 gchar *metadata_path;
376 gchar *metadata_pathl;
379 metadata_path = cache_find_location(CACHE_TYPE_METADATA, fd->path);
380 if (metadata_path && (!except || strcmp(metadata_path, except) != 0))
382 metadata_pathl = path_from_utf8(metadata_path);
383 unlink(metadata_pathl);
384 g_free(metadata_pathl);
385 g_free(metadata_path);
387 metadata_path = cache_find_location(CACHE_TYPE_XMP_METADATA, fd->path);
388 if (metadata_path && (!except || strcmp(metadata_path, except) != 0))
390 metadata_pathl = path_from_utf8(metadata_path);
391 unlink(metadata_pathl);
392 g_free(metadata_pathl);
393 g_free(metadata_path);
397 static gint metadata_legacy_read(FileData *fd, GList **keywords, gchar **comment)
399 gchar *metadata_path;
400 gchar *metadata_pathl;
401 gint success = FALSE;
402 if (!fd) return FALSE;
404 metadata_path = cache_find_location(CACHE_TYPE_METADATA, fd->path);
405 if (!metadata_path) return FALSE;
407 metadata_pathl = path_from_utf8(metadata_path);
409 success = metadata_file_read(metadata_pathl, keywords, comment);
411 g_free(metadata_pathl);
412 g_free(metadata_path);
417 static GList *remove_duplicate_strings_from_list(GList *list)
420 GHashTable *hashtable = g_hash_table_new(g_str_hash, g_str_equal);
421 GList *newlist = NULL;
425 gchar *key = work->data;
427 if (g_hash_table_lookup(hashtable, key) == NULL)
429 g_hash_table_insert(hashtable, (gpointer) key, GINT_TO_POINTER(1));
430 newlist = g_list_prepend(newlist, key);
435 g_hash_table_destroy(hashtable);
438 return g_list_reverse(newlist);
441 GList *metadata_read_list(FileData *fd, const gchar *key)
445 if (!fd) return NULL;
447 /* unwritten data overide everything */
448 if (fd->modified_xmp)
450 list = g_hash_table_lookup(fd->modified_xmp, key);
451 if (list) return string_list_copy(list);
455 Legacy metadata file is the primary source if it exists.
456 Merging the lists does not make much sense, because the existence of
457 legacy metadata file indicates that the other metadata sources are not
458 writable and thus it would not be possible to delete the keywords
459 that comes from the image file.
461 if (strcmp(key, KEYWORD_KEY) == 0)
463 if (metadata_legacy_read(fd, &list, NULL)) return list;
466 if (strcmp(key, COMMENT_KEY) == 0)
468 gchar *comment = NULL;
469 if (metadata_legacy_read(fd, NULL, &comment)) return g_list_append(NULL, comment);
472 exif = exif_read_fd(fd); /* this is cached, thus inexpensive */
473 if (!exif) return NULL;
474 list = exif_get_metadata(exif, key);
475 exif_free_fd(fd, exif);
479 gchar *metadata_read_string(FileData *fd, const gchar *key)
481 GList *string_list = metadata_read_list(fd, key);
484 gchar *str = string_list->data;
485 string_list->data = NULL;
486 string_list_free(string_list);
492 gboolean metadata_append_string(FileData *fd, const gchar *key, const char *value)
494 gchar *str = metadata_read_string(fd, key);
498 return metadata_write_string(fd, key, value);
502 gchar *new_string = g_strconcat(str, value, NULL);
503 gboolean ret = metadata_write_string(fd, key, new_string);
510 gboolean metadata_append_list(FileData *fd, const gchar *key, const GList *values)
512 GList *list = metadata_read_list(fd, key);
516 return metadata_write_list(fd, key, values);
521 list = g_list_concat(list, string_list_copy(values));
522 list = remove_duplicate_strings_from_list(list);
524 ret = metadata_write_list(fd, key, list);
525 string_list_free(list);
530 gboolean find_string_in_list(GList *list, const gchar *string)
534 gchar *haystack = list->data;
536 if (haystack && string && strcmp(haystack, string) == 0) return TRUE;
544 #define KEYWORDS_SEPARATOR(c) ((c) == ',' || (c) == ';' || (c) == '\n' || (c) == '\r' || (c) == '\b')
546 GList *string_to_keywords_list(const gchar *text)
549 const gchar *ptr = text;
556 while (KEYWORDS_SEPARATOR(*ptr)) ptr++;
558 while (*ptr != '\0' && !KEYWORDS_SEPARATOR(*ptr))
564 /* trim starting and ending whitespaces */
565 while (l > 0 && g_ascii_isspace(*begin)) begin++, l--;
566 while (l > 0 && g_ascii_isspace(begin[l-1])) l--;
570 gchar *keyword = g_strndup(begin, l);
572 /* only add if not already in the list */
573 if (find_string_in_list(list, keyword) == FALSE)
574 list = g_list_append(list, keyword);
588 gboolean meta_data_get_keyword_mark(FileData *fd, gint n, gpointer data)
591 gboolean found = FALSE;
592 keywords = metadata_read_list(fd, KEYWORD_KEY);
595 GList *work = keywords;
599 gchar *kw = work->data;
602 if (strcmp(kw, data) == 0)
608 string_list_free(keywords);
613 gboolean meta_data_set_keyword_mark(FileData *fd, gint n, gboolean value, gpointer data)
615 GList *keywords = NULL;
616 gboolean found = FALSE;
617 gboolean changed = FALSE;
619 keywords = metadata_read_list(fd, KEYWORD_KEY);
625 gchar *kw = work->data;
627 if (strcmp(kw, data) == 0)
633 keywords = g_list_delete_link(keywords, work);
643 keywords = g_list_append(keywords, g_strdup(data));
646 if (changed) metadata_write_list(fd, KEYWORD_KEY, keywords);
648 string_list_free(keywords);
653 /* vim: set shiftwidth=8 softtabstop=0 cindent cinoptions={1s: */