Mercurial > geeqie.yaz
comparison src/bar_info.c @ 1178:f6449c17306b
Move comments/keywords read and write stuff to new metadata.{c,h}.
author | zas_ |
---|---|
date | Tue, 25 Nov 2008 17:32:51 +0000 |
parents | 0bea79d87065 |
children | bb02d0e2a573 |
comparison
equal
deleted
inserted
replaced
1177:5a20c47e7a14 | 1178:f6449c17306b |
---|---|
12 | 12 |
13 | 13 |
14 #include "main.h" | 14 #include "main.h" |
15 #include "bar_info.h" | 15 #include "bar_info.h" |
16 | 16 |
17 #include "cache.h" | |
18 #include "exif.h" | |
19 #include "filedata.h" | 17 #include "filedata.h" |
20 #include "history_list.h" | 18 #include "history_list.h" |
21 #include "info.h" | 19 #include "info.h" |
20 #include "metadata.h" | |
22 #include "misc.h" | 21 #include "misc.h" |
23 #include "secure_save.h" | |
24 #include "ui_fileops.h" | 22 #include "ui_fileops.h" |
25 #include "ui_misc.h" | 23 #include "ui_misc.h" |
26 #include "ui_utildlg.h" | 24 #include "ui_utildlg.h" |
27 #include "utilops.h" | 25 #include "utilops.h" |
28 | 26 |
41 }; | 39 }; |
42 | 40 |
43 | 41 |
44 static void bar_info_keyword_update_all(void); | 42 static void bar_info_keyword_update_all(void); |
45 | 43 |
46 | |
47 /* | 44 /* |
48 *------------------------------------------------------------------- | 45 *------------------------------------------------------------------- |
49 * keyword / comment utils | 46 * keyword / comment utils |
50 *------------------------------------------------------------------- | 47 *------------------------------------------------------------------- |
51 */ | 48 */ |
52 | 49 |
53 static gint comment_file_write(gchar *path, GList *keywords, const gchar *comment) | |
54 { | |
55 SecureSaveInfo *ssi; | |
56 | |
57 ssi = secure_open(path); | |
58 if (!ssi) return FALSE; | |
59 | |
60 secure_fprintf(ssi, "#%s comment (%s)\n\n", GQ_APPNAME, VERSION); | |
61 | |
62 secure_fprintf(ssi, "[keywords]\n"); | |
63 while (keywords && secsave_errno == SS_ERR_NONE) | |
64 { | |
65 const gchar *word = keywords->data; | |
66 keywords = keywords->next; | |
67 | |
68 secure_fprintf(ssi, "%s\n", word); | |
69 } | |
70 secure_fputc(ssi, '\n'); | |
71 | |
72 secure_fprintf(ssi, "[comment]\n"); | |
73 secure_fprintf(ssi, "%s\n", (comment) ? comment : ""); | |
74 | |
75 secure_fprintf(ssi, "#end\n"); | |
76 | |
77 return (secure_close(ssi) == 0); | |
78 } | |
79 | |
80 static gint comment_legacy_write(FileData *fd, GList *keywords, const gchar *comment) | |
81 { | |
82 gchar *comment_path; | |
83 gint success = FALSE; | |
84 | |
85 /* If an existing metadata file exists, we will try writing to | |
86 * it's location regardless of the user's preference. | |
87 */ | |
88 comment_path = cache_find_location(CACHE_TYPE_METADATA, fd->path); | |
89 if (comment_path && !access_file(comment_path, W_OK)) | |
90 { | |
91 g_free(comment_path); | |
92 comment_path = NULL; | |
93 } | |
94 | |
95 if (!comment_path) | |
96 { | |
97 gchar *comment_dir; | |
98 mode_t mode = 0755; | |
99 | |
100 comment_dir = cache_get_location(CACHE_TYPE_METADATA, fd->path, FALSE, &mode); | |
101 if (recursive_mkdir_if_not_exists(comment_dir, mode)) | |
102 { | |
103 gchar *filename = g_strconcat(fd->name, GQ_CACHE_EXT_METADATA, NULL); | |
104 | |
105 comment_path = g_build_filename(comment_dir, filename, NULL); | |
106 g_free(filename); | |
107 } | |
108 g_free(comment_dir); | |
109 } | |
110 | |
111 if (comment_path) | |
112 { | |
113 gchar *comment_pathl; | |
114 | |
115 DEBUG_1("Saving comment: %s", comment_path); | |
116 | |
117 comment_pathl = path_from_utf8(comment_path); | |
118 | |
119 success = comment_file_write(comment_pathl, keywords, comment); | |
120 | |
121 g_free(comment_pathl); | |
122 g_free(comment_path); | |
123 } | |
124 | |
125 return success; | |
126 } | |
127 | |
128 typedef enum { | |
129 MK_NONE, | |
130 MK_KEYWORDS, | |
131 MK_COMMENT | |
132 } MetadataKey; | |
133 | |
134 static gint comment_file_read(gchar *path, GList **keywords, gchar **comment) | |
135 { | |
136 FILE *f; | |
137 gchar s_buf[1024]; | |
138 MetadataKey key = MK_NONE; | |
139 GList *list = NULL; | |
140 GString *comment_build = NULL; | |
141 | |
142 f = fopen(path, "r"); | |
143 if (!f) return FALSE; | |
144 | |
145 while (fgets(s_buf, sizeof(s_buf), f)) | |
146 { | |
147 gchar *ptr = s_buf; | |
148 | |
149 if (*ptr == '#') continue; | |
150 if (*ptr == '[' && key != MK_COMMENT) | |
151 { | |
152 gchar *keystr = ++ptr; | |
153 | |
154 key = MK_NONE; | |
155 while (*ptr != ']' && *ptr != '\n' && *ptr != '\0') ptr++; | |
156 | |
157 if (*ptr == ']') | |
158 { | |
159 *ptr = '\0'; | |
160 if (g_ascii_strcasecmp(keystr, "keywords") == 0) | |
161 key = MK_KEYWORDS; | |
162 else if (g_ascii_strcasecmp(keystr, "comment") == 0) | |
163 key = MK_COMMENT; | |
164 } | |
165 continue; | |
166 } | |
167 | |
168 switch(key) | |
169 { | |
170 case MK_NONE: | |
171 break; | |
172 case MK_KEYWORDS: | |
173 { | |
174 while (*ptr != '\n' && *ptr != '\0') ptr++; | |
175 *ptr = '\0'; | |
176 if (strlen(s_buf) > 0) | |
177 { | |
178 gchar *kw = utf8_validate_or_convert(s_buf); | |
179 | |
180 list = g_list_prepend(list, kw); | |
181 } | |
182 } | |
183 break; | |
184 case MK_COMMENT: | |
185 if (!comment_build) comment_build = g_string_new(""); | |
186 g_string_append(comment_build, s_buf); | |
187 break; | |
188 } | |
189 } | |
190 | |
191 fclose(f); | |
192 | |
193 *keywords = g_list_reverse(list); | |
194 if (comment_build) | |
195 { | |
196 if (comment) | |
197 { | |
198 gint len; | |
199 gchar *ptr = comment_build->str; | |
200 | |
201 /* strip leading and trailing newlines */ | |
202 while (*ptr == '\n') ptr++; | |
203 len = strlen(ptr); | |
204 while (len > 0 && ptr[len - 1] == '\n') len--; | |
205 if (ptr[len] == '\n') len++; /* keep the last one */ | |
206 if (len > 0) | |
207 { | |
208 gchar *text = g_strndup(ptr, len); | |
209 | |
210 *comment = utf8_validate_or_convert(text); | |
211 g_free(text); | |
212 } | |
213 } | |
214 g_string_free(comment_build, TRUE); | |
215 } | |
216 | |
217 return TRUE; | |
218 } | |
219 | |
220 static gint comment_delete_legacy(FileData *fd) | |
221 { | |
222 gchar *comment_path; | |
223 gchar *comment_pathl; | |
224 gint success = FALSE; | |
225 if (!fd) return FALSE; | |
226 | |
227 comment_path = cache_find_location(CACHE_TYPE_METADATA, fd->path); | |
228 if (!comment_path) return FALSE; | |
229 | |
230 comment_pathl = path_from_utf8(comment_path); | |
231 | |
232 success = !unlink(comment_pathl); | |
233 | |
234 g_free(comment_pathl); | |
235 g_free(comment_path); | |
236 | |
237 return success; | |
238 } | |
239 | |
240 static gint comment_legacy_read(FileData *fd, GList **keywords, gchar **comment) | |
241 { | |
242 gchar *comment_path; | |
243 gchar *comment_pathl; | |
244 gint success = FALSE; | |
245 if (!fd) return FALSE; | |
246 | |
247 comment_path = cache_find_location(CACHE_TYPE_METADATA, fd->path); | |
248 if (!comment_path) return FALSE; | |
249 | |
250 comment_pathl = path_from_utf8(comment_path); | |
251 | |
252 success = comment_file_read(comment_pathl, keywords, comment); | |
253 | |
254 g_free(comment_pathl); | |
255 g_free(comment_path); | |
256 | |
257 return success; | |
258 } | |
259 | |
260 static GList *remove_duplicate_strings_from_list(GList *list) | |
261 { | |
262 GList *work = list; | |
263 GHashTable *hashtable = g_hash_table_new(g_str_hash, g_str_equal); | |
264 GList *newlist = NULL; | |
265 | |
266 while (work) | |
267 { | |
268 gchar *key = work->data; | |
269 | |
270 if (g_hash_table_lookup(hashtable, key) == NULL) | |
271 { | |
272 g_hash_table_insert(hashtable, (gpointer) key, GINT_TO_POINTER(1)); | |
273 newlist = g_list_prepend(newlist, key); | |
274 } | |
275 work = work->next; | |
276 } | |
277 | |
278 g_hash_table_destroy(hashtable); | |
279 g_list_free(list); | |
280 | |
281 return g_list_reverse(newlist); | |
282 } | |
283 | |
284 #define COMMENT_KEY "Xmp.dc.description" | |
285 #define KEYWORD_KEY "Xmp.dc.subject" | |
286 | |
287 static gint comment_xmp_read(FileData *fd, GList **keywords, gchar **comment) | |
288 { | |
289 ExifData *exif; | |
290 | |
291 exif = exif_read_fd(fd); | |
292 if (!exif) return FALSE; | |
293 | |
294 if (comment) | |
295 { | |
296 gchar *text; | |
297 ExifItem *item = exif_get_item(exif, COMMENT_KEY); | |
298 | |
299 text = exif_item_get_string(item, 0); | |
300 *comment = utf8_validate_or_convert(text); | |
301 g_free(text); | |
302 } | |
303 | |
304 if (keywords) | |
305 { | |
306 ExifItem *item; | |
307 guint i; | |
308 | |
309 *keywords = NULL; | |
310 item = exif_get_item(exif, KEYWORD_KEY); | |
311 for (i = 0; i < exif_item_get_elements(item); i++) | |
312 { | |
313 gchar *kw = exif_item_get_string(item, i); | |
314 gchar *utf8_kw; | |
315 | |
316 if (!kw) break; | |
317 | |
318 utf8_kw = utf8_validate_or_convert(kw); | |
319 *keywords = g_list_append(*keywords, (gpointer) utf8_kw); | |
320 g_free(kw); | |
321 } | |
322 | |
323 /* FIXME: | |
324 * Exiv2 handles Iptc keywords as multiple entries with the | |
325 * same key, thus exif_get_item returns only the first keyword | |
326 * and the only way to get all keywords is to iterate through | |
327 * the item list. | |
328 */ | |
329 for (item = exif_get_first_item(exif); | |
330 item; | |
331 item = exif_get_next_item(exif)) | |
332 { | |
333 guint tag; | |
334 | |
335 tag = exif_item_get_tag_id(item); | |
336 if (tag == 0x0019) | |
337 { | |
338 gchar *tag_name = exif_item_get_tag_name(item); | |
339 | |
340 if (strcmp(tag_name, "Iptc.Application2.Keywords") == 0) | |
341 { | |
342 gchar *kw; | |
343 gchar *utf8_kw; | |
344 | |
345 kw = exif_item_get_data_as_text(item); | |
346 if (!kw) continue; | |
347 | |
348 utf8_kw = utf8_validate_or_convert(kw); | |
349 *keywords = g_list_append(*keywords, (gpointer) utf8_kw); | |
350 g_free(kw); | |
351 } | |
352 g_free(tag_name); | |
353 } | |
354 } | |
355 } | |
356 | |
357 exif_free_fd(fd, exif); | |
358 | |
359 return (comment && *comment) || (keywords && *keywords); | |
360 } | |
361 | |
362 static gint comment_xmp_write(FileData *fd, GList *keywords, const gchar *comment) | |
363 { | |
364 gint success; | |
365 gint write_comment = (comment && comment[0]); | |
366 ExifData *exif; | |
367 ExifItem *item; | |
368 | |
369 exif = exif_read_fd(fd); | |
370 if (!exif) return FALSE; | |
371 | |
372 item = exif_get_item(exif, COMMENT_KEY); | |
373 if (item && !write_comment) | |
374 { | |
375 exif_item_delete(exif, item); | |
376 item = NULL; | |
377 } | |
378 | |
379 if (!item && write_comment) item = exif_add_item(exif, COMMENT_KEY); | |
380 if (item) exif_item_set_string(item, comment); | |
381 | |
382 while ((item = exif_get_item(exif, KEYWORD_KEY))) | |
383 { | |
384 exif_item_delete(exif, item); | |
385 } | |
386 | |
387 if (keywords) | |
388 { | |
389 GList *work; | |
390 | |
391 item = exif_add_item(exif, KEYWORD_KEY); | |
392 | |
393 work = keywords; | |
394 while (work) | |
395 { | |
396 exif_item_set_string(item, (gchar *) work->data); | |
397 work = work->next; | |
398 } | |
399 } | |
400 | |
401 success = exif_write(exif); | |
402 | |
403 exif_free_fd(fd, exif); | |
404 | |
405 return success; | |
406 } | |
407 | |
408 gint comment_write(FileData *fd, GList *keywords, const gchar *comment) | |
409 { | |
410 if (!fd) return FALSE; | |
411 | |
412 if (options->save_metadata_in_image_file && | |
413 comment_xmp_write(fd, keywords, comment)) | |
414 { | |
415 comment_delete_legacy(fd); | |
416 return TRUE; | |
417 } | |
418 | |
419 return comment_legacy_write(fd, keywords, comment); | |
420 } | |
421 | |
422 gint comment_read(FileData *fd, GList **keywords, gchar **comment) | |
423 { | |
424 GList *keywords1 = NULL; | |
425 GList *keywords2 = NULL; | |
426 gchar *comment1 = NULL; | |
427 gchar *comment2 = NULL; | |
428 gint res1, res2; | |
429 | |
430 if (!fd) return FALSE; | |
431 | |
432 res1 = comment_xmp_read(fd, &keywords1, &comment1); | |
433 res2 = comment_legacy_read(fd, &keywords2, &comment2); | |
434 | |
435 if (!res1 && !res2) | |
436 { | |
437 return FALSE; | |
438 } | |
439 | |
440 if (keywords) | |
441 { | |
442 if (res1 && res2) | |
443 *keywords = g_list_concat(keywords1, keywords2); | |
444 else | |
445 *keywords = res1 ? keywords1 : keywords2; | |
446 | |
447 *keywords = remove_duplicate_strings_from_list(*keywords); | |
448 } | |
449 else | |
450 { | |
451 if (res1) string_list_free(keywords1); | |
452 if (res2) string_list_free(keywords2); | |
453 } | |
454 | |
455 | |
456 if (comment) | |
457 { | |
458 if (res1 && res2 && comment1 && comment2 && comment1[0] && comment2[0]) | |
459 *comment = g_strdup_printf("%s\n%s", comment1, comment2); | |
460 else | |
461 *comment = res1 ? comment1 : comment2; | |
462 } | |
463 if (res1 && (!comment || *comment != comment1)) g_free(comment1); | |
464 if (res2 && (!comment || *comment != comment2)) g_free(comment2); | |
465 | |
466 // return FALSE in the following cases: | |
467 // - only looking for a comment and didn't find one | |
468 // - only looking for keywords and didn't find any | |
469 // - looking for either a comment or keywords, but found nothing | |
470 if ((!keywords && comment && !*comment) || | |
471 (!comment && keywords && !*keywords) || | |
472 ( comment && !*comment && keywords && !*keywords)) | |
473 return FALSE; | |
474 | |
475 return TRUE; | |
476 } | |
477 | |
478 | 50 |
479 static gchar *comment_pull(GtkWidget *textview) | 51 static gchar *comment_pull(GtkWidget *textview) |
480 { | 52 { |
481 GtkTextBuffer *buffer; | 53 GtkTextBuffer *buffer; |
482 GtkTextIter start, end; | 54 GtkTextIter start, end; |
485 gtk_text_buffer_get_bounds(buffer, &start, &end); | 57 gtk_text_buffer_get_bounds(buffer, &start, &end); |
486 | 58 |
487 return gtk_text_buffer_get_text(buffer, &start, &end, FALSE); | 59 return gtk_text_buffer_get_text(buffer, &start, &end, FALSE); |
488 } | 60 } |
489 | 61 |
490 static gint keyword_list_find(GList *list, const gchar *keyword) | |
491 { | |
492 while (list) | |
493 { | |
494 gchar *haystack = list->data; | |
495 | |
496 if (haystack && keyword && strcmp(haystack, keyword) == 0) return TRUE; | |
497 | |
498 list = list->next; | |
499 } | |
500 | |
501 return FALSE; | |
502 } | |
503 | |
504 GList *keyword_list_pull(GtkWidget *text_widget) | 62 GList *keyword_list_pull(GtkWidget *text_widget) |
505 { | 63 { |
506 GList *list = NULL; | 64 GList *list; |
507 gchar *text; | 65 gchar *text; |
508 gchar *ptr; | |
509 | 66 |
510 if (GTK_IS_TEXT_VIEW(text_widget)) | 67 if (GTK_IS_TEXT_VIEW(text_widget)) |
511 { | 68 { |
512 text = comment_pull(text_widget); | 69 text = comment_pull(text_widget); |
513 } | 70 } |
517 } | 74 } |
518 else | 75 else |
519 { | 76 { |
520 return NULL; | 77 return NULL; |
521 } | 78 } |
522 | 79 |
523 ptr = text; | 80 list = string_to_keywords_list(text); |
524 while (*ptr != '\0') | |
525 { | |
526 gchar *begin; | |
527 gint l = 0; | |
528 | |
529 #define KEYWORDS_SEPARATOR(c) ((c) == ',' || (c) == ';' || (c) == '\n' || (c) == '\r' || (c) == '\b') | |
530 while (KEYWORDS_SEPARATOR(*ptr)) ptr++; | |
531 begin = ptr; | |
532 while (*ptr != '\0' && !KEYWORDS_SEPARATOR(*ptr)) | |
533 { | |
534 ptr++; | |
535 l++; | |
536 } | |
537 | |
538 /* trim starting and ending whitespaces */ | |
539 while (l > 0 && g_ascii_isspace(*begin)) begin++, l--; | |
540 while (l > 0 && g_ascii_isspace(begin[l-1])) l--; | |
541 | |
542 if (l > 0) | |
543 { | |
544 gchar *keyword = g_strndup(begin, l); | |
545 | |
546 /* only add if not already in the list */ | |
547 if (keyword_list_find(list, keyword) == FALSE) | |
548 list = g_list_append(list, keyword); | |
549 else | |
550 g_free(keyword); | |
551 } | |
552 } | |
553 | 81 |
554 g_free(text); | 82 g_free(text); |
555 | 83 |
556 return list; | 84 return list; |
557 } | 85 } |
577 | 105 |
578 list = list->next; | 106 list = list->next; |
579 } | 107 } |
580 } | 108 } |
581 | 109 |
582 static void metadata_set_keywords(FileData *fd, GList *keywords_to_use, gchar *comment_to_use, gint add) | |
583 { | |
584 gchar *comment = NULL; | |
585 GList *keywords = NULL; | |
586 GList *save_list = NULL; | |
587 | |
588 comment_read(fd, &keywords, &comment); | |
589 | |
590 if (comment_to_use) | |
591 { | |
592 if (add && comment && *comment) | |
593 { | |
594 gchar *tmp = comment; | |
595 | |
596 comment = g_strconcat(tmp, comment_to_use, NULL); | |
597 g_free(tmp); | |
598 } | |
599 else | |
600 { | |
601 g_free(comment); | |
602 comment = g_strdup(comment_to_use); | |
603 } | |
604 } | |
605 | |
606 if (keywords_to_use) | |
607 { | |
608 if (add && keywords && g_list_length(keywords) > 0) | |
609 { | |
610 GList *work; | |
611 | |
612 work = keywords_to_use; | |
613 while (work) | |
614 { | |
615 gchar *key; | |
616 GList *p; | |
617 | |
618 key = work->data; | |
619 work = work->next; | |
620 | |
621 p = keywords; | |
622 while (p && key) | |
623 { | |
624 gchar *needle = p->data; | |
625 p = p->next; | |
626 | |
627 if (strcmp(needle, key) == 0) key = NULL; | |
628 } | |
629 | |
630 if (key) keywords = g_list_append(keywords, g_strdup(key)); | |
631 } | |
632 save_list = keywords; | |
633 } | |
634 else | |
635 { | |
636 save_list = keywords_to_use; | |
637 } | |
638 } | |
639 | |
640 comment_write(fd, save_list, comment); | |
641 | |
642 string_list_free(keywords); | |
643 g_free(comment); | |
644 } | |
645 | 110 |
646 /* | 111 /* |
647 *------------------------------------------------------------------- | 112 *------------------------------------------------------------------- |
648 * keyword list dialog | 113 * keyword list dialog |
649 *------------------------------------------------------------------- | 114 *------------------------------------------------------------------- |