1 /*
2  * Copyright (C) 2009, Nokia <ivan.frade@nokia.com>
3  *
4  * This library is free software; you can redistribute it and/or
5  * modify it under the terms of the GNU General Public
6  * License as published by the Free Software Foundation; either
7  * version 2 of the License, or (at your option) any later version.
8  *
9  * This library is distributed in the hope that it will be useful,
10  * but WITHOUT ANY WARRANTY; without even the implied warranty of
11  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
12  * General Public License for more details.
13  *
14  * You should have received a copy of the GNU General Public
15  * License along with this library; if not, write to the
16  * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
17  * Boston, MA  02110-1301, USA.
18  *
19  * Authors: Philip Van Hoof <philip@codeminded.be>
20  */
21 
22 #include "config-miners.h"
23 
24 #include <locale.h>
25 #include <string.h>
26 #include <math.h>
27 
28 #include <exempi/xmp.h>
29 #include <exempi/xmpconsts.h>
30 
31 #include <glib-object.h>
32 #include <gio/gio.h>
33 
34 #include <libtracker-miners-common/tracker-common.h>
35 #include <libtracker-sparql/tracker-ontologies.h>
36 #include <libtracker-sparql/tracker-sparql.h>
37 
38 #include "tracker-writeback-file.h"
39 
40 #define TRACKER_TYPE_WRITEBACK_XMP (tracker_writeback_xmp_get_type ())
41 
42 typedef struct TrackerWritebackXMP TrackerWritebackXMP;
43 typedef struct TrackerWritebackXMPClass TrackerWritebackXMPClass;
44 
45 struct TrackerWritebackXMP {
46 	TrackerWritebackFile parent_instance;
47 };
48 
49 struct TrackerWritebackXMPClass {
50 	TrackerWritebackFileClass parent_class;
51 };
52 
53 static GType                tracker_writeback_xmp_get_type     (void) G_GNUC_CONST;
54 static gboolean             writeback_xmp_update_file_metadata (TrackerWritebackFile     *writeback_file,
55                                                                 GFile                    *file,
56                                                                 GPtrArray                *values,
57                                                                 TrackerSparqlConnection  *connection,
58                                                                 GCancellable             *cancellable,
59                                                                 GError                  **error);
60 static const gchar * const *writeback_xmp_content_types        (TrackerWritebackFile     *writeback_file);
61 
62 G_DEFINE_DYNAMIC_TYPE (TrackerWritebackXMP, tracker_writeback_xmp, TRACKER_TYPE_WRITEBACK_FILE);
63 
64 static void
tracker_writeback_xmp_class_init(TrackerWritebackXMPClass * klass)65 tracker_writeback_xmp_class_init (TrackerWritebackXMPClass *klass)
66 {
67 	TrackerWritebackFileClass *writeback_file_class = TRACKER_WRITEBACK_FILE_CLASS (klass);
68 
69 	xmp_init ();
70 
71 	writeback_file_class->update_file_metadata = writeback_xmp_update_file_metadata;
72 	writeback_file_class->content_types = writeback_xmp_content_types;
73 }
74 
75 static void
tracker_writeback_xmp_class_finalize(TrackerWritebackXMPClass * klass)76 tracker_writeback_xmp_class_finalize (TrackerWritebackXMPClass *klass)
77 {
78 	xmp_terminate ();
79 }
80 
81 static void
tracker_writeback_xmp_init(TrackerWritebackXMP * wbx)82 tracker_writeback_xmp_init (TrackerWritebackXMP *wbx)
83 {
84 }
85 
86 static const gchar * const *
writeback_xmp_content_types(TrackerWritebackFile * wbf)87 writeback_xmp_content_types (TrackerWritebackFile *wbf)
88 {
89 	static const gchar *content_types[] = {
90 		"image/png",   /* .png files */
91 		"sketch/png",  /* .sketch.png files on Maemo*/
92 		"image/jpeg",  /* .jpg & .jpeg files */
93 		"image/tiff",  /* .tiff & .tif files */
94 		"video/mp4",   /* .mp4 files */
95 		"video/3gpp",  /* .3gpp files */
96                 "image/gif",   /* .gif files */
97 		NULL
98 	};
99 
100 	/* "application/pdf"                  .pdf files
101 	   "application/rdf+xml"              .xmp files
102 	   "application/postscript"           .ps files
103 	   "application/x-shockwave-flash"    .swf files
104 	   "video/quicktime"                  .mov files
105 	   "video/mpeg"                       .mpeg & .mpg files
106 	   "audio/mpeg"                       .mp3, etc files */
107 
108 	return content_types;
109 }
110 
111 static gboolean
writeback_xmp_update_file_metadata(TrackerWritebackFile * wbf,GFile * file,GPtrArray * values,TrackerSparqlConnection * connection,GCancellable * cancellable,GError ** error)112 writeback_xmp_update_file_metadata (TrackerWritebackFile     *wbf,
113                                     GFile                    *file,
114                                     GPtrArray                *values,
115                                     TrackerSparqlConnection  *connection,
116                                     GCancellable             *cancellable,
117                                     GError                  **error)
118 {
119 	gchar *path;
120 	guint n;
121 	XmpFilePtr xmp_files;
122 	XmpPtr xmp;
123 #ifdef DEBUG_XMP
124 	XmpStringPtr str;
125 #endif
126 	GString *keywords = NULL;
127 	const gchar *urn = NULL;
128 
129 	path = g_file_get_path (file);
130 
131 	xmp_files = xmp_files_open_new (path, XMP_OPEN_FORUPDATE);
132 
133 	if (!xmp_files) {
134 		g_set_error (error,
135 		             G_IO_ERROR,
136 		             G_IO_ERROR_FAILED,
137 		             "Can't open '%s' for update with Exempi (Exempi error code = %d)",
138 		             path,
139 		             xmp_get_error ());
140 		g_free (path);
141 		return FALSE;
142 	}
143 
144 	xmp = xmp_files_get_new_xmp (xmp_files);
145 
146 	if (!xmp) {
147 		xmp = xmp_new_empty ();
148 	}
149 
150 #ifdef DEBUG_XMP
151 	str = xmp_string_new ();
152 	g_print ("\nBEFORE: ---- \n");
153 	xmp_serialize_and_format (xmp, str, 0, 0, "\n", "\t", 1);
154 	g_print ("%s\n", xmp_string_cstr (str));
155 	xmp_string_free (str);
156 #endif
157 
158 	for (n = 0; n < values->len; n++) {
159 		const GStrv row = g_ptr_array_index (values, n);
160 
161 		urn = row[1]; /* The urn is at 1 */
162 
163 		if (g_strcmp0 (row[2], TRACKER_PREFIX_NIE "title") == 0) {
164 			xmp_delete_property (xmp, NS_EXIF, "Title");
165 			xmp_set_property (xmp, NS_EXIF, "Title", row[3], 0);
166 			xmp_delete_property (xmp, NS_DC, "title");
167 			xmp_set_property (xmp, NS_DC, "title", row[3], 0);
168 		}
169 
170 		if (g_strcmp0 (row[2], TRACKER_PREFIX_NCO "creator") == 0) {
171 			TrackerSparqlCursor *cursor;
172 			GError *error = NULL;
173 			gchar *query;
174 
175 			query = g_strdup_printf ("SELECT ?fullname { "
176 			                         "  <%s> nco:fullname ?fullname "
177 			                         "}", row[3]);
178 			cursor = tracker_sparql_connection_query (connection, query, NULL, &error);
179 			g_free (query);
180 			if (!error) {
181 				while (tracker_sparql_cursor_next (cursor, NULL, NULL)) {
182 					xmp_delete_property (xmp, NS_DC, "creator");
183 					xmp_set_property (xmp, NS_DC, "creator",
184 					                  tracker_sparql_cursor_get_string (cursor, 0, NULL),
185 					                  0);
186 				}
187 			}
188 			g_object_unref (cursor);
189 			g_clear_error (&error);
190 		}
191 
192 		if (g_strcmp0 (row[2], TRACKER_PREFIX_NCO "contributor") == 0) {
193 			TrackerSparqlCursor *cursor;
194 			GError *error = NULL;
195 			gchar *query;
196 
197 			query = g_strdup_printf ("SELECT ?fullname { "
198 			                         "  <%s> nco:fullname ?fullname "
199 			                         "}", row[3]);
200 
201 			cursor = tracker_sparql_connection_query (connection, query, NULL, &error);
202 			g_free (query);
203 			if (!error) {
204 				while (tracker_sparql_cursor_next (cursor, NULL, NULL)) {
205 					xmp_delete_property (xmp, NS_DC, "contributor");
206 					xmp_set_property (xmp, NS_DC, "contributor", tracker_sparql_cursor_get_string (cursor, 0, NULL), 0);
207 				}
208 			}
209 			g_object_unref (cursor);
210 			g_clear_error (&error);
211 		}
212 
213 		if (g_strcmp0 (row[2], TRACKER_PREFIX_NIE "description") == 0) {
214 			xmp_delete_property (xmp, NS_DC, "description");
215 			xmp_set_property (xmp, NS_DC, "description", row[3], 0);
216 		}
217 
218 		if (g_strcmp0 (row[2], TRACKER_PREFIX_NIE "copyright") == 0) {
219 			xmp_delete_property (xmp, NS_EXIF, "Copyright");
220 			xmp_set_property (xmp, NS_EXIF, "Copyright", row[3], 0);
221 		}
222 
223 		if (g_strcmp0 (row[2], TRACKER_PREFIX_NIE "comment") == 0) {
224 			xmp_delete_property (xmp, NS_EXIF, "UserComment");
225 			xmp_set_property (xmp, NS_EXIF, "UserComment", row[3], 0);
226 		}
227 
228 		if (g_strcmp0 (row[2], TRACKER_PREFIX_NIE "keyword") == 0) {
229 			if (!keywords) {
230 				keywords = g_string_new (row[3]);
231 			} else {
232 				g_string_append_printf (keywords, ", %s", row[3]);
233 			}
234 		}
235 
236 
237 		if (g_strcmp0 (row[2], TRACKER_PREFIX_NAO "hasTag") == 0) {
238 			TrackerSparqlCursor *cursor;
239 			GError *error = NULL;
240 			gchar *query;
241 
242 			query = g_strdup_printf ("SELECT ?label { "
243 			                         "  <%s> nao:prefLabel ?label "
244 			                         "}", row[3]);
245 
246 			cursor = tracker_sparql_connection_query (connection, query, NULL, &error);
247 			g_free (query);
248 			if (!error) {
249 				while (tracker_sparql_cursor_next (cursor, NULL, NULL)) {
250 					if (!keywords) {
251 						keywords = g_string_new (tracker_sparql_cursor_get_string (cursor, 0, NULL));
252 					} else {
253 						g_string_append_printf (keywords, ", %s", tracker_sparql_cursor_get_string (cursor, 0, NULL));
254 					}
255 				}
256 			}
257 			g_object_unref (cursor);
258 			g_clear_error (&error);
259 		}
260 
261 		if (g_strcmp0 (row[2], TRACKER_PREFIX_NIE "contentCreated") == 0) {
262 			xmp_delete_property (xmp, NS_EXIF, "Date");
263 			xmp_set_property (xmp, NS_EXIF, "Date", row[3], 0);
264 			xmp_delete_property (xmp,  NS_DC, "date");
265 			xmp_set_property (xmp,  NS_DC, "date", row[3], 0);
266 		}
267 
268 		if (g_strcmp0 (row[2], TRACKER_PREFIX_NFO "orientation") == 0) {
269 
270 			xmp_delete_property (xmp, NS_EXIF, "Orientation");
271 
272 			if        (g_strcmp0 (row[3], TRACKER_PREFIX_NFO "orientation-top") == 0) {
273 				xmp_set_property (xmp, NS_EXIF, "Orientation", "top - left", 0);
274 			} else if (g_strcmp0 (row[3], TRACKER_PREFIX_NFO "orientation-top-mirror") == 0) {
275 				xmp_set_property (xmp, NS_EXIF, "Orientation", "top - right", 0);
276 			} else if (g_strcmp0 (row[3], TRACKER_PREFIX_NFO "orientation-bottom") == 0) {
277 				xmp_set_property (xmp, NS_EXIF, "Orientation", "bottom - left", 0);
278 			} else if (g_strcmp0 (row[3], TRACKER_PREFIX_NFO "orientation-bottom-mirror") == 0) {
279 				xmp_set_property (xmp, NS_EXIF, "Orientation", "bottom - right", 0);
280 			} else if (g_strcmp0 (row[3], TRACKER_PREFIX_NFO "orientation-left-mirror") == 0) {
281 				xmp_set_property (xmp, NS_EXIF, "Orientation", "left - top", 0);
282 			} else if (g_strcmp0 (row[3], TRACKER_PREFIX_NFO "orientation-right") == 0) {
283 				xmp_set_property (xmp, NS_EXIF, "Orientation", "right - top", 0);
284 			} else if (g_strcmp0 (row[3], TRACKER_PREFIX_NFO "orientation-right-mirror") == 0) {
285 					xmp_set_property (xmp, NS_EXIF, "Orientation", "right - bottom", 0);
286 			} else if (g_strcmp0 (row[3], TRACKER_PREFIX_NFO "orientation-left") == 0) {
287 				xmp_set_property (xmp, NS_EXIF, "Orientation", "left - bottom", 0);
288 			}
289 		}
290 
291 #ifdef SET_TYPICAL_CAMERA_FIELDS
292 		/* Default we don't do this, we shouldn't overwrite fields that are
293 		 * typically set by the camera itself. What do we know (better) than
294 		 * the actual camera did, anyway? Even if the user overwrites them in
295 		 * the RDF store ... (does he know what he's doing anyway?) */
296 
297 		if (g_strcmp0 (row[2], TRACKER_PREFIX_NMM "meteringMode") == 0) {
298 
299 			xmp_delete_property (xmp, NS_EXIF, "MeteringMode");
300 
301 			/* 0 = Unknown
302 			   1 = Average
303 			   2 = CenterWeightedAverage
304 			   3 = Spot
305 			   4 = MultiSpot
306 			   5 = Pattern
307 			   6 = Partial
308 			   255 = other  */
309 
310 			if        (g_strcmp0 (row[3], TRACKER_PREFIX_NMM "metering-mode-center-weighted-average") == 0) {
311 				xmp_set_property (xmp, NS_EXIF, "MeteringMode", "0", 0);
312 			} else if (g_strcmp0 (row[3], TRACKER_PREFIX_NMM "metering-mode-average") == 0) {
313 				xmp_set_property (xmp, NS_EXIF, "MeteringMode", "1", 0);
314 			} else if (g_strcmp0 (row[3], TRACKER_PREFIX_NMM "metering-mode-spot") == 0) {
315 				xmp_set_property (xmp, NS_EXIF, "MeteringMode", "3", 0);
316 			} else if (g_strcmp0 (row[3], TRACKER_PREFIX_NMM "metering-mode-multispot") == 0) {
317 				xmp_set_property (xmp, NS_EXIF, "MeteringMode", "4", 0);
318 			} else if (g_strcmp0 (row[3], TRACKER_PREFIX_NMM "metering-mode-pattern") == 0) {
319 				xmp_set_property (xmp, NS_EXIF, "MeteringMode", "5", 0);
320 			} else if (g_strcmp0 (row[3], TRACKER_PREFIX_NMM "metering-mode-partial") == 0) {
321 				xmp_set_property (xmp, NS_EXIF, "MeteringMode", "6", 0);
322 			} else {
323 				xmp_set_property (xmp, NS_EXIF, "MeteringMode", "255", 0);
324 			}
325 		}
326 
327 		if (g_strcmp0 (row[2], TRACKER_PREFIX_NMM "whiteBalance") == 0) {
328 
329 			xmp_delete_property (xmp, NS_EXIF, "WhiteBalance");
330 
331 			if (g_strcmp0 (row[3], TRACKER_PREFIX_NMM "white-balance-auto") == 0) {
332 				/* 0 = Auto white balance
333 				 * 1 = Manual white balance */
334 				xmp_set_property (xmp, NS_EXIF, "WhiteBalance", "0", 0);
335 			} else {
336 				xmp_set_property (xmp, NS_EXIF, "WhiteBalance", "1", 0);
337 			}
338 		}
339 
340 		if (g_strcmp0 (row[2], TRACKER_PREFIX_NMM "flash") == 0) {
341 
342 			xmp_delete_property (xmp, NS_EXIF, "Flash");
343 
344 			if (g_strcmp0 (row[3], TRACKER_PREFIX_NMM "flash-on") == 0) {
345 				/* 0 = Flash did not fire
346 				 * 1 = Flash fired */
347 				xmp_set_property (xmp, NS_EXIF, "Flash", "1", 0);
348 			} else {
349 				xmp_set_property (xmp, NS_EXIF, "Flash", "0", 0);
350 			}
351 		}
352 
353 
354 		/* TODO: Don't write row[3] as-is here, read xmp_specification.pdf,
355 		   page 84 (bottom). */
356 
357 		if (g_strcmp0 (row[2], TRACKER_PREFIX_NMM "focalLength") == 0) {
358 			xmp_delete_property (xmp, NS_EXIF, "FocalLength");
359 			xmp_set_property (xmp, NS_EXIF, "FocalLength", row[3], 0);
360 		}
361 
362 		if (g_strcmp0 (row[2], TRACKER_PREFIX_NMM "exposureTime") == 0) {
363 			xmp_delete_property (xmp, NS_EXIF, "ExposureTime");
364 			xmp_set_property (xmp, NS_EXIF, "ExposureTime", row[3], 0);
365 		}
366 
367 		if (g_strcmp0 (row[2], TRACKER_PREFIX_NMM "isoSpeed") == 0) {
368 			xmp_delete_property (xmp, NS_EXIF, "ISOSpeedRatings");
369 			xmp_set_property (xmp, NS_EXIF, "ISOSpeedRatings", row[3], 0);
370 		}
371 
372 		if (g_strcmp0 (row[2], TRACKER_PREFIX_NMM "fnumber") == 0) {
373 			xmp_delete_property (xmp, NS_EXIF, "FNumber");
374 			xmp_set_property (xmp, NS_EXIF, "FNumber", row[3], 0);
375 		}
376 
377 
378 		/* Totally deprecated: this uses nfo:Equipment nowadays */
379 		if (g_strcmp0 (row[2], TRACKER_PREFIX_NMM "camera") == 0) {
380 			gchar *work_on = g_strdup (row[3]);
381 			gchar *ptr = strchr (work_on, ' ');
382 
383 			if (ptr) {
384 
385 				*ptr = '\0';
386 				ptr++;
387 
388 				xmp_delete_property (xmp, NS_EXIF, "Make");
389 				xmp_set_property (xmp, NS_EXIF, "Make", work_on, 0);
390 				xmp_delete_property (xmp, NS_EXIF, "Model");
391 				xmp_set_property (xmp, NS_EXIF, "Model", ptr, 0);
392 			} else {
393 				xmp_delete_property (xmp, NS_EXIF, "Make");
394 				xmp_delete_property (xmp, NS_EXIF, "Model");
395 				xmp_set_property (xmp, NS_EXIF, "Model", work_on, 0);
396 			}
397 
398 			g_free (work_on);
399 		}
400 #endif /* SET_TYPICAL_CAMERA_FIELDS */
401 
402 		if (g_strcmp0 (row[2], TRACKER_PREFIX_NFO "heading") == 0) {
403 			xmp_delete_property (xmp, NS_EXIF, "GPSImgDirection");
404 			xmp_set_property (xmp, NS_EXIF, "GPSImgDirection", row[3], 0);
405 		}
406 	}
407 
408 	if (urn != NULL) {
409 		TrackerSparqlCursor *cursor;
410 		GError *error = NULL;
411 		gchar *query;
412 
413 		query = g_strdup_printf ("SELECT "
414 		                         "nco:locality (?addr) "
415 		                         "nco:region (?addr) "
416 		                         "nco:streetAddress (?addr) "
417 		                         "nco:country (?addr) "
418 		                         "slo:altitude (?loc) "
419 		                         "slo:longitude (?loc) "
420 		                         "slo:latitude (?loc) "
421 		                         "WHERE { <%s> slo:location ?loc . "
422 		                                 "?loc slo:postalAddress ?addr . }",
423 		                         urn);
424 
425 		cursor = tracker_sparql_connection_query (connection, query, NULL, &error);
426 		g_free (query);
427 		if (!error) {
428 			if (tracker_sparql_cursor_next (cursor, NULL, NULL)) {
429 				const gchar *city = NULL, *subl = NULL, *country = NULL,
430 				            *state = NULL, *altitude = NULL, *longitude = NULL,
431 				            *latitude = NULL;
432 
433 				if (tracker_sparql_cursor_get_value_type (cursor, 0) != TRACKER_SPARQL_VALUE_TYPE_UNBOUND) {
434 					city = tracker_sparql_cursor_get_string (cursor, 0, NULL);
435 				}
436 
437 				if (tracker_sparql_cursor_get_value_type (cursor, 1) != TRACKER_SPARQL_VALUE_TYPE_UNBOUND) {
438 					state = tracker_sparql_cursor_get_string (cursor, 1, NULL);
439 				}
440 
441 				if (tracker_sparql_cursor_get_value_type (cursor, 2) != TRACKER_SPARQL_VALUE_TYPE_UNBOUND) {
442 					subl = tracker_sparql_cursor_get_string (cursor, 2, NULL);
443 				}
444 
445 				if (tracker_sparql_cursor_get_value_type (cursor, 3) != TRACKER_SPARQL_VALUE_TYPE_UNBOUND) {
446 					country = tracker_sparql_cursor_get_string (cursor, 3, NULL);
447 				}
448 
449 				if (tracker_sparql_cursor_get_value_type (cursor, 4) != TRACKER_SPARQL_VALUE_TYPE_UNBOUND) {
450 					altitude = tracker_sparql_cursor_get_string (cursor, 4, NULL);
451 				}
452 
453 				if (tracker_sparql_cursor_get_value_type (cursor, 5) != TRACKER_SPARQL_VALUE_TYPE_UNBOUND) {
454 					longitude = tracker_sparql_cursor_get_string (cursor, 5, NULL);
455 				}
456 
457 				if (tracker_sparql_cursor_get_value_type (cursor, 6) != TRACKER_SPARQL_VALUE_TYPE_UNBOUND) {
458 					latitude = tracker_sparql_cursor_get_string (cursor, 6, NULL);
459 				}
460 
461 				/* TODO: A lot of these location fields are pretty vague and ambigious.
462 				 * We should go through them one by one and ensure that all of them are
463 				 * used sanely */
464 
465 				xmp_delete_property (xmp, NS_IPTC4XMP, "City");
466 				xmp_delete_property (xmp, NS_PHOTOSHOP, "City");
467 				if (city != NULL) {
468 					xmp_set_property (xmp, NS_IPTC4XMP, "City", city, 0);
469 					xmp_set_property (xmp, NS_PHOTOSHOP, "City", city, 0);
470 				}
471 
472 				xmp_delete_property (xmp, NS_IPTC4XMP, "State");
473 				xmp_delete_property (xmp, NS_IPTC4XMP, "Province");
474 				xmp_delete_property (xmp, NS_PHOTOSHOP, "State");
475 				if (state != NULL) {
476 					xmp_set_property (xmp, NS_IPTC4XMP, "State", state, 0);
477 					xmp_set_property (xmp, NS_IPTC4XMP, "Province", state, 0);
478 					xmp_set_property (xmp, NS_PHOTOSHOP, "State", state, 0);
479 				}
480 
481 				xmp_delete_property (xmp, NS_IPTC4XMP, "SubLocation");
482 				xmp_delete_property (xmp, NS_PHOTOSHOP, "Location");
483 				if (subl != NULL) {
484 					xmp_set_property (xmp, NS_IPTC4XMP, "SubLocation", subl, 0);
485 					xmp_set_property (xmp, NS_PHOTOSHOP, "Location", subl, 0);
486 				}
487 
488 				xmp_delete_property (xmp, NS_PHOTOSHOP, "Country");
489 				xmp_delete_property (xmp, NS_IPTC4XMP, "Country");
490 				xmp_delete_property (xmp, NS_IPTC4XMP, "PrimaryLocationName");
491 				xmp_delete_property (xmp, NS_IPTC4XMP, "CountryName");
492 				if (country != NULL) {
493 					xmp_set_property (xmp, NS_PHOTOSHOP, "Country", country, 0);
494 					xmp_set_property (xmp, NS_IPTC4XMP, "Country", country, 0);
495 					xmp_set_property (xmp, NS_IPTC4XMP, "PrimaryLocationName", country, 0);
496 					xmp_set_property (xmp, NS_IPTC4XMP, "CountryName", country, 0);
497 				}
498 
499 				xmp_delete_property (xmp, NS_EXIF, "GPSAltitude");
500 				if (altitude != NULL) {
501 					xmp_set_property (xmp, NS_EXIF, "GPSAltitude", altitude, 0);
502 				}
503 
504 				xmp_delete_property (xmp, NS_EXIF, "GPSLongitude");
505 				if (longitude != NULL) {
506 					double coord = atof (longitude);
507 					double degrees, minutes;
508 					gchar *val;
509 
510 					minutes = modf (coord, &degrees);
511 
512 					val = g_strdup_printf ("%3d,%f%c",
513 					                       (int) fabs(degrees),
514 					                       minutes,
515 					                       coord >= 0 ? 'E' : 'W');
516 
517 					xmp_set_property (xmp, NS_EXIF, "GPSLongitude", val, 0);
518 
519 					g_free (val);
520 				}
521 
522 				xmp_delete_property (xmp, NS_EXIF, "GPSLatitude");
523 				if (latitude != NULL) {
524 					double coord = atof (latitude);
525 					double degrees, minutes;
526 					gchar *val;
527 
528 					minutes = modf (coord, &degrees);
529 
530 					val = g_strdup_printf ("%3d,%f%c",
531 					                       (int) fabs(degrees),
532 					                       minutes,
533 					                       coord >= 0 ? 'N' : 'S');
534 
535 					xmp_set_property (xmp, NS_EXIF, "GPSLatitude", val, 0);
536 
537 					g_free (val);
538 				}
539 			}
540 		}
541 
542 		g_object_unref (cursor);
543 		g_clear_error (&error);
544 	}
545 
546 	if (keywords) {
547 		xmp_delete_property (xmp, NS_DC, "subject");
548 		xmp_set_property (xmp, NS_DC, "subject", keywords->str, 0);
549 		g_string_free (keywords, TRUE);
550 	}
551 
552 #ifdef DEBUG_XMP
553 	g_print ("\nAFTER: ---- \n");
554 	str = xmp_string_new ();
555 	xmp_serialize_and_format (xmp, str, 0, 0, "\n", "\t", 1);
556 	g_print ("%s\n", xmp_string_cstr (str));
557 	xmp_string_free (str);
558 	g_print ("\n --------- \n");
559 #endif
560 
561 	if (xmp_files_can_put_xmp (xmp_files, xmp)) {
562 		xmp_files_put_xmp (xmp_files, xmp);
563 	}
564 
565 	/* Note: We don't currently use XMP_CLOSE_SAFEUPDATE because it uses
566 	 * a hidden temporary file in the same directory, which is then
567 	 * renamed to the final name. This triggers two events:
568 	 *  - DELETE(A) + MOVE(.hidden->A)
569 	 * and we really don't want the first DELETE(A) here
570 	 */
571 	xmp_files_close (xmp_files, XMP_CLOSE_NOOPTION);
572 
573 	xmp_free (xmp);
574 	xmp_files_free (xmp_files);
575 	g_free (path);
576 
577 	return TRUE;
578 }
579 
580 TrackerWriteback *
writeback_module_create(GTypeModule * module)581 writeback_module_create (GTypeModule *module)
582 {
583 	tracker_writeback_xmp_register_type (module);
584 
585 	return g_object_new (TRACKER_TYPE_WRITEBACK_XMP, NULL);
586 }
587 
588 const gchar * const *
writeback_module_get_rdf_types(void)589 writeback_module_get_rdf_types (void)
590 {
591 	static const gchar *rdf_types[] = {
592 		TRACKER_PREFIX_NFO "Image",
593 		TRACKER_PREFIX_NFO "Audio",
594 		TRACKER_PREFIX_NFO "Video",
595 		NULL
596 	};
597 
598 	return rdf_types;
599 }
600