1 /* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
2 
3 /*
4  *  GThumb
5  *
6  *  Copyright (C) 2009 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 
23 #include <config.h>
24 #include <glib/gi18n.h>
25 #include <glib.h>
26 #include <gthumb.h>
27 #include "gth-catalog.h"
28 
29 
30 #define CATALOG_FORMAT "1.0"
31 
32 
33 struct _GthCatalogPrivate {
34 	GthCatalogType  type;
35 	GFile          *file;
36 	GList          *file_list;
37 	GHashTable     *file_hash;  /* used to avoid duplicates */
38 	char           *name;
39 	GthDateTime    *date_time;
40 	gboolean        active;
41 	char           *order;
42 	gboolean        order_inverse;
43 };
44 
45 
G_DEFINE_TYPE_WITH_CODE(GthCatalog,gth_catalog,G_TYPE_OBJECT,G_ADD_PRIVATE (GthCatalog))46 G_DEFINE_TYPE_WITH_CODE (GthCatalog,
47 			 gth_catalog,
48 			 G_TYPE_OBJECT,
49 			 G_ADD_PRIVATE (GthCatalog))
50 
51 
52 static void
53 gth_catalog_finalize (GObject *object)
54 {
55 	GthCatalog *catalog = GTH_CATALOG (object);
56 
57 	g_value_hash_unref (catalog->attributes);
58 
59 	if (catalog->priv->file != NULL)
60 		g_object_unref (catalog->priv->file);
61 	g_free (catalog->priv->name);
62 	_g_object_list_unref (catalog->priv->file_list);
63 	g_hash_table_destroy (catalog->priv->file_hash);
64 	gth_datetime_free (catalog->priv->date_time);
65 	g_free (catalog->priv->order);
66 
67 	G_OBJECT_CLASS (gth_catalog_parent_class)->finalize (object);
68 }
69 
70 
71 static DomElement *
base_create_root(GthCatalog * catalog,DomDocument * doc)72 base_create_root (GthCatalog  *catalog,
73 		  DomDocument *doc)
74 {
75 	return dom_document_create_element (doc, "catalog",
76 					    "version", CATALOG_FORMAT,
77 					    NULL);
78 }
79 
80 
81 static void
base_read_from_doc(GthCatalog * catalog,DomElement * root)82 base_read_from_doc (GthCatalog *catalog,
83 		    DomElement *root)
84 {
85 	GList      *file_list;
86 	DomElement *child;
87 
88 	file_list = NULL;
89 	for (child = root->first_child; child; child = child->next_sibling) {
90 		if (g_strcmp0 (child->tag_name, "files") == 0) {
91 			DomElement *file;
92 
93 			for (file = child->first_child; file; file = file->next_sibling) {
94 				const char *uri;
95 
96 				uri = dom_element_get_attribute (file, "uri");
97 				if (uri != NULL)
98 					file_list = g_list_prepend (file_list, g_file_new_for_uri (uri));
99 			}
100 			file_list = g_list_reverse (file_list);
101 		}
102 		if (g_strcmp0 (child->tag_name, "order") == 0)
103 			gth_catalog_set_order (catalog,
104 					       dom_element_get_attribute (child, "type"),
105 					       g_strcmp0 (dom_element_get_attribute (child, "inverse"), "1") == 0);
106 		if (g_strcmp0 (child->tag_name, "date") == 0)
107 			gth_datetime_from_exif_date (catalog->priv->date_time, dom_element_get_inner_text (child));
108 		if (g_strcmp0 (child->tag_name, "name") == 0)
109 			gth_catalog_set_name (catalog, dom_element_get_inner_text (child));
110 	}
111 	gth_catalog_set_file_list (catalog, file_list);
112 
113 	gth_hook_invoke ("gth-catalog-read-from-doc", catalog, root);
114 
115 	_g_object_list_unref (file_list);
116 }
117 
118 
119 static void
read_catalog_data_from_xml(GthCatalog * catalog,const char * buffer,gsize count,GError ** error)120 read_catalog_data_from_xml (GthCatalog  *catalog,
121 		   	    const char  *buffer,
122 		   	    gsize        count,
123 		   	    GError     **error)
124 {
125 	DomDocument *doc;
126 
127 	doc = dom_document_new ();
128 	if (dom_document_load (doc, buffer, count, error))
129 		GTH_CATALOG_GET_CLASS (catalog)->read_from_doc (catalog, DOM_ELEMENT (doc)->first_child);
130 
131 	g_object_unref (doc);
132 }
133 
134 
135 static void
read_catalog_data_old_format(GthCatalog * catalog,const char * buffer,gsize count)136 read_catalog_data_old_format (GthCatalog *catalog,
137 			      const char *buffer,
138 			      gsize       count)
139 {
140 	GInputStream     *mem_stream;
141 	GDataInputStream *data_stream;
142 	gboolean          is_search;
143 	int               list_start;
144 	int               n_line;
145 	char             *line;
146 
147 	mem_stream = g_memory_input_stream_new_from_data (buffer, count, NULL);
148 	data_stream = g_data_input_stream_new (mem_stream);
149 
150 	is_search = (strncmp (buffer, "# Search", 8) == 0);
151 	if (is_search)
152 		list_start = 10;
153 	else
154 		list_start = 1;
155 
156 	gth_catalog_set_file_list (catalog, NULL);
157 
158 	n_line = 0;
159 	while ((line = g_data_input_stream_read_line (data_stream, NULL, NULL, NULL)) != NULL) {
160 		n_line++;
161 
162 		if (is_search) {
163 			/* FIXME: read the search metadata here. */
164 		}
165 
166 		if (n_line > list_start) {
167 			char *uri;
168 
169 			uri = g_strndup (line + 1, strlen (line) - 2);
170 			catalog->priv->file_list = g_list_prepend (catalog->priv->file_list, g_file_new_for_uri (uri));
171 
172 			g_free (uri);
173 		}
174 		g_free (line);
175 	}
176 
177 	catalog->priv->file_list = g_list_reverse (catalog->priv->file_list);
178 
179 	g_object_unref (data_stream);
180 	g_object_unref (mem_stream);
181 }
182 
183 
184 static void
base_write_to_doc(GthCatalog * catalog,DomDocument * doc,DomElement * root)185 base_write_to_doc (GthCatalog  *catalog,
186 		   DomDocument *doc,
187 		   DomElement  *root)
188 {
189 	if (catalog->priv->name != NULL)
190 		dom_element_append_child (root, dom_document_create_element_with_text (doc, catalog->priv->name, "name", NULL));
191 
192 	if (gth_datetime_valid_date (catalog->priv->date_time)) {
193 		char *s;
194 
195 		s = gth_datetime_to_exif_date (catalog->priv->date_time);
196 		dom_element_append_child (root, dom_document_create_element_with_text (doc, s, "date", NULL));
197 		g_free (s);
198 	}
199 
200 	if (catalog->priv->order != NULL)
201 		dom_element_append_child (root, dom_document_create_element (doc, "order",
202 									     "type", catalog->priv->order,
203 									     "inverse", (catalog->priv->order_inverse ? "1" : "0"),
204 									     NULL));
205 
206 	if (catalog->priv->file_list != NULL) {
207 		DomElement *node;
208 		GList      *scan;
209 
210 		node = dom_document_create_element (doc, "files", NULL);
211 		dom_element_append_child (root, node);
212 
213 		for (scan = catalog->priv->file_list; scan; scan = scan->next) {
214 			GFile *file = scan->data;
215 			char  *uri;
216 
217 			uri = g_file_get_uri (file);
218 			dom_element_append_child (node, dom_document_create_element (doc, "file", "uri", uri, NULL));
219 
220 			g_free (uri);
221 		}
222 	}
223 
224 	gth_hook_invoke ("gth-catalog-write-to-doc", catalog, doc, root);
225 }
226 
227 
228 static void
gth_catalog_class_init(GthCatalogClass * class)229 gth_catalog_class_init (GthCatalogClass *class)
230 {
231 	GObjectClass *object_class;
232 
233 	object_class = (GObjectClass*) class;
234 	object_class->finalize = gth_catalog_finalize;
235 
236 	class->create_root = base_create_root;
237 	class->read_from_doc = base_read_from_doc;
238 	class->write_to_doc = base_write_to_doc;
239 }
240 
241 
242 static void
gth_catalog_init(GthCatalog * catalog)243 gth_catalog_init (GthCatalog *catalog)
244 {
245 	catalog->attributes = g_value_hash_new ();
246 	catalog->priv = gth_catalog_get_instance_private (catalog);
247 	catalog->priv->type = GTH_CATALOG_TYPE_INVALID;
248 	catalog->priv->file = NULL;
249 	catalog->priv->file_list = NULL;
250 	catalog->priv->file_hash = g_hash_table_new_full (g_file_hash, (GEqualFunc) g_file_equal, NULL, NULL);
251 	catalog->priv->name = NULL;
252 	catalog->priv->date_time = gth_datetime_new ();
253 	catalog->priv->order = NULL;
254 	catalog->priv->order_inverse = FALSE;
255 }
256 
257 
258 GthCatalog *
gth_catalog_new(void)259 gth_catalog_new (void)
260 {
261 	return (GthCatalog *) g_object_new (GTH_TYPE_CATALOG, NULL);
262 }
263 
264 
265 GthCatalog *
gth_catalog_new_for_file(GFile * file)266 gth_catalog_new_for_file (GFile *file)
267 {
268 	char       *uri;
269 	GthCatalog *catalog;
270 
271 	if (file == NULL)
272 		return NULL;
273 
274 	uri = g_file_get_uri (file);
275 	catalog = gth_hook_invoke_get ("gth-catalog-new-for-uri", uri);
276 
277 	g_free (uri);
278 
279 	return catalog;
280 }
281 
282 
283 GthCatalog *
gth_catalog_new_from_data(const void * buffer,gsize count,GError ** error)284 gth_catalog_new_from_data (const void  *buffer,
285 			   gsize        count,
286 			   GError     **error)
287 {
288 	char       *text_buffer;
289 	GthCatalog *catalog = NULL;
290 
291 	text_buffer = (char *) buffer;
292 	if ((text_buffer == NULL) || (*text_buffer == 0))
293 		return NULL;
294 
295 	if (strncmp (text_buffer, "<?xml ", 6) == 0) {
296 		catalog = gth_hook_invoke_get ("gth-catalog-load-from-data", (gpointer) buffer);
297 		if (catalog != NULL)
298 			read_catalog_data_from_xml (catalog, text_buffer, count, error);
299 		else
300 			g_set_error_literal (error, G_IO_ERROR, G_IO_ERROR_FAILED, _("Invalid file format"));
301 	}
302 	else {
303 		catalog = gth_catalog_new ();
304 		read_catalog_data_old_format (catalog, text_buffer, count);
305 	}
306 
307 	return catalog;
308 }
309 
310 
311 void
gth_catalog_set_file(GthCatalog * catalog,GFile * file)312 gth_catalog_set_file (GthCatalog *catalog,
313 		      GFile      *file)
314 {
315 	if (catalog->priv->file != NULL) {
316 		g_object_unref (catalog->priv->file);
317 		catalog->priv->file = NULL;
318 	}
319 
320 	if (file != NULL)
321 		catalog->priv->file = g_file_dup (file);
322 
323 	catalog->priv->type = GTH_CATALOG_TYPE_CATALOG;
324 }
325 
326 
327 GFile *
gth_catalog_get_file(GthCatalog * catalog)328 gth_catalog_get_file (GthCatalog *catalog)
329 {
330 	return catalog->priv->file;
331 }
332 
333 
334 void
gth_catalog_set_name(GthCatalog * catalog,const char * name)335 gth_catalog_set_name (GthCatalog *catalog,
336 		      const char *name)
337 {
338 	g_free (catalog->priv->name);
339 	catalog->priv->name = NULL;
340 	if ((name != NULL) && (strcmp (name, "") != 0))
341 		catalog->priv->name = g_strdup (name);;
342 }
343 
344 
345 const char *
gth_catalog_get_name(GthCatalog * catalog)346 gth_catalog_get_name (GthCatalog *catalog)
347 {
348 	return catalog->priv->name;
349 }
350 
351 
352 void
gth_catalog_set_date(GthCatalog * catalog,GthDateTime * date_time)353 gth_catalog_set_date (GthCatalog  *catalog,
354 		      GthDateTime *date_time)
355 {
356 	if (g_date_valid (date_time->date))
357 		g_date_set_dmy (catalog->priv->date_time->date,
358 				g_date_get_day (date_time->date),
359 				g_date_get_month (date_time->date),
360 				g_date_get_year (date_time->date));
361 	else
362 		g_date_clear (catalog->priv->date_time->date, 1);
363 	gth_time_set_hms (catalog->priv->date_time->time, 0, 0, 0, 0);
364 }
365 
366 
367 GthDateTime *
gth_catalog_get_date(GthCatalog * catalog)368 gth_catalog_get_date (GthCatalog *catalog)
369 {
370 	return catalog->priv->date_time;
371 }
372 
373 
374 void
gth_catalog_set_order(GthCatalog * catalog,const char * order,gboolean inverse)375 gth_catalog_set_order (GthCatalog *catalog,
376 		       const char *order,
377 		       gboolean    inverse)
378 {
379 	g_free (catalog->priv->order);
380 	catalog->priv->order = NULL;
381 
382 	if (order != NULL)
383 		catalog->priv->order = g_strdup (order);
384 	catalog->priv->order_inverse = inverse;
385 }
386 
387 
388 const char *
gth_catalog_get_order(GthCatalog * catalog,gboolean * inverse)389 gth_catalog_get_order (GthCatalog *catalog,
390 		       gboolean   *inverse)
391 {
392 	*inverse = catalog->priv->order_inverse;
393 	return catalog->priv->order;
394 }
395 
396 
397 char *
gth_catalog_to_data(GthCatalog * catalog,gsize * length)398 gth_catalog_to_data (GthCatalog *catalog,
399 		     gsize      *length)
400 {
401 	DomDocument *doc;
402 	DomElement  *root;
403 	char        *data;
404 
405 	doc = dom_document_new ();
406 	root = GTH_CATALOG_GET_CLASS (catalog)->create_root (catalog, doc);
407 	dom_element_append_child (DOM_ELEMENT (doc), root);
408 	GTH_CATALOG_GET_CLASS (catalog)->write_to_doc (catalog, doc, root);
409 	data = dom_document_dump (doc, length);
410 
411 	g_object_unref (doc);
412 
413 	return data;
414 }
415 
416 
417 void
gth_catalog_set_file_list(GthCatalog * catalog,GList * file_list)418 gth_catalog_set_file_list (GthCatalog *catalog,
419 			   GList      *file_list)
420 {
421 	_g_object_list_unref (catalog->priv->file_list);
422 	catalog->priv->file_list = NULL;
423 	g_hash_table_remove_all (catalog->priv->file_hash);
424 
425 	if (file_list != NULL) {
426 		GList *list;
427 		GList *scan;
428 
429 		list = NULL;
430 		for (scan = file_list; scan; scan = scan->next) {
431 			GFile *file = scan->data;
432 
433 			if (g_hash_table_lookup (catalog->priv->file_hash, file) != NULL)
434 				continue;
435 			file = g_file_dup (file);
436 			list = g_list_prepend (list, file);
437 			g_hash_table_insert (catalog->priv->file_hash, file, GINT_TO_POINTER (1));
438 		}
439 		catalog->priv->file_list = g_list_reverse (list);
440 	}
441 }
442 
443 
444 GList *
gth_catalog_get_file_list(GthCatalog * catalog)445 gth_catalog_get_file_list (GthCatalog *catalog)
446 {
447 	return catalog->priv->file_list;
448 }
449 
450 
451 gboolean
gth_catalog_insert_file(GthCatalog * catalog,GFile * file,int pos)452 gth_catalog_insert_file (GthCatalog *catalog,
453 			 GFile      *file,
454 			 int         pos)
455 {
456 	if (g_hash_table_lookup (catalog->priv->file_hash, file) != NULL)
457 		return FALSE;
458 
459 	file = g_file_dup (file);
460 	catalog->priv->file_list = g_list_insert (catalog->priv->file_list, file, pos);
461 	g_hash_table_insert (catalog->priv->file_hash, file, GINT_TO_POINTER (1));
462 
463 	return TRUE;
464 }
465 
466 
467 int
gth_catalog_remove_file(GthCatalog * catalog,GFile * file)468 gth_catalog_remove_file (GthCatalog *catalog,
469 			 GFile      *file)
470 {
471 	GList *scan;
472 	int    i = 0;
473 
474 	g_return_val_if_fail (catalog != NULL, -1);
475 	g_return_val_if_fail (file != NULL, -1);
476 
477 	for (scan = catalog->priv->file_list; scan; scan = scan->next, i++)
478 		if (g_file_equal ((GFile *) scan->data, file))
479 			break;
480 
481 	if (scan == NULL)
482 		return -1;
483 
484 	catalog->priv->file_list = g_list_remove_link (catalog->priv->file_list, scan);
485 	g_hash_table_remove (catalog->priv->file_hash, file);
486 
487 	_g_object_list_unref (scan);
488 
489 	return i;
490 }
491 
492 
493 static char *
get_display_name(GFile * file,const char * name,GthDateTime * date_time)494 get_display_name (GFile       *file,
495 		  const char  *name,
496 		  GthDateTime *date_time)
497 {
498 	GString *display_name;
499 	char    *basename;
500 
501 	display_name = g_string_new ("");
502 	basename = g_file_get_basename (file);
503 	if ((basename == NULL) || (strcmp (basename, "/") == 0)) {
504 		 g_string_append (display_name, _("Catalogs"));
505 	}
506 	else {
507 		if ((name == NULL) && ! gth_datetime_valid_date (date_time)) {
508 			char *name;
509 			char *utf8_name;
510 
511 			name = _g_path_remove_extension (basename);
512 			utf8_name = g_filename_to_utf8 (name, -1, NULL, NULL, NULL);
513 			g_string_append (display_name, utf8_name);
514 
515 			g_free (utf8_name);
516 			g_free (name);
517 		}
518 		else {
519 			if (name != NULL)
520 				g_string_append (display_name, name);
521 
522 			if (gth_datetime_valid_date (date_time)) {
523 				char *formatted;
524 
525 				formatted = gth_datetime_strftime (date_time, "%x");
526 				if ((name == NULL) || (strstr (name, formatted) == NULL)) {
527 					if (name != NULL)
528 						g_string_append (display_name, " (");
529 					g_string_append (display_name, formatted);
530 					if (name != NULL)
531 						g_string_append (display_name, ")");
532 				}
533 				g_free (formatted);
534 			}
535 		}
536 	}
537 
538 	g_free (basename);
539 
540 	return g_string_free (display_name, FALSE);
541 }
542 
543 
544 static char *
get_edit_name(GFile * file,const char * name,GthDateTime * date_time)545 get_edit_name (GFile       *file,
546 	       const char  *name,
547 	       GthDateTime *date_time)
548 {
549 	GString *display_name;
550 	char    *basename;
551 
552 	display_name = g_string_new ("");
553 	basename = g_file_get_basename (file);
554 	if ((basename == NULL) || (strcmp (basename, "/") == 0)) {
555 		 g_string_append (display_name, _("Catalogs"));
556 	}
557 	else {
558 		if (name == NULL) {
559 			char *name;
560 			char *utf8_name;
561 
562 			name = _g_path_remove_extension (basename);
563 			utf8_name = g_filename_to_utf8 (name, -1, NULL, NULL, NULL);
564 			g_string_append (display_name, utf8_name);
565 
566 			g_free (utf8_name);
567 			g_free (name);
568 		}
569 		else
570 			g_string_append (display_name, name);
571 	}
572 
573 	g_free (basename);
574 
575 	return g_string_free (display_name, FALSE);
576 }
577 
578 
579 static void
update_standard_attributes(GFile * file,GFileInfo * info,const char * name,GthDateTime * date_time)580 update_standard_attributes (GFile       *file,
581 			    GFileInfo   *info,
582 			    const char  *name,
583 			    GthDateTime *date_time)
584 {
585 	char *display_name;
586 	char *edit_name;
587 
588 	if (gth_datetime_valid_date (date_time)) {
589 		char *sort_order_s;
590 
591 		sort_order_s = gth_datetime_strftime (date_time, "%Y%m%d");
592 		_g_file_info_set_secondary_sort_order (info, atoi (sort_order_s));
593 
594 		g_free (sort_order_s);
595 	}
596 	else
597 		g_file_info_remove_attribute (info, "gth::standard::secondary-sort-order");
598 
599 	display_name = get_display_name (file, name, date_time);
600 	if (display_name != NULL) {
601 		g_file_info_set_display_name (info, display_name);
602 		g_free (display_name);
603 	}
604 
605 	edit_name = get_edit_name (file, name, date_time);
606 	if (edit_name != NULL) {
607 		g_file_info_set_edit_name (info, edit_name);
608 		g_free (edit_name);
609 	}
610 }
611 
612 
613 void
gth_catalog_update_metadata(GthCatalog * catalog,GthFileData * file_data)614 gth_catalog_update_metadata (GthCatalog  *catalog,
615 			     GthFileData *file_data)
616 {
617 	const char *sort_type;
618 	gboolean    sort_inverse;
619 
620 	/* sort::type,sort::inverse */
621 
622 	sort_type = gth_catalog_get_order (catalog, &sort_inverse);
623 	if (sort_type != NULL) {
624 		g_file_info_set_attribute_string (file_data->info, "sort::type", sort_type);
625 		g_file_info_set_attribute_boolean (file_data->info, "sort::inverse", sort_inverse);
626 	}
627 	else {
628 		g_file_info_remove_attribute (file_data->info, "sort::type");
629 		g_file_info_remove_attribute (file_data->info, "sort::inverse");
630 	}
631 
632 	/* general::event-date */
633 
634 	if (gth_datetime_valid_date (catalog->priv->date_time)) {
635 		GObject *metadata;
636 		char    *raw;
637 		char    *formatted;
638 		char    *sort_order_s;
639 
640 		metadata = (GObject *) gth_metadata_new ();
641 		raw = gth_datetime_to_exif_date (catalog->priv->date_time);
642 		formatted = gth_datetime_strftime (catalog->priv->date_time, "%x");
643 		g_object_set (metadata,
644 			      "id", "general::event-date",
645 			      "raw", raw,
646 			      "formatted", formatted,
647 			      NULL);
648 		g_file_info_set_attribute_object (file_data->info, "general::event-date", metadata);
649 
650 		sort_order_s = gth_datetime_strftime (catalog->priv->date_time, "%Y%m%d");
651 		_g_file_info_set_secondary_sort_order (file_data->info, atoi (sort_order_s));
652 
653 		g_free (formatted);
654 		g_free (raw);
655 		g_object_unref (metadata);
656 	}
657 	else {
658 		g_file_info_remove_attribute (file_data->info, "general::event-date");
659 		g_file_info_remove_attribute (file_data->info, "gth::standard::secondary-sort-order");
660 	}
661 
662 	/* standard::display-name,standard::sort-order */
663 
664 	update_standard_attributes (file_data->file,
665 				    file_data->info,
666 				    catalog->priv->name,
667 				    catalog->priv->date_time);
668 
669 	gth_hook_invoke ("gth-catalog-write-metadata", catalog, file_data);
670 }
671 
672 
673 int
gth_catalog_get_size(GthCatalog * catalog)674 gth_catalog_get_size (GthCatalog *catalog)
675 {
676 	return g_hash_table_size (catalog->priv->file_hash);
677 }
678 
679 
680 /* utils */
681 
682 
683 GFile *
gth_catalog_get_base(void)684 gth_catalog_get_base (void)
685 {
686 	return gth_user_dir_get_file_for_read (GTH_DIR_DATA, GTHUMB_DIR, "catalogs", NULL);
687 }
688 
689 
690 GFile *
gth_catalog_file_to_gio_file(GFile * file)691 gth_catalog_file_to_gio_file (GFile *file)
692 {
693 	GFile      *gio_file = NULL;
694 	char       *uri;
695 	UriParts    file_parts;
696 
697 	if (! g_file_has_uri_scheme (file, "catalog"))
698 		return g_file_dup (file);
699 
700 	uri = g_file_get_uri (file);
701 	if (! _g_uri_split (uri, &file_parts))
702 		return NULL;
703 
704 	if (file_parts.query != NULL) {
705 		char *new_uri;
706 
707 		new_uri = g_uri_unescape_string (file_parts.query, NULL);
708 		gio_file = g_file_new_for_uri (new_uri);
709 
710 		g_free (new_uri);
711 	}
712 	else {
713 		GFile *base;
714 		char  *base_uri;
715 		char  *new_uri;
716 
717 		base = gth_catalog_get_base ();
718 		base_uri = g_file_get_uri (base);
719 		new_uri = _g_uri_append_path (base_uri, file_parts.path);
720 		gio_file = g_file_new_for_uri (new_uri);
721 
722 		g_free (new_uri);
723 		g_free (base_uri);
724 		g_object_unref (base);
725 	}
726 
727 	g_free (uri);
728 
729 	return gio_file;
730 }
731 
732 
733 GFile *
gth_catalog_file_from_gio_file(GFile * gio_file,GFile * catalog)734 gth_catalog_file_from_gio_file (GFile *gio_file,
735 				GFile *catalog)
736 {
737 	GFile *gio_base;
738 	GFile *file = NULL;
739 	char  *path;
740 
741 	gio_base = gth_catalog_get_base ();
742 	if (g_file_equal (gio_base, gio_file)) {
743 		g_object_unref (gio_base);
744 		return g_file_new_for_uri ("catalog:///");
745 	}
746 
747 	path = g_file_get_relative_path (gio_base, gio_file);
748 	if (path != NULL) {
749 		GFile *base;
750 
751 		base = g_file_new_for_uri ("catalog:///");
752 		file = _g_file_append_path (base, path);
753 
754 		g_object_unref (base);
755 	}
756 	else if (catalog != NULL) {
757 		char *catalog_uri;
758 		char *file_uri;
759 		char *query;
760 		char *uri;
761 
762 		catalog_uri = g_file_get_uri (catalog);
763 		file_uri = g_file_get_uri (gio_file);
764 		query = g_uri_escape_string (file_uri, G_URI_RESERVED_CHARS_ALLOWED_IN_PATH, FALSE);
765 		uri = g_strconcat (file_uri, "?", query, NULL);
766 		file = g_file_new_for_uri (uri);
767 
768 		g_free (uri);
769 		g_free (query);
770 		g_free (file_uri);
771 		g_free (catalog_uri);
772 	}
773 
774 	g_free (path);
775 	g_object_unref (gio_base);
776 
777 	return file;
778 }
779 
780 
781 GFile *
gth_catalog_file_from_relative_path(const char * name,const char * file_extension)782 gth_catalog_file_from_relative_path (const char *name,
783 				     const char *file_extension)
784 {
785 	char  *path;
786 	char  *uri;
787 	GFile *file;
788 
789 	path = g_strconcat (name, file_extension, NULL);
790 	uri = _g_uri_append_path ("catalog:///", path);
791 	file = g_file_new_for_uri (uri);
792 
793 	g_free (uri);
794 	g_free (path);
795 
796 	return file;
797 }
798 
799 
800 char *
gth_catalog_get_relative_path(GFile * file)801 gth_catalog_get_relative_path (GFile *file)
802 {
803 	GFile *base;
804 	char  *path;
805 
806 	base = gth_catalog_get_base ();
807 	path = g_file_get_relative_path (base, file);
808 
809 	g_object_unref (base);
810 
811 	return path;
812 }
813 
814 
815 GIcon *
gth_catalog_get_icon(GFile * file)816 gth_catalog_get_icon (GFile *file)
817 {
818 	char  *uri;
819 	GIcon *icon;
820 
821 	uri = g_file_get_uri (file);
822 	if (g_str_has_suffix (uri, ".catalog"))
823 		icon = g_themed_icon_new ("file-catalog-symbolic");
824 	else
825 		icon = g_themed_icon_new ("file-library-symbolic");
826 
827 	g_free (uri);
828 
829 	return icon;
830 }
831 
832 
833 static char *
get_tag_value(const char * buffer,const char * tag_start,const char * tag_end)834 get_tag_value (const char *buffer,
835 	       const char *tag_start,
836 	       const char *tag_end)
837 {
838 	char *value;
839 	char *begin_tag;
840 
841 	value = NULL;
842 	begin_tag = strstr (buffer, tag_start);
843 	if (begin_tag != NULL) {
844 		char        *end_tag;
845 		char        *xml;
846 		DomDocument *doc;
847 
848 		end_tag = strstr (begin_tag, tag_end);
849 		xml = g_strndup (begin_tag, (end_tag - begin_tag) + strlen (tag_end));
850 		doc = dom_document_new ();
851 		if (dom_document_load (doc, xml, strlen (xml), NULL))
852 			value = g_strdup (dom_element_get_inner_text (DOM_ELEMENT (doc)->first_child));
853 
854 		g_object_unref (doc);
855 		g_free (xml);
856 	}
857 
858 	return value;
859 }
860 
861 
862 void
gth_catalog_update_standard_attributes(GFile * file,GFileInfo * info)863 gth_catalog_update_standard_attributes (GFile     *file,
864 				        GFileInfo *info)
865 {
866 	char *display_name = NULL;
867 	char *edit_name = NULL;
868 	char *basename;
869 
870 	basename = g_file_get_basename (file);
871 	if ((basename != NULL) && (strcmp (basename, "/") != 0)) {
872 		char        *name;
873 		GthDateTime *date_time;
874 
875 		name = NULL;
876 		date_time = gth_datetime_new ();
877 		{
878 			GFile            *gio_file;
879 			GFileInputStream *istream;
880 			const int         buffer_size = 256;
881 			char              buffer[buffer_size];
882 
883 			gio_file = gth_catalog_file_to_gio_file (file);
884 			istream = g_file_read (gio_file, NULL, NULL);
885 			if (istream != NULL) {
886 				gsize bytes_read;
887 
888 				if (g_input_stream_read_all (G_INPUT_STREAM (istream),
889 							     buffer,
890 							     buffer_size - 1,
891 							     &bytes_read,
892 							     NULL,
893 							     NULL))
894 				{
895 					char *exif_date;
896 
897 					buffer[bytes_read] = '\0';
898 					name = get_tag_value (buffer, "<name>", "</name>");
899 					exif_date = get_tag_value (buffer, "<date>", "</date>");
900 					if (exif_date != NULL)
901 						gth_datetime_from_exif_date (date_time, exif_date);
902 
903 					g_free (exif_date);
904 				}
905 				g_object_unref (istream);
906 			}
907 			g_object_unref (gio_file);
908 		}
909 
910 		update_standard_attributes (file, info, name, date_time);
911 
912 		gth_datetime_free (date_time);
913 		g_free (name);
914 	}
915 	else {
916 		display_name = g_strdup (_("Catalogs"));
917 		edit_name = g_strdup (_("Catalogs"));
918 	}
919 
920 	if (display_name != NULL)
921 		g_file_info_set_display_name (info, display_name);
922 	if (edit_name != NULL)
923 		g_file_info_set_edit_name (info, edit_name);
924 
925 	g_free (edit_name);
926 	g_free (display_name);
927 	g_free (basename);
928 }
929 
930 
931 /* -- gth_catalog_load_from_file --*/
932 
933 
934 typedef struct {
935 	GFile         *file;
936 	ReadyCallback  ready_func;
937 	gpointer       user_data;
938 } LoadData;
939 
940 
941 static void
load__catalog_buffer_ready_cb(void ** buffer,gsize count,GError * error,gpointer user_data)942 load__catalog_buffer_ready_cb (void     **buffer,
943 			       gsize      count,
944 			       GError    *error,
945 			       gpointer   user_data)
946 {
947 	LoadData   *load_data = user_data;
948 	GthCatalog *catalog;
949 
950 	if (error == NULL) {
951 		catalog = gth_catalog_new_from_data (*buffer, count, &error);
952 		if (catalog == NULL)
953 			catalog = gth_catalog_new_for_file (load_data->file);
954 	}
955 	else
956 		catalog = NULL;
957 	load_data->ready_func (G_OBJECT (catalog), error, load_data->user_data);
958 
959 	g_object_unref (load_data->file);
960 	g_free (load_data);
961 }
962 
963 
964 void
gth_catalog_load_from_file_async(GFile * file,GCancellable * cancellable,ReadyCallback ready_func,gpointer user_data)965 gth_catalog_load_from_file_async (GFile         *file,
966 				  GCancellable  *cancellable,
967 				  ReadyCallback  ready_func,
968 				  gpointer       user_data)
969 {
970 	LoadData *load_data;
971 	GFile    *gio_file;
972 
973 	load_data = g_new0 (LoadData, 1);
974 	load_data->file = g_object_ref (file);
975 	load_data->ready_func = ready_func;
976 	load_data->user_data = user_data;
977 
978 	gio_file = gth_catalog_file_to_gio_file (file);
979 	_g_file_load_async (gio_file,
980 			    G_PRIORITY_DEFAULT,
981 			    cancellable,
982 			    load__catalog_buffer_ready_cb,
983 			    load_data);
984 
985 	g_object_unref (gio_file);
986 }
987 
988 
989 GFile *
gth_catalog_get_file_for_date(GthDateTime * date_time,const char * extension)990 gth_catalog_get_file_for_date (GthDateTime *date_time,
991 			       const char  *extension)
992 {
993 	char  *year;
994 	char  *uri;
995 	char  *display_name;
996 	GFile *catalog_file;
997 
998 	year = gth_datetime_strftime (date_time, "%Y");
999 	uri = g_strconcat ("catalog:///", year, "/", NULL);
1000 	display_name = gth_datetime_strftime (date_time, "%Y-%m-%d");
1001 	catalog_file = _g_file_new_for_display_name (uri, display_name, extension);
1002 
1003 	g_free (display_name);
1004 	g_free (uri);
1005 	g_free (year);
1006 
1007 	return catalog_file;
1008 }
1009 
1010 
1011 GFile *
gth_catalog_get_file_for_tag(const char * tag,const char * extension)1012 gth_catalog_get_file_for_tag (const char *tag,
1013 			      const char *extension)
1014 {
1015 	char  *uri;
1016 	GFile *catalog_file;
1017 
1018 	uri = g_strconcat ("catalog:///", _("Tags"), "/", NULL);
1019 	catalog_file = _g_file_new_for_display_name (uri, tag, extension);
1020 
1021 	g_free (uri);
1022 
1023 	return catalog_file;
1024 }
1025 
1026 
1027 GthCatalog *
gth_catalog_load_from_file(GFile * file)1028 gth_catalog_load_from_file (GFile *file)
1029 {
1030 	GthCatalog *catalog;
1031 	GFile      *gio_file;
1032 	void       *buffer;
1033 	gsize       buffer_size;
1034 
1035 	gio_file = gth_catalog_file_to_gio_file (file);
1036 	if (! _g_file_load_in_buffer (gio_file, &buffer, &buffer_size, NULL, NULL))
1037 		return NULL;
1038 
1039 	catalog = gth_catalog_new_from_data (buffer, buffer_size, NULL);
1040 
1041 	g_free (buffer);
1042 	g_object_unref (gio_file);
1043 
1044 	return catalog;
1045 }
1046 
1047 
1048 void
gth_catalog_save(GthCatalog * catalog)1049 gth_catalog_save (GthCatalog *catalog)
1050 {
1051 	GFile  *file;
1052 	GFile  *gio_file;
1053 	GFile  *gio_parent;
1054 	char   *data;
1055 	gsize   size;
1056 	GError *error = NULL;
1057 
1058 	file = gth_catalog_get_file (catalog);
1059 	gio_file = gth_catalog_file_to_gio_file (file);
1060 
1061 	gio_parent = g_file_get_parent (gio_file);
1062 	if (gio_parent != NULL)
1063 		g_file_make_directory_with_parents (gio_parent, NULL, NULL);
1064 	data = gth_catalog_to_data (catalog, &size);
1065 	if (! _g_file_write (gio_file,
1066 			     FALSE,
1067 			     G_FILE_CREATE_NONE,
1068 			     data,
1069 			     size,
1070 			     NULL,
1071 			     &error))
1072 	{
1073 		g_warning ("%s", error->message);
1074 		g_clear_error (&error);
1075 	}
1076 	else {
1077 		GFile *parent_parent;
1078 		GFile *parent;
1079 		GList *list;
1080 
1081 		parent = g_file_get_parent (file);
1082 		parent_parent = g_file_get_parent (parent);
1083 		if (parent_parent != NULL) {
1084 			list = g_list_append (NULL, parent);
1085 			gth_monitor_folder_changed (gth_main_get_default_monitor (),
1086 						    parent_parent,
1087 					            list,
1088 						    GTH_MONITOR_EVENT_CREATED);
1089 			g_list_free (list);
1090 		}
1091 
1092 		list = g_list_append (NULL, file);
1093 		gth_monitor_folder_changed (gth_main_get_default_monitor (),
1094 				            parent,
1095 				            list,
1096 					    GTH_MONITOR_EVENT_CREATED);
1097 
1098 		g_list_free (list);
1099 		g_object_unref (parent);
1100 	}
1101 
1102 	g_free (data);
1103 	_g_object_unref (gio_parent);
1104 	g_object_unref (gio_file);
1105 }
1106 
1107 
1108 /* -- gth_catalog_list_async --  */
1109 
1110 
1111 typedef struct {
1112 	GthCatalog           *catalog;
1113 	const char           *attributes;
1114 	CatalogReadyCallback  list_ready_func;
1115 	gpointer              user_data;
1116 	GList                *current_file;
1117 	GList                *files;
1118 	GCancellable         *cancellable;
1119 } ListData;
1120 
1121 
1122 static void
gth_catalog_list_done(ListData * list_data,GError * error)1123 gth_catalog_list_done (ListData *list_data,
1124 		       GError   *error)
1125 {
1126 	if (list_data->list_ready_func != NULL) {
1127 		list_data->files = g_list_reverse (list_data->files);
1128 		list_data->list_ready_func (list_data->catalog, list_data->files, error, list_data->user_data);
1129 	}
1130 
1131 	_g_object_list_unref (list_data->files);
1132 	_g_object_unref (list_data->cancellable);
1133 	_g_object_unref (list_data->catalog);
1134 	g_free (list_data);
1135 }
1136 
1137 
1138 static void
catalog_file_info_ready_cb(GObject * source_object,GAsyncResult * result,gpointer user_data)1139 catalog_file_info_ready_cb (GObject      *source_object,
1140 			    GAsyncResult *result,
1141 			    gpointer      user_data)
1142 {
1143 	ListData   *list_data = user_data;
1144 	GFile      *file;
1145 	GFileInfo  *info;
1146 
1147 	file = (GFile*) source_object;
1148 	info = g_file_query_info_finish (file, result, NULL);
1149 	if (info != NULL) {
1150 		list_data->files = g_list_prepend (list_data->files, gth_file_data_new (file, info));
1151 		g_object_unref (info);
1152 	}
1153 
1154 	list_data->current_file = list_data->current_file->next;
1155 	if (list_data->current_file == NULL) {
1156 		gth_catalog_list_done (list_data, NULL);
1157 		return;
1158 	}
1159 
1160 	g_file_query_info_async ((GFile *) list_data->current_file->data,
1161 				 list_data->attributes,
1162 				 0,
1163 				 G_PRIORITY_DEFAULT,
1164 				 list_data->cancellable,
1165 				 catalog_file_info_ready_cb,
1166 				 list_data);
1167 }
1168 
1169 
1170 static void
list__catalog_buffer_ready_cb(void ** buffer,gsize count,GError * error,gpointer user_data)1171 list__catalog_buffer_ready_cb (void     **buffer,
1172 			       gsize      count,
1173 			       GError    *error,
1174 			       gpointer   user_data)
1175 {
1176 	ListData *list_data = user_data;
1177 
1178 	if ((error == NULL) && (*buffer != NULL)) {
1179 		list_data->catalog = gth_catalog_new_from_data (*buffer,  count, &error);
1180 		if (list_data->catalog == NULL) {
1181 			gth_catalog_list_done (list_data, error);
1182 			return;
1183 		}
1184 
1185 		list_data->current_file = list_data->catalog->priv->file_list;
1186 		if (list_data->current_file == NULL) {
1187 			gth_catalog_list_done (list_data, NULL);
1188 			return;
1189 		}
1190 
1191 		g_file_query_info_async ((GFile *) list_data->current_file->data,
1192 					 list_data->attributes,
1193 					 0,
1194 					 G_PRIORITY_DEFAULT,
1195 					 list_data->cancellable,
1196 					 catalog_file_info_ready_cb,
1197 					 list_data);
1198 	}
1199 	else
1200 		gth_catalog_list_done (list_data, error);
1201 }
1202 
1203 
1204 void
gth_catalog_list_async(GFile * file,const char * attributes,GCancellable * cancellable,CatalogReadyCallback ready_func,gpointer user_data)1205 gth_catalog_list_async (GFile                *file,
1206 			const char           *attributes,
1207 			GCancellable         *cancellable,
1208 			CatalogReadyCallback  ready_func,
1209 			gpointer              user_data)
1210 {
1211 	ListData *list_data;
1212 
1213 	list_data = g_new0 (ListData, 1);
1214 	list_data->attributes = attributes;
1215 	list_data->list_ready_func = ready_func;
1216 	list_data->user_data = user_data;
1217 	list_data->cancellable = _g_object_ref (cancellable);
1218 
1219 	_g_file_load_async (file,
1220 			    G_PRIORITY_DEFAULT,
1221 			    list_data->cancellable,
1222 			    list__catalog_buffer_ready_cb,
1223 			    list_data);
1224 }
1225 
1226