1 /* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
2 
3 /*
4  *  GThumb
5  *
6  *  Copyright (C) 2009 The Free Software Foundation, Inc.
7  *
8  *  This program is free software; you can redistribute it and/or modify
9  *  it under the terms of the GNU General Public License as published by
10  *  the Free Software Foundation; either version 2 of the License, or
11  *  (at your option) any later version.
12  *
13  *  This program is distributed in the hope that it will be useful,
14  *  but WITHOUT ANY WARRANTY; without even the implied warranty of
15  *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
16  *  GNU General Public License for more details.
17  *
18  *  You should have received a copy of the GNU General Public License
19  *  along with this program.  If not, see <http://www.gnu.org/licenses/>.
20  */
21 
22 #include <config.h>
23 #include <string.h>
24 #include <gthumb.h>
25 #include "gth-comment.h"
26 
27 
28 #define COMMENT_VERSION "3.0"
29 
30 
31 struct _GthCommentPrivate { /* All strings in utf8 format. */
32 	char       *caption;
33 	char       *note;
34 	char       *place;
35 	int         rating;
36 	GPtrArray  *categories;
37 	GDate      *date;
38 	GthTime    *time_of_day;
39 	gboolean    changed;
40 	gboolean    utf8;
41 };
42 
43 
44 static void gth_comment_gth_duplicable_interface_init (GthDuplicableInterface *iface);
45 static void gth_comment_dom_domizable_interface_init (DomDomizableInterface *iface);
46 
47 
G_DEFINE_TYPE_WITH_CODE(GthComment,gth_comment,G_TYPE_OBJECT,G_ADD_PRIVATE (GthComment)G_IMPLEMENT_INTERFACE (GTH_TYPE_DUPLICABLE,gth_comment_gth_duplicable_interface_init)G_IMPLEMENT_INTERFACE (DOM_TYPE_DOMIZABLE,gth_comment_dom_domizable_interface_init))48 G_DEFINE_TYPE_WITH_CODE (GthComment,
49 			 gth_comment,
50 			 G_TYPE_OBJECT,
51 			 G_ADD_PRIVATE (GthComment)
52 			 G_IMPLEMENT_INTERFACE (GTH_TYPE_DUPLICABLE,
53 						gth_comment_gth_duplicable_interface_init)
54 			 G_IMPLEMENT_INTERFACE (DOM_TYPE_DOMIZABLE,
55 						gth_comment_dom_domizable_interface_init))
56 
57 
58 static void
59 gth_comment_free_data (GthComment *self)
60 {
61 	if (self->priv->place != NULL) {
62 		g_free (self->priv->place);
63 		self->priv->place = NULL;
64 	}
65 
66 	if (self->priv->note != NULL) {
67 		g_free (self->priv->note);
68 		self->priv->note = NULL;
69 	}
70 
71 	if (self->priv->caption != NULL) {
72 		g_free (self->priv->caption);
73 		self->priv->caption = NULL;
74 	}
75 }
76 
77 
78 static void
gth_comment_finalize(GObject * obj)79 gth_comment_finalize (GObject *obj)
80 {
81 	GthComment *self = GTH_COMMENT (obj);
82 
83 	gth_comment_free_data (self);
84 	gth_comment_clear_categories (self);
85 	g_ptr_array_unref (self->priv->categories);
86 	g_date_free (self->priv->date);
87 	gth_time_free (self->priv->time_of_day);
88 
89 	G_OBJECT_CLASS (gth_comment_parent_class)->finalize (obj);
90 }
91 
92 
93 static void
gth_comment_class_init(GthCommentClass * klass)94 gth_comment_class_init (GthCommentClass *klass)
95 {
96 	G_OBJECT_CLASS (klass)->finalize = gth_comment_finalize;
97 }
98 
99 
100 static void
gth_comment_init(GthComment * self)101 gth_comment_init (GthComment *self)
102 {
103 	self->priv = gth_comment_get_instance_private (self);
104 	self->priv->caption = NULL;
105 	self->priv->note = NULL;
106 	self->priv->place = NULL;
107 	self->priv->rating = 0;
108 	self->priv->categories = g_ptr_array_new ();
109 	self->priv->date = g_date_new ();
110 	self->priv->time_of_day = gth_time_new ();
111 }
112 
113 
114 static GObject *
gth_comment_real_duplicate(GthDuplicable * base)115 gth_comment_real_duplicate (GthDuplicable *base)
116 {
117 	return (GObject *) gth_comment_dup ((GthComment*) base);
118 }
119 
120 
121 static void
gth_comment_gth_duplicable_interface_init(GthDuplicableInterface * iface)122 gth_comment_gth_duplicable_interface_init (GthDuplicableInterface *iface)
123 {
124 	iface->duplicate = gth_comment_real_duplicate;
125 }
126 
127 
128 static DomElement*
gth_comment_real_create_element(DomDomizable * base,DomDocument * doc)129 gth_comment_real_create_element (DomDomizable *base,
130 				 DomDocument  *doc)
131 {
132 	GthComment *self;
133 	DomElement *element;
134 	char       *value;
135 	GPtrArray  *categories;
136 	DomElement *categories_element;
137 	int         i;
138 
139 	g_return_val_if_fail (DOM_IS_DOCUMENT (doc), NULL);
140 
141 	self = GTH_COMMENT (base);
142 	element = dom_document_create_element (doc, "comment",
143 					       "version", COMMENT_VERSION,
144 					       NULL);
145 
146 	dom_element_append_child (element, dom_document_create_element_with_text (doc, self->priv->caption, "caption", NULL));
147 	dom_element_append_child (element, dom_document_create_element_with_text (doc, self->priv->note, "note", NULL));
148 	dom_element_append_child (element, dom_document_create_element_with_text (doc, self->priv->place, "place", NULL));
149 
150 	if (self->priv->rating > 0) {
151 		value = g_strdup_printf ("%d", self->priv->rating);
152 		dom_element_append_child (element, dom_document_create_element (doc, "rating", "value", value, NULL));
153 		g_free (value);
154 	}
155 
156 	value = gth_comment_get_time_as_exif_format (self);
157 	if (value != NULL) {
158 		dom_element_append_child (element, dom_document_create_element (doc,  "time", "value", value, NULL));
159 		g_free (value);
160 	}
161 
162 	categories = gth_comment_get_categories (self);
163 	categories_element = dom_document_create_element (doc, "categories", NULL);
164 	dom_element_append_child (element, categories_element);
165 	for (i = 0; i < categories->len; i++)
166 		dom_element_append_child (categories_element, dom_document_create_element (doc, "category", "value", g_ptr_array_index (categories, i), NULL));
167 
168 	return element;
169 }
170 
171 
172 static void
gth_comment_real_load_from_element(DomDomizable * base,DomElement * element)173 gth_comment_real_load_from_element (DomDomizable *base,
174 				    DomElement   *element)
175 {
176 	GthComment *self;
177 	DomElement *node;
178 
179 	g_return_if_fail (DOM_IS_ELEMENT (element));
180 
181 	self = GTH_COMMENT (base);
182 	gth_comment_reset (self);
183 
184 	if (g_strcmp0 (dom_element_get_attribute (element, "format"), "2.0") == 0) {
185 		for (node = element->first_child; node; node = node->next_sibling) {
186 			if (g_strcmp0 (node->tag_name, "Note") == 0)
187 				gth_comment_set_note (self, dom_element_get_inner_text (node));
188 			else if (g_strcmp0 (node->tag_name, "Place") == 0)
189 				gth_comment_set_place (self, dom_element_get_inner_text (node));
190 			else if (g_strcmp0 (node->tag_name, "Time") == 0)
191 				gth_comment_set_time_from_time_t (self, atol (dom_element_get_inner_text (node)));
192 			else if (g_strcmp0 (node->tag_name, "Keywords") == 0) {
193 				const char  *text;
194 
195 				text = dom_element_get_inner_text (node);
196 				if (text != NULL) {
197 					char **categories;
198 					int    i;
199 
200 					categories = g_strsplit (text, ",", -1);
201 					for (i = 0; categories[i] != NULL; i++)
202 						gth_comment_add_category (self, categories[i]);
203 					g_strfreev (categories);
204 				}
205 			}
206 		}
207 	}
208 	else if (g_strcmp0 (dom_element_get_attribute (element, "version"), "3.0") == 0) {
209 		for (node = element->first_child; node; node = node->next_sibling) {
210 			if (g_strcmp0 (node->tag_name, "caption") == 0)
211 				gth_comment_set_caption (self, dom_element_get_inner_text (node));
212 			else if (g_strcmp0 (node->tag_name, "note") == 0)
213 				gth_comment_set_note (self, dom_element_get_inner_text (node));
214 			else if (g_strcmp0 (node->tag_name, "place") == 0)
215 				gth_comment_set_place (self, dom_element_get_inner_text (node));
216 			else if (g_strcmp0 (node->tag_name, "time") == 0)
217 				gth_comment_set_time_from_exif_format (self, dom_element_get_attribute (node, "value"));
218 			else if (g_strcmp0 (node->tag_name, "rating") == 0) {
219 				int v;
220 
221 				sscanf (dom_element_get_attribute (node, "value"), "%d", &v);
222 				gth_comment_set_rating (self, v);
223 			}
224 			else if (g_strcmp0 (node->tag_name, "categories") == 0) {
225 				DomElement *child;
226 
227 				for (child = node->first_child; child != NULL; child = child->next_sibling)
228 					if (strcmp (child->tag_name, "category") == 0)
229 						gth_comment_add_category (self, dom_element_get_attribute (child, "value"));
230 			}
231 		}
232 	}
233 }
234 
235 
236 static void
gth_comment_dom_domizable_interface_init(DomDomizableInterface * iface)237 gth_comment_dom_domizable_interface_init (DomDomizableInterface *iface)
238 {
239 	iface->create_element = gth_comment_real_create_element;
240 	iface->load_from_element = gth_comment_real_load_from_element;
241 }
242 
243 
244 GthComment *
gth_comment_new(void)245 gth_comment_new (void)
246 {
247 	return g_object_new (GTH_TYPE_COMMENT, NULL);
248 }
249 
250 
251 GFile *
gth_comment_get_comment_file(GFile * file)252 gth_comment_get_comment_file (GFile *file)
253 {
254 	GFile *parent;
255 	char  *basename;
256 	char  *comment_basename;
257 	GFile *comment_file;
258 
259 	parent = g_file_get_parent (file);
260 	if (parent == NULL)
261 		return NULL;
262 
263 	basename = g_file_get_basename (file);
264 	comment_basename = g_strconcat (basename, ".xml", NULL);
265 	comment_file = _g_file_get_child (parent, ".comments", comment_basename, NULL);
266 
267 	g_free (comment_basename);
268 	g_free (basename);
269 	g_object_unref (parent);
270 
271 	return comment_file;
272 }
273 
274 
275 GthComment *
gth_comment_new_for_file(GFile * file,GCancellable * cancellable,GError ** error)276 gth_comment_new_for_file (GFile         *file,
277 			  GCancellable  *cancellable,
278 			  GError       **error)
279 {
280 	GFile       *comment_file;
281 	GthComment  *comment;
282 	void        *zipped_buffer;
283 	gsize        zipped_size;
284 	void        *buffer;
285 	gsize        size;
286 	DomDocument *doc;
287 
288 	comment_file = gth_comment_get_comment_file (file);
289 	if (comment_file == NULL)
290 		return NULL;
291 
292 	if (! _g_file_load_in_buffer (comment_file, &zipped_buffer, &zipped_size, cancellable, error)) {
293 		g_object_unref (comment_file);
294 		return NULL;
295 	}
296 	g_object_unref (comment_file);
297 
298 	if ((zipped_buffer != NULL) && (((char *) zipped_buffer)[0] != '<')) {
299 		if (! zlib_decompress_buffer (zipped_buffer, zipped_size, &buffer, &size))
300 			return NULL;
301 	}
302 	else {
303 		buffer = zipped_buffer;
304 		size = zipped_size;
305 
306 		zipped_buffer = NULL;
307 	}
308 
309 	comment = gth_comment_new ();
310 	doc = dom_document_new ();
311 	if (dom_document_load (doc, buffer, size, error)) {
312 		dom_domizable_load_from_element (DOM_DOMIZABLE (comment), DOM_ELEMENT (doc)->first_child);
313 	}
314 	else {
315 		buffer = NULL;
316 		g_object_unref (comment);
317 		comment = NULL;
318 	}
319 
320 	g_object_unref (doc);
321 	g_free (buffer);
322 	g_free (zipped_buffer);
323 
324 	return comment;
325 }
326 
327 
328 char *
gth_comment_to_data(GthComment * comment,gsize * length)329 gth_comment_to_data (GthComment *comment,
330 		     gsize      *length)
331 {
332 	DomDocument *doc;
333 	char        *data;
334 
335 	doc = dom_document_new ();
336 	dom_element_append_child (DOM_ELEMENT (doc), dom_domizable_create_element (DOM_DOMIZABLE (comment), doc));
337 	data = dom_document_dump (doc, length);
338 
339 	g_object_unref (doc);
340 
341 	return data;
342 }
343 
344 
345 GthComment *
gth_comment_dup(GthComment * self)346 gth_comment_dup (GthComment *self)
347 {
348 	GthComment *comment;
349 	char       *time;
350 	int         i;
351 
352 	if (self == NULL)
353 		return NULL;
354 
355 	comment = gth_comment_new ();
356 	gth_comment_set_caption (comment, gth_comment_get_caption (self));
357 	gth_comment_set_note (comment, gth_comment_get_note (self));
358 	gth_comment_set_place (comment, gth_comment_get_place (self));
359 	gth_comment_set_rating (comment, gth_comment_get_rating (self));
360 	time = gth_comment_get_time_as_exif_format (self);
361 	gth_comment_set_time_from_exif_format (comment, time);
362 	for (i = 0; i < self->priv->categories->len; i++)
363 		gth_comment_add_category (comment, g_ptr_array_index (self->priv->categories, i));
364 
365 	g_free (time);
366 
367 	return comment;
368 }
369 
370 
371 void
gth_comment_reset(GthComment * self)372 gth_comment_reset (GthComment *self)
373 {
374 	gth_comment_free_data (self);
375 	gth_comment_clear_categories (self);
376 	gth_comment_reset_time (self);
377 }
378 
379 
380 void
gth_comment_set_caption(GthComment * comment,const char * value)381 gth_comment_set_caption (GthComment  *comment,
382 			 const char  *value)
383 {
384 	g_free (comment->priv->caption);
385 	comment->priv->caption = NULL;
386 
387 	if ((value != NULL) && (strcmp (value, "") != 0))
388 		comment->priv->caption = g_strdup (value);
389 }
390 
391 
392 void
gth_comment_set_note(GthComment * comment,const char * value)393 gth_comment_set_note (GthComment *comment,
394 		      const char *value)
395 {
396 	g_free (comment->priv->note);
397 	comment->priv->note = NULL;
398 
399 	if ((value != NULL) && (strcmp (value, "") != 0))
400 		comment->priv->note = g_strdup (value);
401 }
402 
403 
404 void
gth_comment_set_place(GthComment * comment,const char * value)405 gth_comment_set_place (GthComment *comment,
406 		       const char *value)
407 {
408 	g_free (comment->priv->place);
409 	comment->priv->place = NULL;
410 
411 	if ((value != NULL) && (strcmp (value, "") != 0))
412 		comment->priv->place = g_strdup (value);
413 }
414 
415 
416 void
gth_comment_set_rating(GthComment * comment,int value)417 gth_comment_set_rating (GthComment *comment,
418 		        int         value)
419 {
420 	comment->priv->rating = value;
421 }
422 
423 
424 void
gth_comment_clear_categories(GthComment * self)425 gth_comment_clear_categories (GthComment *self)
426 {
427 	g_ptr_array_foreach (self->priv->categories, (GFunc) g_free, NULL);
428 	g_ptr_array_unref (self->priv->categories);
429 	self->priv->categories = g_ptr_array_new ();
430 }
431 
432 
433 void
gth_comment_add_category(GthComment * comment,const char * value)434 gth_comment_add_category (GthComment *comment,
435 			  const char *value)
436 {
437 	g_return_if_fail (value != NULL);
438 
439 	g_ptr_array_add (comment->priv->categories, g_strdup (value));
440 }
441 
442 
443 void
gth_comment_reset_time(GthComment * self)444 gth_comment_reset_time (GthComment *self)
445 {
446 	g_date_clear (self->priv->date, 1);
447 	gth_time_clear (self->priv->time_of_day);
448 }
449 
450 
451 void
gth_comment_set_time_from_exif_format(GthComment * comment,const char * value)452 gth_comment_set_time_from_exif_format (GthComment *comment,
453 				       const char *value)
454 {
455 	unsigned int y, m, d, hh, mm, ss;
456 
457 	gth_comment_reset_time (comment);
458 
459 	if ((value == NULL) || (*value == '\0'))
460 		return;
461 
462 	if (sscanf (value, "%u:%u:%u %u:%u:%u", &y, &m, &d, &hh, &mm, &ss) != 6) {
463 		g_warning ("invalid time format: %s", value);
464 		return;
465 	}
466 
467 	if (g_date_valid_dmy (d, m, y)) {
468 		g_date_set_dmy (comment->priv->date, d, m, y);
469 		gth_time_set_hms (comment->priv->time_of_day, hh, mm, ss, 0);
470 	}
471 }
472 
473 
474 void
gth_comment_set_time_from_time_t(GthComment * comment,time_t value)475 gth_comment_set_time_from_time_t (GthComment *comment,
476 				  time_t      value)
477 {
478 	struct tm *tm;
479 
480 	if (value == 0)
481 		return;
482 
483 	tm = localtime (&value);
484 	g_date_set_dmy (comment->priv->date, tm->tm_mday, tm->tm_mon + 1, 1900 + tm->tm_year);
485 	gth_time_set_hms (comment->priv->time_of_day, tm->tm_hour, tm->tm_min, tm->tm_sec, 0);
486 }
487 
488 
489 const char *
gth_comment_get_caption(GthComment * comment)490 gth_comment_get_caption (GthComment *comment)
491 {
492 	return comment->priv->caption;
493 }
494 
495 
496 const char *
gth_comment_get_note(GthComment * comment)497 gth_comment_get_note (GthComment *comment)
498 {
499 	return comment->priv->note;
500 }
501 
502 
503 const char *
gth_comment_get_place(GthComment * comment)504 gth_comment_get_place (GthComment *comment)
505 {
506 	return comment->priv->place;
507 }
508 
509 
510 int
gth_comment_get_rating(GthComment * comment)511 gth_comment_get_rating (GthComment *comment)
512 {
513 	return comment->priv->rating;
514 }
515 
516 
517 GPtrArray *
gth_comment_get_categories(GthComment * comment)518 gth_comment_get_categories (GthComment *comment)
519 {
520 	return comment->priv->categories;
521 }
522 
523 
524 GDate *
gth_comment_get_date(GthComment * comment)525 gth_comment_get_date (GthComment *comment)
526 {
527 	return comment->priv->date;
528 }
529 
530 
531 GthTime *
gth_comment_get_time_of_day(GthComment * comment)532 gth_comment_get_time_of_day (GthComment *comment)
533 {
534 	return comment->priv->time_of_day;
535 }
536 
537 
538 char *
gth_comment_get_time_as_exif_format(GthComment * comment)539 gth_comment_get_time_as_exif_format (GthComment *comment)
540 {
541 	char *s;
542 
543 	if (! g_date_valid (comment->priv->date))
544 		return NULL;
545 
546 	s = g_strdup_printf ("%04u:%02u:%02u %02u:%02u:%02u",
547 			     g_date_get_year (comment->priv->date),
548 			     g_date_get_month (comment->priv->date),
549 			     g_date_get_day (comment->priv->date),
550 			     comment->priv->time_of_day->hour,
551 			     comment->priv->time_of_day->min,
552 			     comment->priv->time_of_day->sec);
553 
554 	return s;
555 }
556 
557 
558 void
gth_comment_update_general_attributes(GthFileData * file_data)559 gth_comment_update_general_attributes (GthFileData *file_data)
560 {
561 	const char *value;
562 
563 	value = g_file_info_get_attribute_string (file_data->info, "comment::note");
564 	if (value != NULL)
565 		set_attribute_from_string (file_data->info,
566 					   "general::description",
567 					   value,
568 					   NULL);
569 
570 	value = g_file_info_get_attribute_string (file_data->info, "comment::caption");
571 	if (value != NULL)
572 		set_attribute_from_string (file_data->info,
573 					   "general::title",
574 					   value,
575 					   NULL);
576 
577 	value = g_file_info_get_attribute_string (file_data->info, "comment::place");
578 	if (value != NULL)
579 		set_attribute_from_string (file_data->info,
580 					   "general::location",
581 					   value,
582 					   NULL);
583 
584 	if (g_file_info_has_attribute (file_data->info, "comment::rating")) {
585 		char *v;
586 
587 		v = g_strdup_printf ("%d", g_file_info_get_attribute_int32 (file_data->info, "comment::rating"));
588 		set_attribute_from_string (file_data->info, "general::rating", v, NULL);
589 
590 		g_free (v);
591 	}
592 
593 	if (g_file_info_has_attribute (file_data->info, "comment::categories"))
594 		g_file_info_set_attribute_object (file_data->info,
595 						  "general::tags",
596 						  g_file_info_get_attribute_object (file_data->info, "comment::categories"));
597 
598 	if (g_file_info_has_attribute (file_data->info, "comment::time"))
599 		g_file_info_set_attribute_object (file_data->info,
600 						  "general::datetime",
601 						  g_file_info_get_attribute_object (file_data->info, "comment::time"));
602 }
603 
604 
605 void
gth_comment_update_from_general_attributes(GthFileData * file_data)606 gth_comment_update_from_general_attributes (GthFileData *file_data)
607 {
608 	gboolean       write_comment;
609 	GthMetadata   *metadata;
610 	GthStringList *comment_categories;
611 	GList         *scan;
612 	const char    *text;
613 	GthComment    *comment;
614 	GthStringList *categories;
615 
616 	write_comment = FALSE;
617 
618 	comment = gth_comment_new ();
619 	gth_comment_set_note (comment, g_file_info_get_attribute_string (file_data->info, "comment::note"));
620 	gth_comment_set_caption (comment, g_file_info_get_attribute_string (file_data->info, "comment::caption"));
621 	gth_comment_set_place (comment, g_file_info_get_attribute_string (file_data->info, "comment::place"));
622 
623 	metadata = (GthMetadata *) g_file_info_get_attribute_object (file_data->info, "comment::time");
624 	if (metadata != NULL)
625 		gth_comment_set_time_from_exif_format (comment, gth_metadata_get_raw (metadata));
626 
627 	metadata = (GthMetadata *) g_file_info_get_attribute_object (file_data->info, "comment::categories");
628 	comment_categories = gth_metadata_get_string_list (metadata);
629 	if (comment_categories != NULL)
630 		for (scan = gth_string_list_get_list (comment_categories); scan; scan = scan->next)
631 			gth_comment_add_category (comment, (char *) scan->data);
632 
633 	gth_comment_set_rating (comment, g_file_info_get_attribute_int32 (file_data->info, "comment::rating"));
634 
635 	/* sync embedded data and .comment data if required */
636 
637 	metadata = (GthMetadata *) g_file_info_get_attribute_object (file_data->info, "general::description");
638 	if (metadata != NULL) {
639 		text = g_file_info_get_attribute_string (file_data->info, "comment::note");
640 		if (! dom_str_equal (gth_metadata_get_formatted (metadata), text)) {
641 			char *value = _g_utf8_try_from_any (gth_metadata_get_formatted (metadata));
642 			if (value != NULL) {
643 				gth_comment_set_note (comment, value);
644 				g_free (value);
645 				write_comment = TRUE;
646 			}
647 		}
648 	}
649 
650 	metadata = (GthMetadata *) g_file_info_get_attribute_object (file_data->info, "general::title");
651 	if (metadata != NULL) {
652 		text = g_file_info_get_attribute_string (file_data->info, "comment::caption");
653 		if (! dom_str_equal (gth_metadata_get_formatted (metadata), text)) {
654 			char *value = _g_utf8_try_from_any (gth_metadata_get_formatted (metadata));
655 			if (value != NULL) {
656 				gth_comment_set_caption (comment, value);
657 				g_free (value);
658 				write_comment = TRUE;
659 			}
660 		}
661 	}
662 
663 	metadata = (GthMetadata *) g_file_info_get_attribute_object (file_data->info, "general::location");
664 	if (metadata != NULL) {
665 		text = g_file_info_get_attribute_string (file_data->info, "comment::place");
666 		if (! dom_str_equal (gth_metadata_get_formatted (metadata), text)) {
667 			char *value = _g_utf8_try_from_any (gth_metadata_get_formatted (metadata));
668 			if (value != NULL) {
669 				gth_comment_set_place (comment, value);
670 				g_free (value);
671 				write_comment = TRUE;
672 			}
673 		}
674 	}
675 
676 	metadata = (GthMetadata *) g_file_info_get_attribute_object (file_data->info, "general::datetime");
677 	if (metadata != NULL) {
678 		GthMetadata *comment_time;
679 
680 		text = gth_metadata_get_raw (metadata);
681 		comment_time = (GthMetadata *) g_file_info_get_attribute_object (file_data->info, "comment::time");
682 		if (comment_time != NULL) {
683 			if (! dom_str_equal (gth_metadata_get_raw (comment_time), text)) {
684 				gth_comment_set_time_from_exif_format (comment, gth_metadata_get_raw (metadata));
685 				write_comment = TRUE;
686 			}
687 		}
688 	}
689 
690 	metadata = (GthMetadata *) g_file_info_get_attribute_object (file_data->info, "general::tags");
691 	categories = gth_metadata_get_string_list (metadata);
692 	if (categories != NULL) {
693 		metadata = (GthMetadata *) g_file_info_get_attribute_object (file_data->info, "comment::categories");
694 		comment_categories = gth_metadata_get_string_list (metadata);
695 		if (! gth_string_list_equal_custom (categories, comment_categories, (GCompareFunc) dom_str_find)) {
696 			GList *scan;
697 
698 			gth_comment_clear_categories (comment);
699 			for (scan = gth_string_list_get_list (categories); scan; scan = scan->next) {
700 				char *value = _g_utf8_try_from_any (scan->data);
701 				if (value != NULL) {
702 					gth_comment_add_category (comment, value);
703 					g_free (value);
704 				}
705 			}
706 			write_comment = TRUE;
707 		}
708 	}
709 
710 	if (write_comment) {
711 		GFile *comment_file;
712 		GFile *comment_directory;
713 		char  *buffer;
714 		gsize  size;
715 
716 		comment_file = gth_comment_get_comment_file (file_data->file);
717 		comment_directory = g_file_get_parent (comment_file);
718 		if (! g_file_query_exists (comment_directory, NULL))
719 			g_file_make_directory (comment_directory, NULL, NULL);
720 
721 		buffer = gth_comment_to_data (comment, &size);
722 		if (_g_file_write (comment_file,
723 				   FALSE,
724 				   G_FILE_CREATE_NONE,
725 				   buffer,
726 				   size,
727 				   NULL,
728 				   NULL))
729 		{
730 			GFile *parent;
731 			GList *list;
732 
733 			parent = g_file_get_parent (file_data->file);
734 			list = g_list_prepend (NULL, file_data->file);
735 			gth_monitor_folder_changed (gth_main_get_default_monitor (),
736 						    parent,
737 						    list,
738 						    GTH_MONITOR_EVENT_CHANGED);
739 
740 			g_list_free (list);
741 			g_object_unref (parent);
742 		}
743 
744 		g_free (buffer);
745 		g_object_unref (comment_directory);
746 		g_object_unref (comment_file);
747 	}
748 
749 	g_object_unref (comment);
750 }
751