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 <stdlib.h>
25 #include "glib-utils.h"
26 #include "gth-duplicable.h"
27 #include "gth-metadata.h"
28 #include "gth-file-data.h"
29 #include "gth-string-list.h"
30 
31 
32 const char *FileDataDigitalizationTags[] = {
33 	"Exif::Photo::DateTimeOriginal",
34 	"Xmp::exif::DateTimeOriginal",
35 	"Exif::Photo::DateTimeDigitized",
36 	"Xmp::exif::DateTimeDigitized",
37 	"Xmp::xmp::CreateDate",
38 	"Xmp::photoshop::DateCreated",
39 	"Xmp::xmp::ModifyDate",
40 	"Xmp::xmp::MetadataDate",
41 	NULL
42 };
43 
44 
45 struct _GthFileDataPrivate {
46 	GTimeVal  ctime;   /* creation time */
47 	GTimeVal  mtime;   /* modification time */
48 	GTimeVal  dtime;   /* digitalization time */
49 	char     *sort_key;
50 };
51 
52 
53 static void gth_file_data_gth_duplicable_interface_init (GthDuplicableInterface *iface);
54 
55 
G_DEFINE_TYPE_WITH_CODE(GthFileData,gth_file_data,G_TYPE_OBJECT,G_ADD_PRIVATE (GthFileData)G_IMPLEMENT_INTERFACE (GTH_TYPE_DUPLICABLE,gth_file_data_gth_duplicable_interface_init))56 G_DEFINE_TYPE_WITH_CODE (GthFileData,
57 			 gth_file_data,
58 			 G_TYPE_OBJECT,
59 			 G_ADD_PRIVATE (GthFileData)
60 			 G_IMPLEMENT_INTERFACE (GTH_TYPE_DUPLICABLE,
61 						gth_file_data_gth_duplicable_interface_init))
62 
63 
64 static void
65 gth_file_data_finalize (GObject *obj)
66 {
67 	GthFileData *self = GTH_FILE_DATA (obj);
68 
69 	_g_object_unref (self->file);
70 	_g_object_unref (self->info);
71 	g_free (self->priv->sort_key);
72 
73 	G_OBJECT_CLASS (gth_file_data_parent_class)->finalize (obj);
74 }
75 
76 
77 static void
gth_file_data_class_init(GthFileDataClass * klass)78 gth_file_data_class_init (GthFileDataClass *klass)
79 {
80 	G_OBJECT_CLASS (klass)->finalize = gth_file_data_finalize;
81 }
82 
83 
84 static GObject *
gth_file_data_real_duplicate(GthDuplicable * base)85 gth_file_data_real_duplicate (GthDuplicable *base)
86 {
87 	return (GObject *) gth_file_data_dup ((GthFileData*) base);
88 }
89 
90 
91 static void
gth_file_data_gth_duplicable_interface_init(GthDuplicableInterface * iface)92 gth_file_data_gth_duplicable_interface_init (GthDuplicableInterface *iface)
93 {
94 	iface->duplicate = gth_file_data_real_duplicate;
95 }
96 
97 
98 static void
gth_file_data_init(GthFileData * self)99 gth_file_data_init (GthFileData *self)
100 {
101 	self->priv = gth_file_data_get_instance_private (self);
102 	self->priv->dtime.tv_sec = 0;
103 	self->priv->sort_key = NULL;
104 	self->file = NULL;
105 	self->info = NULL;
106 }
107 
108 
109 GthFileData *
gth_file_data_new(GFile * file,GFileInfo * info)110 gth_file_data_new (GFile     *file,
111 		   GFileInfo *info)
112 {
113 	GthFileData *self;
114 
115 	self = g_object_new (GTH_TYPE_FILE_DATA, NULL);
116 	gth_file_data_set_file (self, file);
117 	gth_file_data_set_info (self, info);
118 
119 	return self;
120 }
121 
122 
123 GthFileData *
gth_file_data_new_for_uri(const char * uri,const char * mime_type)124 gth_file_data_new_for_uri (const char *uri,
125 			   const char *mime_type)
126 {
127 	GFile       *file;
128 	GthFileData *file_data;
129 
130 	file = g_file_new_for_uri (uri);
131 	file_data = gth_file_data_new (file, NULL);
132 	gth_file_data_set_mime_type (file_data, mime_type);
133 
134 	g_object_unref (file);
135 
136 	return file_data;
137 }
138 
139 
140 GthFileData *
gth_file_data_dup(GthFileData * self)141 gth_file_data_dup (GthFileData *self)
142 {
143 	GthFileData *file;
144 
145 	if (self == NULL)
146 		return NULL;
147 
148 	file = g_object_new (GTH_TYPE_FILE_DATA, NULL);
149 	file->file = g_file_dup (self->file);
150 	file->info = g_file_info_dup (self->info);
151 
152 	return file;
153 }
154 
155 
156 void
gth_file_data_set_file(GthFileData * self,GFile * file)157 gth_file_data_set_file (GthFileData *self,
158 			GFile       *file)
159 {
160 	if (file != NULL)
161 		g_object_ref (file);
162 
163 	if (self->file != NULL) {
164 		g_object_unref (self->file);
165 		self->file = NULL;
166 	}
167 
168 	if (file != NULL)
169 		self->file = file;
170 }
171 
172 
173 void
gth_file_data_set_info(GthFileData * self,GFileInfo * info)174 gth_file_data_set_info (GthFileData *self,
175 			GFileInfo   *info)
176 {
177 	if (info != NULL)
178 		g_object_ref (info);
179 
180 	if (self->info != NULL)
181 		g_object_unref (self->info);
182 
183 	if (info != NULL)
184 		self->info = info;
185 	else
186 		self->info = g_file_info_new ();
187 
188 	self->priv->dtime.tv_sec = 0;
189 }
190 
191 
192 void
gth_file_data_set_mime_type(GthFileData * self,const char * mime_type)193 gth_file_data_set_mime_type (GthFileData *self,
194 			     const char  *mime_type)
195 {
196 	if (mime_type != NULL) {
197 		g_file_info_set_content_type (self->info, _g_str_get_static (mime_type));
198 		g_file_info_set_attribute_string (self->info, G_FILE_ATTRIBUTE_STANDARD_CONTENT_TYPE, _g_str_get_static (mime_type));
199 		g_file_info_set_attribute_string (self->info, G_FILE_ATTRIBUTE_STANDARD_FAST_CONTENT_TYPE, _g_str_get_static (mime_type));
200 	}
201 }
202 
203 
204 const char *
gth_file_data_get_mime_type(GthFileData * self)205 gth_file_data_get_mime_type (GthFileData *self)
206 {
207 	const char *content_type;
208 
209 	if (self->info == NULL)
210 		return NULL;
211 
212 	content_type = g_file_info_get_attribute_string (self->info, G_FILE_ATTRIBUTE_STANDARD_CONTENT_TYPE);
213 	if (content_type == NULL)
214 		content_type = g_file_info_get_attribute_string (self->info, G_FILE_ATTRIBUTE_STANDARD_FAST_CONTENT_TYPE);
215 
216 	if (content_type == NULL) {
217 		char *filename;
218 
219 		if (self->file == NULL)
220 			return NULL;
221 
222 		filename = g_file_get_basename (self->file);
223 		if (filename == NULL)
224 			return NULL;
225 
226 		content_type = _g_content_type_guess_from_name (filename);
227 		g_file_info_set_attribute_string (self->info, G_FILE_ATTRIBUTE_STANDARD_CONTENT_TYPE, content_type);
228 
229 		g_free (filename);
230 	}
231 
232 	return _g_str_get_static (content_type);
233 }
234 
235 
236 const char *
gth_file_data_get_mime_type_from_content(GthFileData * self,GCancellable * cancellable)237 gth_file_data_get_mime_type_from_content (GthFileData  *self,
238 					  GCancellable *cancellable)
239 {
240 	const char *content_type;
241 
242 	if (self->info == NULL)
243 		return NULL;
244 
245 	content_type = g_file_info_get_attribute_string (self->info, G_FILE_ATTRIBUTE_STANDARD_CONTENT_TYPE);
246 	if (content_type == NULL) {
247 		GInputStream *istream;
248 		GError       *error = NULL;
249 
250 		if (self->file == NULL)
251 			return NULL;
252 
253 		istream = (GInputStream *) g_file_read (self->file, cancellable, &error);
254 		if (istream == NULL) {
255 			g_warning ("%s", error->message);
256 			g_clear_error (&error);
257 			return NULL;
258 		}
259 
260 		content_type = _g_content_type_get_from_stream (istream, self->file, cancellable, &error);
261 		g_file_info_set_attribute_string (self->info, G_FILE_ATTRIBUTE_STANDARD_CONTENT_TYPE, content_type);
262 
263 		g_object_unref (istream);
264 	}
265 
266 	return _g_str_get_static (content_type);
267 }
268 
269 
270 const char *
gth_file_data_get_filename_sort_key(GthFileData * self)271 gth_file_data_get_filename_sort_key (GthFileData *self)
272 {
273 	if (self->info == NULL)
274 		return NULL;
275 
276 	if (self->priv->sort_key == NULL)
277 		self->priv->sort_key = g_utf8_collate_key_for_filename (g_file_info_get_display_name (self->info), -1);
278 
279 	return self->priv->sort_key;
280 }
281 
282 
283 time_t
gth_file_data_get_mtime(GthFileData * self)284 gth_file_data_get_mtime (GthFileData *self)
285 {
286 	g_file_info_get_modification_time (self->info, &self->priv->mtime);
287 	return (time_t) self->priv->mtime.tv_sec;
288 }
289 
290 
291 GTimeVal *
gth_file_data_get_modification_time(GthFileData * self)292 gth_file_data_get_modification_time (GthFileData *self)
293 {
294 	g_file_info_get_modification_time (self->info, &self->priv->mtime);
295 	return &self->priv->mtime;
296 }
297 
298 
299 GTimeVal *
gth_file_data_get_creation_time(GthFileData * self)300 gth_file_data_get_creation_time (GthFileData *self)
301 {
302 	self->priv->ctime.tv_sec = g_file_info_get_attribute_uint64 (self->info, G_FILE_ATTRIBUTE_TIME_CREATED);
303 	self->priv->ctime.tv_usec = g_file_info_get_attribute_uint32 (self->info, G_FILE_ATTRIBUTE_TIME_CREATED_USEC);
304 	return &self->priv->ctime;
305 }
306 
307 
308 gboolean
gth_file_data_get_digitalization_time(GthFileData * self,GTimeVal * _time)309 gth_file_data_get_digitalization_time (GthFileData *self,
310 				       GTimeVal    *_time)
311 {
312 	int i;
313 
314 	if (self->priv->dtime.tv_sec != 0) {
315 		*_time = self->priv->dtime;
316 		return TRUE;
317 	}
318 
319 	for (i = 0; FileDataDigitalizationTags[i] != NULL; i++) {
320 		GthMetadata *m;
321 
322 		m = (GthMetadata *) g_file_info_get_attribute_object (self->info, FileDataDigitalizationTags[i]);
323 		if (m == NULL)
324 			continue;
325 
326 		if (! _g_time_val_from_exif_date (gth_metadata_get_raw (m), &self->priv->dtime))
327 			continue;
328 
329 		*_time = self->priv->dtime;
330 		return TRUE;
331 	}
332 
333 	return FALSE;
334 }
335 
336 
337 gboolean
gth_file_data_is_readable(GthFileData * self)338 gth_file_data_is_readable (GthFileData *self)
339 {
340 	return g_file_info_get_attribute_boolean (self->info, G_FILE_ATTRIBUTE_ACCESS_CAN_READ);
341 }
342 
343 
344 void
gth_file_data_update_info(GthFileData * fd,const char * attributes)345 gth_file_data_update_info (GthFileData *fd,
346 			   const char  *attributes)
347 {
348 	if (attributes == NULL)
349 		attributes = GFILE_STANDARD_ATTRIBUTES;
350 	if (fd->info != NULL)
351 		g_object_unref (fd->info);
352 
353 	fd->info = g_file_query_info (fd->file, attributes, G_FILE_QUERY_INFO_NONE, NULL, NULL);
354 
355 	if (fd->info == NULL)
356 		fd->info = g_file_info_new ();
357 
358 	fd->priv->dtime.tv_sec = 0;
359 }
360 
361 
362 void
gth_file_data_update_mime_type(GthFileData * fd,gboolean fast)363 gth_file_data_update_mime_type (GthFileData *fd,
364 				gboolean     fast)
365 {
366 	gth_file_data_set_mime_type (fd, _g_file_query_mime_type (fd->file, fast || ! g_file_is_native (fd->file)));
367 }
368 
369 
370 void
gth_file_data_update_all(GthFileData * fd,gboolean fast)371 gth_file_data_update_all (GthFileData *fd,
372 			  gboolean     fast)
373 {
374 	gth_file_data_update_info (fd, NULL);
375 	gth_file_data_update_mime_type (fd, fast);
376 }
377 
378 
379 GList*
gth_file_data_list_dup(GList * list)380 gth_file_data_list_dup (GList *list)
381 {
382 	GList *result = NULL;
383 	GList *scan;
384 
385 	for (scan = list; scan; scan = scan->next) {
386 		GthFileData *file_data = scan->data;
387 
388 		result = g_list_prepend (result, gth_file_data_dup (file_data));
389 	}
390 
391 	return g_list_reverse (result);
392 }
393 
394 
395 GList*
gth_file_data_list_from_uri_list(GList * list)396 gth_file_data_list_from_uri_list (GList *list)
397 {
398 	GList *result = NULL;
399 	GList *scan;
400 
401 	for (scan = list; scan; scan = scan->next) {
402 		char  *uri = scan->data;
403 		GFile *file;
404 
405 		file = g_file_new_for_uri (uri);
406 		result = g_list_prepend (result, gth_file_data_new (file, NULL));
407 		g_object_unref (file);
408 	}
409 
410 	return g_list_reverse (result);
411 }
412 
413 
414 GList*
gth_file_data_list_to_uri_list(GList * list)415 gth_file_data_list_to_uri_list (GList *list)
416 {
417 	GList *result = NULL;
418 	GList *scan;
419 
420 	for (scan = list; scan; scan = scan->next) {
421 		GthFileData *file = scan->data;
422 		result = g_list_prepend (result, g_file_get_uri (file->file));
423 	}
424 
425 	return g_list_reverse (result);
426 }
427 
428 
429 GList *
gth_file_data_list_to_file_list(GList * list)430 gth_file_data_list_to_file_list (GList *list)
431 {
432 	GList *result = NULL;
433 	GList *scan;
434 
435 	for (scan = list; scan; scan = scan->next) {
436 		GthFileData *file = scan->data;
437 		result = g_list_prepend (result, g_file_dup (file->file));
438 	}
439 
440 	return g_list_reverse (result);
441 }
442 
443 
444 GList *
gth_file_data_list_find_file(GList * list,GFile * file)445 gth_file_data_list_find_file (GList *list,
446 			      GFile *file)
447 {
448 	GList *scan;
449 
450 	for (scan = list; scan; scan = scan->next) {
451 		GthFileData *file_data = scan->data;
452 
453 		if (g_file_equal (file_data->file, file))
454 			return scan;
455 	}
456 
457 	return NULL;
458 }
459 
460 
461 GList *
gth_file_data_list_find_uri(GList * list,const char * uri)462 gth_file_data_list_find_uri (GList      *list,
463 			     const char *uri)
464 {
465 	GList *scan;
466 
467 	for (scan = list; scan; scan = scan->next) {
468 		GthFileData *file = scan->data;
469 		char    *file_uri;
470 
471 		file_uri = g_file_get_uri (file->file);
472 		if (strcmp (file_uri, uri) == 0) {
473 			g_free (file_uri);
474 			return scan;
475 		}
476 		g_free (file_uri);
477 	}
478 
479 	return NULL;
480 }
481 
482 
483 typedef struct {
484 	GthFileData     *file_data;
485 	GthFileDataFunc  ready_func;
486 	gpointer         user_data;
487 	GError          *error;
488 	guint            id;
489 } ReadyData;
490 
491 
492 static gboolean
exec_ready_func(gpointer user_data)493 exec_ready_func (gpointer user_data)
494 {
495 	ReadyData *data = user_data;
496 
497 	g_source_remove (data->id);
498 	data->ready_func (data->file_data, data->error, data->user_data);
499 
500 	_g_object_unref (data->file_data);
501 	g_free (data);
502 
503 	return FALSE;
504 }
505 
506 
507 void
gth_file_data_ready_with_error(GthFileData * file_data,GthFileDataFunc ready_func,gpointer user_data,GError * error)508 gth_file_data_ready_with_error (GthFileData     *file_data,
509 				GthFileDataFunc  ready_func,
510 				gpointer         user_data,
511 				GError          *error)
512 {
513 	ReadyData *data;
514 
515 	data = g_new0 (ReadyData, 1);
516 	if (file_data != NULL)
517 		data->file_data = g_object_ref (file_data);
518 	data->ready_func = ready_func;
519 	data->user_data = user_data;
520 	data->error = error;
521 	data->id = g_idle_add (exec_ready_func, data);
522 }
523 
524 
525 char *
gth_file_data_get_attribute_as_string(GthFileData * file_data,const char * id)526 gth_file_data_get_attribute_as_string (GthFileData *file_data,
527 				       const char  *id)
528 {
529 	GObject *obj;
530 	char    *value = NULL;
531 
532 	switch (g_file_info_get_attribute_type (file_data->info, id)) {
533 	case G_FILE_ATTRIBUTE_TYPE_OBJECT:
534 		obj = g_file_info_get_attribute_object (file_data->info, id);
535 		if (GTH_IS_METADATA (obj)) {
536 			switch (gth_metadata_get_data_type (GTH_METADATA (obj))) {
537 			case GTH_METADATA_TYPE_STRING:
538 				if (strcmp (id, "general::rating") == 0) {
539 					int n = atoi (gth_metadata_get_formatted (GTH_METADATA (obj)));
540 					if ((n >= 0) && (n <= 5)) {
541 						GString *str = g_string_new ("");
542 						int      i;
543 						for (i = 1; i <= n; i++)
544 							g_string_append (str, "⭐");
545 						value = g_string_free (str, FALSE);
546 					}
547 				}
548 				if (value == NULL)
549 					value = g_strdup (gth_metadata_get_formatted (GTH_METADATA (obj)));
550 				break;
551 			case GTH_METADATA_TYPE_STRING_LIST:
552 				value = gth_string_list_join (GTH_STRING_LIST (gth_metadata_get_string_list (GTH_METADATA (obj))), " ");
553 				break;
554 			}
555 		}
556 		else if (GTH_IS_STRING_LIST (obj))
557 			value = gth_string_list_join (GTH_STRING_LIST (obj), " ");
558 		else
559 			value = g_file_info_get_attribute_as_string (file_data->info, id);
560 		break;
561 	default:
562 		value = g_file_info_get_attribute_as_string (file_data->info, id);
563 		break;
564 	}
565 
566 	return value;
567 }
568 
569 
570 GFileInfo *
gth_file_data_list_get_common_info(GList * file_data_list,const char * attribtues)571 gth_file_data_list_get_common_info (GList      *file_data_list,
572 				    const char *attribtues)
573 {
574 	GFileInfo  *info;
575 	char      **attributes_v;
576 	int         i;
577 
578 	info = g_file_info_new ();
579 
580 	if (file_data_list == NULL)
581 		return info;
582 
583 	g_file_info_copy_into (((GthFileData *) file_data_list->data)->info, info);
584 
585 	attributes_v = g_strsplit (attribtues, ",", -1);
586 	for (i = 0; attributes_v[i] != NULL; i++) {
587 		char  *attribute = attributes_v[i];
588 		char  *first_value;
589 		GList *scan;
590 
591 		first_value = gth_file_data_get_attribute_as_string ((GthFileData *) file_data_list->data, attribute);
592 		for (scan = file_data_list->next; (first_value != NULL) && scan; scan = scan->next) {
593 			GthFileData *file_data = scan->data;
594 			char        *value;
595 
596 			value = gth_file_data_get_attribute_as_string (file_data, attribute);
597 			if (g_strcmp0 (first_value, value) != 0) {
598 				g_free (first_value);
599 				first_value = NULL;
600 			}
601 
602 			g_free (value);
603 		}
604 
605 		if (first_value == NULL)
606 			g_file_info_remove_attribute (info, attribute);
607 
608 		g_free (first_value);
609 	}
610 
611 	g_strfreev (attributes_v);
612 
613 	return info;
614 }
615 
616 
617 gboolean
gth_file_data_attribute_equal(GthFileData * file_data,const char * attribute,const char * value)618 gth_file_data_attribute_equal (GthFileData *file_data,
619 			       const char  *attribute,
620 			       const char  *value)
621 {
622 	char     *v;
623 	gboolean  result;
624 
625 	/* a NULL pointer is equal to an empty string. */
626 
627 	if (g_strcmp0 (value, "") == 0)
628 		value = NULL;
629 
630 	v = gth_file_data_get_attribute_as_string (file_data, attribute);
631 	if (g_strcmp0 (v, "") == 0) {
632 		g_free (v);
633 		v = NULL;
634 	}
635 
636 	result = g_strcmp0 (v, value) == 0;
637 
638 	g_free (v);
639 
640 	return result;
641 }
642 
643 
644 gboolean
gth_file_data_attribute_equal_int(GthFileData * file_data,const char * attribute,const char * value)645 gth_file_data_attribute_equal_int (GthFileData *file_data,
646 				   const char  *attribute,
647 				   const char  *value)
648 {
649 	char     *v;
650 	gboolean  result;
651 
652 	/* a NULL pointer or an empty string is equal to 0. */
653 
654 	if ((g_strcmp0 (value, "") == 0) || (value == NULL))
655 		value = "0";
656 
657 	v = gth_file_data_get_attribute_as_string (file_data, attribute);
658 	if ((g_strcmp0 (v, "") == 0) || (v == NULL)) {
659 		g_free (v);
660 		v = g_strdup ("0");
661 	}
662 
663 	result = g_strcmp0 (v, value) == 0;
664 
665 	g_free (v);
666 
667 	return result;
668 }
669 
670 
671 gboolean
gth_file_data_attribute_equal_string_list(GthFileData * file_data,const char * attribute,GthStringList * value)672 gth_file_data_attribute_equal_string_list (GthFileData    *file_data,
673 					   const char     *attribute,
674 					   GthStringList  *value)
675 {
676 	GthStringList *list;
677 	GObject       *obj;
678 
679 	list = NULL;
680 	obj = g_file_info_get_attribute_object (file_data->info, attribute);
681 	if (GTH_IS_METADATA (obj))
682 		list = gth_metadata_get_string_list (GTH_METADATA (obj));
683 	else if (GTH_IS_STRING_LIST (obj))
684 		list = (GthStringList *) obj;
685 
686 	return gth_string_list_equal (list, value);
687 }
688