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