tracker-main.c 14 KB
Newer Older
1
/*
2
 * Copyright (C) 2006, Jamie McCracken <jamiemcc@gnome.org>
3
 * Copyright (C) 2008, Nokia <ivan.frade@nokia.com>
4
 *
5 6
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
7
 * License as published by the Free Software Foundation; either
8
 * version 2.1 of the License, or (at your option) any later version.
9
 *
10
 * This library is distributed in the hope that it will be useful,
11 12
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
13
 * Lesser General Public License for more details.
14
 *
15 16
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the
17 18 19 20 21 22 23 24 25 26
 * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
 * Boston, MA  02110-1301, USA.
 */

#include "config.h"

#define _XOPEN_SOURCE
#include <time.h>
#include <stdlib.h>
#include <locale.h>
27 28 29
#include <errno.h>
#include <sys/types.h>
#include <unistd.h>
30

31
#include <glib-object.h>
32
#include <glib-unix.h>
33
#include <glib/gi18n.h>
34
#include <glib/gprintf.h>
35
#include <gio/gio.h>
36 37 38 39 40

#ifndef G_OS_WIN32
#include <sys/resource.h>
#endif

41
#include <libtracker-miners-common/tracker-common.h>
42

43
#include "tracker-config.h"
44
#include "tracker-main.h"
45
#include "tracker-extract.h"
46
#include "tracker-extract-controller.h"
47
#include "tracker-extract-decorator.h"
48

49 50 51 52
#ifdef THREAD_ENABLE_TRACE
#warning Main thread traces enabled
#endif /* THREAD_ENABLE_TRACE */

53
#define ABOUT	  \
54
	"Tracker " PACKAGE_VERSION "\n"
55

56
#define LICENSE	  \
57
	"This program is free software and comes without any warranty.\n" \
58 59 60
	"It is licensed under version 2 or later of the General Public " \
	"License which can be viewed at:\n" \
	"\n" \
61 62
	"  http://www.gnu.org/licenses/gpl.txt\n"

63
#define DBUS_NAME_SUFFIX "Tracker1.Miner.Extract"
64 65
#define DBUS_PATH "/org/freedesktop/Tracker1/Miner/Extract"

66
static GMainLoop *main_loop;
67

68 69 70
static gint verbosity = -1;
static gchar *filename;
static gchar *mime_type;
71
static gchar *force_module;
72
static gchar *output_format_name;
73
static gboolean version;
74
static gchar *domain_ontology_name = NULL;
75
static guint shutdown_timeout_id = 0;
76

77
static TrackerConfig *config;
78

79
static GOptionEntry entries[] = {
80 81 82 83 84 85
	{ "verbosity", 'v', 0,
	  G_OPTION_ARG_INT, &verbosity,
	  N_("Logging, 0 = errors only, "
	     "1 = minimal, 2 = detailed and 3 = debug (default = 0)"),
	  NULL },
	{ "file", 'f', 0,
86
	  G_OPTION_ARG_FILENAME, &filename,
87 88
	  N_("File to extract metadata for"),
	  N_("FILE") },
89
	{ "mime", 't', 0,
90 91 92
	  G_OPTION_ARG_STRING, &mime_type,
	  N_("MIME type for file (if not provided, this will be guessed)"),
	  N_("MIME") },
93 94
	{ "force-module", 'm', 0,
	  G_OPTION_ARG_STRING, &force_module,
95
	  N_("Force a module to be used for extraction (e.g. “foo” for “foo.so”)"),
96
	  N_("MODULE") },
97
	{ "output-format", 'o', 0, G_OPTION_ARG_STRING, &output_format_name,
98
	  N_("Output results format: “sparql”, “turtle” or “json-ld”"),
99
	  N_("FORMAT") },
100 101
	{ "domain-ontology", 'd', 0,
	  G_OPTION_ARG_STRING, &domain_ontology_name,
102
	  N_("Runs for a specific domain ontology"),
103
	  NULL },
104 105 106 107
	{ "version", 'V', 0,
	  G_OPTION_ARG_NONE, &version,
	  N_("Displays version information"),
	  NULL },
108 109
	{ NULL }
};
110

111
static void
112 113
initialize_priority_and_scheduling (TrackerSchedIdle sched_idle,
                                    gboolean         first_time_index)
114
{
115
	/* Set CPU priority */
116 117 118 119
	if (sched_idle == TRACKER_SCHED_IDLE_ALWAYS ||
	    (sched_idle == TRACKER_SCHED_IDLE_FIRST_INDEX && first_time_index)) {
		tracker_sched_idle ();
	}
120

121 122 123 124 125 126 127 128
	/* Set disk IO priority and scheduling */
	tracker_ioprio_init ();

	/* Set process priority:
	 * The nice() function uses attribute "warn_unused_result" and
	 * so complains if we do not check its returned value. But it
	 * seems that since glibc 2.2.4, nice() can return -1 on a
	 * successful call so we have to check value of errno too.
129
	 * Stupid...
130
	 */
131
	g_message ("Setting priority nice level to 19");
132 133 134 135 136

	if (nice (19) == -1) {
		const gchar *str = g_strerror (errno);

		g_message ("Couldn't set nice value to 19, %s",
137
		           str ? str : "no error given");
138 139 140
	}
}

141
static void
142
initialize_directories (void)
143
{
144
	gchar *user_data_dir;
145

146 147
	/* NOTE: We don't create the database directories here, the
	 * tracker-db-manager does that for us.
148
	 */
149

150
	user_data_dir = g_build_filename (g_get_user_data_dir (),
151 152 153
	                                  "tracker",
	                                  NULL);

154
	/* g_message ("Checking directory exists:'%s'", user_data_dir); */
155 156 157
	g_mkdir_with_parents (user_data_dir, 00755);

	g_free (user_data_dir);
158 159
}

160 161
static gboolean
signal_handler (gpointer user_data)
162
{
163 164
	int signo = GPOINTER_TO_INT (user_data);

165 166 167 168
	static gboolean in_loop = FALSE;

	/* Die if we get re-entrant signals handler calls */
	if (in_loop) {
169
		_exit (EXIT_FAILURE);
170 171 172 173 174 175
	}

	switch (signo) {
	case SIGTERM:
	case SIGINT:
		in_loop = TRUE;
176 177
		g_main_loop_quit (main_loop);

178
		/* Fall through */
179 180 181
	default:
		if (g_strsignal (signo)) {
			g_print ("\n");
182
			g_print ("Received signal:%d->'%s'\n",
183 184
			         signo,
			         g_strsignal (signo));
185 186 187
		}
		break;
	}
188 189

	return G_SOURCE_CONTINUE;
190 191 192 193 194 195
}

static void
initialize_signal_handler (void)
{
#ifndef G_OS_WIN32
196 197
	g_unix_signal_add (SIGTERM, signal_handler, GINT_TO_POINTER (SIGTERM));
	g_unix_signal_add (SIGINT, signal_handler, GINT_TO_POINTER (SIGINT));
198 199 200
#endif /* G_OS_WIN32 */
}

201 202
static void
log_handler (const gchar    *domain,
203 204 205
             GLogLevelFlags  log_level,
             const gchar    *message,
             gpointer        user_data)
206 207 208 209 210 211 212 213 214 215 216 217 218 219
{
	switch (log_level) {
	case G_LOG_LEVEL_WARNING:
	case G_LOG_LEVEL_CRITICAL:
	case G_LOG_LEVEL_ERROR:
	case G_LOG_FLAG_RECURSION:
	case G_LOG_FLAG_FATAL:
		g_fprintf (stderr, "%s\n", message);
		fflush (stderr);
		break;
	case G_LOG_LEVEL_MESSAGE:
	case G_LOG_LEVEL_INFO:
	case G_LOG_LEVEL_DEBUG:
	case G_LOG_LEVEL_MASK:
220
	default:
221 222 223
		g_fprintf (stdout, "%s\n", message);
		fflush (stdout);
		break;
224
	}
225 226
}

227 228 229 230 231 232
static void
sanity_check_option_values (TrackerConfig *config)
{
	g_message ("General options:");
	g_message ("  Verbosity  ............................  %d",
	           tracker_config_get_verbosity (config));
233 234
	g_message ("  Sched Idle  ...........................  %d",
	           tracker_config_get_sched_idle (config));
235 236 237 238
	g_message ("  Max bytes (per file)  .................  %d",
	           tracker_config_get_max_bytes (config));
}

239 240
TrackerConfig *
tracker_main_get_config (void)
241
{
242
	return config;
243 244
}

245
static int
246
run_standalone (TrackerConfig *config)
247
{
248 249 250
	TrackerExtract *object;
	GFile *file;
	gchar *uri;
251 252 253
	GEnumClass *enum_class;
	GEnumValue *enum_value;
	TrackerSerializationFormat output_format;
254 255 256 257 258 259 260 261

	/* Set log handler for library messages */
	g_log_set_default_handler (log_handler, NULL);

	/* Set the default verbosity if unset */
	if (verbosity == -1) {
		verbosity = 3;
	}
262

263 264 265 266 267 268 269 270 271
	if (!output_format_name) {
		output_format_name = "turtle";
	}

	/* Look up the output format by name */
	enum_class = g_type_class_ref (TRACKER_TYPE_SERIALIZATION_FORMAT);
	enum_value = g_enum_get_value_by_nick (enum_class, output_format_name);
	g_type_class_unref (enum_class);
	if (!enum_value) {
272
		g_printerr (N_("Unsupported serialization format “%s”\n"), output_format_name);
273 274 275 276
		return EXIT_FAILURE;
	}
	output_format = enum_value->value;

277
	tracker_locale_sanity_check ();
278

279
	/* This makes sure we don't steal all the system's resources */
280
	initialize_priority_and_scheduling (tracker_config_get_sched_idle (config), TRUE);
281

282 283 284
	file = g_file_new_for_commandline_arg (filename);
	uri = g_file_get_uri (file);

285
	object = tracker_extract_new (TRUE, force_module);
286 287

	if (!object) {
288
		g_object_unref (file);
289 290
		g_free (uri);
		return EXIT_FAILURE;
291 292
	}

293
	tracker_extract_get_metadata_by_cmdline (object, uri, mime_type, output_format);
294

295 296 297 298 299 300
	g_object_unref (object);
	g_object_unref (file);
	g_free (uri);

	return EXIT_SUCCESS;
}
301

302 303 304 305 306 307 308 309 310
static void
on_domain_vanished (GDBusConnection *connection,
                    const gchar     *name,
                    gpointer         user_data)
{
	GMainLoop *loop = user_data;
	g_main_loop_quit (loop);
}

311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340
static void
on_decorator_items_available (TrackerDecorator *decorator)
{
	if (shutdown_timeout_id) {
		g_source_remove (shutdown_timeout_id);
		shutdown_timeout_id = 0;
	}
}

static gboolean
shutdown_timeout_cb (gpointer user_data)
{
	GMainLoop *loop = user_data;

	g_debug ("Shutting down after 10 seconds inactivity");
	g_main_loop_quit (loop);
	shutdown_timeout_id = 0;
	return G_SOURCE_REMOVE;
}

static void
on_decorator_finished (TrackerDecorator *decorator,
                       GMainLoop        *loop)
{
	if (shutdown_timeout_id != 0)
		return;
	shutdown_timeout_id = g_timeout_add_seconds (10, shutdown_timeout_cb,
	                                             main_loop);
}

341 342 343 344
int
main (int argc, char *argv[])
{
	GOptionContext *context;
345
	GError *error = NULL;
346 347
	TrackerExtract *extract;
	TrackerDecorator *decorator;
348
	TrackerExtractController *controller;
349 350
	gchar *log_filename = NULL;
	GMainLoop *my_main_loop;
351 352
	GDBusConnection *connection;
	TrackerMinerProxy *proxy;
353
	TrackerDomainOntology *domain_ontology;
354
	gchar *domain_name, *dbus_name;
355

356 357 358 359
	bindtextdomain (GETTEXT_PACKAGE, LOCALEDIR);
	bind_textdomain_codeset (GETTEXT_PACKAGE, "UTF-8");
	textdomain (GETTEXT_PACKAGE);

360 361
	/* Translators: this message will appear immediately after the  */
	/* usage string - Usage: COMMAND [OPTION]... <THIS_MESSAGE>     */
362
	context = g_option_context_new (_("— Extract file meta data"));
363

364 365 366
	g_option_context_add_main_entries (context, entries, NULL);
	g_option_context_parse (context, &argc, &argv, &error);

367 368 369 370 371 372
	if (error) {
		g_printerr ("%s\n", error->message);
		g_error_free (error);
		return EXIT_FAILURE;
	}

373 374 375 376
	if (!filename && mime_type) {
		gchar *help;

		g_printerr ("%s\n\n",
377
		            _("Filename and mime type must be provided together"));
378 379 380 381 382 383 384 385 386

		help = g_option_context_get_help (context, TRUE, NULL);
		g_option_context_free (context);
		g_printerr ("%s", help);
		g_free (help);

		return EXIT_FAILURE;
	}

387 388
	g_option_context_free (context);

389 390 391 392 393
	if (version) {
		g_print ("\n" ABOUT "\n" LICENSE "\n");
		return EXIT_SUCCESS;
	}

394
	g_set_application_name ("tracker-extract");
395

396
	setlocale (LC_ALL, "");
397

398 399 400 401 402 403 404 405 406 407
	tracker_sparql_connection_set_domain (domain_ontology_name);

	domain_ontology = tracker_domain_ontology_new (domain_ontology_name, NULL, &error);
	if (error) {
		g_critical ("Could not load domain ontology '%s': %s",
		            domain_ontology_name, error->message);
		g_error_free (error);
		return EXIT_FAILURE;
	}

408 409 410 411 412 413 414 415
	connection = g_bus_get_sync (TRACKER_IPC_BUS, NULL, &error);
	if (error) {
		g_critical ("Could not create DBus connection: %s\n",
		            error->message);
		g_error_free (error);
		return EXIT_FAILURE;
	}

416 417
	config = tracker_config_new ();

418 419 420 421 422
	/* Extractor command line arguments */
	if (verbosity > -1) {
		tracker_config_set_verbosity (config, verbosity);
	}

423
	tracker_log_init (tracker_config_get_verbosity (config), &log_filename);
424 425 426 427 428
	if (log_filename != NULL) {
		g_message ("Using log file:'%s'", log_filename);
		g_free (log_filename);
	}

429 430
	sanity_check_option_values (config);

431 432 433 434 435 436 437 438
	/* Set conditions when we use stand alone settings */
	if (filename) {
		return run_standalone (config);
	}

	/* Initialize subsystems */
	initialize_directories ();

439
	/* This makes sure we don't steal all the system's resources */
440
	initialize_priority_and_scheduling (tracker_config_get_sched_idle (config), TRUE);
441

442
	extract = tracker_extract_new (TRUE, force_module);
443

444
	if (!extract) {
445 446 447 448 449
		g_object_unref (config);
		tracker_log_shutdown ();
		return EXIT_FAILURE;
	}

450 451
	tracker_module_manager_load_modules ();

452
	decorator = tracker_extract_decorator_new (extract, NULL, &error);
453

454 455
	if (error) {
		g_critical ("Could not start decorator: %s\n", error->message);
456
		g_object_unref (config);
457 458
		tracker_log_shutdown ();
		return EXIT_FAILURE;
459
	}
460

461 462 463 464 465 466 467 468 469 470
	proxy = tracker_miner_proxy_new (TRACKER_MINER (decorator), connection, DBUS_PATH, NULL, &error);
	if (error) {
		g_critical ("Could not create miner DBus proxy: %s\n", error->message);
		g_error_free (error);
		g_object_unref (decorator);
		g_object_unref (config);
		tracker_log_shutdown ();
		return EXIT_FAILURE;
	}

471
#ifdef THREAD_ENABLE_TRACE
472 473
	g_debug ("Thread:%p (Main) --- Waiting for extract requests...",
	         g_thread_self ());
474
#endif /* THREAD_ENABLE_TRACE */
475

476
	tracker_locale_sanity_check ();
477

478
	controller = tracker_extract_controller_new (decorator, connection);
479 480
	tracker_miner_start (TRACKER_MINER (decorator));

481 482 483 484 485 486 487 488 489 490 491 492 493
	/* Request DBus name */
	dbus_name = tracker_domain_ontology_get_domain (domain_ontology, DBUS_NAME_SUFFIX);

	if (!tracker_dbus_request_name (connection, dbus_name, &error)) {
		g_critical ("Could not request DBus name '%s': %s",
		            dbus_name, error->message);
		g_error_free (error);
		g_free (dbus_name);
		return EXIT_FAILURE;
	}

	g_free (dbus_name);

494 495
	/* Main loop */
	main_loop = g_main_loop_new (NULL, FALSE);
496

497
	if (domain_ontology && domain_ontology_name) {
498 499 500 501 502 503 504
		/* If we are running for a specific domain, we tie the lifetime of this
		 * process to the domain. For example, if the domain name is
		 * org.example.MyApp then this tracker-extract process will exit as
		 * soon as org.example.MyApp exits.
		 */
		domain_name = tracker_domain_ontology_get_domain (domain_ontology, NULL);
		g_bus_watch_name_on_connection (connection, domain_name,
505 506 507
		                                G_BUS_NAME_WATCHER_FLAGS_NONE,
		                                NULL, on_domain_vanished,
		                                main_loop, NULL);
508
		g_free (domain_name);
509 510
	}

511 512 513 514 515 516 517
	g_signal_connect (decorator, "finished",
	                  G_CALLBACK (on_decorator_finished),
	                  main_loop);
	g_signal_connect (decorator, "items-available",
	                  G_CALLBACK (on_decorator_items_available),
	                  main_loop);

518 519
	initialize_signal_handler ();

520
	g_main_loop_run (main_loop);
521 522 523 524

	my_main_loop = main_loop;
	main_loop = NULL;
	g_main_loop_unref (my_main_loop);
525

526
	tracker_miner_stop (TRACKER_MINER (decorator));
527

528
	/* Shutdown subsystems */
529 530
	g_object_unref (extract);
	g_object_unref (decorator);
531
	g_object_unref (controller);
532 533
	g_object_unref (proxy);
	g_object_unref (connection);
534
	g_object_unref (domain_ontology);
535

536
	tracker_log_shutdown ();
537

538
	g_object_unref (config);
539 540 541

	return EXIT_SUCCESS;
}