tracker-0.16.2/src/libtracker-miner/tracker-crawler.c

No issues found

   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 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.h"
  21 
  22 #include "tracker-crawler.h"
  23 #include "tracker-marshal.h"
  24 #include "tracker-utils.h"
  25 
  26 #define TRACKER_CRAWLER_GET_PRIVATE(obj) (G_TYPE_INSTANCE_GET_PRIVATE ((obj), TRACKER_TYPE_CRAWLER, TrackerCrawlerPrivate))
  27 
  28 #define FILE_ATTRIBUTES	  \
  29 	G_FILE_ATTRIBUTE_STANDARD_NAME "," \
  30 	G_FILE_ATTRIBUTE_STANDARD_TYPE
  31 
  32 #define FILES_QUEUE_PROCESS_INTERVAL 2000
  33 #define FILES_QUEUE_PROCESS_MAX      5000
  34 
  35 /* This is the number of files to be called back with from GIO at a
  36  * time so we don't get called back for every file.
  37  */
  38 #define FILES_GROUP_SIZE             100
  39 
  40 typedef struct DirectoryChildData DirectoryChildData;
  41 typedef struct DirectoryProcessingData DirectoryProcessingData;
  42 typedef struct DirectoryRootInfo DirectoryRootInfo;
  43 
  44 struct DirectoryChildData {
  45 	GFile          *child;
  46 	gboolean        is_dir;
  47 };
  48 
  49 struct DirectoryProcessingData {
  50 	GNode *node;
  51 	GSList *children;
  52 	guint was_inspected : 1;
  53 	guint ignored_by_content : 1;
  54 };
  55 
  56 struct DirectoryRootInfo {
  57 	GFile *directory;
  58 	GNode *tree;
  59 	guint recurse : 1;
  60 
  61 	GQueue *directory_processing_queue;
  62 
  63 	/* Directory stats */
  64 	guint directories_found;
  65 	guint directories_ignored;
  66 	guint files_found;
  67 	guint files_ignored;
  68 };
  69 
  70 struct TrackerCrawlerPrivate {
  71 	/* Directories to crawl */
  72 	GQueue         *directories;
  73 
  74 	GList          *cancellables;
  75 
  76 	/* Idle handler for processing found data */
  77 	guint           idle_id;
  78 
  79 	gdouble         throttle;
  80 
  81 	gchar          *file_attributes;
  82 
  83 	gboolean        recurse;
  84 
  85 	/* Statistics */
  86 	GTimer         *timer;
  87 
  88 	/* Status */
  89 	gboolean        is_running;
  90 	gboolean        is_finished;
  91 	gboolean        is_paused;
  92 	gboolean        was_started;
  93 };
  94 
  95 enum {
  96 	CHECK_DIRECTORY,
  97 	CHECK_FILE,
  98 	CHECK_DIRECTORY_CONTENTS,
  99 	DIRECTORY_CRAWLED,
 100 	FINISHED,
 101 	LAST_SIGNAL
 102 };
 103 
 104 typedef struct {
 105 	TrackerCrawler *crawler;
 106 	DirectoryRootInfo  *root_info;
 107 	DirectoryProcessingData *dir_info;
 108 	GFile *dir_file;
 109 	GCancellable *cancellable;
 110 } EnumeratorData;
 111 
 112 static void     crawler_finalize        (GObject         *object);
 113 static gboolean check_defaults          (TrackerCrawler  *crawler,
 114                                          GFile           *file);
 115 static gboolean check_contents_defaults (TrackerCrawler  *crawler,
 116                                          GFile           *file,
 117                                          GList           *contents);
 118 static void     file_enumerate_next     (GFileEnumerator *enumerator,
 119                                          EnumeratorData  *ed);
 120 static void     file_enumerate_children  (TrackerCrawler          *crawler,
 121 					  DirectoryRootInfo       *info,
 122 					  DirectoryProcessingData *dir_data);
 123 
 124 static void     directory_root_info_free (DirectoryRootInfo *info);
 125 
 126 
 127 static guint signals[LAST_SIGNAL] = { 0, };
 128 static GQuark file_info_quark = 0;
 129 
 130 G_DEFINE_TYPE (TrackerCrawler, tracker_crawler, G_TYPE_OBJECT)
 131 
 132 static void
 133 tracker_crawler_class_init (TrackerCrawlerClass *klass)
 134 {
 135 	GObjectClass *object_class = G_OBJECT_CLASS (klass);
 136 	TrackerCrawlerClass *crawler_class = TRACKER_CRAWLER_CLASS (klass);
 137 
 138 	object_class->finalize = crawler_finalize;
 139 
 140 	crawler_class->check_directory = check_defaults;
 141 	crawler_class->check_file      = check_defaults;
 142 	crawler_class->check_directory_contents = check_contents_defaults;
 143 
 144 	signals[CHECK_DIRECTORY] =
 145 		g_signal_new ("check-directory",
 146 		              G_TYPE_FROM_CLASS (klass),
 147 		              G_SIGNAL_RUN_LAST,
 148 		              G_STRUCT_OFFSET (TrackerCrawlerClass, check_directory),
 149 		              tracker_accumulator_check_file,
 150 		              NULL,
 151 		              tracker_marshal_BOOLEAN__OBJECT,
 152 		              G_TYPE_BOOLEAN,
 153 		              1,
 154 		              G_TYPE_FILE);
 155 	signals[CHECK_FILE] =
 156 		g_signal_new ("check-file",
 157 		              G_TYPE_FROM_CLASS (klass),
 158 		              G_SIGNAL_RUN_LAST,
 159 		              G_STRUCT_OFFSET (TrackerCrawlerClass, check_file),
 160 		              tracker_accumulator_check_file,
 161 		              NULL,
 162 		              tracker_marshal_BOOLEAN__OBJECT,
 163 		              G_TYPE_BOOLEAN,
 164 		              1,
 165 		              G_TYPE_FILE);
 166 	signals[CHECK_DIRECTORY_CONTENTS] =
 167 		g_signal_new ("check-directory-contents",
 168 		              G_TYPE_FROM_CLASS (klass),
 169 		              G_SIGNAL_RUN_LAST,
 170 		              G_STRUCT_OFFSET (TrackerCrawlerClass, check_directory_contents),
 171 		              tracker_accumulator_check_file,
 172 		              NULL,
 173 		              tracker_marshal_BOOLEAN__OBJECT_POINTER,
 174 		              G_TYPE_BOOLEAN,
 175 		              2, G_TYPE_FILE, G_TYPE_POINTER);
 176 	signals[DIRECTORY_CRAWLED] =
 177 		g_signal_new ("directory-crawled",
 178 		              G_TYPE_FROM_CLASS (klass),
 179 		              G_SIGNAL_RUN_LAST,
 180 		              G_STRUCT_OFFSET (TrackerCrawlerClass, directory_crawled),
 181 		              NULL, NULL,
 182 		              tracker_marshal_VOID__OBJECT_POINTER_UINT_UINT_UINT_UINT,
 183 		              G_TYPE_NONE,
 184 		              6,
 185 			      G_TYPE_FILE,
 186 		              G_TYPE_POINTER,
 187 		              G_TYPE_UINT,
 188 		              G_TYPE_UINT,
 189 		              G_TYPE_UINT,
 190 		              G_TYPE_UINT);
 191 	signals[FINISHED] =
 192 		g_signal_new ("finished",
 193 		              G_TYPE_FROM_CLASS (klass),
 194 		              G_SIGNAL_RUN_LAST,
 195 		              G_STRUCT_OFFSET (TrackerCrawlerClass, finished),
 196 		              NULL, NULL,
 197 			      g_cclosure_marshal_VOID__BOOLEAN,
 198 		              G_TYPE_NONE,
 199 		              1, G_TYPE_BOOLEAN);
 200 
 201 	g_type_class_add_private (object_class, sizeof (TrackerCrawlerPrivate));
 202 
 203 	file_info_quark = g_quark_from_static_string ("tracker-crawler-file-info");
 204 }
 205 
 206 static void
 207 tracker_crawler_init (TrackerCrawler *object)
 208 {
 209 	TrackerCrawlerPrivate *priv;
 210 
 211 	object->priv = TRACKER_CRAWLER_GET_PRIVATE (object);
 212 
 213 	priv = object->priv;
 214 
 215 	priv->directories = g_queue_new ();
 216 }
 217 
 218 static void
 219 crawler_finalize (GObject *object)
 220 {
 221 	TrackerCrawlerPrivate *priv;
 222 
 223 	priv = TRACKER_CRAWLER_GET_PRIVATE (object);
 224 
 225 	if (priv->timer) {
 226 		g_timer_destroy (priv->timer);
 227 	}
 228 
 229 	if (priv->idle_id) {
 230 		g_source_remove (priv->idle_id);
 231 	}
 232 
 233 	g_list_free (priv->cancellables);
 234 
 235 	g_queue_foreach (priv->directories, (GFunc) directory_root_info_free, NULL);
 236 	g_queue_free (priv->directories);
 237 
 238 	g_free (priv->file_attributes);
 239 
 240 	G_OBJECT_CLASS (tracker_crawler_parent_class)->finalize (object);
 241 }
 242 
 243 static gboolean
 244 check_defaults (TrackerCrawler *crawler,
 245                 GFile          *file)
 246 {
 247 	return TRUE;
 248 }
 249 
 250 static gboolean
 251 check_contents_defaults (TrackerCrawler  *crawler,
 252                          GFile           *file,
 253                          GList           *contents)
 254 {
 255 	return TRUE;
 256 }
 257 
 258 TrackerCrawler *
 259 tracker_crawler_new (void)
 260 {
 261 	TrackerCrawler *crawler;
 262 
 263 	crawler = g_object_new (TRACKER_TYPE_CRAWLER, NULL);
 264 
 265 	return crawler;
 266 }
 267 
 268 static gboolean
 269 check_file (TrackerCrawler    *crawler,
 270 	    DirectoryRootInfo *info,
 271             GFile             *file)
 272 {
 273 	gboolean use = FALSE;
 274 	TrackerCrawlerPrivate *priv;
 275 
 276 	priv = TRACKER_CRAWLER_GET_PRIVATE (crawler);
 277 
 278 	g_signal_emit (crawler, signals[CHECK_FILE], 0, file, &use);
 279 
 280 	/* Crawler may have been stopped while waiting for the 'use' value,
 281 	 * and the DirectoryRootInfo already disposed... */
 282 	if (!priv->is_running) {
 283 		return FALSE;
 284 	}
 285 
 286 	info->files_found++;
 287 
 288 	if (!use) {
 289 		info->files_ignored++;
 290 	}
 291 
 292 	return use;
 293 }
 294 
 295 static gboolean
 296 check_directory (TrackerCrawler    *crawler,
 297 		 DirectoryRootInfo *info,
 298 		 GFile             *file)
 299 {
 300 	gboolean use = FALSE;
 301 	TrackerCrawlerPrivate *priv;
 302 
 303 	priv = TRACKER_CRAWLER_GET_PRIVATE (crawler);
 304 
 305 	g_signal_emit (crawler, signals[CHECK_DIRECTORY], 0, file, &use);
 306 
 307 	/* Crawler may have been stopped while waiting for the 'use' value,
 308 	 * and the DirectoryRootInfo already disposed... */
 309 	if (!priv->is_running) {
 310 		return FALSE;
 311 	}
 312 
 313 	info->directories_found++;
 314 
 315 	if (!use) {
 316 		info->directories_ignored++;
 317 	}
 318 
 319 	return use;
 320 }
 321 
 322 static DirectoryChildData *
 323 directory_child_data_new (GFile    *child,
 324 			  gboolean  is_dir)
 325 {
 326 	DirectoryChildData *child_data;
 327 
 328 	child_data = g_slice_new (DirectoryChildData);
 329 	child_data->child = g_object_ref (child);
 330 	child_data->is_dir = is_dir;
 331 
 332 	return child_data;
 333 }
 334 
 335 static void
 336 directory_child_data_free (DirectoryChildData *child_data)
 337 {
 338 	g_object_unref (child_data->child);
 339 	g_slice_free (DirectoryChildData, child_data);
 340 }
 341 
 342 static DirectoryProcessingData *
 343 directory_processing_data_new (GNode *node)
 344 {
 345 	DirectoryProcessingData *data;
 346 
 347 	data = g_slice_new0 (DirectoryProcessingData);
 348 	data->node = node;
 349 
 350 	return data;
 351 }
 352 
 353 static void
 354 directory_processing_data_free (DirectoryProcessingData *data)
 355 {
 356 	g_slist_foreach (data->children, (GFunc) directory_child_data_free, NULL);
 357 	g_slist_free (data->children);
 358 
 359 	g_slice_free (DirectoryProcessingData, data);
 360 }
 361 
 362 static void
 363 directory_processing_data_add_child (DirectoryProcessingData *data,
 364 				     GFile                   *child,
 365 				     gboolean                 is_dir)
 366 {
 367 	DirectoryChildData *child_data;
 368 
 369 	child_data = directory_child_data_new (child, is_dir);
 370 	data->children = g_slist_prepend (data->children, child_data);
 371 }
 372 
 373 static DirectoryRootInfo *
 374 directory_root_info_new (GFile    *file,
 375                          gboolean  recurse,
 376                          gchar    *file_attributes)
 377 {
 378 	DirectoryRootInfo *info;
 379 	DirectoryProcessingData *dir_info;
 380 
 381 	info = g_slice_new0 (DirectoryRootInfo);
 382 
 383 	info->directory = g_object_ref (file);
 384 	info->recurse = recurse;
 385 	info->directory_processing_queue = g_queue_new ();
 386 
 387 	info->tree = g_node_new (g_object_ref (file));
 388 
 389 	if (file_attributes) {
 390 		GFileInfo *file_info;
 391 
 392 		file_info = g_file_query_info (file,
 393 		                               file_attributes,
 394 		                               G_FILE_QUERY_INFO_NOFOLLOW_SYMLINKS,
 395 		                               NULL,
 396 		                               NULL);
 397 		g_object_set_qdata_full (G_OBJECT (file),
 398 		                         file_info_quark,
 399 		                         file_info,
 400 		                         (GDestroyNotify) g_object_unref);
 401 	}
 402 
 403 	/* Fill in the processing info for the root node */
 404 	dir_info = directory_processing_data_new (info->tree);
 405 	g_queue_push_tail (info->directory_processing_queue, dir_info);
 406 
 407 	return info;
 408 }
 409 
 410 static gboolean
 411 directory_tree_free_foreach (GNode    *node,
 412 			     gpointer  user_data)
 413 {
 414 	g_object_unref (node->data);
 415 	return FALSE;
 416 }
 417 
 418 static void
 419 directory_root_info_free (DirectoryRootInfo *info)
 420 {
 421 	g_object_unref (info->directory);
 422 
 423 	g_node_traverse (info->tree,
 424 			 G_PRE_ORDER,
 425 			 G_TRAVERSE_ALL,
 426 			 -1,
 427 			 directory_tree_free_foreach,
 428 			 NULL);
 429 	g_node_destroy (info->tree);
 430 
 431 	g_queue_foreach (info->directory_processing_queue,
 432 			 (GFunc) directory_processing_data_free,
 433 			 NULL);
 434 	g_queue_free (info->directory_processing_queue);
 435 
 436 	g_slice_free (DirectoryRootInfo, info);
 437 }
 438 
 439 static gboolean
 440 process_func (gpointer data)
 441 {
 442 	TrackerCrawler          *crawler;
 443 	TrackerCrawlerPrivate   *priv;
 444 	DirectoryRootInfo       *info;
 445 	DirectoryProcessingData *dir_data = NULL;
 446 	gboolean                 stop_idle = FALSE;
 447 
 448 	crawler = TRACKER_CRAWLER (data);
 449 	priv = crawler->priv;
 450 
 451 	if (priv->is_paused) {
 452 		/* Stop the idle func for now until we are unpaused */
 453 		priv->idle_id = 0;
 454 
 455 		return FALSE;
 456 	}
 457 
 458 	info = g_queue_peek_head (priv->directories);
 459 
 460 	if (info) {
 461 		dir_data = g_queue_peek_head (info->directory_processing_queue);
 462 	}
 463 
 464 	if (dir_data) {
 465 		/* One directory inside the tree hierarchy is being inspected */
 466 		if (!dir_data->was_inspected) {
 467 			gboolean iterate;
 468 
 469 			if (G_NODE_IS_ROOT (dir_data->node)) {
 470 				iterate = check_directory (crawler, info, dir_data->node->data);
 471 			} else {
 472 				/* Directory has been already checked in the block below, so
 473 				 * so obey the settings for the current directory root.
 474 				 */
 475 				iterate = info->recurse;
 476 			}
 477 
 478 			dir_data->was_inspected = TRUE;
 479 
 480 			/* Crawler may have been already stopped while we were waiting for the
 481 			 *  check_directory return value, and thus we should check if it's
 482 			 *  running before going on with the iteration */
 483 			if (priv->is_running && iterate) {
 484 				/* Directory contents haven't been inspected yet,
 485 				 * stop this idle function while it's being iterated
 486 				 */
 487 				file_enumerate_children (crawler, info, dir_data);
 488 				stop_idle = TRUE;
 489 			}
 490 		} else if (dir_data->was_inspected &&
 491 			   !dir_data->ignored_by_content &&
 492 			   dir_data->children != NULL) {
 493 			DirectoryChildData *child_data;
 494 			GNode *child_node = NULL;
 495 
 496 			/* Directory has been already inspected, take children
 497 			 * one by one and check whether they should be incorporated
 498 			 * to the tree.
 499 			 */
 500 			child_data = dir_data->children->data;
 501 			dir_data->children = g_slist_remove (dir_data->children, child_data);
 502 
 503 			if (((child_data->is_dir &&
 504 			      check_directory (crawler, info, child_data->child)) ||
 505 			     (!child_data->is_dir &&
 506 			      check_file (crawler, info, child_data->child))) &&
 507 			    /* Crawler may have been already stopped while we were waiting for the
 508 			     *	check_directory or check_file return value, and thus we should
 509 			     *	 check if it's running before going on */
 510 			    priv->is_running) {
 511 				child_node = g_node_prepend_data (dir_data->node,
 512 								  g_object_ref (child_data->child));
 513 			}
 514 
 515 			if (info->recurse && priv->is_running &&
 516 			    child_node && child_data->is_dir) {
 517 				DirectoryProcessingData *child_dir_data;
 518 
 519 				child_dir_data = directory_processing_data_new (child_node);
 520 				g_queue_push_tail (info->directory_processing_queue, child_dir_data);
 521 			}
 522 
 523 			directory_child_data_free (child_data);
 524 		} else {
 525 			/* No (more) children, or directory ignored. stop processing. */
 526 			g_queue_pop_head (info->directory_processing_queue);
 527 			directory_processing_data_free (dir_data);
 528 		}
 529 	} else if (!dir_data && info) {
 530 		/* Current directory being crawled doesn't have anything else
 531 		 * to process, emit ::directory-crawled and free data.
 532 		 */
 533 		g_signal_emit (crawler, signals[DIRECTORY_CRAWLED], 0,
 534 			       info->directory,
 535 			       info->tree,
 536 			       info->directories_found,
 537 			       info->directories_ignored,
 538 			       info->files_found,
 539 			       info->files_ignored);
 540 
 541 		g_queue_pop_head (priv->directories);
 542 		directory_root_info_free (info);
 543 	}
 544 
 545 	if (!g_queue_peek_head (priv->directories)) {
 546 		/* There's nothing else to process */
 547 		priv->is_finished = TRUE;
 548 		tracker_crawler_stop (crawler);
 549 		stop_idle = TRUE;
 550 	}
 551 
 552 	if (stop_idle) {
 553 		priv->idle_id = 0;
 554 		return FALSE;
 555 	}
 556 
 557 	return TRUE;
 558 }
 559 
 560 static gboolean
 561 process_func_start (TrackerCrawler *crawler)
 562 {
 563 	if (crawler->priv->is_paused) {
 564 		return FALSE;
 565 	}
 566 
 567 	if (crawler->priv->is_finished) {
 568 		return FALSE;
 569 	}
 570 
 571 	if (crawler->priv->idle_id == 0) {
 572 		crawler->priv->idle_id = g_idle_add (process_func, crawler);
 573 	}
 574 
 575 	return TRUE;
 576 }
 577 
 578 static void
 579 process_func_stop (TrackerCrawler *crawler)
 580 {
 581 	if (crawler->priv->idle_id != 0) {
 582 		g_source_remove (crawler->priv->idle_id);
 583 		crawler->priv->idle_id = 0;
 584 	}
 585 }
 586 
 587 static EnumeratorData *
 588 enumerator_data_new (TrackerCrawler          *crawler,
 589 		     DirectoryRootInfo       *root_info,
 590 		     DirectoryProcessingData *dir_info)
 591 {
 592 	EnumeratorData *ed;
 593 
 594 	ed = g_slice_new (EnumeratorData);
 595 
 596 	ed->crawler = g_object_ref (crawler);
 597 	ed->root_info = root_info;
 598 	ed->dir_info = dir_info;
 599 	/* Make sure there's always a ref of the GFile while we're
 600 	 * iterating it */
 601 	ed->dir_file = g_object_ref (G_FILE (dir_info->node->data));
 602 	ed->cancellable = g_cancellable_new ();
 603 
 604 	crawler->priv->cancellables = g_list_prepend (crawler->priv->cancellables,
 605 						      ed->cancellable);
 606 	return ed;
 607 }
 608 
 609 static void
 610 enumerator_data_process (EnumeratorData *ed)
 611 {
 612 	TrackerCrawler *crawler;
 613 	GSList *l;
 614 	GList *children = NULL;
 615 	gboolean use;
 616 
 617 	crawler = ed->crawler;
 618 
 619 	for (l = ed->dir_info->children; l; l = l->next) {
 620 		DirectoryChildData *child_data;
 621 
 622 		child_data = l->data;
 623 		children = g_list_prepend (children, child_data->child);
 624 	}
 625 
 626 	g_signal_emit (crawler, signals[CHECK_DIRECTORY_CONTENTS], 0, ed->dir_info->node->data, children, &use);
 627 	g_list_free (children);
 628 
 629 	if (!use) {
 630 		ed->dir_info->ignored_by_content = TRUE;
 631 		/* FIXME: Update stats */
 632 		return;
 633 	}
 634 }
 635 
 636 static void
 637 enumerator_data_free (EnumeratorData *ed)
 638 {
 639 	ed->crawler->priv->cancellables =
 640 		g_list_remove (ed->crawler->priv->cancellables,
 641 			       ed->cancellable);
 642 
 643 	g_object_unref (ed->dir_file);
 644 	g_object_unref (ed->crawler);
 645 	g_object_unref (ed->cancellable);
 646 	g_slice_free (EnumeratorData, ed);
 647 }
 648 
 649 static void
 650 file_enumerator_close_cb (GObject      *enumerator,
 651                           GAsyncResult *result,
 652                           gpointer      user_data)
 653 {
 654 	TrackerCrawler *crawler;
 655 	GError *error = NULL;
 656 
 657 	crawler = TRACKER_CRAWLER (user_data);
 658 
 659 	if (!g_file_enumerator_close_finish (G_FILE_ENUMERATOR (enumerator),
 660 	                                     result,
 661 	                                     &error)) {
 662 		g_warning ("Couldn't close GFileEnumerator (%p): %s", enumerator,
 663 		           (error) ? error->message : "No reason");
 664 
 665 		g_clear_error (&error);
 666 	}
 667 
 668 	/* Processing of directory is now finished,
 669 	 * continue with queued files/directories.
 670 	 */
 671 	process_func_start (crawler);
 672 }
 673 
 674 static void
 675 file_enumerate_next_cb (GObject      *object,
 676                         GAsyncResult *result,
 677                         gpointer      user_data)
 678 {
 679 	TrackerCrawler *crawler;
 680 	EnumeratorData *ed;
 681 	GFileEnumerator *enumerator;
 682 	GFile *parent, *child;
 683 	GFileInfo *info;
 684 	GList *files, *l;
 685 	GError *error = NULL;
 686 	gboolean cancelled;
 687 
 688 	enumerator = G_FILE_ENUMERATOR (object);
 689 
 690 	ed = user_data;
 691 	crawler = ed->crawler;
 692 	cancelled = g_cancellable_is_cancelled (ed->cancellable);
 693 
 694 	files = g_file_enumerator_next_files_finish (enumerator,
 695 	                                             result,
 696 	                                             &error);
 697 
 698 	if (error || !files || !crawler->priv->is_running) {
 699 		if (error && !cancelled) {
 700 			g_critical ("Could not crawl through directory: %s", error->message);
 701 			g_error_free (error);
 702 		}
 703 
 704 		/* No more files or we are stopping anyway, so clean
 705 		 * up and close all file enumerators.
 706 		 */
 707 		if (files) {
 708 			g_list_foreach (files, (GFunc) g_object_unref, NULL);
 709 			g_list_free (files);
 710 		}
 711 
 712 		if (!cancelled) {
 713 			enumerator_data_process (ed);
 714 		}
 715 
 716 		enumerator_data_free (ed);
 717 		g_file_enumerator_close_async (enumerator,
 718 		                               G_PRIORITY_DEFAULT,
 719 		                               NULL,
 720 		                               file_enumerator_close_cb,
 721 		                               crawler);
 722 		g_object_unref (enumerator);
 723 
 724 		return;
 725 	}
 726 
 727 	parent = ed->dir_info->node->data;
 728 
 729 	for (l = files; l; l = l->next) {
 730 		const gchar *child_name;
 731 		gboolean is_dir;
 732 
 733 		info = l->data;
 734 
 735 		child_name = g_file_info_get_name (info);
 736 		child = g_file_get_child (parent, child_name);
 737 		is_dir = g_file_info_get_file_type (info) == G_FILE_TYPE_DIRECTORY;
 738 
 739 		if (crawler->priv->file_attributes) {
 740 			/* Store the file info for future retrieval */
 741 			g_object_set_qdata_full (G_OBJECT (child),
 742 						 file_info_quark,
 743 						 g_object_ref (info),
 744 						 (GDestroyNotify) g_object_unref);
 745 		}
 746 
 747 		directory_processing_data_add_child (ed->dir_info, child, is_dir);
 748 
 749 		g_object_unref (child);
 750 		g_object_unref (info);
 751 	}
 752 
 753 	g_list_free (files);
 754 
 755 	/* Get next files */
 756 	file_enumerate_next (enumerator, ed);
 757 }
 758 
 759 static void
 760 file_enumerate_next (GFileEnumerator *enumerator,
 761                      EnumeratorData  *ed)
 762 {
 763 	g_file_enumerator_next_files_async (enumerator,
 764 	                                    FILES_GROUP_SIZE,
 765 	                                    G_PRIORITY_DEFAULT,
 766 	                                    ed->cancellable,
 767 	                                    file_enumerate_next_cb,
 768 	                                    ed);
 769 }
 770 
 771 static void
 772 file_enumerate_children_cb (GObject      *file,
 773                             GAsyncResult *result,
 774                             gpointer      user_data)
 775 {
 776 	TrackerCrawler *crawler;
 777 	EnumeratorData *ed;
 778 	GFileEnumerator *enumerator;
 779 	GFile *parent;
 780 	GError *error = NULL;
 781 	gboolean cancelled;
 782 
 783 	parent = G_FILE (file);
 784 	ed = (EnumeratorData*) user_data;
 785 	crawler = ed->crawler;
 786 	cancelled = g_cancellable_is_cancelled (ed->cancellable);
 787 	enumerator = g_file_enumerate_children_finish (parent, result, &error);
 788 
 789 	if (!enumerator) {
 790 		if (error && !cancelled) {
 791 			gchar *path;
 792 
 793 			path = g_file_get_path (parent);
 794 
 795 			g_warning ("Could not open directory '%s': %s",
 796 			           path, error->message);
 797 
 798 			g_error_free (error);
 799 			g_free (path);
 800 		}
 801 
 802 		enumerator_data_free (ed);
 803 		process_func_start (crawler);
 804 		return;
 805 	}
 806 
 807 	/* Start traversing the directory's files */
 808 	file_enumerate_next (enumerator, ed);
 809 }
 810 
 811 static void
 812 file_enumerate_children (TrackerCrawler          *crawler,
 813 			 DirectoryRootInfo       *info,
 814 			 DirectoryProcessingData *dir_data)
 815 {
 816 	EnumeratorData *ed;
 817 	gchar *attrs;
 818 
 819 	ed = enumerator_data_new (crawler, info, dir_data);
 820 
 821 	if (crawler->priv->file_attributes) {
 822 		attrs = g_strconcat (FILE_ATTRIBUTES ",",
 823 		                     crawler->priv->file_attributes,
 824 		                     NULL);
 825 	} else {
 826 		attrs = g_strdup (FILE_ATTRIBUTES);
 827 	}
 828 
 829 	g_file_enumerate_children_async (ed->dir_file,
 830 	                                 attrs,
 831 	                                 G_FILE_QUERY_INFO_NOFOLLOW_SYMLINKS,
 832 	                                 G_PRIORITY_LOW,
 833 	                                 ed->cancellable,
 834 	                                 file_enumerate_children_cb,
 835 	                                 ed);
 836 
 837 	g_free (attrs);
 838 }
 839 
 840 gboolean
 841 tracker_crawler_start (TrackerCrawler *crawler,
 842                        GFile          *file,
 843                        gboolean        recurse)
 844 {
 845 	TrackerCrawlerPrivate *priv;
 846 	DirectoryRootInfo *info;
 847 
 848 	g_return_val_if_fail (TRACKER_IS_CRAWLER (crawler), FALSE);
 849 	g_return_val_if_fail (G_IS_FILE (file), FALSE);
 850 
 851 	priv = crawler->priv;
 852 
 853 	if (!g_file_query_exists (file, NULL)) {
 854 		/* This shouldn't happen, unless the removal/unmount notification
 855 		 * didn't yet reach the TrackerFileNotifier.
 856 		 */
 857 		return FALSE;
 858 	}
 859 
 860 	priv->was_started = TRUE;
 861 	priv->recurse = recurse;
 862 
 863 	/* Time the event */
 864 	if (priv->timer) {
 865 		g_timer_destroy (priv->timer);
 866 	}
 867 
 868 	priv->timer = g_timer_new ();
 869 
 870 	if (priv->is_paused) {
 871 		g_timer_stop (priv->timer);
 872 	}
 873 
 874 	/* Set as running now */
 875 	priv->is_running = TRUE;
 876 	priv->is_finished = FALSE;
 877 
 878 	info = directory_root_info_new (file, recurse, priv->file_attributes);
 879 	g_queue_push_tail (priv->directories, info);
 880 
 881 	process_func_start (crawler);
 882 
 883 	return TRUE;
 884 }
 885 
 886 void
 887 tracker_crawler_stop (TrackerCrawler *crawler)
 888 {
 889 	TrackerCrawlerPrivate *priv;
 890 
 891 	g_return_if_fail (TRACKER_IS_CRAWLER (crawler));
 892 
 893 	priv = crawler->priv;
 894 
 895 	/* If already not running, just ignore */
 896 	if (!priv->is_running) {
 897 		return;
 898 	}
 899 
 900 	priv->is_running = FALSE;
 901 	g_list_foreach (priv->cancellables, (GFunc) g_cancellable_cancel, NULL);
 902 
 903 	process_func_stop (crawler);
 904 
 905 	if (priv->timer) {
 906 		g_timer_destroy (priv->timer);
 907 		priv->timer = NULL;
 908 	}
 909 
 910 	/* Clean up queue */
 911 	g_queue_foreach (priv->directories, (GFunc) directory_root_info_free, NULL);
 912 	g_queue_clear (priv->directories);
 913 
 914 	g_signal_emit (crawler, signals[FINISHED], 0,
 915 	               !priv->is_finished);
 916 
 917 	/* We don't free the queue in case the crawler is reused, it
 918 	 * is only freed in finalize.
 919 	 */
 920 }
 921 
 922 void
 923 tracker_crawler_pause (TrackerCrawler *crawler)
 924 {
 925 	g_return_if_fail (TRACKER_IS_CRAWLER (crawler));
 926 
 927 	crawler->priv->is_paused = TRUE;
 928 
 929 	if (crawler->priv->is_running) {
 930 		g_timer_stop (crawler->priv->timer);
 931 		process_func_stop (crawler);
 932 	}
 933 
 934 	g_message ("Crawler is paused, %s",
 935 	           crawler->priv->is_running ? "currently running" : "not running");
 936 }
 937 
 938 void
 939 tracker_crawler_resume (TrackerCrawler *crawler)
 940 {
 941 	g_return_if_fail (TRACKER_IS_CRAWLER (crawler));
 942 
 943 	crawler->priv->is_paused = FALSE;
 944 
 945 	if (crawler->priv->is_running) {
 946 		g_timer_continue (crawler->priv->timer);
 947 		process_func_start (crawler);
 948 	}
 949 
 950 	g_message ("Crawler is resuming, %s",
 951 	           crawler->priv->is_running ? "currently running" : "not running");
 952 }
 953 
 954 void
 955 tracker_crawler_set_throttle (TrackerCrawler *crawler,
 956                               gdouble         throttle)
 957 {
 958 	g_return_if_fail (TRACKER_IS_CRAWLER (crawler));
 959 
 960 	throttle = CLAMP (throttle, 0, 1);
 961 	crawler->priv->throttle = throttle;
 962 
 963 	/* Update timeouts */
 964 	if (crawler->priv->idle_id != 0) {
 965 		guint interval, idle_id;
 966 
 967 		interval = TRACKER_MAX_TIMEOUT_INTERVAL * crawler->priv->throttle;
 968 
 969 		g_source_remove (crawler->priv->idle_id);
 970 
 971 		if (interval == 0) {
 972 			idle_id = g_idle_add (process_func, crawler);
 973 		} else {
 974 			idle_id = g_timeout_add (interval, process_func, crawler);
 975 		}
 976 
 977 		crawler->priv->idle_id = idle_id;
 978 	}
 979 }
 980 
 981 /**
 982  * tracker_crawler_set_file_attributes:
 983  * @crawler: a #TrackerCrawler
 984  * @file_attributes: file attributes to extract
 985  *
 986  * Sets the file attributes that @crawler will fetch for every
 987  * file it gets, this info may be requested through
 988  * tracker_crawler_get_file_info() in any #TrackerCrawler callback
 989  **/
 990 void
 991 tracker_crawler_set_file_attributes (TrackerCrawler *crawler,
 992 				     const gchar    *file_attributes)
 993 {
 994 	g_return_if_fail (TRACKER_IS_CRAWLER (crawler));
 995 
 996 	g_free (crawler->priv->file_attributes);
 997 	crawler->priv->file_attributes = g_strdup (file_attributes);
 998 }
 999 
1000 /**
1001  * tracker_crawler_get_file_attributes:
1002  * @crawler: a #TrackerCrawler
1003  *
1004  * Returns the file attributes that @crawler will fetch
1005  *
1006  * Returns: the file attributes as a string.
1007  **/
1008 const gchar *
1009 tracker_crawler_get_file_attributes (TrackerCrawler *crawler)
1010 {
1011 	g_return_val_if_fail (TRACKER_IS_CRAWLER (crawler), NULL);
1012 
1013 	return crawler->priv->file_attributes;
1014 }
1015 
1016 /**
1017  * tracker_crawler_get_file_info:
1018  * @crawler: a #TrackerCrawler
1019  * @file: a #GFile returned by @crawler
1020  *
1021  * Returns a #GFileInfo with the file attributes requested through
1022  * tracker_crawler_set_file_attributes().
1023  *
1024  * Returns: (transfer none): a #GFileInfo with the file information
1025  **/
1026 GFileInfo *
1027 tracker_crawler_get_file_info (TrackerCrawler *crawler,
1028 			       GFile          *file)
1029 {
1030 	GFileInfo *info;
1031 
1032 	g_return_val_if_fail (TRACKER_IS_CRAWLER (crawler), NULL);
1033 	g_return_val_if_fail (G_IS_FILE (file), NULL);
1034 
1035 	info = g_object_get_qdata (G_OBJECT (file), file_info_quark);
1036 	return info;
1037 }