1 /*
2 * Copyright (C) 2008, 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 Lesser General Public
6 * License as published by the Free Software Foundation; either
7 * version 2.1 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 * Lesser General Public License for more details.
13 *
14 * You should have received a copy of the GNU Lesser 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
20 #include "config-miners.h"
21
22 #include <string.h>
23 #include <unistd.h>
24
25 #include <gmodule.h>
26 #include <glib/gi18n.h>
27 #include <gio/gio.h>
28
29 #include <gio/gunixoutputstream.h>
30 #include <gio/gunixinputstream.h>
31 #include <gio/gunixfdlist.h>
32
33 #include <libtracker-miners-common/tracker-common.h>
34
35 #include <libtracker-extract/tracker-extract.h>
36
37 #include "tracker-extract.h"
38 #include "tracker-main.h"
39
40 #ifdef THREAD_ENABLE_TRACE
41 #warning Main thread traces enabled
42 #endif /* THREAD_ENABLE_TRACE */
43
44 #define TRACKER_EXTRACT_GET_PRIVATE(obj) (tracker_extract_get_instance_private (TRACKER_EXTRACT (obj)))
45
46 G_DEFINE_QUARK (TrackerExtractError, tracker_extract_error)
47
48 #define DEADLINE_SECONDS 30
49
50 extern gboolean debug;
51
52 typedef struct {
53 gint extracted_count;
54 gint failed_count;
55 } StatisticsData;
56
57 typedef struct {
58 GHashTable *statistics_data;
59 GList *running_tasks;
60
61 /* used to maintain the running tasks
62 * and stats from different threads
63 */
64 GMutex task_mutex;
65
66 /* Thread pool for multi-threaded extractors */
67 GThreadPool *thread_pool;
68
69 /* module -> async queue hashtable
70 * for single-threaded extractors
71 */
72 GHashTable *single_thread_extractors;
73
74 gboolean disable_shutdown;
75 gboolean disable_summary_on_finalize;
76
77 gchar *force_module;
78
79 gint unhandled_count;
80 } TrackerExtractPrivate;
81
82 typedef struct {
83 TrackerExtract *extract;
84 GCancellable *cancellable;
85 GAsyncResult *res;
86 gchar *file;
87 gchar *mimetype;
88
89 TrackerMimetypeInfo *mimetype_handlers;
90
91 /* to be fed from mimetype_handlers */
92 TrackerExtractMetadataFunc cur_func;
93 GModule *cur_module;
94
95 guint signal_id;
96 guint timeout_id;
97 guint success : 1;
98 } TrackerExtractTask;
99
100 static void tracker_extract_finalize (GObject *object);
101 static void report_statistics (GObject *object);
102 static gboolean get_metadata (TrackerExtractTask *task);
103 static gboolean dispatch_task_cb (TrackerExtractTask *task);
104
105
G_DEFINE_TYPE_WITH_PRIVATE(TrackerExtract,tracker_extract,G_TYPE_OBJECT)106 G_DEFINE_TYPE_WITH_PRIVATE(TrackerExtract, tracker_extract, G_TYPE_OBJECT)
107
108 static void
109 tracker_extract_class_init (TrackerExtractClass *klass)
110 {
111 GObjectClass *object_class;
112
113 object_class = G_OBJECT_CLASS (klass);
114
115 object_class->finalize = tracker_extract_finalize;
116 }
117
118 static void
statistics_data_free(StatisticsData * data)119 statistics_data_free (StatisticsData *data)
120 {
121 g_slice_free (StatisticsData, data);
122 }
123
124 static void
tracker_extract_init(TrackerExtract * object)125 tracker_extract_init (TrackerExtract *object)
126 {
127 TrackerExtractPrivate *priv;
128
129 priv = TRACKER_EXTRACT_GET_PRIVATE (object);
130 priv->statistics_data = g_hash_table_new_full (NULL, NULL, NULL,
131 (GDestroyNotify) statistics_data_free);
132 priv->single_thread_extractors = g_hash_table_new (NULL, NULL);
133 priv->thread_pool = g_thread_pool_new ((GFunc) get_metadata,
134 NULL, 10, TRUE, NULL);
135
136 g_mutex_init (&priv->task_mutex);
137 }
138
139 static void
tracker_extract_finalize(GObject * object)140 tracker_extract_finalize (GObject *object)
141 {
142 TrackerExtractPrivate *priv;
143
144 priv = TRACKER_EXTRACT_GET_PRIVATE (object);
145
146 /* FIXME: Shutdown modules? */
147
148 g_hash_table_destroy (priv->single_thread_extractors);
149 g_thread_pool_free (priv->thread_pool, TRUE, FALSE);
150
151 if (!priv->disable_summary_on_finalize) {
152 report_statistics (object);
153 }
154
155 g_hash_table_destroy (priv->statistics_data);
156
157 g_mutex_clear (&priv->task_mutex);
158
159 G_OBJECT_CLASS (tracker_extract_parent_class)->finalize (object);
160 }
161
162 static void
report_statistics(GObject * object)163 report_statistics (GObject *object)
164 {
165 TrackerExtractPrivate *priv;
166 GHashTableIter iter;
167 gpointer key, value;
168
169 priv = TRACKER_EXTRACT_GET_PRIVATE (object);
170
171 g_mutex_lock (&priv->task_mutex);
172
173 g_message ("--------------------------------------------------");
174 g_message ("Statistics:");
175
176 g_hash_table_iter_init (&iter, priv->statistics_data);
177
178 while (g_hash_table_iter_next (&iter, &key, &value)) {
179 GModule *module = key;
180 StatisticsData *data = value;
181
182 if (data->extracted_count > 0 || data->failed_count > 0) {
183 const gchar *name, *name_without_path;
184
185 name = g_module_name (module);
186 name_without_path = strrchr (name, G_DIR_SEPARATOR) + 1;
187
188 g_message (" Module:'%s', extracted:%d, failures:%d",
189 name_without_path,
190 data->extracted_count,
191 data->failed_count);
192 }
193 }
194
195 g_message ("Unhandled files: %d", priv->unhandled_count);
196
197 if (priv->unhandled_count == 0 &&
198 g_hash_table_size (priv->statistics_data) < 1) {
199 g_message (" No files handled");
200 }
201
202 g_message ("--------------------------------------------------");
203
204 g_mutex_unlock (&priv->task_mutex);
205 }
206
207 TrackerExtract *
tracker_extract_new(gboolean disable_shutdown,const gchar * force_module)208 tracker_extract_new (gboolean disable_shutdown,
209 const gchar *force_module)
210 {
211 TrackerExtract *object;
212 TrackerExtractPrivate *priv;
213
214 if (!tracker_extract_module_manager_init ()) {
215 return NULL;
216 }
217
218 /* Set extractors */
219 object = g_object_new (TRACKER_TYPE_EXTRACT, NULL);
220
221 priv = TRACKER_EXTRACT_GET_PRIVATE (object);
222
223 priv->disable_shutdown = disable_shutdown;
224 priv->force_module = g_strdup (force_module);
225
226 return object;
227 }
228
229 static void
notify_task_finish(TrackerExtractTask * task,gboolean success)230 notify_task_finish (TrackerExtractTask *task,
231 gboolean success)
232 {
233 TrackerExtract *extract;
234 TrackerExtractPrivate *priv;
235 StatisticsData *stats_data;
236
237 extract = task->extract;
238 priv = TRACKER_EXTRACT_GET_PRIVATE (extract);
239
240 /* Reports and ongoing tasks may be
241 * accessed from other threads.
242 */
243 g_mutex_lock (&priv->task_mutex);
244
245 if (task->cur_module) {
246 stats_data = g_hash_table_lookup (priv->statistics_data,
247 task->cur_module);
248
249 if (!stats_data) {
250 stats_data = g_slice_new0 (StatisticsData);
251 g_hash_table_insert (priv->statistics_data,
252 task->cur_module,
253 stats_data);
254 }
255
256 stats_data->extracted_count++;
257
258 if (!success) {
259 stats_data->failed_count++;
260 }
261 } else {
262 priv->unhandled_count++;
263 }
264
265 priv->running_tasks = g_list_remove (priv->running_tasks, task);
266
267 g_mutex_unlock (&priv->task_mutex);
268 }
269
270 static gboolean
get_file_metadata(TrackerExtractTask * task,TrackerExtractInfo ** info_out)271 get_file_metadata (TrackerExtractTask *task,
272 TrackerExtractInfo **info_out)
273 {
274 TrackerExtractInfo *info;
275 GFile *file;
276 gchar *mime_used = NULL;
277
278 *info_out = NULL;
279
280 file = g_file_new_for_uri (task->file);
281 info = tracker_extract_info_new (file, task->mimetype);
282 g_object_unref (file);
283
284 if (task->mimetype && *task->mimetype) {
285 /* We know the mime */
286 mime_used = g_strdup (task->mimetype);
287 } else {
288 tracker_extract_info_unref (info);
289 return FALSE;
290 }
291
292 /* Now we have sanity checked everything, actually get the
293 * data we need from the extractors.
294 */
295 if (mime_used) {
296 if (task->cur_func) {
297 g_debug ("Using %s...",
298 task->cur_module ?
299 g_module_name (task->cur_module) :
300 "Dummy extraction");
301
302 task->success = (task->cur_func) (info);
303 }
304
305 g_free (mime_used);
306 }
307
308 if (!task->success) {
309 tracker_extract_info_unref (info);
310 info = NULL;
311 }
312
313 *info_out = info;
314
315 return task->success;
316 }
317
318 /* This function is called on the thread calling g_cancellable_cancel() */
319 static void
task_cancellable_cancelled_cb(GCancellable * cancellable,TrackerExtractTask * task)320 task_cancellable_cancelled_cb (GCancellable *cancellable,
321 TrackerExtractTask *task)
322 {
323 TrackerExtractPrivate *priv;
324 TrackerExtract *extract;
325
326 extract = task->extract;
327 priv = TRACKER_EXTRACT_GET_PRIVATE (extract);
328
329 g_mutex_lock (&priv->task_mutex);
330
331 if (g_list_find (priv->running_tasks, task)) {
332 g_message ("Cancelled task for '%s' was currently being "
333 "processed, _exit()ing immediately",
334 task->file);
335 _exit (0);
336 }
337
338 g_mutex_unlock (&priv->task_mutex);
339 }
340
341 static gboolean
task_deadline_cb(gpointer user_data)342 task_deadline_cb (gpointer user_data)
343 {
344 TrackerExtractTask *task = user_data;
345
346 g_warning ("File '%s' took too long to process. Shutting down everything",
347 task->file);
348
349 if (task->cancellable)
350 g_cancellable_cancel (task->cancellable);
351 _exit (0);
352 }
353
354 static TrackerExtractTask *
extract_task_new(TrackerExtract * extract,const gchar * uri,const gchar * mimetype,GCancellable * cancellable,GAsyncResult * res,GError ** error)355 extract_task_new (TrackerExtract *extract,
356 const gchar *uri,
357 const gchar *mimetype,
358 GCancellable *cancellable,
359 GAsyncResult *res,
360 GError **error)
361 {
362 TrackerExtractTask *task;
363 gchar *mimetype_used;
364
365 if (!mimetype || !*mimetype) {
366 GFile *file;
367 GFileInfo *info;
368 GError *internal_error = NULL;
369
370 file = g_file_new_for_uri (uri);
371 info = g_file_query_info (file,
372 G_FILE_ATTRIBUTE_STANDARD_CONTENT_TYPE,
373 G_FILE_QUERY_INFO_NONE,
374 NULL,
375 &internal_error);
376
377 g_object_unref (file);
378
379 if (internal_error) {
380 g_propagate_error (error, internal_error);
381 return NULL;
382 }
383
384 mimetype_used = g_strdup (g_file_info_get_content_type (info));
385 g_object_unref (info);
386 g_message ("MIME type guessed as '%s' (from GIO)", mimetype_used);
387 } else {
388 mimetype_used = g_strdup (mimetype);
389 g_message ("MIME type passed to us as '%s'", mimetype_used);
390 }
391
392 task = g_slice_new0 (TrackerExtractTask);
393 task->cancellable = (cancellable) ? g_object_ref (cancellable) : NULL;
394 task->res = (res) ? g_object_ref (res) : NULL;
395 task->file = g_strdup (uri);
396 task->mimetype = mimetype_used;
397 task->extract = extract;
398
399 if (task->res) {
400 GSource *source;
401
402 source = g_timeout_source_new_seconds (DEADLINE_SECONDS);
403 g_source_set_callback (source, task_deadline_cb, task, NULL);
404 task->timeout_id =
405 g_source_attach (source, g_task_get_context (G_TASK (task->res)));
406 }
407
408 if (task->cancellable) {
409 task->signal_id = g_cancellable_connect (cancellable,
410 G_CALLBACK (task_cancellable_cancelled_cb),
411 task, NULL);
412 }
413
414 return task;
415 }
416
417 static void
extract_task_free(TrackerExtractTask * task)418 extract_task_free (TrackerExtractTask *task)
419 {
420 if (task->cancellable && task->signal_id != 0) {
421 g_cancellable_disconnect (task->cancellable, task->signal_id);
422 }
423
424 notify_task_finish (task, task->success);
425
426 if (task->timeout_id)
427 g_source_remove (task->timeout_id);
428
429 if (task->res) {
430 g_object_unref (task->res);
431 }
432
433 if (task->cancellable) {
434 g_object_unref (task->cancellable);
435 }
436
437 if (task->mimetype_handlers) {
438 tracker_mimetype_info_free (task->mimetype_handlers);
439 }
440
441 g_free (task->mimetype);
442 g_free (task->file);
443
444 g_slice_free (TrackerExtractTask, task);
445 }
446
447 static gboolean
filter_module(TrackerExtract * extract,GModule * module)448 filter_module (TrackerExtract *extract,
449 GModule *module)
450 {
451 TrackerExtractPrivate *priv;
452 gchar *module_basename, *filter_name;
453 gboolean filter;
454
455 if (!module) {
456 return FALSE;
457 }
458
459 priv = TRACKER_EXTRACT_GET_PRIVATE (extract);
460
461 if (!priv->force_module) {
462 return FALSE;
463 }
464
465 /* Module name is the full path to it */
466 module_basename = g_path_get_basename (g_module_name (module));
467
468 if (g_str_has_prefix (priv->force_module, "lib") &&
469 g_str_has_suffix (priv->force_module, "." G_MODULE_SUFFIX)) {
470 filter_name = g_strdup (priv->force_module);
471 } else {
472 filter_name = g_strdup_printf ("libextract-%s.so",
473 priv->force_module);
474 }
475
476 filter = strcmp (module_basename, filter_name) != 0;
477
478 if (filter) {
479 g_debug ("Module filtered out '%s' (due to --force-module='%s')",
480 module_basename,
481 filter_name);
482 } else {
483 g_debug ("Module used '%s' (due to --force-module='%s')",
484 module_basename,
485 filter_name);
486 }
487
488 g_free (module_basename);
489 g_free (filter_name);
490
491 return filter;
492 }
493
494 static gboolean
get_metadata(TrackerExtractTask * task)495 get_metadata (TrackerExtractTask *task)
496 {
497 TrackerExtractInfo *info;
498
499 #ifdef THREAD_ENABLE_TRACE
500 g_debug ("Thread:%p --> '%s': Collected metadata",
501 g_thread_self (),
502 task->file);
503 #endif /* THREAD_ENABLE_TRACE */
504
505 if (g_task_return_error_if_cancelled (G_TASK (task->res))) {
506 extract_task_free (task);
507 return FALSE;
508 }
509
510 if (!filter_module (task->extract, task->cur_module) &&
511 get_file_metadata (task, &info)) {
512 g_task_return_pointer (G_TASK (task->res), info,
513 (GDestroyNotify) tracker_extract_info_unref);
514 extract_task_free (task);
515 } else {
516 /* Reinject the task into the main thread
517 * queue, so the next module kicks in.
518 */
519 g_idle_add ((GSourceFunc) dispatch_task_cb, task);
520 }
521
522 return FALSE;
523 }
524
525 static gpointer
single_thread_get_metadata(GAsyncQueue * queue)526 single_thread_get_metadata (GAsyncQueue *queue)
527 {
528 if (!tracker_seccomp_init ())
529 g_assert_not_reached ();
530
531 while (TRUE) {
532 TrackerExtractTask *task;
533
534 task = g_async_queue_pop (queue);
535 #ifdef THREAD_ENABLE_TRACE
536 g_debug ("Thread:%p --> '%s': Dispatching in dedicated thread",
537 g_thread_self(), task->file);
538 #endif /* THREAD_ENABLE_TRACE */
539 get_metadata (task);
540 }
541
542 return NULL;
543 }
544
545 /* This function is executed in the main thread, decides the
546 * module that's going to be run for a given task, and dispatches
547 * the task according to the threading strategy of that module.
548 */
549 static gboolean
dispatch_task_cb(TrackerExtractTask * task)550 dispatch_task_cb (TrackerExtractTask *task)
551 {
552 TrackerExtractPrivate *priv;
553 GError *error = NULL;
554 GAsyncQueue *async_queue;
555 GModule *module;
556
557 #ifdef THREAD_ENABLE_TRACE
558 g_debug ("Thread:%p (Main) <-- '%s': Handling task...\n",
559 g_thread_self (),
560 task->file);
561 #endif /* THREAD_ENABLE_TRACE */
562
563 priv = TRACKER_EXTRACT_GET_PRIVATE (task->extract);
564
565 if (!task->mimetype) {
566 error = g_error_new (tracker_extract_error_quark (),
567 TRACKER_EXTRACT_ERROR_NO_MIMETYPE,
568 "No mimetype for '%s'", task->file);
569 } else {
570 if (!task->mimetype_handlers) {
571 /* First iteration for task, get the mimetype handlers */
572 task->mimetype_handlers = tracker_extract_module_manager_get_mimetype_handlers (task->mimetype);
573
574 if (!task->mimetype_handlers) {
575 error = g_error_new (tracker_extract_error_quark (),
576 TRACKER_EXTRACT_ERROR_NO_EXTRACTOR,
577 "No mimetype extractor handlers for uri:'%s' and mime:'%s'",
578 task->file, task->mimetype);
579 }
580 } else {
581 /* Any further iteration, should happen rarely if
582 * most specific handlers know nothing about the file
583 */
584 if (!tracker_mimetype_info_iter_next (task->mimetype_handlers)) {
585 g_message ("There's no next extractor");
586
587 error = g_error_new (tracker_extract_error_quark (),
588 TRACKER_EXTRACT_ERROR_NO_EXTRACTOR,
589 "Could not get any metadata for uri:'%s' and mime:'%s'",
590 task->file, task->mimetype);
591 } else {
592 g_message ("Trying next extractor for '%s'", task->file);
593 }
594 }
595 }
596
597 if (error) {
598 g_task_return_error (G_TASK (task->res), error);
599 extract_task_free (task);
600
601 return FALSE;
602 }
603
604 task->cur_module = module = tracker_mimetype_info_get_module (task->mimetype_handlers, &task->cur_func);
605
606 if (!task->cur_func) {
607 g_warning ("Discarding task, no module able to handle '%s'", task->file);
608 priv->unhandled_count++;
609 extract_task_free (task);
610 return FALSE;
611 }
612
613 async_queue = g_hash_table_lookup (priv->single_thread_extractors, module);
614
615 if (!async_queue) {
616 GThread *thread;
617
618 /* No thread created yet for this module, create it
619 * together with the async queue used to pass data to it
620 */
621 async_queue = g_async_queue_new ();
622 thread = g_thread_try_new ("single",
623 (GThreadFunc) single_thread_get_metadata,
624 g_async_queue_ref (async_queue),
625 &error);
626 if (!thread) {
627 g_task_return_error (G_TASK (task->res), error);
628 extract_task_free (task);
629 return FALSE;
630 }
631
632 /* We won't join the thread, so just unref it here */
633 g_thread_unref (thread);
634
635 g_hash_table_insert (priv->single_thread_extractors, module, async_queue);
636 }
637
638 g_async_queue_push (async_queue, task);
639
640 return FALSE;
641 }
642
643 /* This function can be called in any thread */
644 void
tracker_extract_file(TrackerExtract * extract,const gchar * file,const gchar * mimetype,GCancellable * cancellable,GAsyncReadyCallback cb,gpointer user_data)645 tracker_extract_file (TrackerExtract *extract,
646 const gchar *file,
647 const gchar *mimetype,
648 GCancellable *cancellable,
649 GAsyncReadyCallback cb,
650 gpointer user_data)
651 {
652 GError *error = NULL;
653 TrackerExtractTask *task;
654 GTask *async_task;
655
656 g_return_if_fail (TRACKER_IS_EXTRACT (extract));
657 g_return_if_fail (file != NULL);
658 g_return_if_fail (cb != NULL);
659
660 #ifdef THREAD_ENABLE_TRACE
661 g_debug ("Thread:%p <-- '%s': Processing file\n",
662 g_thread_self (),
663 file);
664 #endif /* THREAD_ENABLE_TRACE */
665
666 async_task = g_task_new (extract, cancellable, cb, user_data);
667
668 task = extract_task_new (extract, file, mimetype, cancellable,
669 G_ASYNC_RESULT (async_task), &error);
670
671 if (error) {
672 g_warning ("Could not get mimetype, %s", error->message);
673 g_task_return_error (async_task, error);
674 } else {
675 TrackerExtractPrivate *priv;
676
677 priv = TRACKER_EXTRACT_GET_PRIVATE (task->extract);
678
679 g_mutex_lock (&priv->task_mutex);
680 priv->running_tasks = g_list_prepend (priv->running_tasks, task);
681 g_mutex_unlock (&priv->task_mutex);
682
683 g_idle_add ((GSourceFunc) dispatch_task_cb, task);
684 }
685
686 /* Task takes a ref and if this fails, we want to unref anyway */
687 g_object_unref (async_task);
688 }
689
690 void
tracker_extract_get_metadata_by_cmdline(TrackerExtract * object,const gchar * uri,const gchar * mime,TrackerSerializationFormat output_format)691 tracker_extract_get_metadata_by_cmdline (TrackerExtract *object,
692 const gchar *uri,
693 const gchar *mime,
694 TrackerSerializationFormat output_format)
695 {
696 GError *error = NULL;
697 TrackerExtractPrivate *priv;
698 TrackerExtractTask *task;
699 TrackerExtractInfo *info;
700 gboolean no_data_or_modules = TRUE;
701
702 priv = TRACKER_EXTRACT_GET_PRIVATE (object);
703 priv->disable_summary_on_finalize = TRUE;
704
705 g_return_if_fail (uri != NULL);
706
707 task = extract_task_new (object, uri, mime, NULL, NULL, &error);
708
709 if (error) {
710 g_printerr ("%s, %s\n",
711 _("Metadata extraction failed"),
712 error->message);
713 g_error_free (error);
714
715 return;
716 }
717
718 task->mimetype_handlers = tracker_extract_module_manager_get_mimetype_handlers (task->mimetype);
719 if (task->mimetype_handlers) {
720 task->cur_module = tracker_mimetype_info_get_module (task->mimetype_handlers, &task->cur_func);
721 }
722
723 while (task->cur_func) {
724 if (!filter_module (object, task->cur_module) &&
725 get_file_metadata (task, &info)) {
726 TrackerResource *resource = tracker_extract_info_get_resource (info);
727
728 if (resource == NULL)
729 break;
730
731 no_data_or_modules = FALSE;
732
733 if (output_format == TRACKER_SERIALIZATION_FORMAT_SPARQL) {
734 char *text;
735
736 /* If this was going into the tracker-store we'd generate a unique ID
737 * here, so that the data persisted across file renames.
738 */
739 tracker_resource_set_identifier (resource, uri);
740
741 text = tracker_resource_print_sparql_update (resource, NULL, NULL);
742
743 g_print ("%s\n", text);
744
745 g_free (text);
746 } else if (output_format == TRACKER_SERIALIZATION_FORMAT_TURTLE) {
747 char *turtle;
748
749 /* If this was going into the tracker-store we'd generate a unique ID
750 * here, so that the data persisted across file renames.
751 */
752 tracker_resource_set_identifier (resource, uri);
753
754 turtle = tracker_resource_print_turtle (resource, NULL);
755
756 if (turtle) {
757 g_print ("%s\n", turtle);
758 g_free (turtle);
759 }
760 } else {
761 /* JSON-LD extraction */
762 char *json;
763
764 /* If this was going into the tracker-store we'd generate a unique ID
765 * here, so that the data persisted across file renames.
766 */
767 tracker_resource_set_identifier (resource, uri);
768
769 json = tracker_resource_print_jsonld (resource, NULL);
770 if (json) {
771 g_print ("%s\n", json);
772 g_free (json);
773 }
774 }
775
776 tracker_extract_info_unref (info);
777 break;
778 } else {
779 if (!tracker_mimetype_info_iter_next (task->mimetype_handlers)) {
780 break;
781 }
782
783 task->cur_module = tracker_mimetype_info_get_module (task->mimetype_handlers,
784 &task->cur_func);
785 }
786 }
787
788 if (no_data_or_modules) {
789 g_printerr ("%s: %s\n",
790 uri,
791 _("No metadata or extractor modules found to handle this file"));
792 }
793
794 extract_task_free (task);
795 }
796
797 TrackerExtractInfo *
tracker_extract_file_finish(TrackerExtract * extract,GAsyncResult * res,GError ** error)798 tracker_extract_file_finish (TrackerExtract *extract,
799 GAsyncResult *res,
800 GError **error)
801 {
802 g_return_val_if_fail (TRACKER_IS_EXTRACT (extract), NULL);
803 g_return_val_if_fail (G_IS_ASYNC_RESULT (res), NULL);
804 g_return_val_if_fail (!error || !*error, NULL);
805
806 return g_task_propagate_pointer (G_TASK (res), error);
807 }
808