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);
57 layout_status_update_write_all();
60 if (metadata_write_idle_id != -1)
62 g_source_remove(metadata_write_idle_id);
63 metadata_write_idle_id = -1;
66 if (options->metadata.confirm_timeout > 0)
68 metadata_write_idle_id = g_timeout_add(options->metadata.confirm_timeout * 1000, metadata_write_queue_idle_cb, NULL);
73 gboolean metadata_write_queue_remove(FileData *fd)
75 g_hash_table_destroy(fd->modified_xmp);
76 fd->modified_xmp = NULL;
78 metadata_write_queue = g_list_remove(metadata_write_queue, fd);
80 file_data_increment_version(fd);
81 file_data_send_notification(fd, NOTIFY_TYPE_REREAD);
85 layout_status_update_write_all();
89 gboolean metadata_write_queue_remove_list(GList *list)
97 FileData *fd = work->data;
99 ret = ret && metadata_write_queue_remove(fd);
105 gboolean metadata_write_queue_confirm(FileUtilDoneFunc done_func, gpointer done_data)
108 GList *to_approve = NULL;
110 work = metadata_write_queue;
113 FileData *fd = work->data;
116 if (fd->change) continue; /* another operation in progress, skip this file for now */
118 to_approve = g_list_prepend(to_approve, file_data_ref(fd));
121 file_util_write_metadata(NULL, to_approve, NULL, done_func, done_data);
123 filelist_free(to_approve);
125 return (metadata_write_queue != NULL);
128 static gboolean metadata_write_queue_idle_cb(gpointer data)
130 metadata_write_queue_confirm(NULL, NULL);
131 metadata_write_idle_id = -1;
135 gboolean metadata_write_perform(FileData *fd)
140 g_assert(fd->change);
142 if (fd->change->dest &&
143 strcmp(extension_from_path(fd->change->dest), GQ_CACHE_EXT_METADATA) == 0)
145 success = metadata_legacy_write(fd);
146 if (success) metadata_legacy_delete(fd, fd->change->dest);
150 /* write via exiv2 */
151 /* we can either use cached metadata which have fd->modified_xmp already applied
152 or read metadata from file and apply fd->modified_xmp
153 metadata are read also if the file was modified meanwhile */
154 exif = exif_read_fd(fd);
155 if (!exif) return FALSE;
157 success = (fd->change->dest) ? exif_write_sidecar(exif, fd->change->dest) : exif_write(exif); /* write modified metadata */
158 exif_free_fd(fd, exif);
160 if (success) metadata_legacy_delete(fd, fd->change->dest);
164 gint metadata_queue_length(void)
166 return g_list_length(metadata_write_queue);
169 static gboolean metadata_check_key(const gchar *keys[], const gchar *key)
171 const gchar **k = keys;
175 if (strcmp(key, *k) == 0) return TRUE;
181 gboolean metadata_write_list(FileData *fd, const gchar *key, const GList *values)
183 if (!fd->modified_xmp)
185 fd->modified_xmp = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, (GDestroyNotify)string_list_free);
187 g_hash_table_insert(fd->modified_xmp, g_strdup(key), string_list_copy((GList *)values));
190 exif_update_metadata(fd->exif, key, values);
192 metadata_write_queue_add(fd);
193 file_data_increment_version(fd);
194 file_data_send_notification(fd, NOTIFY_TYPE_INTERNAL);
196 if (options->metadata.sync_grouped_files && metadata_check_key(group_keys, key))
198 GList *work = fd->sidecar_files;
202 FileData *sfd = work->data;
205 if (filter_file_class(sfd->extension, FORMAT_CLASS_META)) continue;
207 metadata_write_list(sfd, key, values);
215 gboolean metadata_write_string(FileData *fd, const gchar *key, const char *value)
217 GList *list = g_list_append(NULL, g_strdup(value));
218 gboolean ret = metadata_write_list(fd, key, list);
219 string_list_free(list);
225 *-------------------------------------------------------------------
226 * keyword / comment read/write
227 *-------------------------------------------------------------------
230 static gint metadata_file_write(gchar *path, GHashTable *modified_xmp)
233 GList *keywords = g_hash_table_lookup(modified_xmp, KEYWORD_KEY);
234 GList *comment_l = g_hash_table_lookup(modified_xmp, COMMENT_KEY);
235 gchar *comment = comment_l ? comment_l->data : NULL;
237 ssi = secure_open(path);
238 if (!ssi) return FALSE;
240 secure_fprintf(ssi, "#%s comment (%s)\n\n", GQ_APPNAME, VERSION);
242 secure_fprintf(ssi, "[keywords]\n");
243 while (keywords && secsave_errno == SS_ERR_NONE)
245 const gchar *word = keywords->data;
246 keywords = keywords->next;
248 secure_fprintf(ssi, "%s\n", word);
250 secure_fputc(ssi, '\n');
252 secure_fprintf(ssi, "[comment]\n");
253 secure_fprintf(ssi, "%s\n", (comment) ? comment : "");
255 secure_fprintf(ssi, "#end\n");
257 return (secure_close(ssi) == 0);
260 static gint metadata_legacy_write(FileData *fd)
262 gint success = FALSE;
264 g_assert(fd->change && fd->change->dest);
265 gchar *metadata_pathl;
267 DEBUG_1("Saving comment: %s", fd->change->dest);
269 metadata_pathl = path_from_utf8(fd->change->dest);
271 success = metadata_file_write(metadata_pathl, fd->modified_xmp);
273 g_free(metadata_pathl);
278 static gint metadata_file_read(gchar *path, GList **keywords, gchar **comment)
282 MetadataKey key = MK_NONE;
284 GString *comment_build = NULL;
286 f = fopen(path, "r");
287 if (!f) return FALSE;
289 while (fgets(s_buf, sizeof(s_buf), f))
293 if (*ptr == '#') continue;
294 if (*ptr == '[' && key != MK_COMMENT)
296 gchar *keystr = ++ptr;
299 while (*ptr != ']' && *ptr != '\n' && *ptr != '\0') ptr++;
304 if (g_ascii_strcasecmp(keystr, "keywords") == 0)
306 else if (g_ascii_strcasecmp(keystr, "comment") == 0)
318 while (*ptr != '\n' && *ptr != '\0') ptr++;
320 if (strlen(s_buf) > 0)
322 gchar *kw = utf8_validate_or_convert(s_buf);
324 list = g_list_prepend(list, kw);
329 if (!comment_build) comment_build = g_string_new("");
330 g_string_append(comment_build, s_buf);
339 *keywords = g_list_reverse(list);
343 string_list_free(list);
351 gchar *ptr = comment_build->str;
353 /* strip leading and trailing newlines */
354 while (*ptr == '\n') ptr++;
356 while (len > 0 && ptr[len - 1] == '\n') len--;
357 if (ptr[len] == '\n') len++; /* keep the last one */
360 gchar *text = g_strndup(ptr, len);
362 *comment = utf8_validate_or_convert(text);
366 g_string_free(comment_build, TRUE);
372 static void metadata_legacy_delete(FileData *fd, const gchar *except)
374 gchar *metadata_path;
375 gchar *metadata_pathl;
378 metadata_path = cache_find_location(CACHE_TYPE_METADATA, fd->path);
379 if (metadata_path && (!except || strcmp(metadata_path, except) != 0))
381 metadata_pathl = path_from_utf8(metadata_path);
382 unlink(metadata_pathl);
383 g_free(metadata_pathl);
384 g_free(metadata_path);
386 metadata_path = cache_find_location(CACHE_TYPE_XMP_METADATA, fd->path);
387 if (metadata_path && (!except || strcmp(metadata_path, except) != 0))
389 metadata_pathl = path_from_utf8(metadata_path);
390 unlink(metadata_pathl);
391 g_free(metadata_pathl);
392 g_free(metadata_path);
396 static gint metadata_legacy_read(FileData *fd, GList **keywords, gchar **comment)
398 gchar *metadata_path;
399 gchar *metadata_pathl;
400 gint success = FALSE;
401 if (!fd) return FALSE;
403 metadata_path = cache_find_location(CACHE_TYPE_METADATA, fd->path);
404 if (!metadata_path) return FALSE;
406 metadata_pathl = path_from_utf8(metadata_path);
408 success = metadata_file_read(metadata_pathl, keywords, comment);
410 g_free(metadata_pathl);
411 g_free(metadata_path);
416 static GList *remove_duplicate_strings_from_list(GList *list)
419 GHashTable *hashtable = g_hash_table_new(g_str_hash, g_str_equal);
420 GList *newlist = NULL;
424 gchar *key = work->data;
426 if (g_hash_table_lookup(hashtable, key) == NULL)
428 g_hash_table_insert(hashtable, (gpointer) key, GINT_TO_POINTER(1));
429 newlist = g_list_prepend(newlist, key);
434 g_hash_table_destroy(hashtable);
437 return g_list_reverse(newlist);
440 GList *metadata_read_list(FileData *fd, const gchar *key)
444 if (!fd) return NULL;
446 /* unwritten data overide everything */
447 if (fd->modified_xmp)
449 list = g_hash_table_lookup(fd->modified_xmp, key);
450 if (list) return string_list_copy(list);
454 Legacy metadata file is the primary source if it exists.
455 Merging the lists does not make much sense, because the existence of
456 legacy metadata file indicates that the other metadata sources are not
457 writable and thus it would not be possible to delete the keywords
458 that comes from the image file.
460 if (strcmp(key, KEYWORD_KEY) == 0)
462 if (metadata_legacy_read(fd, &list, NULL)) return list;
465 if (strcmp(key, COMMENT_KEY) == 0)
467 gchar *comment = NULL;
468 if (metadata_legacy_read(fd, NULL, &comment)) return g_list_append(NULL, comment);
471 exif = exif_read_fd(fd); /* this is cached, thus inexpensive */
472 if (!exif) return NULL;
473 list = exif_get_metadata(exif, key);
474 exif_free_fd(fd, exif);
478 gchar *metadata_read_string(FileData *fd, const gchar *key)
480 GList *string_list = metadata_read_list(fd, key);
483 gchar *str = string_list->data;
484 string_list->data = NULL;
485 string_list_free(string_list);
491 gboolean metadata_append_string(FileData *fd, const gchar *key, const char *value)
493 gchar *str = metadata_read_string(fd, key);
497 return metadata_write_string(fd, key, value);
501 gchar *new_string = g_strconcat(str, value, NULL);
502 gboolean ret = metadata_write_string(fd, key, new_string);
509 gboolean metadata_append_list(FileData *fd, const gchar *key, const GList *values)
511 GList *list = metadata_read_list(fd, key);
515 return metadata_write_list(fd, key, values);
520 list = g_list_concat(list, string_list_copy(values));
521 list = remove_duplicate_strings_from_list(list);
523 ret = metadata_write_list(fd, key, list);
524 string_list_free(list);
529 gboolean find_string_in_list(GList *list, const gchar *string)
533 gchar *haystack = list->data;
535 if (haystack && string && strcmp(haystack, string) == 0) return TRUE;
543 #define KEYWORDS_SEPARATOR(c) ((c) == ',' || (c) == ';' || (c) == '\n' || (c) == '\r' || (c) == '\b')
545 GList *string_to_keywords_list(const gchar *text)
548 const gchar *ptr = text;
555 while (KEYWORDS_SEPARATOR(*ptr)) ptr++;
557 while (*ptr != '\0' && !KEYWORDS_SEPARATOR(*ptr))
563 /* trim starting and ending whitespaces */
564 while (l > 0 && g_ascii_isspace(*begin)) begin++, l--;
565 while (l > 0 && g_ascii_isspace(begin[l-1])) l--;
569 gchar *keyword = g_strndup(begin, l);
571 /* only add if not already in the list */
572 if (find_string_in_list(list, keyword) == FALSE)
573 list = g_list_append(list, keyword);
587 gboolean meta_data_get_keyword_mark(FileData *fd, gint n, gpointer data)
590 gboolean found = FALSE;
591 keywords = metadata_read_list(fd, KEYWORD_KEY);
594 GList *work = keywords;
598 gchar *kw = work->data;
601 if (strcmp(kw, data) == 0)
607 string_list_free(keywords);
612 gboolean meta_data_set_keyword_mark(FileData *fd, gint n, gboolean value, gpointer data)
614 GList *keywords = NULL;
615 gboolean found = FALSE;
616 gboolean changed = FALSE;
618 keywords = metadata_read_list(fd, KEYWORD_KEY);
624 gchar *kw = work->data;
626 if (strcmp(kw, data) == 0)
632 keywords = g_list_delete_link(keywords, work);
642 keywords = g_list_append(keywords, g_strdup(data));
645 if (changed) metadata_write_list(fd, KEYWORD_KEY, keywords);
647 string_list_free(keywords);
652 /* vim: set shiftwidth=8 softtabstop=0 cindent cinoptions={1s: */