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, °rees);
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, °rees);
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