[tracker-miners/wip/carlosg/shuffle-libtracker-miner: 1/116] Restore libtracker-miner in this repo



commit 3c9f4a38278115148efa04b8ac5c5f5725782352
Author: Carlos Garnacho <carlosg gnome org>
Date:   Wed Dec 11 13:04:52 2019 +0100

    Restore libtracker-miner in this repo
    
    This library is going private, and into tracker-miners control.
    Bring it back just enough so that we can apply the relevant
    patches that went in in the tracker repo after this change.
    
    This is a partial revert of commit
    63af0cfe2dc3642d46f34159a89569db16ae0b61.

 meson.build                                        |   11 +-
 meson_options.txt                                  |    2 +
 src/libtracker-miner/.gitignore                    |    6 +
 src/libtracker-miner/COPYING.LIB                   |  510 ++++
 src/libtracker-miner/TrackerMiner-1.0.metadata     |   18 +
 src/libtracker-miner/meson.build                   |  104 +
 src/libtracker-miner/tracker-crawler.c             | 1271 +++++++++
 src/libtracker-miner/tracker-crawler.h             |   96 +
 src/libtracker-miner/tracker-data-provider.c       |  207 ++
 src/libtracker-miner/tracker-data-provider.h       |  117 +
 src/libtracker-miner/tracker-decorator-fs.c        |  332 +++
 src/libtracker-miner/tracker-decorator-fs.h        |   73 +
 src/libtracker-miner/tracker-decorator-private.h   |   27 +
 src/libtracker-miner/tracker-decorator.c           | 1695 +++++++++++
 src/libtracker-miner/tracker-decorator.h           |  138 +
 src/libtracker-miner/tracker-file-data-provider.c  |  240 ++
 src/libtracker-miner/tracker-file-data-provider.h  |   62 +
 src/libtracker-miner/tracker-file-notifier.c       | 2116 ++++++++++++++
 src/libtracker-miner/tracker-file-notifier.h       |  102 +
 src/libtracker-miner/tracker-file-system.c         | 1062 +++++++
 src/libtracker-miner/tracker-file-system.h         |  107 +
 src/libtracker-miner/tracker-indexing-tree.c       | 1218 ++++++++
 src/libtracker-miner/tracker-indexing-tree.h       |  134 +
 .../tracker-miner-enum-types.c.template            |   44 +
 .../tracker-miner-enum-types.h.template            |   26 +
 src/libtracker-miner/tracker-miner-enums.h         |  131 +
 src/libtracker-miner/tracker-miner-fs.c            | 2973 ++++++++++++++++++++
 src/libtracker-miner/tracker-miner-fs.h            |  168 ++
 src/libtracker-miner/tracker-miner-object.c        |  631 +++++
 src/libtracker-miner/tracker-miner-object.h        |  185 ++
 src/libtracker-miner/tracker-miner-online.c        |  407 +++
 src/libtracker-miner/tracker-miner-online.h        |   81 +
 src/libtracker-miner/tracker-miner-proxy.c         |  851 ++++++
 src/libtracker-miner/tracker-miner-proxy.h         |   61 +
 src/libtracker-miner/tracker-miner.deps            |    1 +
 src/libtracker-miner/tracker-miner.h               |   38 +
 src/libtracker-miner/tracker-miner.pc.in           |   11 +
 src/libtracker-miner/tracker-miner.vapi            |  208 ++
 src/libtracker-miner/tracker-miner.xml             |   56 +
 src/libtracker-miner/tracker-monitor.c             | 1748 ++++++++++++
 src/libtracker-miner/tracker-monitor.h             |   84 +
 src/libtracker-miner/tracker-priority-queue.c      |  455 +++
 src/libtracker-miner/tracker-priority-queue.h      |   74 +
 src/libtracker-miner/tracker-sparql-buffer.c       |  795 ++++++
 src/libtracker-miner/tracker-sparql-buffer.h       |   92 +
 src/libtracker-miner/tracker-task-pool.c           |  348 +++
 src/libtracker-miner/tracker-task-pool.h           |   92 +
 src/libtracker-miner/tracker-utils.c               |   36 +
 src/libtracker-miner/tracker-utils.h               |   38 +
 src/meson.build                                    |    1 +
 tests/libtracker-miner/.gitignore                  |   15 +
 tests/libtracker-miner/data/dir/empty-dir/.hidden  |    0
 tests/libtracker-miner/data/dir/file1              |    0
 tests/libtracker-miner/data/dir/file2              |    0
 tests/libtracker-miner/data/empty-dir/.hidden      |    0
 tests/libtracker-miner/data/file1                  |    0
 tests/libtracker-miner/empty-gobject.c             |  140 +
 tests/libtracker-miner/empty-gobject.h             |   43 +
 tests/libtracker-miner/meson.build                 |   69 +
 tests/libtracker-miner/miners-mock.c               |  276 ++
 tests/libtracker-miner/miners-mock.h               |   42 +
 .../mock-miners/mock-miner-1.desktop               |    5 +
 .../mock-miners/mock-miner-2.desktop               |    5 +
 tests/libtracker-miner/thumbnailer-mock.c          |  133 +
 tests/libtracker-miner/thumbnailer-mock.h          |   33 +
 .../libtracker-miner/tracker-connection-mock.vala  |   97 +
 tests/libtracker-miner/tracker-crawler-test.c      |  350 +++
 .../tracker-file-enumerator-test.c                 |   94 +
 .../libtracker-miner/tracker-file-notifier-test.c  |  794 ++++++
 tests/libtracker-miner/tracker-file-system-test.c  |  254 ++
 .../libtracker-miner/tracker-indexing-tree-test.c  |  986 +++++++
 tests/libtracker-miner/tracker-miner-mock.vala     |   70 +
 tests/libtracker-miner/tracker-monitor-test.c      | 2020 +++++++++++++
 .../libtracker-miner/tracker-priority-queue-test.c |  252 ++
 tests/libtracker-miner/tracker-task-pool-test.c    |  186 ++
 tests/libtracker-miner/tracker-thumbnailer-test.c  |  156 +
 tests/meson.build                                  |    1 +
 77 files changed, 25301 insertions(+), 3 deletions(-)
---
diff --git a/meson.build b/meson.build
index f896b40ba..4cdda7dce 100644
--- a/meson.build
+++ b/meson.build
@@ -16,10 +16,9 @@ glib_required = '2.40.0'
 
 if get_option('tracker_core') == 'system'
   tracker_sparql = dependency('tracker-sparql-2.0', version: '>= 2.2.0', required: false)
-  tracker_miner = dependency('tracker-miner-2.0', version: '>= 2.2.0', required: false)
   tracker_testutils = dependency('tracker-testutils-2.0', required: false)
 
-  if not tracker_sparql.found() or not tracker_miner.found() or not tracker_testutils.found()
+  if not tracker_sparql.found() or not tracker_testutils.found()
     error('Did not find the required versions of the Tracker core libraries ' +
           'installed in the system. Please ensure they are installed, or ' +
           'use the -Dtracker_core=subproject option to build from Git.')
@@ -42,6 +41,7 @@ if get_option('tracker_core') == 'system'
   tracker_uninstalled_nepomuk_ontologies_dir = 
join_paths(tracker_sparql.get_pkgconfig_variable('ontologies_dir'), 'nepomuk')
   tracker_uninstalled_stop_words_dir = join_paths(tracker_sparql.get_pkgconfig_variable('datadir'), 
'tracker', 'stop-words')
   tracker_uninstalled_testutils_dir = tracker_testutils.get_pkgconfig_variable('python_path')
+  tracker_uninstalled_nepomuk_ontologies_dir = 
join_paths(tracker_sparql.get_pkgconfig_variable('ontologies_dir'), 'nepomuk')
 else
   tracker_subproject = subproject('tracker',
     default_options: [
@@ -51,7 +51,6 @@ else
     ])
 
   tracker_sparql = tracker_subproject.get_variable('tracker_sparql_dep')
-  tracker_miner = tracker_subproject.get_variable('tracker_miner_dep')
 
   tracker_store = tracker_subproject.get_variable('tracker_store')
   tracker_store_path = tracker_store.full_path()
@@ -62,6 +61,7 @@ else
   tracker_uninstalled_nepomuk_ontologies_dir = 
tracker_subproject.get_variable('tracker_uninstalled_nepomuk_ontologies_dir')
   tracker_uninstalled_stop_words_dir = tracker_subproject.get_variable('tracker_uninstalled_stop_words_dir')
   tracker_uninstalled_testutils_dir = tracker_subproject.get_variable('tracker_uninstalled_testutils_dir')
+  tracker_uninstalled_nepomuk_ontologies_dir = 
tracker_subproject.get_variable('tracker_uninstalled_nepomuk_ontologies_dir')
 
   tracker_common_enums_header = tracker_subproject.get_variable('tracker_common_enums_header')
   tracker_gsettings_schemas = tracker_subproject.get_variable('tracker_gsettings_schemas')
@@ -107,6 +107,9 @@ zlib = dependency('zlib')
 libgif = cc.find_library('gif', required: get_option('gif'))
 libmath = cc.find_library('m', required: false)
 
+network_manager = dependency('libnm', required: get_option('network_manager'))
+have_network_manager = network_manager.found()
+
 have_tracker_extract = get_option('extract')
 have_tracker_miner_fs = get_option('miner_fs')
 have_tracker_miner_rss = get_option('miner_rss')
@@ -342,6 +345,7 @@ conf.set('HAVE_LIBEXIF', libexif.found())
 conf.set('HAVE_LIBIPTCDATA', libiptcdata.found())
 conf.set('HAVE_LIBSECCOMP', libseccomp.found())
 conf.set('HAVE_UPOWER', battery_detection_library_name == 'upower')
+conf.set('HAVE_NETWORK_MANAGER', have_network_manager)
 
 conf.set('HAVE_GETLINE', cc.has_function('getline', prefix : '#include <stdio.h>'))
 conf.set('HAVE_POSIX_FADVISE', cc.has_function('posix_fadvise', prefix : '#include <fcntl.h>'))
@@ -451,6 +455,7 @@ summary = [
   '\nFeature Support:',
   '    Battery/mains power detection:          ' + battery_detection_library_name,
   '    Build with Journal support:             ' + get_option('journal').to_string(),
+  '    Support for network status detection:   ' + have_network_manager.to_string(),
   '\nData Miners / Writebacks:',
   '    FS (File System):                       ' + have_tracker_miner_fs.to_string(),
   '    RSS:                                    ' + have_tracker_miner_rss.to_string(),
diff --git a/meson_options.txt b/meson_options.txt
index ca00f1fc1..87ce733ad 100644
--- a/meson_options.txt
+++ b/meson_options.txt
@@ -18,6 +18,8 @@ option('miner_rss', type: 'boolean', value: true,
 option('writeback', type: 'boolean', value: true,
        description: 'Enable Tracker writeback feature')
 
+option('network_manager', type: 'feature', value: 'auto',
+       description: 'Connection detection through NetworkManager')
 option('abiword', type: 'boolean', value: 'true',
        description: 'Enable extractor for AbiWord files')
 option('dvi', type: 'boolean', value: 'true',
diff --git a/src/libtracker-miner/.gitignore b/src/libtracker-miner/.gitignore
new file mode 100644
index 000000000..4664a2c8c
--- /dev/null
+++ b/src/libtracker-miner/.gitignore
@@ -0,0 +1,6 @@
+tracker-miner-*.deps
+tracker-miner-*.vapi
+tracker-miner-web-full.xml
+tracker-miner-enum-types.c
+tracker-miner-enum-types.h
+*.pc
diff --git a/src/libtracker-miner/COPYING.LIB b/src/libtracker-miner/COPYING.LIB
new file mode 100644
index 000000000..2d2d780e6
--- /dev/null
+++ b/src/libtracker-miner/COPYING.LIB
@@ -0,0 +1,510 @@
+
+                  GNU LESSER GENERAL PUBLIC LICENSE
+                       Version 2.1, February 1999
+
+ Copyright (C) 1991, 1999 Free Software Foundation, Inc.
+       51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
+ Everyone is permitted to copy and distribute verbatim copies
+ of this license document, but changing it is not allowed.
+
+[This is the first released version of the Lesser GPL.  It also counts
+ as the successor of the GNU Library Public License, version 2, hence
+ the version number 2.1.]
+
+                            Preamble
+
+  The licenses for most software are designed to take away your
+freedom to share and change it.  By contrast, the GNU General Public
+Licenses are intended to guarantee your freedom to share and change
+free software--to make sure the software is free for all its users.
+
+  This license, the Lesser General Public License, applies to some
+specially designated software packages--typically libraries--of the
+Free Software Foundation and other authors who decide to use it.  You
+can use it too, but we suggest you first think carefully about whether
+this license or the ordinary General Public License is the better
+strategy to use in any particular case, based on the explanations
+below.
+
+  When we speak of free software, we are referring to freedom of use,
+not price.  Our General Public Licenses are designed to make sure that
+you have the freedom to distribute copies of free software (and charge
+for this service if you wish); that you receive source code or can get
+it if you want it; that you can change the software and use pieces of
+it in new free programs; and that you are informed that you can do
+these things.
+
+  To protect your rights, we need to make restrictions that forbid
+distributors to deny you these rights or to ask you to surrender these
+rights.  These restrictions translate to certain responsibilities for
+you if you distribute copies of the library or if you modify it.
+
+  For example, if you distribute copies of the library, whether gratis
+or for a fee, you must give the recipients all the rights that we gave
+you.  You must make sure that they, too, receive or can get the source
+code.  If you link other code with the library, you must provide
+complete object files to the recipients, so that they can relink them
+with the library after making changes to the library and recompiling
+it.  And you must show them these terms so they know their rights.
+
+  We protect your rights with a two-step method: (1) we copyright the
+library, and (2) we offer you this license, which gives you legal
+permission to copy, distribute and/or modify the library.
+
+  To protect each distributor, we want to make it very clear that
+there is no warranty for the free library.  Also, if the library is
+modified by someone else and passed on, the recipients should know
+that what they have is not the original version, so that the original
+author's reputation will not be affected by problems that might be
+introduced by others.
+
+  Finally, software patents pose a constant threat to the existence of
+any free program.  We wish to make sure that a company cannot
+effectively restrict the users of a free program by obtaining a
+restrictive license from a patent holder.  Therefore, we insist that
+any patent license obtained for a version of the library must be
+consistent with the full freedom of use specified in this license.
+
+  Most GNU software, including some libraries, is covered by the
+ordinary GNU General Public License.  This license, the GNU Lesser
+General Public License, applies to certain designated libraries, and
+is quite different from the ordinary General Public License.  We use
+this license for certain libraries in order to permit linking those
+libraries into non-free programs.
+
+  When a program is linked with a library, whether statically or using
+a shared library, the combination of the two is legally speaking a
+combined work, a derivative of the original library.  The ordinary
+General Public License therefore permits such linking only if the
+entire combination fits its criteria of freedom.  The Lesser General
+Public License permits more lax criteria for linking other code with
+the library.
+
+  We call this license the "Lesser" General Public License because it
+does Less to protect the user's freedom than the ordinary General
+Public License.  It also provides other free software developers Less
+of an advantage over competing non-free programs.  These disadvantages
+are the reason we use the ordinary General Public License for many
+libraries.  However, the Lesser license provides advantages in certain
+special circumstances.
+
+  For example, on rare occasions, there may be a special need to
+encourage the widest possible use of a certain library, so that it
+becomes a de-facto standard.  To achieve this, non-free programs must
+be allowed to use the library.  A more frequent case is that a free
+library does the same job as widely used non-free libraries.  In this
+case, there is little to gain by limiting the free library to free
+software only, so we use the Lesser General Public License.
+
+  In other cases, permission to use a particular library in non-free
+programs enables a greater number of people to use a large body of
+free software.  For example, permission to use the GNU C Library in
+non-free programs enables many more people to use the whole GNU
+operating system, as well as its variant, the GNU/Linux operating
+system.
+
+  Although the Lesser General Public License is Less protective of the
+users' freedom, it does ensure that the user of a program that is
+linked with the Library has the freedom and the wherewithal to run
+that program using a modified version of the Library.
+
+  The precise terms and conditions for copying, distribution and
+modification follow.  Pay close attention to the difference between a
+"work based on the library" and a "work that uses the library".  The
+former contains code derived from the library, whereas the latter must
+be combined with the library in order to run.
+
+                  GNU LESSER GENERAL PUBLIC LICENSE
+   TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
+
+  0. This License Agreement applies to any software library or other
+program which contains a notice placed by the copyright holder or
+other authorized party saying it may be distributed under the terms of
+this Lesser General Public License (also called "this License").
+Each licensee is addressed as "you".
+
+  A "library" means a collection of software functions and/or data
+prepared so as to be conveniently linked with application programs
+(which use some of those functions and data) to form executables.
+
+  The "Library", below, refers to any such software library or work
+which has been distributed under these terms.  A "work based on the
+Library" means either the Library or any derivative work under
+copyright law: that is to say, a work containing the Library or a
+portion of it, either verbatim or with modifications and/or translated
+straightforwardly into another language.  (Hereinafter, translation is
+included without limitation in the term "modification".)
+
+  "Source code" for a work means the preferred form of the work for
+making modifications to it.  For a library, complete source code means
+all the source code for all modules it contains, plus any associated
+interface definition files, plus the scripts used to control
+compilation and installation of the library.
+
+  Activities other than copying, distribution and modification are not
+covered by this License; they are outside its scope.  The act of
+running a program using the Library is not restricted, and output from
+such a program is covered only if its contents constitute a work based
+on the Library (independent of the use of the Library in a tool for
+writing it).  Whether that is true depends on what the Library does
+and what the program that uses the Library does.
+
+  1. You may copy and distribute verbatim copies of the Library's
+complete source code as you receive it, in any medium, provided that
+you conspicuously and appropriately publish on each copy an
+appropriate copyright notice and disclaimer of warranty; keep intact
+all the notices that refer to this License and to the absence of any
+warranty; and distribute a copy of this License along with the
+Library.
+
+  You may charge a fee for the physical act of transferring a copy,
+and you may at your option offer warranty protection in exchange for a
+fee.
+
+  2. You may modify your copy or copies of the Library or any portion
+of it, thus forming a work based on the Library, and copy and
+distribute such modifications or work under the terms of Section 1
+above, provided that you also meet all of these conditions:
+
+    a) The modified work must itself be a software library.
+
+    b) You must cause the files modified to carry prominent notices
+    stating that you changed the files and the date of any change.
+
+    c) You must cause the whole of the work to be licensed at no
+    charge to all third parties under the terms of this License.
+
+    d) If a facility in the modified Library refers to a function or a
+    table of data to be supplied by an application program that uses
+    the facility, other than as an argument passed when the facility
+    is invoked, then you must make a good faith effort to ensure that,
+    in the event an application does not supply such function or
+    table, the facility still operates, and performs whatever part of
+    its purpose remains meaningful.
+
+    (For example, a function in a library to compute square roots has
+    a purpose that is entirely well-defined independent of the
+    application.  Therefore, Subsection 2d requires that any
+    application-supplied function or table used by this function must
+    be optional: if the application does not supply it, the square
+    root function must still compute square roots.)
+
+These requirements apply to the modified work as a whole.  If
+identifiable sections of that work are not derived from the Library,
+and can be reasonably considered independent and separate works in
+themselves, then this License, and its terms, do not apply to those
+sections when you distribute them as separate works.  But when you
+distribute the same sections as part of a whole which is a work based
+on the Library, the distribution of the whole must be on the terms of
+this License, whose permissions for other licensees extend to the
+entire whole, and thus to each and every part regardless of who wrote
+it.
+
+Thus, it is not the intent of this section to claim rights or contest
+your rights to work written entirely by you; rather, the intent is to
+exercise the right to control the distribution of derivative or
+collective works based on the Library.
+
+In addition, mere aggregation of another work not based on the Library
+with the Library (or with a work based on the Library) on a volume of
+a storage or distribution medium does not bring the other work under
+the scope of this License.
+
+  3. You may opt to apply the terms of the ordinary GNU General Public
+License instead of this License to a given copy of the Library.  To do
+this, you must alter all the notices that refer to this License, so
+that they refer to the ordinary GNU General Public License, version 2,
+instead of to this License.  (If a newer version than version 2 of the
+ordinary GNU General Public License has appeared, then you can specify
+that version instead if you wish.)  Do not make any other change in
+these notices.
+
+  Once this change is made in a given copy, it is irreversible for
+that copy, so the ordinary GNU General Public License applies to all
+subsequent copies and derivative works made from that copy.
+
+  This option is useful when you wish to copy part of the code of
+the Library into a program that is not a library.
+
+  4. You may copy and distribute the Library (or a portion or
+derivative of it, under Section 2) in object code or executable form
+under the terms of Sections 1 and 2 above provided that you accompany
+it with the complete corresponding machine-readable source code, which
+must be distributed under the terms of Sections 1 and 2 above on a
+medium customarily used for software interchange.
+
+  If distribution of object code is made by offering access to copy
+from a designated place, then offering equivalent access to copy the
+source code from the same place satisfies the requirement to
+distribute the source code, even though third parties are not
+compelled to copy the source along with the object code.
+
+  5. A program that contains no derivative of any portion of the
+Library, but is designed to work with the Library by being compiled or
+linked with it, is called a "work that uses the Library".  Such a
+work, in isolation, is not a derivative work of the Library, and
+therefore falls outside the scope of this License.
+
+  However, linking a "work that uses the Library" with the Library
+creates an executable that is a derivative of the Library (because it
+contains portions of the Library), rather than a "work that uses the
+library".  The executable is therefore covered by this License.
+Section 6 states terms for distribution of such executables.
+
+  When a "work that uses the Library" uses material from a header file
+that is part of the Library, the object code for the work may be a
+derivative work of the Library even though the source code is not.
+Whether this is true is especially significant if the work can be
+linked without the Library, or if the work is itself a library.  The
+threshold for this to be true is not precisely defined by law.
+
+  If such an object file uses only numerical parameters, data
+structure layouts and accessors, and small macros and small inline
+functions (ten lines or less in length), then the use of the object
+file is unrestricted, regardless of whether it is legally a derivative
+work.  (Executables containing this object code plus portions of the
+Library will still fall under Section 6.)
+
+  Otherwise, if the work is a derivative of the Library, you may
+distribute the object code for the work under the terms of Section 6.
+Any executables containing that work also fall under Section 6,
+whether or not they are linked directly with the Library itself.
+
+  6. As an exception to the Sections above, you may also combine or
+link a "work that uses the Library" with the Library to produce a
+work containing portions of the Library, and distribute that work
+under terms of your choice, provided that the terms permit
+modification of the work for the customer's own use and reverse
+engineering for debugging such modifications.
+
+  You must give prominent notice with each copy of the work that the
+Library is used in it and that the Library and its use are covered by
+this License.  You must supply a copy of this License.  If the work
+during execution displays copyright notices, you must include the
+copyright notice for the Library among them, as well as a reference
+directing the user to the copy of this License.  Also, you must do one
+of these things:
+
+    a) Accompany the work with the complete corresponding
+    machine-readable source code for the Library including whatever
+    changes were used in the work (which must be distributed under
+    Sections 1 and 2 above); and, if the work is an executable linked
+    with the Library, with the complete machine-readable "work that
+    uses the Library", as object code and/or source code, so that the
+    user can modify the Library and then relink to produce a modified
+    executable containing the modified Library.  (It is understood
+    that the user who changes the contents of definitions files in the
+    Library will not necessarily be able to recompile the application
+    to use the modified definitions.)
+
+    b) Use a suitable shared library mechanism for linking with the
+    Library.  A suitable mechanism is one that (1) uses at run time a
+    copy of the library already present on the user's computer system,
+    rather than copying library functions into the executable, and (2)
+    will operate properly with a modified version of the library, if
+    the user installs one, as long as the modified version is
+    interface-compatible with the version that the work was made with.
+
+    c) Accompany the work with a written offer, valid for at least
+    three years, to give the same user the materials specified in
+    Subsection 6a, above, for a charge no more than the cost of
+    performing this distribution.
+
+    d) If distribution of the work is made by offering access to copy
+    from a designated place, offer equivalent access to copy the above
+    specified materials from the same place.
+
+    e) Verify that the user has already received a copy of these
+    materials or that you have already sent this user a copy.
+
+  For an executable, the required form of the "work that uses the
+Library" must include any data and utility programs needed for
+reproducing the executable from it.  However, as a special exception,
+the materials to be distributed need not include anything that is
+normally distributed (in either source or binary form) with the major
+components (compiler, kernel, and so on) of the operating system on
+which the executable runs, unless that component itself accompanies
+the executable.
+
+  It may happen that this requirement contradicts the license
+restrictions of other proprietary libraries that do not normally
+accompany the operating system.  Such a contradiction means you cannot
+use both them and the Library together in an executable that you
+distribute.
+
+  7. You may place library facilities that are a work based on the
+Library side-by-side in a single library together with other library
+facilities not covered by this License, and distribute such a combined
+library, provided that the separate distribution of the work based on
+the Library and of the other library facilities is otherwise
+permitted, and provided that you do these two things:
+
+    a) Accompany the combined library with a copy of the same work
+    based on the Library, uncombined with any other library
+    facilities.  This must be distributed under the terms of the
+    Sections above.
+
+    b) Give prominent notice with the combined library of the fact
+    that part of it is a work based on the Library, and explaining
+    where to find the accompanying uncombined form of the same work.
+
+  8. You may not copy, modify, sublicense, link with, or distribute
+the Library except as expressly provided under this License.  Any
+attempt otherwise to copy, modify, sublicense, link with, or
+distribute the Library is void, and will automatically terminate your
+rights under this License.  However, parties who have received copies,
+or rights, from you under this License will not have their licenses
+terminated so long as such parties remain in full compliance.
+
+  9. You are not required to accept this License, since you have not
+signed it.  However, nothing else grants you permission to modify or
+distribute the Library or its derivative works.  These actions are
+prohibited by law if you do not accept this License.  Therefore, by
+modifying or distributing the Library (or any work based on the
+Library), you indicate your acceptance of this License to do so, and
+all its terms and conditions for copying, distributing or modifying
+the Library or works based on it.
+
+  10. Each time you redistribute the Library (or any work based on the
+Library), the recipient automatically receives a license from the
+original licensor to copy, distribute, link with or modify the Library
+subject to these terms and conditions.  You may not impose any further
+restrictions on the recipients' exercise of the rights granted herein.
+You are not responsible for enforcing compliance by third parties with
+this License.
+
+  11. If, as a consequence of a court judgment or allegation of patent
+infringement or for any other reason (not limited to patent issues),
+conditions are imposed on you (whether by court order, agreement or
+otherwise) that contradict the conditions of this License, they do not
+excuse you from the conditions of this License.  If you cannot
+distribute so as to satisfy simultaneously your obligations under this
+License and any other pertinent obligations, then as a consequence you
+may not distribute the Library at all.  For example, if a patent
+license would not permit royalty-free redistribution of the Library by
+all those who receive copies directly or indirectly through you, then
+the only way you could satisfy both it and this License would be to
+refrain entirely from distribution of the Library.
+
+If any portion of this section is held invalid or unenforceable under
+any particular circumstance, the balance of the section is intended to
+apply, and the section as a whole is intended to apply in other
+circumstances.
+
+It is not the purpose of this section to induce you to infringe any
+patents or other property right claims or to contest validity of any
+such claims; this section has the sole purpose of protecting the
+integrity of the free software distribution system which is
+implemented by public license practices.  Many people have made
+generous contributions to the wide range of software distributed
+through that system in reliance on consistent application of that
+system; it is up to the author/donor to decide if he or she is willing
+to distribute software through any other system and a licensee cannot
+impose that choice.
+
+This section is intended to make thoroughly clear what is believed to
+be a consequence of the rest of this License.
+
+  12. If the distribution and/or use of the Library is restricted in
+certain countries either by patents or by copyrighted interfaces, the
+original copyright holder who places the Library under this License
+may add an explicit geographical distribution limitation excluding those
+countries, so that distribution is permitted only in or among
+countries not thus excluded.  In such case, this License incorporates
+the limitation as if written in the body of this License.
+
+  13. The Free Software Foundation may publish revised and/or new
+versions of the Lesser General Public License from time to time.
+Such new versions will be similar in spirit to the present version,
+but may differ in detail to address new problems or concerns.
+
+Each version is given a distinguishing version number.  If the Library
+specifies a version number of this License which applies to it and
+"any later version", you have the option of following the terms and
+conditions either of that version or of any later version published by
+the Free Software Foundation.  If the Library does not specify a
+license version number, you may choose any version ever published by
+the Free Software Foundation.
+
+  14. If you wish to incorporate parts of the Library into other free
+programs whose distribution conditions are incompatible with these,
+write to the author to ask for permission.  For software which is
+copyrighted by the Free Software Foundation, write to the Free
+Software Foundation; we sometimes make exceptions for this.  Our
+decision will be guided by the two goals of preserving the free status
+of all derivatives of our free software and of promoting the sharing
+and reuse of software generally.
+
+                            NO WARRANTY
+
+  15. BECAUSE THE LIBRARY IS LICENSED FREE OF CHARGE, THERE IS NO
+WARRANTY FOR THE LIBRARY, TO THE EXTENT PERMITTED BY APPLICABLE LAW.
+EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR
+OTHER PARTIES PROVIDE THE LIBRARY "AS IS" WITHOUT WARRANTY OF ANY
+KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE
+IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+PURPOSE.  THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE
+LIBRARY IS WITH YOU.  SHOULD THE LIBRARY PROVE DEFECTIVE, YOU ASSUME
+THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
+
+  16. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN
+WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY
+AND/OR REDISTRIBUTE THE LIBRARY AS PERMITTED ABOVE, BE LIABLE TO YOU
+FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR
+CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE
+LIBRARY (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING
+RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A
+FAILURE OF THE LIBRARY TO OPERATE WITH ANY OTHER SOFTWARE), EVEN IF
+SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH
+DAMAGES.
+
+                     END OF TERMS AND CONDITIONS
+
+           How to Apply These Terms to Your New Libraries
+
+  If you develop a new library, and you want it to be of the greatest
+possible use to the public, we recommend making it free software that
+everyone can redistribute and change.  You can do so by permitting
+redistribution under these terms (or, alternatively, under the terms
+of the ordinary General Public License).
+
+  To apply these terms, attach the following notices to the library.
+It is safest to attach them to the start of each source file to most
+effectively convey the exclusion of warranty; and each file should
+have at least the "copyright" line and a pointer to where the full
+notice is found.
+
+
+    <one line to give the library's name and a brief idea of what it does.>
+    Copyright (C) <year>  <name of author>
+
+    This library is free software; you can redistribute it and/or
+    modify it under the terms of the GNU Lesser General Public
+    License as published by the Free Software Foundation; either
+    version 2.1 of the License, or (at your option) any later version.
+
+    This library is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+    Lesser General Public License for more details.
+
+    You should have received a copy of the GNU Lesser General Public
+    License along with this library; if not, write to the Free Software
+    Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
+
+Also add information on how to contact you by electronic and paper mail.
+
+You should also get your employer (if you work as a programmer) or
+your school, if any, to sign a "copyright disclaimer" for the library,
+if necessary.  Here is a sample; alter the names:
+
+  Yoyodyne, Inc., hereby disclaims all copyright interest in the
+  library `Frob' (a library for tweaking knobs) written by James
+  Random Hacker.
+
+  <signature of Ty Coon>, 1 April 1990
+  Ty Coon, President of Vice
+
+That's all there is to it!
+
+
diff --git a/src/libtracker-miner/TrackerMiner-1.0.metadata b/src/libtracker-miner/TrackerMiner-1.0.metadata
new file mode 100644
index 000000000..670bf1973
--- /dev/null
+++ b/src/libtracker-miner/TrackerMiner-1.0.metadata
@@ -0,0 +1,18 @@
+*.*.cancellable#parameter nullable default=null
+
+DecoratorInfo
+       .get_sparql type="Tracker.Sparql.Builder"
+
+Miner
+       .get_connection type="Tracker.Sparql.Connection"
+       .progress#virtual_method skip
+
+MinerFS
+       .finished_root#virtual_method skip
+       .process_file.builder type="Tracker.Sparql.Builder"
+       .process_file_attributes.builder type="Tracker.Sparql.Builder"
+       .writeback_file#method skip
+
+DecoratorError errordomain
+MinerError errordomain
+MinerFSError errordomain
diff --git a/src/libtracker-miner/meson.build b/src/libtracker-miner/meson.build
new file mode 100644
index 000000000..4fcf973aa
--- /dev/null
+++ b/src/libtracker-miner/meson.build
@@ -0,0 +1,104 @@
+shared_libtracker_miner_monitor_sources = files('tracker-monitor.c')
+shared_libtracker_miner_file_system_sources = files('tracker-file-system.c')
+shared_libtracker_miner_crawler_sources = files('tracker-crawler.c')
+
+miner_enums = gnome.mkenums('tracker-miner-enum-types',
+    sources: 'tracker-miner-enums.h',
+    c_template: 'tracker-miner-enum-types.c.template',
+    h_template: 'tracker-miner-enum-types.h.template',
+)
+
+private_sources = [
+    'tracker-crawler.c',
+    'tracker-file-data-provider.c',
+    'tracker-file-notifier.c',
+    'tracker-file-system.c',
+    'tracker-priority-queue.c',
+    'tracker-task-pool.c',
+    'tracker-sparql-buffer.c',
+    'tracker-utils.c']
+
+miner_headers = [
+    'tracker-miner-online.h',
+    'tracker-data-provider.h',
+    'tracker-indexing-tree.h',
+    'tracker-decorator-fs.h',
+    'tracker-miner-fs.h',
+    'tracker-miner-object.h',
+    'tracker-miner-proxy.h',
+    'tracker-decorator.h',
+    'tracker-miner-enums.h',
+    'tracker-miner.h',
+]
+
+miner_sources = (
+    shared_libtracker_miner_monitor_sources +
+    shared_libtracker_miner_file_system_sources +
+    shared_libtracker_miner_crawler_sources +
+    ['tracker-data-provider.c',
+    'tracker-decorator.c',
+    'tracker-decorator-fs.c',
+    'tracker-indexing-tree.c',
+    'tracker-miner-object.c',
+    'tracker-miner-online.c',
+    'tracker-miner-proxy.c',
+    'tracker-miner-fs.c'])
+
+libtracker_miner_private = static_library(
+    'tracker-miner-private',
+    miner_enums[0], miner_enums[1], private_sources,
+    dependencies: [tracker_miners_common_dep, tracker_sparql],
+    c_args: tracker_c_args,
+)
+
+tracker_miner_dependencies = []
+if network_manager.found()
+    tracker_miner_dependencies += network_manager
+endif
+
+libtracker_miner = library(
+    'tracker-miner-' + tracker_api_version,
+    miner_enums[0], miner_enums[1], miner_sources,
+    c_args: tracker_c_args,
+    install: true,
+    install_rpath: tracker_internal_libs_dir,
+    # This doesn't depend on tracker_common_dep because of
+    # https://github.com/mesonbuild/meson/issues/671
+    include_directories: [commoninc, configinc, srcinc],
+    dependencies: [tracker_sparql] + tracker_miner_dependencies,
+    link_with: [libtracker_miner_private],
+)
+
+tracker_miner = declare_dependency(
+    sources: miner_enums[1],
+    link_with: libtracker_miner,
+    include_directories: include_directories('.')
+)
+
+tracker_miner_gir = gnome.generate_gir(libtracker_miner,
+    sources: miner_sources + miner_headers,
+    nsversion: tracker_api_version,
+    namespace: 'TrackerMiner',
+    identifier_prefix: 'Tracker',
+    symbol_prefix: 'tracker',
+    # FIXME: also depends on Tracker-1.0.gir (output of libtracker-sparql)
+    # but we can't currently access that from the Vala target
+    includes : ['GLib-2.0', 'GObject-2.0', 'Gio-2.0' ],
+    install: true,
+    extra_args: tracker_c_args + [
+        '--c-include=libtracker-miner/tracker-miner.h',
+    ])
+
+configure_file(
+    input: 'tracker-miner.pc.in',
+    output: 'tracker-miner-1.0.pc',
+    configuration: conf,
+    install: true,
+    install_dir: join_paths(get_option('prefix'), get_option('libdir'), 'pkgconfig'))
+
+install_headers(miner_headers, subdir: 'tracker-1.0/libtracker-miner')
+
+# Work around https://github.com/mesonbuild/meson/issues/705
+meson.add_install_script('../install-generated-header.sh',
+    join_paths(meson.current_build_dir(), 'tracker-miner-enum-types.h'),
+    join_paths(get_option('prefix'), get_option('includedir'), 'tracker-1.0', 'libtracker-miner'))
diff --git a/src/libtracker-miner/tracker-crawler.c b/src/libtracker-miner/tracker-crawler.c
new file mode 100644
index 000000000..bf6e79470
--- /dev/null
+++ b/src/libtracker-miner/tracker-crawler.c
@@ -0,0 +1,1271 @@
+/*
+ * Copyright (C) 2009, Nokia <ivan frade nokia com>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ * Boston, MA  02110-1301, USA.
+ */
+
+#include "config-miners.h"
+
+#include "tracker-crawler.h"
+#include "tracker-file-data-provider.h"
+#include "tracker-miner-enums.h"
+#include "tracker-miner-enum-types.h"
+#include "tracker-utils.h"
+
+#define TRACKER_CRAWLER_GET_PRIVATE(obj) (G_TYPE_INSTANCE_GET_PRIVATE ((obj), TRACKER_TYPE_CRAWLER, 
TrackerCrawlerPrivate))
+
+#define FILE_ATTRIBUTES          \
+       G_FILE_ATTRIBUTE_STANDARD_NAME "," \
+       G_FILE_ATTRIBUTE_STANDARD_TYPE
+
+#define FILES_QUEUE_PROCESS_INTERVAL 2000
+#define FILES_QUEUE_PROCESS_MAX      5000
+
+/* This is the number of files to be called back with from GIO at a
+ * time so we don't get called back for every file.
+ */
+#define FILES_GROUP_SIZE             100
+
+#define MAX_SIMULTANEOUS_ITEMS       64
+
+typedef struct DirectoryChildData DirectoryChildData;
+typedef struct DirectoryProcessingData DirectoryProcessingData;
+typedef struct DirectoryRootInfo DirectoryRootInfo;
+
+typedef struct {
+       TrackerCrawler *crawler;
+       GFileEnumerator *enumerator;
+       DirectoryRootInfo  *root_info;
+       DirectoryProcessingData *dir_info;
+       GFile *dir_file;
+       GList *files;
+} DataProviderData;
+
+struct DirectoryChildData {
+       GFile          *child;
+       gboolean        is_dir;
+};
+
+struct DirectoryProcessingData {
+       GNode *node;
+       GSList *children;
+       guint was_inspected : 1;
+       guint ignored_by_content : 1;
+};
+
+struct DirectoryRootInfo {
+       GFile *directory;
+       GNode *tree;
+       gint max_depth;
+
+       GQueue *directory_processing_queue;
+
+       TrackerDirectoryFlags flags;
+
+       DataProviderData *dpd;
+
+       /* Directory stats */
+       guint directories_found;
+       guint directories_ignored;
+       guint files_found;
+       guint files_ignored;
+};
+
+struct TrackerCrawlerPrivate {
+       TrackerDataProvider *data_provider;
+
+       /* Directories to crawl */
+       GQueue         *directories;
+
+       GCancellable   *cancellable;
+
+       /* Idle handler for processing found data */
+       guint           idle_id;
+
+       gdouble         throttle;
+
+       gchar          *file_attributes;
+
+       /* Statistics */
+       GTimer         *timer;
+
+       /* Status */
+       gboolean        is_running;
+       gboolean        is_finished;
+       gboolean        is_paused;
+       gboolean        was_started;
+
+       gint            max_depth;
+};
+
+enum {
+       CHECK_DIRECTORY,
+       CHECK_FILE,
+       CHECK_DIRECTORY_CONTENTS,
+       DIRECTORY_CRAWLED,
+       FINISHED,
+       LAST_SIGNAL
+};
+
+enum {
+       PROP_0,
+       PROP_DATA_PROVIDER,
+};
+
+static void     crawler_get_property     (GObject         *object,
+                                          guint            prop_id,
+                                          GValue          *value,
+                                          GParamSpec      *pspec);
+static void     crawler_set_property     (GObject         *object,
+                                          guint            prop_id,
+                                          const GValue    *value,
+                                          GParamSpec      *pspec);
+static void     crawler_finalize         (GObject         *object);
+static gboolean check_defaults           (TrackerCrawler  *crawler,
+                                          GFile           *file);
+static gboolean check_contents_defaults  (TrackerCrawler  *crawler,
+                                          GFile           *file,
+                                          GList           *contents);
+static void     data_provider_data_free  (DataProviderData        *dpd);
+
+static void     data_provider_begin      (TrackerCrawler          *crawler,
+                                         DirectoryRootInfo       *info,
+                                         DirectoryProcessingData *dir_data);
+static void     data_provider_end        (TrackerCrawler          *crawler,
+                                          DirectoryRootInfo       *info);
+static void     directory_root_info_free (DirectoryRootInfo *info);
+
+
+static guint signals[LAST_SIGNAL] = { 0, };
+static GQuark file_info_quark = 0;
+
+G_DEFINE_TYPE (TrackerCrawler, tracker_crawler, G_TYPE_OBJECT)
+
+static void
+tracker_crawler_class_init (TrackerCrawlerClass *klass)
+{
+       GObjectClass *object_class = G_OBJECT_CLASS (klass);
+       TrackerCrawlerClass *crawler_class = TRACKER_CRAWLER_CLASS (klass);
+
+       object_class->set_property = crawler_set_property;
+       object_class->get_property = crawler_get_property;
+       object_class->finalize = crawler_finalize;
+
+       crawler_class->check_directory = check_defaults;
+       crawler_class->check_file      = check_defaults;
+       crawler_class->check_directory_contents = check_contents_defaults;
+
+       signals[CHECK_DIRECTORY] =
+               g_signal_new ("check-directory",
+                             G_TYPE_FROM_CLASS (klass),
+                             G_SIGNAL_RUN_LAST,
+                             G_STRUCT_OFFSET (TrackerCrawlerClass, check_directory),
+                             tracker_accumulator_check_file,
+                             NULL,
+                             NULL,
+                             G_TYPE_BOOLEAN,
+                             1,
+                             G_TYPE_FILE);
+       signals[CHECK_FILE] =
+               g_signal_new ("check-file",
+                             G_TYPE_FROM_CLASS (klass),
+                             G_SIGNAL_RUN_LAST,
+                             G_STRUCT_OFFSET (TrackerCrawlerClass, check_file),
+                             tracker_accumulator_check_file,
+                             NULL,
+                             NULL,
+                             G_TYPE_BOOLEAN,
+                             1,
+                             G_TYPE_FILE);
+       signals[CHECK_DIRECTORY_CONTENTS] =
+               g_signal_new ("check-directory-contents",
+                             G_TYPE_FROM_CLASS (klass),
+                             G_SIGNAL_RUN_LAST,
+                             G_STRUCT_OFFSET (TrackerCrawlerClass, check_directory_contents),
+                             tracker_accumulator_check_file,
+                             NULL,
+                             NULL,
+                             G_TYPE_BOOLEAN,
+                             2, G_TYPE_FILE, G_TYPE_POINTER);
+       signals[DIRECTORY_CRAWLED] =
+               g_signal_new ("directory-crawled",
+                             G_TYPE_FROM_CLASS (klass),
+                             G_SIGNAL_RUN_LAST,
+                             G_STRUCT_OFFSET (TrackerCrawlerClass, directory_crawled),
+                             NULL, NULL,
+                             NULL,
+                             G_TYPE_NONE,
+                             6,
+                             G_TYPE_FILE,
+                             G_TYPE_POINTER,
+                             G_TYPE_UINT,
+                             G_TYPE_UINT,
+                             G_TYPE_UINT,
+                             G_TYPE_UINT);
+       signals[FINISHED] =
+               g_signal_new ("finished",
+                             G_TYPE_FROM_CLASS (klass),
+                             G_SIGNAL_RUN_LAST,
+                             G_STRUCT_OFFSET (TrackerCrawlerClass, finished),
+                             NULL, NULL,
+                             NULL,
+                             G_TYPE_NONE,
+                             1, G_TYPE_BOOLEAN);
+
+       g_object_class_install_property (object_class,
+                                        PROP_DATA_PROVIDER,
+                                        g_param_spec_object ("data-provider",
+                                                             "Data provider",
+                                                             "Data provider to use to crawl structures 
populating data, e.g. like GFileEnumerator",
+                                                             TRACKER_TYPE_DATA_PROVIDER,
+                                                             G_PARAM_READWRITE |
+                                                             G_PARAM_CONSTRUCT_ONLY));
+
+       g_type_class_add_private (object_class, sizeof (TrackerCrawlerPrivate));
+
+       file_info_quark = g_quark_from_static_string ("tracker-crawler-file-info");
+}
+
+static void
+tracker_crawler_init (TrackerCrawler *object)
+{
+       TrackerCrawlerPrivate *priv;
+
+       object->priv = TRACKER_CRAWLER_GET_PRIVATE (object);
+
+       priv = object->priv;
+
+       priv->max_depth = -1;
+       priv->directories = g_queue_new ();
+}
+
+static void
+crawler_set_property (GObject      *object,
+                      guint         prop_id,
+                      const GValue *value,
+                      GParamSpec   *pspec)
+{
+       TrackerCrawlerPrivate *priv;
+
+       priv = TRACKER_CRAWLER (object)->priv;
+
+       switch (prop_id) {
+       case PROP_DATA_PROVIDER:
+               priv->data_provider = g_value_dup_object (value);
+               break;
+       default:
+               G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+               break;
+       }
+}
+
+static void
+crawler_get_property (GObject    *object,
+                      guint       prop_id,
+                      GValue     *value,
+                      GParamSpec *pspec)
+{
+       TrackerCrawlerPrivate *priv;
+
+       priv = TRACKER_CRAWLER (object)->priv;
+
+       switch (prop_id) {
+       case PROP_DATA_PROVIDER:
+               g_value_set_object (value, priv->data_provider);
+               break;
+       default:
+               G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+               break;
+       }
+}
+
+static void
+crawler_finalize (GObject *object)
+{
+       TrackerCrawlerPrivate *priv;
+
+       priv = TRACKER_CRAWLER_GET_PRIVATE (object);
+
+       if (priv->timer) {
+               g_timer_destroy (priv->timer);
+       }
+
+       if (priv->idle_id) {
+               g_source_remove (priv->idle_id);
+       }
+
+       if (priv->cancellable) {
+               g_cancellable_cancel (priv->cancellable);
+               g_object_unref (priv->cancellable);
+       }
+
+       g_queue_foreach (priv->directories, (GFunc) directory_root_info_free, NULL);
+       g_queue_free (priv->directories);
+
+       g_free (priv->file_attributes);
+
+       if (priv->data_provider) {
+               g_object_unref (priv->data_provider);
+       }
+
+       G_OBJECT_CLASS (tracker_crawler_parent_class)->finalize (object);
+}
+
+static gboolean
+check_defaults (TrackerCrawler *crawler,
+                GFile          *file)
+{
+       return TRUE;
+}
+
+static gboolean
+check_contents_defaults (TrackerCrawler  *crawler,
+                         GFile           *file,
+                         GList           *contents)
+{
+       return TRUE;
+}
+
+TrackerCrawler *
+tracker_crawler_new (TrackerDataProvider *data_provider)
+{
+       TrackerCrawler *crawler;
+       TrackerDataProvider *default_data_provider = NULL;
+
+       if (G_LIKELY (!data_provider)) {
+               /* Default to the file data_provider if none is passed */
+               data_provider = default_data_provider = tracker_file_data_provider_new ();
+       }
+
+       crawler = g_object_new (TRACKER_TYPE_CRAWLER,
+                               "data-provider", data_provider,
+                               NULL);
+
+       /* When a data provider is passed to us, we add a reference in
+        * the set_properties() function for this class, however, if
+        * we create the data provider, we also have the original
+        * reference for the created object which needs to be cleared
+        * up here.
+        */
+       if (default_data_provider) {
+               g_object_unref (default_data_provider);
+       }
+
+       return crawler;
+}
+
+static gboolean
+check_file (TrackerCrawler    *crawler,
+           DirectoryRootInfo *info,
+            GFile             *file)
+{
+       gboolean use = FALSE;
+       TrackerCrawlerPrivate *priv;
+
+       priv = TRACKER_CRAWLER_GET_PRIVATE (crawler);
+
+       g_signal_emit (crawler, signals[CHECK_FILE], 0, file, &use);
+
+       /* Crawler may have been stopped while waiting for the 'use' value,
+        * and the DirectoryRootInfo already disposed... */
+       if (!priv->is_running) {
+               return FALSE;
+       }
+
+       info->files_found++;
+
+       if (!use) {
+               info->files_ignored++;
+       }
+
+       return use;
+}
+
+static gboolean
+check_directory (TrackerCrawler    *crawler,
+                DirectoryRootInfo *info,
+                GFile             *file)
+{
+       gboolean use = FALSE;
+       TrackerCrawlerPrivate *priv;
+
+       priv = TRACKER_CRAWLER_GET_PRIVATE (crawler);
+
+       g_signal_emit (crawler, signals[CHECK_DIRECTORY], 0, file, &use);
+
+       /* Crawler may have been stopped while waiting for the 'use' value,
+        * and the DirectoryRootInfo already disposed... */
+       if (!priv->is_running) {
+               return FALSE;
+       }
+
+       info->directories_found++;
+
+       if (!use) {
+               info->directories_ignored++;
+       }
+
+       return use;
+}
+
+static DirectoryChildData *
+directory_child_data_new (GFile    *child,
+                         gboolean  is_dir)
+{
+       DirectoryChildData *child_data;
+
+       child_data = g_slice_new (DirectoryChildData);
+       child_data->child = g_object_ref (child);
+       child_data->is_dir = is_dir;
+
+       return child_data;
+}
+
+static void
+directory_child_data_free (DirectoryChildData *child_data)
+{
+       g_object_unref (child_data->child);
+       g_slice_free (DirectoryChildData, child_data);
+}
+
+static DirectoryProcessingData *
+directory_processing_data_new (GNode *node)
+{
+       DirectoryProcessingData *data;
+
+       data = g_slice_new0 (DirectoryProcessingData);
+       data->node = node;
+
+       return data;
+}
+
+static void
+directory_processing_data_free (DirectoryProcessingData *data)
+{
+       g_slist_foreach (data->children, (GFunc) directory_child_data_free, NULL);
+       g_slist_free (data->children);
+
+       g_slice_free (DirectoryProcessingData, data);
+}
+
+static void
+directory_processing_data_add_child (DirectoryProcessingData *data,
+                                    GFile                   *child,
+                                    gboolean                 is_dir)
+{
+       DirectoryChildData *child_data;
+
+       child_data = directory_child_data_new (child, is_dir);
+       data->children = g_slist_prepend (data->children, child_data);
+}
+
+static DirectoryRootInfo *
+directory_root_info_new (GFile                 *file,
+                         gint                   max_depth,
+                         gchar                 *file_attributes,
+                         TrackerDirectoryFlags  flags)
+{
+       DirectoryRootInfo *info;
+       DirectoryProcessingData *dir_info;
+       gboolean allow_stat = TRUE;
+
+       info = g_slice_new0 (DirectoryRootInfo);
+
+       info->directory = g_object_ref (file);
+       info->max_depth = max_depth;
+       info->directory_processing_queue = g_queue_new ();
+
+       info->tree = g_node_new (g_object_ref (file));
+
+       info->flags = flags;
+
+       if ((info->flags & TRACKER_DIRECTORY_FLAG_NO_STAT) != 0) {
+               allow_stat = FALSE;
+       }
+
+       /* NOTE: GFileInfo is ABSOLUTELY required here, without it the
+        * TrackerFileNotifier will think that top level roots have
+        * been deleted because the GFileInfo GQuark does not exist.
+        *
+        * This is seen easily by mounting a removable device,
+        * indexing, then removing, then re-inserting that same
+        * device.
+        *
+        * The check is done later in the TrackerFileNotifier by
+        * looking up the qdata that we set in both conditions below.
+        */
+       if (allow_stat && file_attributes) {
+               GFileInfo *file_info;
+               GFileQueryInfoFlags file_flags;
+
+               file_flags = G_FILE_QUERY_INFO_NOFOLLOW_SYMLINKS;
+
+               file_info = g_file_query_info (file,
+                                              file_attributes,
+                                              file_flags,
+                                              NULL,
+                                              NULL);
+               g_object_set_qdata_full (G_OBJECT (file),
+                                        file_info_quark,
+                                        file_info,
+                                        (GDestroyNotify) g_object_unref);
+       } else {
+               GFileInfo *file_info;
+               gchar *basename;
+
+               file_info = g_file_info_new ();
+               g_file_info_set_file_type (file_info, G_FILE_TYPE_DIRECTORY);
+
+               basename = g_file_get_basename (file);
+               g_file_info_set_name (file_info, basename);
+               g_free (basename);
+
+               /* Only thing missing is mtime, we can't know this.
+                * Not setting it means 0 is assumed, but if we set it
+                * to 'now' then the state machines above us will
+                * assume the directory is always newer when it may
+                * not be.
+                */
+
+               g_file_info_set_content_type (file_info, "inode/directory");
+
+               g_object_set_qdata_full (G_OBJECT (file),
+                                        file_info_quark,
+                                        file_info,
+                                        (GDestroyNotify) g_object_unref);
+       }
+
+       /* Fill in the processing info for the root node */
+       dir_info = directory_processing_data_new (info->tree);
+       g_queue_push_tail (info->directory_processing_queue, dir_info);
+
+       return info;
+}
+
+static gboolean
+directory_tree_free_foreach (GNode    *node,
+                            gpointer  user_data)
+{
+       g_object_unref (node->data);
+       return FALSE;
+}
+
+static void
+directory_root_info_free (DirectoryRootInfo *info)
+{
+       if (info->dpd)  {
+               data_provider_end (info->dpd->crawler, info);
+       }
+
+       g_object_unref (info->directory);
+
+       g_node_traverse (info->tree,
+                        G_PRE_ORDER,
+                        G_TRAVERSE_ALL,
+                        -1,
+                        directory_tree_free_foreach,
+                        NULL);
+       g_node_destroy (info->tree);
+
+       g_queue_foreach (info->directory_processing_queue,
+                        (GFunc) directory_processing_data_free,
+                        NULL);
+       g_queue_free (info->directory_processing_queue);
+
+       g_slice_free (DirectoryRootInfo, info);
+}
+
+static gboolean
+process_next (TrackerCrawler *crawler)
+{
+       TrackerCrawlerPrivate   *priv;
+       DirectoryRootInfo       *info;
+       DirectoryProcessingData *dir_data = NULL;
+       gboolean                 stop_idle = FALSE;
+
+       priv = crawler->priv;
+
+       if (priv->is_paused) {
+               /* Stop the idle func for now until we are unpaused */
+               priv->idle_id = 0;
+
+               return FALSE;
+       }
+
+       info = g_queue_peek_head (priv->directories);
+
+       if (info) {
+               dir_data = g_queue_peek_head (info->directory_processing_queue);
+       }
+
+       if (dir_data) {
+               gint depth = g_node_depth (dir_data->node) - 1;
+               gboolean iterate;
+
+               iterate = (info->max_depth >= 0) ? depth < info->max_depth : TRUE;
+
+               /* One directory inside the tree hierarchy is being inspected */
+               if (!dir_data->was_inspected) {
+                       dir_data->was_inspected = TRUE;
+
+                       /* Crawler may have been already stopped while we were waiting for the
+                        *  check_directory return value, and thus we should check if it's
+                        *  running before going on with the iteration */
+                       if (priv->is_running && iterate) {
+                               /* Directory contents haven't been inspected yet,
+                                * stop this idle function while it's being iterated
+                                */
+                               data_provider_begin (crawler, info, dir_data);
+                               stop_idle = TRUE;
+                       }
+               } else if (dir_data->was_inspected &&
+                          !dir_data->ignored_by_content &&
+                          dir_data->children != NULL) {
+                       DirectoryChildData *child_data;
+                       GNode *child_node = NULL;
+
+                       /* Directory has been already inspected, take children
+                        * one by one and check whether they should be incorporated
+                        * to the tree.
+                        */
+                       child_data = dir_data->children->data;
+                       dir_data->children = g_slist_remove (dir_data->children, child_data);
+
+                       if (((child_data->is_dir &&
+                             check_directory (crawler, info, child_data->child)) ||
+                            (!child_data->is_dir &&
+                             check_file (crawler, info, child_data->child))) &&
+                           /* Crawler may have been already stopped while we were waiting for the
+                            *  check_directory or check_file return value, and thus we should
+                            *   check if it's running before going on */
+                           priv->is_running) {
+                               child_node = g_node_prepend_data (dir_data->node,
+                                                                 g_object_ref (child_data->child));
+                       }
+
+                       if (iterate && priv->is_running &&
+                           child_node && child_data->is_dir) {
+                               DirectoryProcessingData *child_dir_data;
+
+                               child_dir_data = directory_processing_data_new (child_node);
+                               g_queue_push_tail (info->directory_processing_queue, child_dir_data);
+                       }
+
+                       directory_child_data_free (child_data);
+               } else {
+                       /* No (more) children, or directory ignored. stop processing. */
+                       g_queue_pop_head (info->directory_processing_queue);
+                       directory_processing_data_free (dir_data);
+               }
+       } else if (!dir_data && info) {
+               /* Current directory being crawled doesn't have anything else
+                * to process, emit ::directory-crawled and free data.
+                */
+               g_signal_emit (crawler, signals[DIRECTORY_CRAWLED], 0,
+                              info->directory,
+                              info->tree,
+                              info->directories_found,
+                              info->directories_ignored,
+                              info->files_found,
+                              info->files_ignored);
+
+               data_provider_end (crawler, info);
+               g_queue_pop_head (priv->directories);
+               directory_root_info_free (info);
+       }
+
+       if (!g_queue_peek_head (priv->directories)) {
+               /* There's nothing else to process */
+               priv->is_finished = TRUE;
+               tracker_crawler_stop (crawler);
+               stop_idle = TRUE;
+       }
+
+       if (stop_idle) {
+               priv->idle_id = 0;
+               return FALSE;
+       }
+
+       return TRUE;
+}
+
+static gboolean
+process_func (gpointer data)
+{
+       TrackerCrawler *crawler = data;
+       gboolean retval = FALSE;
+       gint i;
+
+       for (i = 0; i < MAX_SIMULTANEOUS_ITEMS; i++) {
+               retval = process_next (crawler);
+               if (retval == FALSE)
+                       break;
+       }
+
+       return retval;
+}
+
+static gboolean
+process_func_start (TrackerCrawler *crawler)
+{
+       if (crawler->priv->is_paused) {
+               return FALSE;
+       }
+
+       if (crawler->priv->is_finished) {
+               return FALSE;
+       }
+
+       if (crawler->priv->idle_id == 0) {
+               crawler->priv->idle_id = g_idle_add (process_func, crawler);
+       }
+
+       return TRUE;
+}
+
+static void
+process_func_stop (TrackerCrawler *crawler)
+{
+       if (crawler->priv->idle_id != 0) {
+               g_source_remove (crawler->priv->idle_id);
+               crawler->priv->idle_id = 0;
+       }
+}
+
+static DataProviderData *
+data_provider_data_new (TrackerCrawler          *crawler,
+                        DirectoryRootInfo       *root_info,
+                        DirectoryProcessingData *dir_info)
+{
+       DataProviderData *dpd;
+
+       dpd = g_slice_new0 (DataProviderData);
+
+       dpd->crawler = g_object_ref (crawler);
+       dpd->root_info = root_info;
+       dpd->dir_info = dir_info;
+       /* Make sure there's always a ref of the GFile while we're
+        * iterating it */
+       dpd->dir_file = g_object_ref (G_FILE (dir_info->node->data));
+
+       return dpd;
+}
+
+static void
+data_provider_data_process (DataProviderData *dpd)
+{
+       TrackerCrawler *crawler;
+       GSList *l;
+       GList *children = NULL;
+       gboolean use;
+
+       crawler = dpd->crawler;
+
+       for (l = dpd->dir_info->children; l; l = l->next) {
+               DirectoryChildData *child_data;
+
+               child_data = l->data;
+               children = g_list_prepend (children, child_data->child);
+       }
+
+       g_signal_emit (crawler, signals[CHECK_DIRECTORY_CONTENTS], 0, dpd->dir_file, children, &use);
+       g_list_free (children);
+
+       if (!use) {
+               dpd->dir_info->ignored_by_content = TRUE;
+               /* FIXME: Update stats */
+               return;
+       }
+}
+
+static void
+data_provider_data_add (DataProviderData *dpd)
+{
+       TrackerCrawler *crawler;
+       GFile *parent;
+       GList *l;
+
+       crawler = dpd->crawler;
+       parent = dpd->dir_file;
+
+       for (l = dpd->files; l; l = l->next) {
+               GFileInfo *info;
+               GFile *child;
+               const gchar *child_name;
+               gboolean is_dir;
+
+               info = l->data;
+
+               child_name = g_file_info_get_name (info);
+               child = g_file_get_child (parent, child_name);
+               is_dir = g_file_info_get_file_type (info) == G_FILE_TYPE_DIRECTORY;
+
+               if (crawler->priv->file_attributes) {
+                       /* Store the file info for future retrieval */
+                       g_object_set_qdata_full (G_OBJECT (child),
+                                                file_info_quark,
+                                                g_object_ref (info),
+                                                (GDestroyNotify) g_object_unref);
+               }
+
+               directory_processing_data_add_child (dpd->dir_info, child, is_dir);
+
+               g_object_unref (child);
+               g_object_unref (info);
+       }
+
+       g_list_free (dpd->files);
+       dpd->files = NULL;
+}
+
+static void
+data_provider_data_free (DataProviderData *dpd)
+{
+       g_object_unref (dpd->dir_file);
+       g_object_unref (dpd->crawler);
+
+       if (dpd->files) {
+               g_list_free_full (dpd->files, g_object_unref);
+       }
+
+       if (dpd->enumerator) {
+               g_object_unref (dpd->enumerator);
+       }
+
+       g_slice_free (DataProviderData, dpd);
+}
+
+static void
+data_provider_end_cb (GObject      *object,
+                      GAsyncResult *result,
+                      gpointer      user_data)
+{
+       DataProviderData *dpd;
+       GError *error = NULL;
+
+       g_file_enumerator_close_finish (G_FILE_ENUMERATOR (object), result, &error);
+       dpd = user_data;
+
+       if (error) {
+               if (!g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED)) {
+                       gchar *uri = g_file_get_uri (dpd->dir_file);
+                       g_warning ("Could not end data provider for container / directory '%s', %s",
+                                  uri, error ? error->message : "no error given");
+                       g_free (uri);
+               }
+               g_clear_error (&error);
+       }
+
+       data_provider_data_free (dpd);
+}
+
+static void
+data_provider_end (TrackerCrawler    *crawler,
+                   DirectoryRootInfo *info)
+{
+       DataProviderData *dpd;
+
+       g_return_if_fail (info != NULL);
+
+       if (info->dpd == NULL) {
+               /* Nothing to do */
+               return;
+       }
+
+       /* We detach the DataProviderData from the DirectoryRootInfo
+        * here so it's not freed early. We can't use
+        * DirectoryRootInfo as user data for the async function below
+        * because it's freed before that callback will be called.
+        */
+       dpd = info->dpd;
+       info->dpd = NULL;
+
+       if (dpd->enumerator) {
+               g_file_enumerator_close_async (dpd->enumerator,
+                                              G_PRIORITY_LOW, NULL,
+                                              data_provider_end_cb,
+                                              dpd);
+       } else {
+               data_provider_data_free (dpd);
+       }
+}
+
+static void
+enumerate_next_cb (GObject      *object,
+                   GAsyncResult *result,
+                   gpointer      user_data)
+{
+       DataProviderData *dpd;
+       GList *info;
+       GError *error = NULL;
+
+       info = g_file_enumerator_next_files_finish (G_FILE_ENUMERATOR (object), result, &error);
+       dpd = user_data;
+
+       if (!info) {
+               /* Could be due to:
+                * a) error,
+                * b) no more items
+                */
+               if (error) {
+                       /* We don't consider cancellation an error, so we only
+                        * log errors which are not cancellations.
+                        */
+                       if (!g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED)) {
+                               gchar *uri = g_file_get_uri (dpd->dir_file);
+                               g_warning ("Could not enumerate next item in container / directory '%s', %s",
+                                          uri, error ? error->message : "no error given");
+                               g_free (uri);
+                       }
+
+                       g_clear_error (&error);
+               } else {
+                       /* Done enumerating, start processing what we got ... */
+                       data_provider_data_add (dpd);
+                       data_provider_data_process (dpd);
+               }
+
+               process_func_start (dpd->crawler);
+       } else {
+               /* More work to do, we keep reference given to us */
+               dpd->files = g_list_concat (dpd->files, info);
+               g_file_enumerator_next_files_async (G_FILE_ENUMERATOR (object),
+                                                   MAX_SIMULTANEOUS_ITEMS,
+                                                   G_PRIORITY_LOW,
+                                                   dpd->crawler->priv->cancellable,
+                                                   enumerate_next_cb,
+                                                   dpd);
+       }
+}
+
+static void
+data_provider_begin_cb (GObject      *object,
+                        GAsyncResult *result,
+                        gpointer      user_data)
+{
+       GFileEnumerator *enumerator;
+       DirectoryRootInfo *info;
+       DataProviderData *dpd;
+       GError *error = NULL;
+
+       enumerator = tracker_data_provider_begin_finish (TRACKER_DATA_PROVIDER (object), result, &error);
+
+       info = user_data;
+
+       if (error) {
+               if (!g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED)) {
+                       gchar *uri;
+
+                       dpd = info->dpd;
+                       uri = g_file_get_uri (dpd->dir_file);
+                       g_warning ("Could not enumerate container / directory '%s', %s",
+                                  uri, error ? error->message : "no error given");
+                       g_free (uri);
+                       process_func_start (dpd->crawler);
+               }
+               g_clear_error (&error);
+               return;
+       }
+
+       dpd = info->dpd;
+       dpd->enumerator = enumerator;
+       g_file_enumerator_next_files_async (enumerator,
+                                           MAX_SIMULTANEOUS_ITEMS,
+                                           G_PRIORITY_LOW,
+                                           dpd->crawler->priv->cancellable,
+                                           enumerate_next_cb,
+                                           dpd);
+}
+
+static void
+data_provider_begin (TrackerCrawler          *crawler,
+                     DirectoryRootInfo       *info,
+                     DirectoryProcessingData *dir_data)
+{
+       DataProviderData *dpd;
+       gchar *attrs;
+
+       /* DataProviderData is freed in data_provider_end() call. This
+        * call must _ALWAYS_ be reached even on cancellation or
+        * failure, this is normally the case when we return to the
+        * process_func() and finish a directory.
+        */
+       dir_data->was_inspected = TRUE;
+       dpd = data_provider_data_new (crawler, info, dir_data);
+       info->dpd = dpd;
+
+       if (crawler->priv->file_attributes) {
+               attrs = g_strconcat (FILE_ATTRIBUTES ",",
+                                    crawler->priv->file_attributes,
+                                    NULL);
+       } else {
+               attrs = g_strdup (FILE_ATTRIBUTES);
+       }
+
+       tracker_data_provider_begin_async (crawler->priv->data_provider,
+                                          dpd->dir_file,
+                                          attrs,
+                                          info->flags,
+                                          G_PRIORITY_LOW,
+                                          crawler->priv->cancellable,
+                                          data_provider_begin_cb,
+                                          info);
+       g_free (attrs);
+}
+
+gboolean
+tracker_crawler_start (TrackerCrawler        *crawler,
+                       GFile                 *file,
+                       TrackerDirectoryFlags  flags,
+                       gint                   max_depth)
+{
+       TrackerCrawlerPrivate *priv;
+       DirectoryProcessingData *dir_data;
+       DirectoryRootInfo *info;
+       gboolean enable_stat;
+
+       g_return_val_if_fail (TRACKER_IS_CRAWLER (crawler), FALSE);
+       g_return_val_if_fail (G_IS_FILE (file), FALSE);
+
+       priv = crawler->priv;
+
+       enable_stat = (flags & TRACKER_DIRECTORY_FLAG_NO_STAT) == 0;
+
+       if (enable_stat && !g_file_query_exists (file, NULL)) {
+               /* This shouldn't happen, unless the removal/unmount notification
+                * didn't yet reach the TrackerFileNotifier.
+                */
+               return FALSE;
+       }
+
+       priv->was_started = TRUE;
+
+       /* Time the event */
+       if (priv->timer) {
+               g_timer_destroy (priv->timer);
+       }
+
+       priv->timer = g_timer_new ();
+
+       if (priv->is_paused) {
+               g_timer_stop (priv->timer);
+       }
+
+       /* Set a brand new cancellable */
+       if (priv->cancellable) {
+               g_cancellable_cancel (priv->cancellable);
+               g_object_unref (priv->cancellable);
+       }
+
+       priv->cancellable = g_cancellable_new ();
+
+       /* Set as running now */
+       priv->is_running = TRUE;
+       priv->is_finished = FALSE;
+       priv->max_depth = max_depth;
+
+       info = directory_root_info_new (file, max_depth, priv->file_attributes, flags);
+
+       if (!check_directory (crawler, info, file)) {
+               directory_root_info_free (info);
+
+               g_timer_destroy (priv->timer);
+               priv->timer = NULL;
+
+               priv->is_running = FALSE;
+               priv->is_finished = TRUE;
+
+               return FALSE;
+       }
+
+       g_queue_push_tail (priv->directories, info);
+
+       dir_data = g_queue_peek_head (info->directory_processing_queue);
+
+       if (dir_data)
+               data_provider_begin (crawler, info, dir_data);
+
+       return TRUE;
+}
+
+void
+tracker_crawler_stop (TrackerCrawler *crawler)
+{
+       TrackerCrawlerPrivate *priv;
+
+       g_return_if_fail (TRACKER_IS_CRAWLER (crawler));
+
+       priv = crawler->priv;
+
+       /* If already not running, just ignore */
+       if (!priv->is_running) {
+               return;
+       }
+
+       priv->is_running = FALSE;
+       g_cancellable_cancel (priv->cancellable);
+
+       process_func_stop (crawler);
+
+       if (priv->timer) {
+               g_timer_destroy (priv->timer);
+               priv->timer = NULL;
+       }
+
+       /* Clean up queue */
+       g_queue_foreach (priv->directories, (GFunc) directory_root_info_free, NULL);
+       g_queue_clear (priv->directories);
+
+       g_signal_emit (crawler, signals[FINISHED], 0,
+                      !priv->is_finished);
+
+       /* We don't free the queue in case the crawler is reused, it
+        * is only freed in finalize.
+        */
+}
+
+void
+tracker_crawler_pause (TrackerCrawler *crawler)
+{
+       g_return_if_fail (TRACKER_IS_CRAWLER (crawler));
+
+       crawler->priv->is_paused = TRUE;
+
+       if (crawler->priv->is_running) {
+               g_timer_stop (crawler->priv->timer);
+               process_func_stop (crawler);
+       }
+
+       g_message ("Crawler is paused, %s",
+                  crawler->priv->is_running ? "currently running" : "not running");
+}
+
+void
+tracker_crawler_resume (TrackerCrawler *crawler)
+{
+       g_return_if_fail (TRACKER_IS_CRAWLER (crawler));
+
+       crawler->priv->is_paused = FALSE;
+
+       if (crawler->priv->is_running) {
+               g_timer_continue (crawler->priv->timer);
+               process_func_start (crawler);
+       }
+
+       g_message ("Crawler is resuming, %s",
+                  crawler->priv->is_running ? "currently running" : "not running");
+}
+
+void
+tracker_crawler_set_throttle (TrackerCrawler *crawler,
+                              gdouble         throttle)
+{
+       g_return_if_fail (TRACKER_IS_CRAWLER (crawler));
+
+       throttle = CLAMP (throttle, 0, 1);
+       crawler->priv->throttle = throttle;
+
+       /* Update timeouts */
+       if (crawler->priv->idle_id != 0) {
+               guint interval, idle_id;
+
+               interval = TRACKER_CRAWLER_MAX_TIMEOUT_INTERVAL * crawler->priv->throttle;
+
+               g_source_remove (crawler->priv->idle_id);
+
+               if (interval == 0) {
+                       idle_id = g_idle_add (process_func, crawler);
+               } else {
+                       idle_id = g_timeout_add (interval, process_func, crawler);
+               }
+
+               crawler->priv->idle_id = idle_id;
+       }
+}
+
+/**
+ * tracker_crawler_set_file_attributes:
+ * @crawler: a #TrackerCrawler
+ * @file_attributes: file attributes to extract
+ *
+ * Sets the file attributes that @crawler will fetch for every
+ * file it gets, this info may be requested through
+ * tracker_crawler_get_file_info() in any #TrackerCrawler callback
+ **/
+void
+tracker_crawler_set_file_attributes (TrackerCrawler *crawler,
+                                    const gchar    *file_attributes)
+{
+       g_return_if_fail (TRACKER_IS_CRAWLER (crawler));
+
+       g_free (crawler->priv->file_attributes);
+       crawler->priv->file_attributes = g_strdup (file_attributes);
+}
+
+/**
+ * tracker_crawler_get_file_attributes:
+ * @crawler: a #TrackerCrawler
+ *
+ * Returns the file attributes that @crawler will fetch
+ *
+ * Returns: the file attributes as a string.
+ **/
+const gchar *
+tracker_crawler_get_file_attributes (TrackerCrawler *crawler)
+{
+       g_return_val_if_fail (TRACKER_IS_CRAWLER (crawler), NULL);
+
+       return crawler->priv->file_attributes;
+}
+
+/**
+ * tracker_crawler_get_max_depth:
+ * @crawler: a #TrackerCrawler
+ *
+ * Returns the max depth that @crawler got passed on tracker_crawler_start
+ *
+ * Returns: the max depth
+ **/
+
+gint
+tracker_crawler_get_max_depth (TrackerCrawler *crawler)
+{
+       g_return_val_if_fail (TRACKER_IS_CRAWLER (crawler), 0);
+       return crawler->priv->max_depth;
+}
+
+/**
+ * tracker_crawler_get_file_info:
+ * @crawler: a #TrackerCrawler
+ * @file: a #GFile returned by @crawler
+ *
+ * Returns a #GFileInfo with the file attributes requested through
+ * tracker_crawler_set_file_attributes().
+ *
+ * Returns: (transfer none): a #GFileInfo with the file information
+ **/
+GFileInfo *
+tracker_crawler_get_file_info (TrackerCrawler *crawler,
+                              GFile          *file)
+{
+       GFileInfo *info;
+
+       g_return_val_if_fail (TRACKER_IS_CRAWLER (crawler), NULL);
+       g_return_val_if_fail (G_IS_FILE (file), NULL);
+
+       info = g_object_steal_qdata (G_OBJECT (file), file_info_quark);
+       return info;
+}
diff --git a/src/libtracker-miner/tracker-crawler.h b/src/libtracker-miner/tracker-crawler.h
new file mode 100644
index 000000000..1395e3a80
--- /dev/null
+++ b/src/libtracker-miner/tracker-crawler.h
@@ -0,0 +1,96 @@
+/*
+ * Copyright (C) 2009, Nokia <ivan frade nokia com>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ * Boston, MA  02110-1301, USA.
+ */
+
+#ifndef __LIBTRACKER_MINER_CRAWLER_H__
+#define __LIBTRACKER_MINER_CRAWLER_H__
+
+#if !defined (__LIBTRACKER_MINER_H_INSIDE__) && !defined (TRACKER_COMPILATION)
+#error "Only <libtracker-miner/tracker-miner.h> can be included directly."
+#endif
+
+#include <glib-object.h>
+#include <gio/gio.h>
+
+#include "tracker-data-provider.h"
+
+G_BEGIN_DECLS
+
+#define TRACKER_TYPE_CRAWLER            (tracker_crawler_get_type ())
+#define TRACKER_CRAWLER(object)         (G_TYPE_CHECK_INSTANCE_CAST ((object), TRACKER_TYPE_CRAWLER, 
TrackerCrawler))
+#define TRACKER_CRAWLER_CLASS(klass)    (G_TYPE_CHECK_CLASS_CAST ((klass), TRACKER_TYPE_CRAWLER, 
TrackerCrawlerClass))
+#define TRACKER_IS_CRAWLER(object)      (G_TYPE_CHECK_INSTANCE_TYPE ((object), TRACKER_TYPE_CRAWLER))
+#define TRACKER_IS_CRAWLER_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), TRACKER_TYPE_CRAWLER))
+#define TRACKER_CRAWLER_GET_CLASS(obj)  (G_TYPE_INSTANCE_GET_CLASS ((obj), TRACKER_TYPE_CRAWLER, 
TrackerCrawlerClass))
+
+/* Max timeouts time (in msec) */
+#define TRACKER_CRAWLER_MAX_TIMEOUT_INTERVAL 1000
+
+typedef struct TrackerCrawler         TrackerCrawler;
+typedef struct TrackerCrawlerClass    TrackerCrawlerClass;
+typedef struct TrackerCrawlerPrivate  TrackerCrawlerPrivate;
+
+struct TrackerCrawler {
+       GObject parent;
+       TrackerCrawlerPrivate *priv;
+};
+
+struct TrackerCrawlerClass {
+       GObjectClass parent;
+
+       gboolean (* check_directory)          (TrackerCrawler *crawler,
+                                              GFile          *file);
+       gboolean (* check_file)               (TrackerCrawler *crawler,
+                                              GFile          *file);
+       gboolean (* check_directory_contents) (TrackerCrawler *crawler,
+                                              GFile          *file,
+                                              GList          *contents);
+       void     (* directory_crawled)        (TrackerCrawler *crawler,
+                                              GFile          *directory,
+                                              GNode          *tree,
+                                              guint           directories_found,
+                                              guint           directories_ignored,
+                                              guint           files_found,
+                                              guint           files_ignored);
+       void     (* finished)                 (TrackerCrawler *crawler,
+                                              gboolean        interrupted);
+};
+
+GType           tracker_crawler_get_type     (void);
+TrackerCrawler *tracker_crawler_new          (TrackerDataProvider *data_provider);
+gboolean        tracker_crawler_start        (TrackerCrawler *crawler,
+                                              GFile          *file,
+                                              TrackerDirectoryFlags flags,
+                                             gint            max_depth);
+void            tracker_crawler_stop         (TrackerCrawler *crawler);
+void            tracker_crawler_pause        (TrackerCrawler *crawler);
+void            tracker_crawler_resume       (TrackerCrawler *crawler);
+void            tracker_crawler_set_throttle (TrackerCrawler *crawler,
+                                              gdouble         throttle);
+
+void            tracker_crawler_set_file_attributes (TrackerCrawler *crawler,
+                                                    const gchar    *file_attributes);
+const gchar *   tracker_crawler_get_file_attributes (TrackerCrawler *crawler);
+
+GFileInfo *     tracker_crawler_get_file_info       (TrackerCrawler *crawler,
+                                                    GFile          *file);
+gint            tracker_crawler_get_max_depth       (TrackerCrawler *crawler);
+
+G_END_DECLS
+
+#endif /* __LIBTRACKER_MINER_CRAWLER_H__ */
diff --git a/src/libtracker-miner/tracker-data-provider.c b/src/libtracker-miner/tracker-data-provider.c
new file mode 100644
index 000000000..e1d51e052
--- /dev/null
+++ b/src/libtracker-miner/tracker-data-provider.c
@@ -0,0 +1,207 @@
+/*
+ * Copyright (C) 2014, Softathome <contact softathome com>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ * Boston, MA  02110-1301, USA.
+ *
+ * Author: Martyn Russell <martyn lanedo com>
+ */
+
+#include "config-miners.h"
+
+#include <glib/gi18n.h>
+
+#include "tracker-data-provider.h"
+
+/**
+ * SECTION:tracker-data-provider
+ * @short_description: Provide data to be indexed
+ * @include: libtracker-miner/miner.h
+ *
+ * #TrackerDataProvider allows you to operate on a set of #GFiles,
+ * returning a #GFileInfo structure for each file enumerated (e.g.
+ * tracker_data_provider_begin() will return a #GFileEnumerator
+ * which can be used to enumerate resources provided by the
+ * #TrackerDataProvider.
+ *
+ * There is also a #TrackerFileDataProvider which is an implementation
+ * of this #TrackerDataProvider interface.
+ *
+ * The #TrackerMinerFS class which is a subclass to #TrackerMiner
+ * takes a #TrackerDataProvider property which is passed down to the
+ * TrackerCrawler created upon instantiation. This property is
+ * #TrackerMinerFS:data-provider.
+ *
+ * Since: 1.2
+ **/
+
+typedef TrackerDataProviderIface TrackerDataProviderInterface;
+G_DEFINE_INTERFACE (TrackerDataProvider, tracker_data_provider, G_TYPE_OBJECT)
+
+static void
+tracker_data_provider_default_init (TrackerDataProviderInterface *iface)
+{
+}
+
+/**
+ * tracker_data_provider_begin:
+ * @data_provider: a #TrackerDataProvider
+ * @url: a #GFile to enumerate
+ * @attributes: an attribute query string
+ * @flags: a set of #TrackerDirectoryFlags
+ * @cancellable: (allow-none): optional #GCancellable object, %NULL to ignore.
+ * @error: location to store the error occurring, or %NULL to ignore
+ *
+ * Creates a #GFileEnumerator to enumerate children at the URI
+ * provided by @url.
+ *
+ * The attributes value is a string that specifies the file attributes
+ * that should be gathered. It is not an error if it's not possible to
+ * read a particular requested attribute from a file - it just won't
+ * be set. attributes should be a comma-separated list of attributes
+ * or attribute wildcards. The wildcard "*" means all attributes, and
+ * a wildcard like "standard::*" means all attributes in the standard
+ * namespace. An example attribute query be "standard::*,owner::user".
+ * The standard attributes are available as defines, like
+ * G_FILE_ATTRIBUTE_STANDARD_NAME. See g_file_enumerate_children() for
+ * more details.
+ *
+ * Returns: (transfer full): a #GFileEnumerator or %NULL on failure.
+ * This must be freed with g_object_unref().
+ *
+ * Since: 1.2
+ **/
+GFileEnumerator *
+tracker_data_provider_begin (TrackerDataProvider    *data_provider,
+                             GFile                  *url,
+                             const gchar            *attributes,
+                             TrackerDirectoryFlags   flags,
+                             GCancellable           *cancellable,
+                             GError                **error)
+{
+       TrackerDataProviderIface *iface;
+
+       g_return_val_if_fail (TRACKER_IS_DATA_PROVIDER (data_provider), NULL);
+
+       if (g_cancellable_set_error_if_cancelled (cancellable, error)) {
+               return NULL;
+       }
+
+       iface = TRACKER_DATA_PROVIDER_GET_IFACE (data_provider);
+
+       if (iface->begin == NULL) {
+               g_set_error_literal (error,
+                                    G_IO_ERROR,
+                                    G_IO_ERROR_NOT_SUPPORTED,
+                                    _("Operation not supported"));
+               return NULL;
+       }
+
+       return (* iface->begin) (data_provider, url, attributes, flags, cancellable, error);
+}
+
+/**
+ * tracker_data_provider_begin_async:
+ * @data_provider: a #TrackerDataProvider.
+ * @url: a #GFile to enumerate
+ * @attributes: an attribute query string
+ * @flags: a set of #TrackerDirectoryFlags
+ * @io_priority: the [I/O priority][io-priority] of the request
+ * @cancellable: (allow-none): optional #GCancellable object, %NULL to
+ * ignore
+ * @callback: (scope async): a #GAsyncReadyCallback to call when the
+ * request is satisfied
+ * @user_data: (closure): the data to pass to callback function
+ *
+ * Precisely the same operation as tracker_data_provider_begin()
+ * is performing, but asynchronously.
+ *
+ * When all i/o for the operation is finished the @callback will be
+ * called with the requested information.
+
+ * See the documentation of #TrackerDataProvider for information about the
+ * order of returned files.
+ *
+ * In case of a partial error the callback will be called with any
+ * succeeding items and no error, and on the next request the error
+ * will be reported. If a request is cancelled the callback will be
+ * called with %G_IO_ERROR_CANCELLED.
+ *
+ * During an async request no other sync and async calls are allowed,
+ * and will result in %G_IO_ERROR_PENDING errors.
+ *
+ * Any outstanding i/o request with higher priority (lower numerical
+ * value) will be executed before an outstanding request with lower
+ * priority. Default priority is %G_PRIORITY_DEFAULT.
+ *
+ * Since: 1.2
+ **/
+void
+tracker_data_provider_begin_async (TrackerDataProvider   *data_provider,
+                                   GFile                 *url,
+                                   const gchar           *attributes,
+                                   TrackerDirectoryFlags  flags,
+                                   int                    io_priority,
+                                   GCancellable          *cancellable,
+                                   GAsyncReadyCallback    callback,
+                                   gpointer               user_data)
+{
+       TrackerDataProviderIface *iface;
+
+       g_return_if_fail (TRACKER_IS_DATA_PROVIDER (data_provider));
+
+       iface = TRACKER_DATA_PROVIDER_GET_IFACE (data_provider);
+
+       if (iface->begin_async == NULL) {
+               g_critical (_("Operation not supported"));
+               return;
+       }
+
+       (* iface->begin_async) (data_provider, url, attributes, flags, io_priority, cancellable, callback, 
user_data);
+}
+
+/**
+ * tracker_data_provider_begin_finish:
+ * @data_provider: a #TrackerDataProvider.
+ * @result: a #GAsyncResult.
+ * @error: a #GError location to store the error occurring, or %NULL
+ * to ignore.
+ *
+ * Finishes the asynchronous operation started with
+ * tracker_data_provider_begin_async().
+ *
+ * Returns: (transfer full): a #GFileEnumerator or %NULL on failure.
+ * This must be freed with g_object_unref().
+ *
+ * Since: 1.2
+ **/
+GFileEnumerator *
+tracker_data_provider_begin_finish (TrackerDataProvider  *data_provider,
+                                    GAsyncResult         *result,
+                                    GError              **error)
+{
+       TrackerDataProviderIface *iface;
+
+       g_return_val_if_fail (TRACKER_IS_DATA_PROVIDER (data_provider), NULL);
+       g_return_val_if_fail (G_IS_ASYNC_RESULT (result), NULL);
+
+       iface = TRACKER_DATA_PROVIDER_GET_IFACE (data_provider);
+
+       if (g_async_result_legacy_propagate_error (result, error)) {
+               return NULL;
+       }
+
+       return (* iface->begin_finish) (data_provider, result, error);
+}
diff --git a/src/libtracker-miner/tracker-data-provider.h b/src/libtracker-miner/tracker-data-provider.h
new file mode 100644
index 000000000..e780f7fc4
--- /dev/null
+++ b/src/libtracker-miner/tracker-data-provider.h
@@ -0,0 +1,117 @@
+/*
+ * Copyright (C) 2014, Softathome <contact softathome com>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ * Boston, MA  02110-1301, USA.
+ *
+ * Author: Martyn Russell <martyn lanedo com>
+ */
+
+#ifndef __LIBTRACKER_MINER_DATA_PROVIDER_H__
+#define __LIBTRACKER_MINER_DATA_PROVIDER_H__
+
+#if !defined (__LIBTRACKER_MINER_H_INSIDE__) && !defined (TRACKER_COMPILATION)
+#error "Only <libtracker-miner/tracker-miner.h> can be included directly."
+#endif
+
+#include <gio/gio.h>
+
+#include "tracker-miner-enums.h"
+
+G_BEGIN_DECLS
+
+#define TRACKER_TYPE_DATA_PROVIDER           (tracker_data_provider_get_type ())
+#define TRACKER_DATA_PROVIDER(obj)           (G_TYPE_CHECK_INSTANCE_CAST ((obj), TRACKER_TYPE_DATA_PROVIDER, 
TrackerDataProvider))
+#define TRACKER_IS_DATA_PROVIDER(obj)        (G_TYPE_CHECK_INSTANCE_TYPE ((obj), TRACKER_TYPE_DATA_PROVIDER))
+#define TRACKER_DATA_PROVIDER_GET_IFACE(obj) (G_TYPE_INSTANCE_GET_INTERFACE ((obj), 
TRACKER_TYPE_DATA_PROVIDER, TrackerDataProviderIface))
+
+/**
+ * TrackerDataProvider:
+ *
+ * An interface to enumerate URIs and feed the data to Tracker.
+ **/
+typedef struct _TrackerDataProvider TrackerDataProvider;
+typedef struct _TrackerDataProviderIface TrackerDataProviderIface;
+
+/**
+ * TrackerDataProviderIface:
+ * @g_iface: Parent interface type.
+ * @begin: Called when the data_provider is synchronously
+ * opening and starting the iteration of a given location.
+ * @begin_async: Called when the data_provider is synchronously
+ * opening and starting the iteration of a given location. Completed
+ * using @begin_finish.
+ * @begin_finish: Called when the data_provider is completing the
+ * asynchronous operation provided by @begin_async.
+ * @end: Called when the data_provider is synchronously
+ * closing and cleaning up the iteration of a given location.
+ * @end_async: Called when the data_provider is asynchronously
+ * closing and cleaning up the iteration of a given location.
+ * Completed using @end_finish.
+ * @end_finish: Called when the data_provider is completing the
+ * asynchronous operation provided by @end_async.
+ *
+ * Virtual methods left to implement.
+ **/
+struct _TrackerDataProviderIface {
+       GTypeInterface g_iface;
+
+       /* Virtual Table */
+
+       /* Start the data_provider for a given location, attributes and flags */
+       GFileEnumerator *     (* begin)              (TrackerDataProvider    *data_provider,
+                                                     GFile                  *url,
+                                                     const gchar            *attributes,
+                                                     TrackerDirectoryFlags   flags,
+                                                     GCancellable           *cancellable,
+                                                     GError                **error);
+       void                  (* begin_async)        (TrackerDataProvider    *data_provider,
+                                                     GFile                  *url,
+                                                     const gchar            *attributes,
+                                                     TrackerDirectoryFlags   flags,
+                                                     gint                    io_priority,
+                                                     GCancellable           *cancellable,
+                                                     GAsyncReadyCallback     callback,
+                                                     gpointer                user_data);
+       GFileEnumerator *    (* begin_finish)       (TrackerDataProvider    *data_provider,
+                                                     GAsyncResult           *result,
+                                                     GError                **error);
+
+       /*< private >*/
+       gpointer padding[10];
+};
+
+GType              tracker_data_provider_get_type        (void) G_GNUC_CONST;
+GFileEnumerator   *tracker_data_provider_begin           (TrackerDataProvider   *data_provider,
+                                                          GFile                 *url,
+                                                          const gchar           *attributes,
+                                                          TrackerDirectoryFlags  flags,
+                                                          GCancellable          *cancellable,
+                                                          GError               **error);
+void               tracker_data_provider_begin_async     (TrackerDataProvider   *data_provider,
+                                                          GFile                 *url,
+                                                          const gchar           *attributes,
+                                                          TrackerDirectoryFlags  flags,
+                                                          gint                   io_priority,
+                                                          GCancellable          *cancellable,
+                                                          GAsyncReadyCallback    callback,
+                                                          gpointer               user_data);
+GFileEnumerator   *tracker_data_provider_begin_finish    (TrackerDataProvider   *data_provider,
+                                                          GAsyncResult          *result,
+                                                          GError               **error);
+
+G_END_DECLS
+
+#endif /* __LIBTRACKER_MINER_DATA_PROVIDER_H__ */
diff --git a/src/libtracker-miner/tracker-decorator-fs.c b/src/libtracker-miner/tracker-decorator-fs.c
new file mode 100644
index 000000000..9f4d82398
--- /dev/null
+++ b/src/libtracker-miner/tracker-decorator-fs.c
@@ -0,0 +1,332 @@
+/*
+ * Copyright (C) 2014 Carlos Garnacho  <carlosg gnome org>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ * Boston, MA  02110-1301, USA.
+ */
+#include "config-miners.h"
+
+#include <glib.h>
+
+#include <libtracker-miners-common/tracker-common.h>
+#include <libtracker-sparql/tracker-sparql.h>
+
+#include "tracker-decorator-private.h"
+#include "tracker-decorator-fs.h"
+
+#define TRACKER_DECORATOR_FS_GET_PRIVATE(o) (G_TYPE_INSTANCE_GET_PRIVATE ((o), TRACKER_TYPE_DECORATOR_FS, 
TrackerDecoratorFSPrivate))
+
+/**
+ * SECTION:tracker-decorator-fs
+ * @short_description: Filesystem implementation for TrackerDecorator
+ * @include: libtracker-miner/tracker-miner.h
+ * @title: TrackerDecoratorFS
+ * @see_also: #TrackerDecorator
+ *
+ * #TrackerDecoratorFS is used to handle extended metadata extraction
+ * for resources on file systems that are mounted or unmounted.
+ **/
+
+typedef struct _TrackerDecoratorFSPrivate TrackerDecoratorFSPrivate;
+
+struct _TrackerDecoratorFSPrivate {
+       GVolumeMonitor *volume_monitor;
+};
+
+static GInitableIface *parent_initable_iface;
+
+static void tracker_decorator_fs_initable_iface_init (GInitableIface *iface);
+
+G_DEFINE_ABSTRACT_TYPE_WITH_CODE (TrackerDecoratorFS, tracker_decorator_fs,
+                                  TRACKER_TYPE_DECORATOR,
+                                  G_IMPLEMENT_INTERFACE (G_TYPE_INITABLE, 
tracker_decorator_fs_initable_iface_init))
+
+static void
+tracker_decorator_fs_finalize (GObject *object)
+{
+       TrackerDecoratorFSPrivate *priv;
+
+       priv = TRACKER_DECORATOR_FS (object)->priv;
+
+       if (priv->volume_monitor)
+               g_object_unref (priv->volume_monitor);
+
+       G_OBJECT_CLASS (tracker_decorator_fs_parent_class)->finalize (object);
+}
+
+static void
+tracker_decorator_fs_class_init (TrackerDecoratorFSClass *klass)
+{
+       GObjectClass *object_class = G_OBJECT_CLASS (klass);
+
+       object_class->finalize = tracker_decorator_fs_finalize;
+
+       g_type_class_add_private (object_class, sizeof (TrackerDecoratorFSPrivate));
+}
+
+static void
+process_files_cb (GObject      *object,
+                  GAsyncResult *result,
+                  gpointer      user_data)
+{
+       TrackerSparqlConnection *conn;
+       TrackerSparqlCursor *cursor;
+       GError *error = NULL;
+
+       conn = TRACKER_SPARQL_CONNECTION (object);
+       cursor = tracker_sparql_connection_query_finish (conn, result, &error);
+
+        if (error) {
+                g_critical ("Could not check files on mount point for missing metadata: %s", error->message);
+                g_error_free (error);
+               return;
+       }
+
+       while (tracker_sparql_cursor_next (cursor, NULL, NULL)) {
+               gint id = tracker_sparql_cursor_get_integer (cursor, 0);
+               gint class_name_id = tracker_sparql_cursor_get_integer (cursor, 1);
+               tracker_decorator_prepend_id (TRACKER_DECORATOR (user_data), id, class_name_id);
+       }
+
+       g_object_unref (cursor);
+}
+
+static void
+remove_files_cb (GObject *object,
+                 GAsyncResult *result,
+                 gpointer      user_data)
+{
+       TrackerSparqlConnection *conn;
+       TrackerSparqlCursor *cursor;
+       GError *error = NULL;
+
+       conn = TRACKER_SPARQL_CONNECTION (object);
+       cursor = tracker_sparql_connection_query_finish (conn, result, &error);
+
+        if (error) {
+                g_critical ("Could not remove files on mount point with missing metadata: %s", 
error->message);
+                g_error_free (error);
+               return;
+       }
+
+       while (tracker_sparql_cursor_next (cursor, NULL, NULL)) {
+               gint id = tracker_sparql_cursor_get_integer (cursor, 0);
+               tracker_decorator_delete_id (TRACKER_DECORATOR (user_data), id);
+       }
+
+       g_object_unref (cursor);
+}
+
+static void
+_tracker_decorator_query_append_rdf_type_filter (TrackerDecorator *decorator,
+                                                 GString          *query)
+{
+       const gchar **class_names;
+       gint i = 0;
+
+       class_names = tracker_decorator_get_class_names (decorator);
+
+       if (!class_names || !*class_names)
+               return;
+
+       g_string_append (query, "&& ?type IN (");
+
+       while (class_names[i]) {
+               if (i != 0)
+                       g_string_append (query, ",");
+
+               g_string_append (query, class_names[i]);
+               i++;
+       }
+
+       g_string_append (query, ") ");
+}
+
+static void
+check_files (TrackerDecorator    *decorator,
+             const gchar         *mount_point_urn,
+             gboolean             available,
+             GAsyncReadyCallback  callback)
+{
+       TrackerSparqlConnection *sparql_conn;
+       const gchar *data_source;
+       GString *query;
+
+       data_source = tracker_decorator_get_data_source (decorator);
+       query = g_string_new ("SELECT tracker:id(?urn) tracker:id(?type) { ?urn ");
+
+       if (mount_point_urn) {
+               g_string_append_printf (query,
+                                       " nie:dataSource <%s> ;",
+                                       mount_point_urn);
+       }
+
+       g_string_append (query, " a nfo:FileDataObject ;"
+                               " a ?type .");
+       g_string_append_printf (query,
+                               "FILTER (! EXISTS { ?urn nie:dataSource <%s> } ",
+                               data_source);
+
+       _tracker_decorator_query_append_rdf_type_filter (decorator, query);
+
+       if (available)
+               g_string_append (query, "&& BOUND(tracker:available(?urn))");
+
+       g_string_append (query, ")}");
+
+       sparql_conn = tracker_miner_get_connection (TRACKER_MINER (decorator));
+       tracker_sparql_connection_query_async (sparql_conn, query->str,
+                                              NULL, callback, decorator);
+       g_string_free (query, TRUE);
+}
+
+static inline gchar *
+mount_point_get_uuid (GMount *mount)
+{
+       GVolume *volume;
+       gchar *uuid = NULL;
+
+       volume = g_mount_get_volume (mount);
+       if (volume) {
+               uuid = g_volume_get_identifier (volume, G_VOLUME_IDENTIFIER_KIND_UUID);
+               if (!uuid) {
+                       gchar *mount_name;
+
+                       mount_name = g_mount_get_name (mount);
+                       uuid = g_compute_checksum_for_string (G_CHECKSUM_MD5, mount_name, -1);
+                       g_free (mount_name);
+               }
+
+               g_object_unref (volume);
+       }
+
+       return uuid;
+}
+
+static void
+mount_point_added_cb (GVolumeMonitor *monitor,
+                      GMount         *mount,
+                      gpointer        user_data)
+{
+       gchar *uuid;
+       gchar *urn;
+
+       uuid = mount_point_get_uuid (mount);
+       urn = g_strdup_printf (TRACKER_PREFIX_DATASOURCE_URN "%s", uuid);
+       check_files (user_data, urn, TRUE, process_files_cb);
+       g_free (urn);
+       g_free (uuid);
+}
+
+static void
+mount_point_removed_cb (GVolumeMonitor *monitor,
+                        GMount         *mount,
+                        gpointer        user_data)
+{
+       gchar *uuid;
+       gchar *urn;
+
+       uuid = mount_point_get_uuid (mount);
+       urn = g_strdup_printf (TRACKER_PREFIX_DATASOURCE_URN "%s", uuid);
+       _tracker_decorator_invalidate_cache (user_data);
+       check_files (user_data, urn, FALSE, remove_files_cb);
+       g_free (urn);
+       g_free (uuid);
+}
+
+static gboolean
+tracker_decorator_fs_iface_init (GInitable     *initable,
+                                 GCancellable  *cancellable,
+                                 GError       **error)
+{
+       TrackerDecoratorFSPrivate *priv;
+
+       priv = TRACKER_DECORATOR_FS (initable)->priv;
+
+       priv->volume_monitor = g_volume_monitor_get ();
+       g_signal_connect_object (priv->volume_monitor, "mount-added",
+                                G_CALLBACK (mount_point_added_cb), initable, 0);
+       g_signal_connect_object (priv->volume_monitor, "mount-pre-unmount",
+                                G_CALLBACK (mount_point_removed_cb), initable, 0);
+       g_signal_connect_object (priv->volume_monitor, "mount-removed",
+                                G_CALLBACK (mount_point_removed_cb), initable, 0);
+
+       return parent_initable_iface->init (initable, cancellable, error);
+}
+
+static void
+tracker_decorator_fs_initable_iface_init (GInitableIface *iface)
+{
+       parent_initable_iface = g_type_interface_peek_parent (iface);
+       iface->init = tracker_decorator_fs_iface_init;
+}
+
+static void
+tracker_decorator_fs_init (TrackerDecoratorFS *decorator)
+{
+       decorator->priv = TRACKER_DECORATOR_FS_GET_PRIVATE (decorator);
+}
+
+/**
+ * tracker_decorator_fs_prepend_file:
+ * @decorator: a #TrackerDecoratorFS
+ * @file: a #GFile to process
+ *
+ * Prepends a file for processing.
+ *
+ * Returns: the tracker:id of the element corresponding to the file
+ *
+ * Since: 1.2
+ **/
+gint
+tracker_decorator_fs_prepend_file (TrackerDecoratorFS *decorator,
+                                   GFile              *file)
+{
+       TrackerSparqlConnection *sparql_conn;
+       TrackerSparqlCursor *cursor;
+       gchar *query, *uri;
+       gint id, class_id;
+
+       g_return_val_if_fail (TRACKER_IS_DECORATOR_FS (decorator), 0);
+       g_return_val_if_fail (G_IS_FILE (file), 0);
+
+       uri = g_file_get_uri (file);
+       query = g_strdup_printf ("SELECT tracker:id(?urn) tracker:id(?type) {"
+                                "  ?urn a ?type; nie:url \"%s\" "
+                                "}", uri);
+       g_free (uri);
+
+       sparql_conn = tracker_miner_get_connection (TRACKER_MINER (decorator));
+       cursor = tracker_sparql_connection_query (sparql_conn, query,
+                                                 NULL, NULL);
+       g_free (query);
+
+       if (!cursor)
+               return 0;
+
+       if (!tracker_sparql_cursor_next (cursor, NULL, NULL)) {
+               g_object_unref (cursor);
+               return 0;
+       }
+
+       id = tracker_sparql_cursor_get_integer (cursor, 0);
+       class_id = tracker_sparql_cursor_get_integer (cursor, 1);
+
+       tracker_decorator_prepend_id (TRACKER_DECORATOR (decorator),
+                                     id, class_id);
+       g_object_unref (cursor);
+
+       return id;
+}
diff --git a/src/libtracker-miner/tracker-decorator-fs.h b/src/libtracker-miner/tracker-decorator-fs.h
new file mode 100644
index 000000000..2afc205a8
--- /dev/null
+++ b/src/libtracker-miner/tracker-decorator-fs.h
@@ -0,0 +1,73 @@
+/*
+ * Copyright (C) 2014 Carlos Garnacho  <carlosg gnome org>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ * Boston, MA  02110-1301, USA.
+ */
+
+#ifndef __LIBTRACKER_MINER_DECORATOR_FS_H__
+#define __LIBTRACKER_MINER_DECORATOR_FS_H__
+
+#if !defined (__LIBTRACKER_MINER_H_INSIDE__) && !defined (TRACKER_COMPILATION)
+#error "Only <libtracker-miner/tracker-miner.h> can be included directly."
+#endif
+
+#include "tracker-decorator.h"
+
+G_BEGIN_DECLS
+
+#define TRACKER_TYPE_DECORATOR_FS         (tracker_decorator_fs_get_type())
+#define TRACKER_DECORATOR_FS(o)           (G_TYPE_CHECK_INSTANCE_CAST ((o), TRACKER_TYPE_DECORATOR_FS, 
TrackerDecoratorFS))
+#define TRACKER_DECORATOR_FS_CLASS(c)     (G_TYPE_CHECK_CLASS_CAST ((c), TRACKER_TYPE_DECORATOR_FS, 
TrackerDecoratorFSClass))
+#define TRACKER_IS_DECORATOR_FS(o)        (G_TYPE_CHECK_INSTANCE_TYPE ((o), TRACKER_TYPE_DECORATOR_FS))
+#define TRACKER_IS_DECORATOR_FS_CLASS(c)  (G_TYPE_CHECK_CLASS_TYPE ((c),  TRACKER_TYPE_DECORATOR_FS))
+#define TRACKER_DECORATOR_FS_GET_CLASS(o) (G_TYPE_INSTANCE_GET_CLASS ((o), TRACKER_TYPE_DECORATOR_FS, 
TrackerDecoratorFSClass))
+
+typedef struct _TrackerDecoratorFS TrackerDecoratorFS;
+typedef struct _TrackerDecoratorFSClass TrackerDecoratorFSClass;
+
+/**
+ * TrackerDecoratorFS:
+ *
+ * A decorator object.
+ **/
+struct _TrackerDecoratorFS {
+       TrackerDecorator parent_instance;
+       gpointer priv;
+};
+
+/**
+ * TrackerDecoratorFSClass:
+ * @parent_class: parent object class.
+ * @padding: Reserved for future API improvements.
+ *
+ * A class that takes care of resources on mount points added or
+ * removed, this is based on #TrackerDecoratorClass.
+ **/
+struct _TrackerDecoratorFSClass {
+       TrackerDecoratorClass parent_class;
+
+       /* <Private> */
+       gpointer padding[10];
+};
+
+GType              tracker_decorator_fs_get_type (void) G_GNUC_CONST;
+
+gint               tracker_decorator_fs_prepend_file (TrackerDecoratorFS *decorator,
+                                                      GFile              *file);
+
+G_END_DECLS
+
+#endif /* __LIBTRACKER_MINER_DECORATOR_FS_H__ */
diff --git a/src/libtracker-miner/tracker-decorator-private.h 
b/src/libtracker-miner/tracker-decorator-private.h
new file mode 100644
index 000000000..8acc7e17c
--- /dev/null
+++ b/src/libtracker-miner/tracker-decorator-private.h
@@ -0,0 +1,27 @@
+/*
+ * Copyright (C) 2014 Carlos Garnacho  <carlosg gnome org>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ * Boston, MA  02110-1301, USA.
+ */
+
+#ifndef __TRACKER_DECORATOR_PRIVATE_H__
+#define __TRACKER_DECORATOR_PRIVATE_H__
+
+#include "tracker-decorator.h"
+
+void _tracker_decorator_invalidate_cache (TrackerDecorator *decorator);
+
+#endif /* __TRACKER_DECORATOR_PRIVATE_H__ */
diff --git a/src/libtracker-miner/tracker-decorator.c b/src/libtracker-miner/tracker-decorator.c
new file mode 100644
index 000000000..9cc3e392d
--- /dev/null
+++ b/src/libtracker-miner/tracker-decorator.c
@@ -0,0 +1,1695 @@
+/*
+ * Copyright (C) 2014 Carlos Garnacho  <carlosg gnome org>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ * Boston, MA  02110-1301, USA.
+ */
+
+#include "config-miners.h"
+
+#include <string.h>
+
+#include "tracker-decorator.h"
+#include "tracker-priority-queue.h"
+#include "tracker-decorator-private.h"
+
+#define QUERY_BATCH_SIZE 100
+#define DEFAULT_BATCH_SIZE 200
+
+#define TRACKER_DECORATOR_GET_PRIVATE(o) (G_TYPE_INSTANCE_GET_PRIVATE ((o), TRACKER_TYPE_DECORATOR, 
TrackerDecoratorPrivate))
+
+/**
+ * SECTION:tracker-decorator
+ * @short_description: A miner tasked with listening for DB resource changes and extracting metadata
+ * @include: libtracker-miner/tracker-miner.h
+ * @title: TrackerDecorator
+ * @see_also: #TrackerDecoratorFS
+ *
+ * #TrackerDecorator watches for signal updates based on content changes
+ * in the database. When new files are added initially, only simple
+ * metadata exists, for example, name, size, mtime, etc. The
+ * #TrackerDecorator queues files for extended metadata extraction
+ * (i.e. for tracker-extract to fetch metadata specific to the file
+ * type) for example 'nmm:whiteBalance' for a picture.
+**/
+
+typedef struct _TrackerDecoratorPrivate TrackerDecoratorPrivate;
+typedef struct _SparqlUpdate SparqlUpdate;
+typedef struct _ClassInfo ClassInfo;
+
+struct _TrackerDecoratorInfo {
+       GTask *task;
+       gchar *urn;
+       gchar *url;
+       gchar *mimetype;
+       gint id;
+       gint ref_count;
+};
+
+struct _ClassInfo {
+       gchar *class_name;
+       gint priority;
+};
+
+struct _SparqlUpdate {
+       gchar *sparql;
+       gint id;
+};
+
+struct _TrackerDecoratorPrivate {
+       TrackerNotifier *notifier;
+       gchar *data_source;
+
+       GArray *classes; /* Array of ClassInfo */
+       gchar **class_names;
+
+       gssize n_remaining_items;
+       gssize n_processed_items;
+
+       GQueue item_cache; /* Queue of TrackerDecoratorInfo */
+
+       /* Arrays of tracker IDs */
+       GArray *prepended_ids;
+       GSequence *blacklist_items;
+
+       GHashTable *tasks; /* Associative array of GTasks */
+       GArray *sparql_buffer; /* Array of SparqlUpdate */
+       GArray *commit_buffer; /* Array of SparqlUpdate */
+       GTimer *timer;
+       GQueue next_elem_queue; /* Queue of incoming tasks */
+
+       gint batch_size;
+
+       guint processing : 1;
+       guint querying   : 1;
+};
+
+enum {
+       PROP_DATA_SOURCE = 1,
+       PROP_CLASS_NAMES,
+       PROP_COMMIT_BATCH_SIZE,
+       PROP_PRIORITY_RDF_TYPES,
+};
+
+enum {
+       ITEMS_AVAILABLE,
+       FINISHED,
+       LAST_SIGNAL
+};
+
+static guint signals[LAST_SIGNAL] = { 0 };
+static GInitableIface *parent_initable_iface;
+
+static void   tracker_decorator_initable_iface_init (GInitableIface   *iface);
+
+static void decorator_task_done (GObject      *object,
+                                 GAsyncResult *result,
+                                 gpointer      user_data);
+static void decorator_cache_next_items (TrackerDecorator *decorator);
+static gboolean decorator_check_commit (TrackerDecorator *decorator);
+
+static void notifier_events_cb (TrackerDecorator *decorator,
+                               GPtrArray        *events,
+                               TrackerNotifier  *notifier);
+
+/**
+ * tracker_decorator_error_quark:
+ *
+ * Gives the caller the #GQuark used to identify #TrackerDecorator errors
+ * in #GError structures. The #GQuark is used as the domain for the error.
+ *
+ * Returns: the #GQuark used for the domain of a #GError.
+ *
+ * Since: 0.18.
+ **/
+G_DEFINE_QUARK (TrackerDecoratorError, tracker_decorator_error)
+
+G_DEFINE_ABSTRACT_TYPE_WITH_CODE (TrackerDecorator, tracker_decorator, TRACKER_TYPE_MINER,
+                                  G_IMPLEMENT_INTERFACE (G_TYPE_INITABLE, 
tracker_decorator_initable_iface_init))
+
+static TrackerDecoratorInfo *
+tracker_decorator_info_new (TrackerDecorator    *decorator,
+                            TrackerSparqlCursor *cursor)
+{
+       TrackerSparqlBuilder *sparql;
+       TrackerDecoratorInfo *info;
+       GCancellable *cancellable;
+
+       info = g_slice_new0 (TrackerDecoratorInfo);
+       info->urn = g_strdup (tracker_sparql_cursor_get_string (cursor, 0, NULL));
+       info->id = tracker_sparql_cursor_get_integer (cursor, 1);
+       info->url = g_strdup (tracker_sparql_cursor_get_string (cursor, 2, NULL));
+       info->mimetype = g_strdup (tracker_sparql_cursor_get_string (cursor, 3, NULL));
+       info->ref_count = 1;
+
+       cancellable = g_cancellable_new ();
+       info->task = g_task_new (decorator, cancellable,
+                                decorator_task_done, info);
+       g_object_unref (cancellable);
+
+       sparql = tracker_sparql_builder_new_update ();
+       g_task_set_task_data (info->task, sparql,
+                             (GDestroyNotify) g_object_unref);
+
+       return info;
+}
+
+/**
+ * tracker_decorator_info_ref:
+ * @info: a #TrackerDecoratorInfo
+ *
+ * Increases the reference count of @info by 1.
+ *
+ * Returns: the same @info passed in, or %NULL on error.
+ *
+ * Since: 0.18.
+ **/
+TrackerDecoratorInfo *
+tracker_decorator_info_ref (TrackerDecoratorInfo *info)
+{
+       g_atomic_int_inc (&info->ref_count);
+       return info;
+}
+
+/**
+ * tracker_decorator_info_unref:
+ * @info: a #TrackerDecoratorInfo
+ *
+ * Decreases the reference count of @info by 1 and frees it when the
+ * reference count reaches 0.
+ *
+ * Since: 0.18.
+ **/
+void
+tracker_decorator_info_unref (TrackerDecoratorInfo *info)
+{
+       if (!g_atomic_int_dec_and_test (&info->ref_count))
+               return;
+
+       if (info->task)
+               g_object_unref (info->task);
+       g_free (info->urn);
+       g_free (info->url);
+       g_free (info->mimetype);
+       g_slice_free (TrackerDecoratorInfo, info);
+}
+
+G_DEFINE_BOXED_TYPE (TrackerDecoratorInfo,
+                     tracker_decorator_info,
+                     tracker_decorator_info_ref,
+                     tracker_decorator_info_unref)
+
+static gint
+sequence_compare_func (gconstpointer data1,
+                       gconstpointer data2,
+                       gpointer      user_data)
+{
+       return GPOINTER_TO_INT (data1) - GPOINTER_TO_INT (data2);
+}
+
+static void
+decorator_blacklist_add (TrackerDecorator *decorator,
+                         gint              id)
+{
+       TrackerDecoratorPrivate *priv = decorator->priv;
+       GSequenceIter *iter;
+
+       iter = g_sequence_search (priv->blacklist_items,
+                                 GINT_TO_POINTER (id),
+                                 sequence_compare_func,
+                                 NULL);
+
+       if (g_sequence_iter_is_end (iter) ||
+           g_sequence_get (g_sequence_iter_prev (iter)) != GINT_TO_POINTER (id))
+               g_sequence_insert_before (iter, GINT_TO_POINTER (id));
+}
+
+static void
+decorator_blacklist_remove (TrackerDecorator *decorator,
+                            gint              id)
+{
+       TrackerDecoratorPrivate *priv = decorator->priv;
+       GSequenceIter *iter;
+
+       iter = g_sequence_lookup (priv->blacklist_items,
+                                 GINT_TO_POINTER (id),
+                                 sequence_compare_func,
+                                 NULL);
+       if (iter)
+               g_sequence_remove (iter);
+}
+
+static void
+decorator_update_state (TrackerDecorator *decorator,
+                        const gchar      *message,
+                        gboolean          estimate_time)
+{
+       TrackerDecoratorPrivate *priv;
+       gint remaining_time = -1;
+       gdouble progress = 1;
+       gsize total_items;
+
+       priv = decorator->priv;
+       remaining_time = 0;
+       total_items = priv->n_remaining_items + priv->n_processed_items;
+
+       if (priv->n_remaining_items > 0)
+               progress = ((gdouble) priv->n_processed_items / total_items);
+
+       if (priv->timer && estimate_time &&
+           !tracker_miner_is_paused (TRACKER_MINER (decorator))) {
+               gdouble elapsed;
+
+               /* FIXME: Quite naive calculation */
+               elapsed = g_timer_elapsed (priv->timer, NULL);
+
+               if (priv->n_processed_items > 0)
+                       remaining_time = (priv->n_remaining_items * elapsed) / priv->n_processed_items;
+       }
+
+       g_object_set (decorator,
+                     "progress", progress,
+                     "remaining-time", remaining_time,
+                     NULL);
+
+       if (message)
+               g_object_set (decorator, "status", message, NULL);
+}
+
+static void
+item_warn (TrackerSparqlConnection *conn,
+           gint                     id,
+           const gchar             *sparql,
+           const GError            *error)
+{
+       TrackerSparqlCursor *cursor;
+       const gchar *elem;
+       gchar *query;
+
+       query = g_strdup_printf ("SELECT COALESCE (nie:url (?u), ?u) {"
+                                "  ?u a rdfs:Resource. "
+                                "  FILTER (tracker:id (?u) = %d)"
+                                "}", id);
+
+       cursor = tracker_sparql_connection_query (conn, query, NULL, NULL);
+       g_free (query);
+
+       g_debug ("--8<------------------------------");
+       g_debug ("The information relevant for a bug report is between "
+                "the dotted lines");
+
+       if (cursor &&
+           tracker_sparql_cursor_next (cursor, NULL, NULL)) {
+               elem = tracker_sparql_cursor_get_string (cursor, 0, NULL);
+               g_warning ("Could not insert metadata for item \"%s\": %s",
+                          elem, error->message);
+       } else {
+               g_warning ("Could not insert metadata for item with ID %d: %s",
+                          id, error->message);
+       }
+
+       g_warning ("If the error above is recurrent for the same item/ID, "
+                  "consider running \"%s\" in the terminal with the "
+                  "TRACKER_VERBOSITY=3 environment variable, and filing a "
+                  "bug with the additional information", g_get_prgname ());
+
+       g_debug ("Sparql was:\n%s", sparql);
+       g_debug ("NOTE: The information above may contain data you "
+                "consider sensitive. Feel free to edit it out, but please "
+                "keep it as unmodified as you possibly can.");
+       g_debug ("------------------------------>8--");
+
+       g_clear_object (&cursor);
+}
+
+static void
+decorator_commit_cb (GObject      *object,
+                     GAsyncResult *result,
+                     gpointer      user_data)
+{
+       TrackerSparqlConnection *conn;
+       TrackerDecoratorPrivate *priv;
+       TrackerDecorator *decorator;
+       GError *error = NULL;
+       GPtrArray *errors;
+       guint i;
+
+       decorator = user_data;
+       priv = decorator->priv;
+       conn = TRACKER_SPARQL_CONNECTION (object);
+       errors = tracker_sparql_connection_update_array_finish (conn, result, &error);
+
+       if (error) {
+               g_warning ("There was an error pushing metadata: %s\n", error->message);
+       }
+
+       if (errors) {
+               for (i = 0; i < errors->len; i++) {
+                       SparqlUpdate *update;
+                       GError *child_error;
+
+                       child_error = g_ptr_array_index (errors, i);
+                       update = &g_array_index (priv->commit_buffer, SparqlUpdate, i);
+
+                       if (!child_error)
+                               continue;
+
+                       decorator_blacklist_add (decorator, update->id);
+                       item_warn (conn, update->id, update->sparql, child_error);
+               }
+
+               g_ptr_array_unref (errors);
+       }
+
+       g_clear_pointer (&priv->commit_buffer, (GDestroyNotify) g_array_unref);
+
+       if (!decorator_check_commit (decorator))
+               decorator_cache_next_items (decorator);
+}
+
+static void
+sparql_update_clear (SparqlUpdate *update)
+{
+       g_free (update->sparql);
+}
+
+static GArray *
+sparql_buffer_new (void)
+{
+       GArray *array;
+
+       array = g_array_new (FALSE, FALSE, sizeof (SparqlUpdate));
+       g_array_set_clear_func (array, (GDestroyNotify) sparql_update_clear);
+
+       return array;
+}
+
+static gboolean
+decorator_commit_info (TrackerDecorator *decorator)
+{
+       TrackerSparqlConnection *sparql_conn;
+       TrackerDecoratorPrivate *priv;
+       GPtrArray *array;
+       gint i;
+
+       priv = decorator->priv;
+
+       if (!priv->sparql_buffer || priv->sparql_buffer->len == 0)
+               return FALSE;
+
+       if (priv->commit_buffer)
+               return FALSE;
+
+       /* Move sparql buffer to commit buffer */
+       priv->commit_buffer = priv->sparql_buffer;
+       priv->sparql_buffer = NULL;
+       array = g_ptr_array_new ();
+
+       for (i = 0; i < priv->commit_buffer->len; i++) {
+               SparqlUpdate *update;
+
+               update = &g_array_index (priv->commit_buffer, SparqlUpdate, i);
+               g_ptr_array_add (array, update->sparql);
+       }
+
+       sparql_conn = tracker_miner_get_connection (TRACKER_MINER (decorator));
+       tracker_sparql_connection_update_array_async (sparql_conn,
+                                                     (gchar **) array->pdata,
+                                                     array->len,
+                                                     G_PRIORITY_DEFAULT,
+                                                     NULL,
+                                                     decorator_commit_cb,
+                                                     decorator);
+
+       decorator_update_state (decorator, NULL, TRUE);
+       g_ptr_array_unref (array);
+       return TRUE;
+}
+
+static gboolean
+decorator_check_commit (TrackerDecorator *decorator)
+{
+       TrackerDecoratorPrivate *priv;
+
+       priv = decorator->priv;
+
+       if (!priv->sparql_buffer ||
+           (priv->n_remaining_items > 0 &&
+            priv->sparql_buffer->len < (guint) priv->batch_size))
+               return FALSE;
+
+       return decorator_commit_info (decorator);
+}
+
+static void
+decorator_notify_task_error (TrackerDecorator *decorator,
+                             GError           *error)
+{
+       TrackerDecoratorPrivate *priv = decorator->priv;
+       GTask *task;
+
+       while (!g_queue_is_empty (&priv->next_elem_queue)) {
+               task = g_queue_pop_head (&priv->next_elem_queue);
+               g_task_return_error (task, g_error_copy (error));
+               g_object_unref (task);
+       }
+}
+
+static void
+decorator_notify_empty (TrackerDecorator *decorator)
+{
+       GError *error;
+
+       error = g_error_new (tracker_decorator_error_quark (),
+                            TRACKER_DECORATOR_ERROR_EMPTY,
+                            "There are no items left");
+       decorator_notify_task_error (decorator, error);
+       g_error_free (error);
+}
+
+static void
+decorator_start (TrackerDecorator *decorator)
+{
+       TrackerDecoratorPrivate *priv = decorator->priv;
+
+       if (priv->processing)
+               return;
+
+       priv->processing = TRUE;
+       g_signal_emit (decorator, signals[ITEMS_AVAILABLE], 0);
+       decorator_update_state (decorator, "Extracting metadata", TRUE);
+}
+
+static void
+decorator_finish (TrackerDecorator *decorator)
+{
+       TrackerDecoratorPrivate *priv = decorator->priv;
+
+       if (!priv->processing)
+               return;
+
+       priv->processing = FALSE;
+       priv->n_remaining_items = priv->n_processed_items = 0;
+       g_signal_emit (decorator, signals[FINISHED], 0);
+       decorator_commit_info (decorator);
+       decorator_notify_empty (decorator);
+       decorator_update_state (decorator, "Idle", FALSE);
+}
+
+static void
+decorator_rebuild_cache (TrackerDecorator *decorator)
+{
+       TrackerDecoratorPrivate *priv = decorator->priv;
+
+       priv->n_remaining_items = 0;
+       g_queue_foreach (&priv->item_cache,
+                        (GFunc) tracker_decorator_info_unref, NULL);
+       g_queue_clear (&priv->item_cache);
+
+        decorator_cache_next_items (decorator);
+}
+
+/* This function is called after the caller has completed the
+ * GTask given on the TrackerDecoratorInfo, this definitely removes
+ * the element being processed from queues.
+ */
+static void
+decorator_task_done (GObject      *object,
+                     GAsyncResult *result,
+                     gpointer      user_data)
+{
+       TrackerDecorator *decorator = TRACKER_DECORATOR (object);
+       TrackerDecoratorInfo *info = user_data;
+       TrackerDecoratorPrivate *priv;
+       GError *error = NULL;
+       gchar *sparql;
+
+       priv = decorator->priv;
+       sparql = g_task_propagate_pointer (G_TASK (result), &error);
+
+       if (!sparql) {
+               /* Blacklist item */
+               decorator_blacklist_add (decorator, info->id);
+
+               if (error) {
+                       g_warning ("Task for '%s' finished with error: %s\n",
+                                  info->url, error->message);
+                       g_error_free (error);
+               }
+       } else {
+               SparqlUpdate update;
+
+               /* Add resulting sparql to buffer and check whether flushing */
+               update.sparql = sparql;
+               update.id = info->id;
+
+               if (!priv->sparql_buffer)
+                       priv->sparql_buffer = sparql_buffer_new ();
+
+               g_array_append_val (priv->sparql_buffer, update);
+       }
+
+       g_hash_table_remove (priv->tasks, result);
+
+       if (priv->n_remaining_items > 0)
+               priv->n_remaining_items--;
+       priv->n_processed_items++;
+
+       decorator_check_commit (decorator);
+
+       if (priv->n_remaining_items == 0) {
+               decorator_finish (decorator);
+               decorator_rebuild_cache (decorator);
+       } else if (g_queue_is_empty (&priv->item_cache) &&
+                  g_hash_table_size (priv->tasks) == 0 &&
+                  (!priv->sparql_buffer || !priv->commit_buffer)) {
+               decorator_cache_next_items (decorator);
+       }
+}
+
+static void
+decorator_cancel_active_tasks (TrackerDecorator *decorator)
+{
+       TrackerDecoratorPrivate *priv = decorator->priv;
+       GHashTableIter iter;
+       GTask *task;
+
+       g_hash_table_iter_init (&iter, priv->tasks);
+
+       while (g_hash_table_iter_next (&iter, NULL, (gpointer*) &task)) {
+               g_cancellable_cancel (g_task_get_cancellable (task));
+       }
+
+       g_hash_table_remove_all (priv->tasks);
+}
+
+static void
+query_append_id (GString *string,
+                 gint     id)
+{
+       if (string->len > 1 && string->str[string->len - 1] != '(')
+               g_string_append_c (string, ',');
+
+       g_string_append_printf (string, "%d", id);
+}
+
+static void
+query_add_blacklisted_filter (TrackerDecorator *decorator,
+                              GString          *query)
+{
+       TrackerDecoratorPrivate *priv = decorator->priv;
+       GSequenceIter *iter;
+
+       if (g_sequence_get_length (priv->blacklist_items) == 0)
+               return;
+
+       g_string_append (query, "&& tracker:id(?urn) NOT IN (");
+
+       iter = g_sequence_get_begin_iter (priv->blacklist_items);
+
+       while (!g_sequence_iter_is_end (iter)) {
+               query_append_id (query, GPOINTER_TO_INT (g_sequence_get (iter)));
+               iter = g_sequence_iter_next (iter);
+       }
+
+       g_string_append (query, ")");
+}
+
+static void
+query_add_update_buffer_ids (GString *query,
+                             GArray  *commit_buffer)
+{
+       SparqlUpdate *update;
+       gint i;
+
+       for (i = 0; i < commit_buffer->len; i++) {
+               update = &g_array_index (commit_buffer, SparqlUpdate, i);
+               query_append_id (query, update->id);
+       }
+}
+
+static void
+query_add_processing_filter (TrackerDecorator *decorator,
+                             GString          *query)
+{
+       TrackerDecoratorPrivate *priv = decorator->priv;
+
+       if ((!priv->sparql_buffer || priv->sparql_buffer->len == 0) &&
+           (!priv->commit_buffer || priv->commit_buffer->len == 0))
+           return;
+
+       g_string_append (query, "&& tracker:id(?urn) NOT IN (");
+
+       if (priv->sparql_buffer && priv->sparql_buffer->len > 0)
+               query_add_update_buffer_ids (query, priv->sparql_buffer);
+       if (priv->commit_buffer && priv->commit_buffer->len > 0)
+               query_add_update_buffer_ids (query, priv->commit_buffer);
+
+       g_string_append (query, ")");
+}
+
+static void
+query_add_id_filter (GString  *query,
+                     GArray   *ids)
+{
+       gint i;
+
+       if (!ids || ids->len == 0)
+               return;
+
+       g_string_append (query, "&& tracker:id(?urn) IN (");
+
+       for (i = 0; i < ids->len; i++) {
+               if (i != 0)
+                       g_string_append (query, ",");
+
+               g_string_append_printf (query, "%d",
+                                       g_array_index (ids, gint, i));
+       }
+
+       g_string_append (query, ")");
+}
+
+static void
+query_append_current_tasks_filter (TrackerDecorator *decorator,
+                                  GString          *query)
+{
+       TrackerDecoratorPrivate *priv = decorator->priv;
+       GHashTableIter iter;
+       gint i = 0, id;
+       GTask *task;
+
+       if (g_hash_table_size (priv->tasks) == 0)
+               return;
+
+       g_string_append (query, "&& tracker:id(?urn) NOT IN (");
+       g_hash_table_iter_init (&iter, priv->tasks);
+
+       while (g_hash_table_iter_next (&iter, NULL, (gpointer *) &task)) {
+               if (i != 0)
+                       g_string_append (query, ",");
+
+               id = GPOINTER_TO_INT (g_task_get_task_data (task));
+               g_string_append_printf (query, "%d", id);
+               i++;
+       }
+
+       g_string_append (query, ")");
+}
+
+static gchar *
+create_query_string (TrackerDecorator  *decorator,
+                     gchar            **select_clauses,
+                     gboolean           for_prepended)
+{
+       TrackerDecoratorPrivate *priv = decorator->priv;
+       ClassInfo *prev = NULL, *cur;
+       GString *query;
+       gint i;
+
+       query = g_string_new ("SELECT ");
+
+       for (i = 0; select_clauses[i]; i++) {
+               g_string_append_printf (query, "%s ", select_clauses[i]);
+       }
+
+       g_string_append (query, "{ SELECT ?urn WHERE {");
+
+       for (i = 0; i < priv->classes->len; i++) {
+               cur = &g_array_index (priv->classes, ClassInfo, i);
+
+               if (!prev || prev->priority != cur->priority) {
+                       if (prev)
+                               g_string_append (query, "))} UNION ");
+
+                       g_string_append_printf (query,
+                                               "{ ?urn a rdfs:Resource;"
+                                               "       a ?type ;"
+                                               "       tracker:available true ."
+                                               "  FILTER (! EXISTS { ?urn nie:dataSource <%s> } ",
+                                               priv->data_source);
+
+                       query_add_blacklisted_filter (decorator, query);
+                       query_add_processing_filter (decorator, query);
+
+                       if (for_prepended && priv->prepended_ids->len > 0) {
+                               query_add_id_filter (query, priv->prepended_ids);
+                               g_array_set_size (priv->prepended_ids, 0);
+                       }
+
+                       query_append_current_tasks_filter (decorator, query);
+                       g_string_append (query, " && ?type IN (");
+               } else {
+                       g_string_append (query, ",");
+               }
+
+               g_string_append_printf (query, "%s", cur->class_name);
+               prev = cur;
+       }
+
+       g_string_append_printf (query, "))}}} LIMIT %d", QUERY_BATCH_SIZE);
+
+       return g_string_free (query, FALSE);
+}
+
+static gchar *
+create_remaining_items_query (TrackerDecorator *decorator)
+{
+       gchar *clauses[] = {
+               "?urn",
+               "tracker:id(?urn)",
+               "nie:url(?urn)",
+               "nie:mimeType(?urn)",
+               NULL
+       };
+
+       return create_query_string (decorator, clauses, TRUE);
+}
+
+static void
+decorator_query_remaining_items_cb (GObject      *object,
+                                    GAsyncResult *result,
+                                    gpointer      user_data)
+{
+       TrackerDecorator *decorator = user_data;
+       TrackerDecoratorPrivate *priv;
+       TrackerSparqlCursor *cursor;
+       GError *error = NULL;
+
+       cursor = tracker_sparql_connection_query_finish (TRACKER_SPARQL_CONNECTION (object),
+                                                        result, &error);
+       priv = decorator->priv;
+        priv->querying = FALSE;
+
+       if (error || !tracker_sparql_cursor_next (cursor, NULL, &error)) {
+               decorator_notify_task_error (decorator, error);
+               g_error_free (error);
+               return;
+       }
+
+       priv->n_remaining_items = g_queue_get_length (&priv->item_cache) +
+               tracker_sparql_cursor_get_integer (cursor, 0);
+       g_object_unref (cursor);
+
+       g_debug ("Found %" G_GSIZE_FORMAT " items to extract", priv->n_remaining_items);
+
+       if (priv->n_remaining_items > 0)
+               decorator_cache_next_items (decorator);
+       else
+               decorator_finish (decorator);
+}
+
+static void
+decorator_query_remaining_items (TrackerDecorator *decorator)
+{
+       gchar *query, *clauses[] = { "COUNT(?urn)", NULL };
+       TrackerSparqlConnection *sparql_conn;
+
+       query = create_query_string (decorator, clauses, FALSE);
+
+       if (query) {
+               sparql_conn = tracker_miner_get_connection (TRACKER_MINER (decorator));
+               tracker_sparql_connection_query_async (sparql_conn, query,
+                                                      NULL, decorator_query_remaining_items_cb,
+                                                      decorator);
+               g_free (query);
+       } else {
+               decorator_notify_empty (decorator);
+       }
+}
+
+static void
+decorator_pair_tasks (TrackerDecorator *decorator)
+{
+       TrackerDecoratorPrivate *priv = decorator->priv;
+       TrackerDecoratorInfo *info;
+       GTask *task;
+
+       while (!g_queue_is_empty (&priv->item_cache) &&
+              !g_queue_is_empty (&priv->next_elem_queue)) {
+               info = g_queue_pop_head (&priv->item_cache);
+               task = g_queue_pop_head (&priv->next_elem_queue);
+
+               g_task_set_task_data (task, GINT_TO_POINTER (info->id), NULL);
+
+               /* Pass ownership of info */
+               g_task_return_pointer (task, info,
+                                      (GDestroyNotify) tracker_decorator_info_unref);
+               g_object_unref (task);
+
+               /* Store the decorator-side task in the active task pool */
+               g_hash_table_add (priv->tasks, info->task);
+       }
+}
+
+static void
+decorator_item_cache_remove (TrackerDecorator *decorator,
+                             gint              id)
+{
+       TrackerDecoratorPrivate *priv = decorator->priv;
+       GList *item;
+
+       for (item = g_queue_peek_head_link (&priv->item_cache);
+            item; item = item->next) {
+               TrackerDecoratorInfo *info = item->data;
+
+               if (info->id != id)
+                       continue;
+
+               g_queue_remove (&priv->item_cache, info);
+               tracker_decorator_info_unref (info);
+       }
+}
+
+static void
+decorator_cache_items_cb (GObject      *object,
+                          GAsyncResult *result,
+                          gpointer      user_data)
+{
+       TrackerDecorator *decorator = user_data;
+       TrackerDecoratorPrivate *priv;
+       TrackerSparqlConnection *conn;
+       TrackerSparqlCursor *cursor;
+       TrackerDecoratorInfo *info;
+       GError *error = NULL;
+
+       conn = TRACKER_SPARQL_CONNECTION (object);
+       cursor = tracker_sparql_connection_query_finish (conn, result, &error);
+       priv = decorator->priv;
+        priv->querying = FALSE;
+
+       if (error) {
+               decorator_notify_task_error (decorator, error);
+               g_error_free (error);
+       } else {
+               while (tracker_sparql_cursor_next (cursor, NULL, NULL)) {
+                       info = tracker_decorator_info_new (decorator, cursor);
+                       g_queue_push_tail (&priv->item_cache, info);
+               }
+       }
+
+       if (!g_queue_is_empty (&priv->item_cache) && !priv->processing) {
+               decorator_start (decorator);
+       } else if (g_queue_is_empty (&priv->item_cache) && priv->processing) {
+               decorator_finish (decorator);
+       }
+
+       decorator_pair_tasks (decorator);
+       g_object_unref (cursor);
+}
+
+static void
+decorator_cache_next_items (TrackerDecorator *decorator)
+{
+       TrackerDecoratorPrivate *priv = decorator->priv;
+
+       if (priv->querying ||
+           g_hash_table_size (priv->tasks) > 0 ||
+           !g_queue_is_empty (&priv->item_cache))
+               return;
+
+        priv->querying = TRUE;
+
+       if (priv->n_remaining_items == 0) {
+               decorator_query_remaining_items (decorator);
+       } else {
+               TrackerSparqlConnection *sparql_conn;
+               gchar *query;
+
+               sparql_conn = tracker_miner_get_connection (TRACKER_MINER (decorator));
+               query = create_remaining_items_query (decorator);
+               tracker_sparql_connection_query_async (sparql_conn, query,
+                                                      NULL, decorator_cache_items_cb,
+                                                      decorator);
+               g_free (query);
+       }
+}
+
+static void
+update_notifier (TrackerDecorator *decorator)
+{
+       TrackerDecoratorPrivate *priv = decorator->priv;
+
+       g_clear_object (&priv->notifier);
+
+       if (priv->class_names) {
+               GError *error = NULL;
+
+               priv->notifier = tracker_notifier_new ((const gchar * const *) priv->class_names,
+                                                      TRACKER_NOTIFIER_FLAG_NOTIFY_UNEXTRACTED,
+                                                      NULL, &error);
+
+               if (error) {
+                       g_warning ("Could not create notifier: %s\n",
+                                  error->message);
+                       g_error_free (error);
+               }
+
+               g_signal_connect_swapped (priv->notifier, "events",
+                                         G_CALLBACK (notifier_events_cb),
+                                         decorator);
+       }
+}
+
+static void
+tracker_decorator_get_property (GObject    *object,
+                                guint       param_id,
+                                GValue     *value,
+                                GParamSpec *pspec)
+{
+       TrackerDecoratorPrivate *priv;
+
+       priv = TRACKER_DECORATOR (object)->priv;
+
+       switch (param_id) {
+       case PROP_DATA_SOURCE:
+               g_value_set_string (value, priv->data_source);
+               break;
+       case PROP_CLASS_NAMES:
+               g_value_set_boxed (value, priv->class_names);
+               break;
+       case PROP_COMMIT_BATCH_SIZE:
+               g_value_set_int (value, priv->batch_size);
+               break;
+       default:
+               G_OBJECT_WARN_INVALID_PROPERTY_ID (object, param_id, pspec);
+       }
+}
+
+static void
+decorator_add_class (TrackerDecorator *decorator,
+                     const gchar      *class)
+{
+       TrackerDecoratorPrivate *priv = decorator->priv;
+       ClassInfo info;
+
+       info.class_name = g_strdup (class);
+       info.priority = G_PRIORITY_DEFAULT;
+       g_array_append_val (priv->classes, info);
+}
+
+static void
+decorator_set_classes (TrackerDecorator  *decorator,
+                       const gchar      **classes)
+{
+       TrackerDecoratorPrivate *priv = decorator->priv;
+       gint i;
+
+       g_strfreev (priv->class_names);
+       priv->class_names = g_strdupv ((gchar **) classes);
+
+       if (priv->classes->len > 0) {
+               g_array_remove_range (priv->classes, 0,
+                                     priv->classes->len);
+       }
+
+       for (i = 0; classes[i]; i++) {
+               decorator_add_class (decorator, classes[i]);
+       }
+
+       update_notifier (decorator);
+}
+
+static void
+tracker_decorator_set_property (GObject      *object,
+                                guint         param_id,
+                                const GValue *value,
+                                GParamSpec   *pspec)
+{
+       TrackerDecorator *decorator = TRACKER_DECORATOR (object);
+       TrackerDecoratorPrivate *priv;
+
+       priv = decorator->priv;
+
+       switch (param_id) {
+       case PROP_DATA_SOURCE:
+               priv->data_source = g_value_dup_string (value);
+               break;
+       case PROP_CLASS_NAMES:
+               decorator_set_classes (decorator, g_value_get_boxed (value));
+               break;
+       case PROP_COMMIT_BATCH_SIZE:
+               priv->batch_size = g_value_get_int (value);
+               break;
+       case PROP_PRIORITY_RDF_TYPES:
+               tracker_decorator_set_priority_rdf_types (decorator,
+                                                         g_value_get_boxed (value));
+               break;
+       default:
+               G_OBJECT_WARN_INVALID_PROPERTY_ID (object, param_id, pspec);
+       }
+}
+
+static void
+notifier_events_cb (TrackerDecorator *decorator,
+                   GPtrArray       *events,
+                   TrackerNotifier *notifier)
+{
+       gboolean check_added = FALSE;
+       gint64 id;
+       gint i;
+
+       for (i = 0; i < events->len; i++) {
+               TrackerNotifierEvent *event;
+
+               event = g_ptr_array_index (events, i);
+               id = tracker_notifier_event_get_id (event);
+
+               switch (tracker_notifier_event_get_event_type (event)) {
+               case TRACKER_NOTIFIER_EVENT_CREATE:
+               case TRACKER_NOTIFIER_EVENT_UPDATE:
+                       /* Merely use this as a hint that there is something
+                        * left to be processed.
+                        */
+                       check_added = TRUE;
+                       break;
+               case TRACKER_NOTIFIER_EVENT_DELETE:
+                       decorator_item_cache_remove (decorator, id);
+                       decorator_blacklist_remove (decorator, id);
+                       break;
+               }
+       }
+
+       if (check_added)
+               decorator_cache_next_items (decorator);
+}
+
+static gboolean
+tracker_decorator_initable_init (GInitable     *initable,
+                                 GCancellable  *cancellable,
+                                 GError       **error)
+{
+       TrackerDecorator *decorator;
+
+       if (!parent_initable_iface->init (initable, cancellable, error))
+               return FALSE;
+
+       decorator = TRACKER_DECORATOR (initable);
+
+       if (g_cancellable_is_cancelled (cancellable))
+               return FALSE;
+
+       update_notifier (decorator);
+
+       decorator_update_state (decorator, "Idle", FALSE);
+       return TRUE;
+}
+
+static void
+tracker_decorator_initable_iface_init (GInitableIface *iface)
+{
+       parent_initable_iface = g_type_interface_peek_parent (iface);
+       iface->init = tracker_decorator_initable_init;
+}
+
+static void
+tracker_decorator_constructed (GObject *object)
+{
+       TrackerDecoratorPrivate *priv;
+
+       G_OBJECT_CLASS (tracker_decorator_parent_class)->constructed (object);
+
+       priv = TRACKER_DECORATOR (object)->priv;
+       g_assert (priv->data_source);
+}
+
+static void
+tracker_decorator_finalize (GObject *object)
+{
+       TrackerDecoratorPrivate *priv;
+       TrackerDecorator *decorator;
+
+       decorator = TRACKER_DECORATOR (object);
+       priv = decorator->priv;
+
+       g_clear_object (&priv->notifier);
+
+       g_queue_foreach (&priv->item_cache,
+                        (GFunc) tracker_decorator_info_unref,
+                        NULL);
+       g_queue_clear (&priv->item_cache);
+
+       decorator_cancel_active_tasks (decorator);
+       decorator_notify_empty (decorator);
+
+       g_strfreev (priv->class_names);
+       g_hash_table_destroy (priv->tasks);
+       g_array_unref (priv->classes);
+       g_array_unref (priv->prepended_ids);
+       g_clear_pointer (&priv->sparql_buffer, (GDestroyNotify) g_array_unref);
+       g_clear_pointer (&priv->commit_buffer, (GDestroyNotify) g_array_unref);
+       g_sequence_free (priv->blacklist_items);
+       g_free (priv->data_source);
+       g_timer_destroy (priv->timer);
+
+       G_OBJECT_CLASS (tracker_decorator_parent_class)->finalize (object);
+}
+
+static void
+tracker_decorator_paused (TrackerMiner *miner)
+{
+       TrackerDecoratorPrivate *priv;
+
+       decorator_cancel_active_tasks (TRACKER_DECORATOR (miner));
+       priv = TRACKER_DECORATOR (miner)->priv;
+       g_timer_stop (priv->timer);
+}
+
+static void
+tracker_decorator_resumed (TrackerMiner *miner)
+{
+       TrackerDecoratorPrivate *priv;
+
+       decorator_cache_next_items (TRACKER_DECORATOR (miner));
+       priv = TRACKER_DECORATOR (miner)->priv;
+       g_timer_continue (priv->timer);
+}
+
+static void
+tracker_decorator_stopped (TrackerMiner *miner)
+{
+       TrackerDecoratorPrivate *priv;
+
+       decorator_cancel_active_tasks (TRACKER_DECORATOR (miner));
+       priv = TRACKER_DECORATOR (miner)->priv;
+       g_timer_stop (priv->timer);
+}
+
+static void
+tracker_decorator_started (TrackerMiner *miner)
+{
+       TrackerDecoratorPrivate *priv;
+       TrackerDecorator *decorator;
+
+       decorator = TRACKER_DECORATOR (miner);
+       priv = decorator->priv;
+
+       g_timer_start (priv->timer);
+       decorator_rebuild_cache (decorator);
+}
+
+static void
+tracker_decorator_class_init (TrackerDecoratorClass *klass)
+{
+       GObjectClass *object_class = G_OBJECT_CLASS (klass);
+       TrackerMinerClass *miner_class = TRACKER_MINER_CLASS (klass);
+
+       object_class->get_property = tracker_decorator_get_property;
+       object_class->set_property = tracker_decorator_set_property;
+       object_class->constructed = tracker_decorator_constructed;
+       object_class->finalize = tracker_decorator_finalize;
+
+       miner_class->paused = tracker_decorator_paused;
+       miner_class->resumed = tracker_decorator_resumed;
+       miner_class->started = tracker_decorator_started;
+       miner_class->stopped = tracker_decorator_stopped;
+
+       g_object_class_install_property (object_class,
+                                        PROP_DATA_SOURCE,
+                                        g_param_spec_string ("data-source",
+                                                             "Data source URN",
+                                                             "nie:DataSource to use in this decorator",
+                                                             NULL,
+                                                             G_PARAM_READWRITE |
+                                                             G_PARAM_CONSTRUCT_ONLY));
+       g_object_class_install_property (object_class,
+                                        PROP_CLASS_NAMES,
+                                        g_param_spec_boxed ("class-names",
+                                                            "Class names",
+                                                            "rdfs:Class objects to listen to for changes",
+                                                            G_TYPE_STRV,
+                                                            G_PARAM_READWRITE));
+       g_object_class_install_property (object_class,
+                                        PROP_COMMIT_BATCH_SIZE,
+                                        g_param_spec_int ("commit-batch-size",
+                                                          "Commit batch size",
+                                                          "Number of items per update batch",
+                                                          0, G_MAXINT, DEFAULT_BATCH_SIZE,
+                                                          G_PARAM_READWRITE));
+       g_object_class_install_property (object_class,
+                                        PROP_PRIORITY_RDF_TYPES,
+                                        g_param_spec_boxed ("priority-rdf-types",
+                                                            "Priority RDF types",
+                                                            "rdf:type that needs to be extracted first",
+                                                            G_TYPE_STRV,
+                                                            G_PARAM_WRITABLE));
+       /**
+        * TrackerDecorator::items-available:
+        * @decorator: the #TrackerDecorator
+        *
+        * The ::items-available signal will be emitted whenever the
+        * #TrackerDecorator sees resources that are available for
+        * extended metadata extraction.
+        *
+        * Since: 0.18.
+        **/
+       signals[ITEMS_AVAILABLE] =
+               g_signal_new ("items-available",
+                             G_OBJECT_CLASS_TYPE (object_class),
+                             G_SIGNAL_RUN_LAST,
+                             G_STRUCT_OFFSET (TrackerDecoratorClass,
+                                              items_available),
+                             NULL, NULL, NULL,
+                             G_TYPE_NONE, 0);
+       /**
+        * TrackerDecorator::finished:
+        * @decorator: the #TrackerDecorator
+        *
+        * The ::finished signal will be emitted whenever the
+        * #TrackerDecorator has finished extracted extended metadata
+        * for resources in the database.
+        *
+        * Since: 0.18.
+        **/
+       signals[FINISHED] =
+               g_signal_new ("finished",
+                             G_OBJECT_CLASS_TYPE (object_class),
+                             G_SIGNAL_RUN_LAST,
+                             G_STRUCT_OFFSET (TrackerDecoratorClass, finished),
+                             NULL, NULL, NULL,
+                             G_TYPE_NONE, 0);
+
+       g_type_class_add_private (object_class, sizeof (TrackerDecoratorPrivate));
+}
+
+static void
+class_info_clear (ClassInfo *info)
+{
+       g_free (info->class_name);
+}
+
+static void
+tracker_decorator_init (TrackerDecorator *decorator)
+{
+       TrackerDecoratorPrivate *priv;
+
+       decorator->priv = priv = TRACKER_DECORATOR_GET_PRIVATE (decorator);
+       priv->classes = g_array_new (FALSE, FALSE, sizeof (ClassInfo));
+       g_array_set_clear_func (priv->classes, (GDestroyNotify) class_info_clear);
+       priv->blacklist_items = g_sequence_new (NULL);
+       priv->prepended_ids = g_array_new (FALSE, FALSE, sizeof (gint));
+       priv->batch_size = DEFAULT_BATCH_SIZE;
+       priv->timer = g_timer_new ();
+
+       g_queue_init (&priv->next_elem_queue);
+       g_queue_init (&priv->item_cache);
+       priv->tasks = g_hash_table_new (NULL, NULL);
+}
+
+/**
+ * tracker_decorator_get_data_source:
+ * @decorator: a #TrackerDecorator.
+ *
+ * The unique string identifying this #TrackerDecorator that has
+ * extracted the extended metadata. This is essentially an identifier
+ * so it's clear WHO has extracted this extended metadata.
+ *
+ * Returns: a const gchar* or #NULL if an error happened.
+ *
+ * Since: 0.18.
+ **/
+const gchar *
+tracker_decorator_get_data_source (TrackerDecorator *decorator)
+{
+       TrackerDecoratorPrivate *priv;
+
+       g_return_val_if_fail (TRACKER_IS_DECORATOR (decorator), NULL);
+
+       priv = decorator->priv;
+       return priv->data_source;
+}
+
+/**
+ * tracker_decorator_get_class_names:
+ * @decorator: a #TrackerDecorator.
+ *
+ * This function returns a string list of class names which are being
+ * updated with extended metadata. An example would be 'nfo:Document'.
+ *
+ * Returns: (transfer none): a const gchar** or #NULL.
+ *
+ * Since: 0.18.
+ **/
+const gchar **
+tracker_decorator_get_class_names (TrackerDecorator *decorator)
+{
+       TrackerDecoratorPrivate *priv;
+
+       g_return_val_if_fail (TRACKER_IS_DECORATOR (decorator), NULL);
+
+       priv = decorator->priv;
+       return (const gchar **) priv->class_names;
+}
+
+/**
+ * tracker_decorator_get_n_items:
+ * @decorator: a #TrackerDecorator
+ *
+ * Get the number of items left in the queue to be processed. This
+ * indicates content that may already exist in Tracker but is waiting
+ * to be further flurished with metadata with a 2nd pass extraction or
+ * index.
+ *
+ * Returns: the number of items queued to be processed, always >= 0.
+ *
+ * Since: 0.18.
+ **/
+guint
+tracker_decorator_get_n_items (TrackerDecorator *decorator)
+{
+       TrackerDecoratorPrivate *priv;
+
+       g_return_val_if_fail (TRACKER_IS_DECORATOR (decorator), 0);
+
+       priv = decorator->priv;
+
+       return priv->n_remaining_items;
+}
+
+/**
+ * tracker_decorator_prepend_id:
+ * @decorator: a #TrackerDecorator.
+ * @id: the ID of the resource ID.
+ * @class_name_id: the ID of the resource's class.
+ *
+ * Adds resource needing extended metadata extraction to the queue.
+ * @id is the same IDs emitted by tracker-store when the database is updated for
+ * consistency. For details, see the GraphUpdated signal.
+ *
+ * Since: 0.18.
+ **/
+void
+tracker_decorator_prepend_id (TrackerDecorator *decorator,
+                              gint              id,
+                              gint              class_name_id)
+{
+       TrackerDecoratorPrivate *priv;
+
+       g_return_if_fail (TRACKER_IS_DECORATOR (decorator));
+
+       priv = decorator->priv;
+       g_array_append_val (priv->prepended_ids, id);
+
+       /* The resource was explicitly requested, remove it from blacklists */
+       decorator_blacklist_remove (decorator, id);
+}
+
+/**
+ * tracker_decorator_delete_id:
+ * @decorator: a #TrackerDecorator.
+ * @id: an ID.
+ *
+ * Deletes resource needing extended metadata extraction from the
+ * queue. @id is the same IDs emitted by tracker-store when the database is
+ * updated for consistency. For details, see the GraphUpdated signal.
+ *
+ * Since: 0.18.
+ **/
+void
+tracker_decorator_delete_id (TrackerDecorator *decorator,
+                             gint              id)
+{
+       TrackerDecoratorPrivate *priv;
+       guint i;
+
+       g_return_if_fail (TRACKER_IS_DECORATOR (decorator));
+
+       priv = decorator->priv;
+
+       for (i = 0; i < priv->prepended_ids->len; i++) {
+               if (id == g_array_index (priv->prepended_ids, gint, i)) {
+                       g_array_remove_index (priv->prepended_ids, i);
+                       break;
+               }
+       }
+
+       /* Blacklist the item so it's not processed in the future */
+       decorator_blacklist_add (decorator, id);
+}
+
+/**
+ * tracker_decorator_next:
+ * @decorator: a #TrackerDecorator.
+ * @cancellable: a #GCancellable.
+ * @callback: a #GAsyncReadyCallback.
+ * @user_data: user_data for @callback.
+ *
+ * Processes the next resource in the queue to have extended metadata
+ * extracted. If the item in the queue has been completed already, it
+ * signals it's completion instead.
+ *
+ * This function will give a #GError if the miner is paused at the
+ * time it is called.
+ *
+ * Since: 0.18.
+ **/
+void
+tracker_decorator_next (TrackerDecorator    *decorator,
+                        GCancellable        *cancellable,
+                        GAsyncReadyCallback  callback,
+                        gpointer             user_data)
+{
+       TrackerDecoratorPrivate *priv;
+       GTask *task;
+
+       g_return_if_fail (TRACKER_IS_DECORATOR (decorator));
+
+       priv = decorator->priv;
+
+       task = g_task_new (decorator, cancellable, callback, user_data);
+
+       if (tracker_miner_is_paused (TRACKER_MINER (decorator))) {
+               GError *error;
+
+               error = g_error_new (tracker_decorator_error_quark (),
+                                    TRACKER_DECORATOR_ERROR_PAUSED,
+                                    "Decorator is paused");
+               g_task_return_error (task, error);
+               g_object_unref (task);
+               return;
+       }
+
+       g_queue_push_tail (&priv->next_elem_queue, task);
+       decorator_pair_tasks (decorator);
+}
+
+/**
+ * tracker_decorator_next_finish:
+ * @decorator: a #TrackerDecorator.
+ * @result: a #GAsyncResult.
+ * @error: return location for a #GError, or NULL.
+ *
+ * Should be called in the callback function provided to
+ * tracker_decorator_next() to return the result of the task be it an
+ * error or not.
+ *
+ * Returns: (transfer full): a #TrackerDecoratorInfo on success or
+ *  #NULL on error. Free with tracker_decorator_info_unref().
+ *
+ * Since: 0.18.
+ **/
+TrackerDecoratorInfo *
+tracker_decorator_next_finish (TrackerDecorator  *decorator,
+                               GAsyncResult      *result,
+                               GError           **error)
+{
+       g_return_val_if_fail (TRACKER_DECORATOR (decorator), NULL);
+       g_return_val_if_fail (G_IS_ASYNC_RESULT (result), NULL);
+       g_return_val_if_fail (!error || !*error, NULL);
+
+       return g_task_propagate_pointer (G_TASK (result), error);
+}
+
+static gint
+class_compare_func (const ClassInfo *a,
+                    const ClassInfo *b)
+{
+       return b->priority - a->priority;
+}
+
+static void
+decorator_set_class_priority (TrackerDecorator *decorator,
+                              const gchar      *class,
+                              gint              priority)
+{
+       TrackerDecoratorPrivate *priv = decorator->priv;
+       ClassInfo *info;
+       gint i;
+
+       for (i = 0; i < priv->classes->len; i++) {
+               info = &g_array_index (priv->classes, ClassInfo, i);
+
+               if (strcmp (info->class_name, class) != 0)
+                       continue;
+
+               info->priority = priority;
+               break;
+       }
+}
+
+/**
+ * tracker_decorator_set_priority_rdf_types:
+ * @decorator: a #TrackerDecorator
+ * @rdf_types: a string array of rdf types
+ *
+ * Re-evaluate the priority queues internally to ensure that
+ * @rdf_types are handled before all other content. This is useful for
+ * applications that need their content available sooner than the
+ * standard time it would take to index content.
+ *
+ * Since: 0.18.
+ **/
+void
+tracker_decorator_set_priority_rdf_types (TrackerDecorator    *decorator,
+                                          const gchar * const *rdf_types)
+{
+       TrackerDecoratorPrivate *priv;
+       gint i;
+
+       g_return_if_fail (TRACKER_DECORATOR (decorator));
+       g_return_if_fail (rdf_types != NULL);
+
+       priv = decorator->priv;
+
+       for (i = 0; rdf_types[i]; i++) {
+               decorator_set_class_priority (decorator, rdf_types[i],
+                                             G_PRIORITY_HIGH);
+       }
+
+       g_array_sort (priv->classes, (GCompareFunc) class_compare_func);
+       decorator_rebuild_cache (decorator);
+}
+
+/**
+ * tracker_decorator_info_get_urn:
+ * @info: a #TrackerDecoratorInfo.
+ *
+ * A URN is a Uniform Resource Name and should be a unique identifier
+ * for a resource in the database.
+ *
+ * Returns: the URN for #TrackerDecoratorInfo on success or #NULL on error.
+ *
+ * Since: 0.18.
+ **/
+const gchar *
+tracker_decorator_info_get_urn (TrackerDecoratorInfo *info)
+{
+       g_return_val_if_fail (info != NULL, NULL);
+       return info->urn;
+}
+
+/**
+ * tracker_decorator_info_get_url:
+ * @info: a #TrackerDecoratorInfo.
+ *
+ * A URL is a Uniform Resource Locator and should be a location associated
+ * with a resource in the database. For example, 'file:///tmp/foo.txt'.
+ *
+ * Returns: the URL for #TrackerDecoratorInfo on success or #NULL on error.
+ *
+ * Since: 0.18.
+ **/
+const gchar *
+tracker_decorator_info_get_url (TrackerDecoratorInfo *info)
+{
+       g_return_val_if_fail (info != NULL, NULL);
+       return info->url;
+}
+
+/**
+ * tracker_decorator_info_get_mimetype:
+ * @info: a #TrackerDecoratorInfo.
+ *
+ * A MIME¹ type is a way of describing the content type of a file or
+ * set of data. An example would be 'text/plain' for a clear text
+ * document or file.
+ *
+ * ¹: http://en.wikipedia.org/wiki/MIME
+ *
+ * Returns: the MIME type for #TrackerDecoratorInfo on success or #NULL on error.
+ *
+ * Since: 0.18.
+ **/
+const gchar *
+tracker_decorator_info_get_mimetype (TrackerDecoratorInfo *info)
+{
+       g_return_val_if_fail (info != NULL, NULL);
+       return info->mimetype;
+}
+
+
+/**
+ * tracker_decorator_info_get_task:
+ * @info: a #TrackerDecoratorInfo.
+ *
+ * Get the #GTask associated with retrieving extended metadata and
+ * information for a URN in Tracker.
+ *
+ * The task object's data (accessible with g_task_get_task_data()) is the
+ * #TrackerSparqlBuilder that you must populate with the results of the
+ * metadata extraction. This can also be accessed with
+ * tracker_decorator_info_get_sparql().
+ *
+ * Returns: (transfer none): the #GTask for #TrackerDecoratorInfo on
+ * success or #NULL if there is no existing #GTask.
+ *
+ * Since: 0.18.
+ **/
+GTask *
+tracker_decorator_info_get_task (TrackerDecoratorInfo *info)
+{
+       g_return_val_if_fail (info != NULL, NULL);
+       return info->task;
+}
+
+/**
+ * tracker_decorator_info_complete:
+ * @info: a #TrackerDecoratorInfo
+ * @sparql: SPARQL string
+ *
+ * Completes the task associated to this #TrackerDecoratorInfo.
+ * Takes ownership of @sparql.
+ *
+ * Since: 2.0
+ **/
+void
+tracker_decorator_info_complete (TrackerDecoratorInfo *info,
+                                 gchar                *sparql)
+{
+       g_task_return_pointer (info->task, sparql, g_free);
+}
+
+/**
+ * tracker_decorator_info_complete_error:
+ * @info: a #TrackerDecoratorInfo
+ * @error: An error occurred during SPARQL generation
+ *
+ * Completes the task associated to this #TrackerDecoratorInfo,
+ * returning the given @error happened during SPARQL generation.
+ *
+ * Since: 2.0
+ **/
+void
+tracker_decorator_info_complete_error (TrackerDecoratorInfo *info,
+                                       GError               *error)
+{
+       g_task_return_error (info->task, error);
+}
+
+void
+_tracker_decorator_invalidate_cache (TrackerDecorator *decorator)
+{
+       decorator_rebuild_cache (decorator);
+}
diff --git a/src/libtracker-miner/tracker-decorator.h b/src/libtracker-miner/tracker-decorator.h
new file mode 100644
index 000000000..af00d566f
--- /dev/null
+++ b/src/libtracker-miner/tracker-decorator.h
@@ -0,0 +1,138 @@
+/*
+ * Copyright (C) 2014 Carlos Garnacho  <carlosg gnome org>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ * Boston, MA  02110-1301, USA.
+ */
+
+#ifndef __LIBTRACKER_MINER_DECORATOR_H__
+#define __LIBTRACKER_MINER_DECORATOR_H__
+
+#if !defined (__LIBTRACKER_MINER_H_INSIDE__) && !defined (TRACKER_COMPILATION)
+#error "Only <libtracker-miner/tracker-miner.h> can be included directly."
+#endif
+
+#include "tracker-miner-object.h"
+
+G_BEGIN_DECLS
+
+#define TRACKER_TYPE_DECORATOR         (tracker_decorator_get_type())
+#define TRACKER_DECORATOR(o)           (G_TYPE_CHECK_INSTANCE_CAST ((o), TRACKER_TYPE_DECORATOR, 
TrackerDecorator))
+#define TRACKER_DECORATOR_CLASS(c)     (G_TYPE_CHECK_CLASS_CAST ((c), TRACKER_TYPE_DECORATOR, 
TrackerDecoratorClass))
+#define TRACKER_IS_DECORATOR(o)        (G_TYPE_CHECK_INSTANCE_TYPE ((o), TRACKER_TYPE_DECORATOR))
+#define TRACKER_IS_DECORATOR_CLASS(c)  (G_TYPE_CHECK_CLASS_TYPE ((c),  TRACKER_TYPE_DECORATOR))
+#define TRACKER_DECORATOR_GET_CLASS(o) (G_TYPE_INSTANCE_GET_CLASS ((o), TRACKER_TYPE_DECORATOR, 
TrackerDecoratorClass))
+
+typedef struct _TrackerDecorator TrackerDecorator;
+typedef struct _TrackerDecoratorClass TrackerDecoratorClass;
+typedef struct _TrackerDecoratorInfo TrackerDecoratorInfo;
+
+/**
+ * TrackerDecorator:
+ *
+ * Abstract miner object for passive extended metadata indexing, i.e.
+ * data past the basic information such as file name, size, etc.
+ **/
+struct _TrackerDecorator {
+       TrackerMiner parent_instance;
+       gpointer priv;
+};
+
+/**
+ * TrackerDecoratorClass:
+ * @parent_class: parent object class.
+ * @items_available: Called when there are resources to be processed.
+ * @finished: Called when all resources have been processed.
+ * @padding: Reserved for future API improvements.
+ *
+ * An implementation that takes care of extracting extra metadata
+ * specific to file types by talking to tracker-extract.
+ *
+ * Based on #TrackerMinerClass.
+ **/
+struct _TrackerDecoratorClass {
+       TrackerMinerClass parent_class;
+
+       void (* items_available) (TrackerDecorator *decorator);
+       void (* finished)        (TrackerDecorator *decorator);
+
+       /* <Private> */
+       gpointer padding[10];
+};
+
+
+/**
+ * TrackerDecoratorError:
+ * @TRACKER_DECORATOR_ERROR_EMPTY: There is no item to be processed
+ * next. It is entirely possible to have a ::items_available signal
+ * emitted and then have this error when calling
+ * tracker_decorator_next_finish() because the signal may apply to a
+ * class which we're not interested in. For example, a new nmo:Email
+ * might have been added to Tracker, but we might only be interested
+ * in nfo:Document. This case would give this error.
+ * @TRACKER_DECORATOR_ERROR_PAUSED: No work was done or will be done
+ * because the miner is currently paused.
+ *
+ * Possible errors returned when calling tracker_decorator_next_finish().
+ **/
+typedef enum {
+       TRACKER_DECORATOR_ERROR_EMPTY,
+       TRACKER_DECORATOR_ERROR_PAUSED
+} TrackerDecoratorError;
+
+
+GType         tracker_decorator_get_type          (void) G_GNUC_CONST;
+GQuark        tracker_decorator_error_quark       (void);
+
+const gchar * tracker_decorator_get_data_source   (TrackerDecorator     *decorator);
+const gchar** tracker_decorator_get_class_names   (TrackerDecorator     *decorator);
+guint         tracker_decorator_get_n_items       (TrackerDecorator     *decorator);
+
+void          tracker_decorator_prepend_id        (TrackerDecorator     *decorator,
+                                                   gint                  id,
+                                                   gint                  class_name_id);
+void          tracker_decorator_delete_id         (TrackerDecorator     *decorator,
+                                                   gint                  id);
+
+void          tracker_decorator_next              (TrackerDecorator     *decorator,
+                                                   GCancellable         *cancellable,
+                                                   GAsyncReadyCallback   callback,
+                                                   gpointer              user_data);
+
+TrackerDecoratorInfo *
+              tracker_decorator_next_finish       (TrackerDecorator     *decorator,
+                                                   GAsyncResult         *result,
+                                                   GError              **error);
+
+void          tracker_decorator_set_priority_rdf_types (TrackerDecorator    *decorator,
+                                                        const gchar * const *rdf_types);
+
+GType         tracker_decorator_info_get_type     (void) G_GNUC_CONST;
+
+TrackerDecoratorInfo *
+              tracker_decorator_info_ref          (TrackerDecoratorInfo *info);
+void          tracker_decorator_info_unref        (TrackerDecoratorInfo *info);
+const gchar * tracker_decorator_info_get_urn      (TrackerDecoratorInfo *info);
+const gchar * tracker_decorator_info_get_url      (TrackerDecoratorInfo *info);
+const gchar * tracker_decorator_info_get_mimetype (TrackerDecoratorInfo *info);
+GTask       * tracker_decorator_info_get_task     (TrackerDecoratorInfo *info);
+void          tracker_decorator_info_complete     (TrackerDecoratorInfo *info,
+                                                   gchar                *sparql);
+void          tracker_decorator_info_complete_error (TrackerDecoratorInfo *info,
+                                                     GError               *error);
+
+G_END_DECLS
+
+#endif /* __LIBTRACKER_MINER_DECORATOR_H__ */
diff --git a/src/libtracker-miner/tracker-file-data-provider.c 
b/src/libtracker-miner/tracker-file-data-provider.c
new file mode 100644
index 000000000..f2a6a4dd0
--- /dev/null
+++ b/src/libtracker-miner/tracker-file-data-provider.c
@@ -0,0 +1,240 @@
+/*
+ * Copyright (C) 2014, Softathome <contact softathome com>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ * Boston, MA  02110-1301, USA.
+ *
+ * Author: Martyn Russell <martyn lanedo com>
+ */
+
+#include "config-miners.h"
+
+#include "tracker-file-data-provider.h"
+
+static void tracker_file_data_provider_file_iface_init (TrackerDataProviderIface *iface);
+
+struct _TrackerFileDataProvider {
+       GObject parent_instance;
+};
+
+typedef struct {
+       GFile *url;
+       gchar *attributes;
+       TrackerDirectoryFlags flags;
+} BeginData;
+
+/**
+ * SECTION:tracker-file-data-provider
+ * @short_description: File based data provider for file:// descendant URIs
+ * @include: libtracker-miner/miner.h
+ *
+ * #TrackerFileDataProvider is a local file implementation of the
+ * #TrackerDataProvider interface, charged with handling all file:// type URIs.
+ *
+ * Underneath it all, this implementation makes use of GIO-based
+ * #GFileEnumerator<!-- -->s.
+ *
+ * Since: 1.2
+ **/
+
+G_DEFINE_TYPE_WITH_CODE (TrackerFileDataProvider, tracker_file_data_provider, G_TYPE_OBJECT,
+                         G_IMPLEMENT_INTERFACE (TRACKER_TYPE_DATA_PROVIDER,
+                                                tracker_file_data_provider_file_iface_init))
+
+static void
+tracker_file_data_provider_finalize (GObject *object)
+{
+       G_OBJECT_CLASS (tracker_file_data_provider_parent_class)->finalize (object);
+}
+
+static void
+tracker_file_data_provider_class_init (TrackerFileDataProviderClass *klass)
+{
+       GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
+
+       gobject_class->finalize = tracker_file_data_provider_finalize;
+}
+
+static void
+tracker_file_data_provider_init (TrackerFileDataProvider *fe)
+{
+}
+
+static BeginData *
+begin_data_new (GFile                 *url,
+                const gchar           *attributes,
+                TrackerDirectoryFlags  flags)
+{
+       BeginData *data;
+
+       data = g_slice_new0 (BeginData);
+       data->url = g_object_ref (url);
+       /* FIXME: inefficient */
+       data->attributes = g_strdup (attributes);
+       data->flags = flags;
+
+       return data;
+}
+
+static void
+begin_data_free (BeginData *data)
+{
+       if (!data) {
+               return;
+       }
+
+       g_object_unref (data->url);
+       g_free (data->attributes);
+       g_slice_free (BeginData, data);
+}
+
+static GFileEnumerator *
+file_data_provider_begin (TrackerDataProvider    *data_provider,
+                          GFile                  *url,
+                          const gchar            *attributes,
+                          TrackerDirectoryFlags   flags,
+                          GCancellable           *cancellable,
+                          GError                **error)
+{
+       GFileQueryInfoFlags file_flags;
+       GFileEnumerator *fe;
+       GError *local_error = NULL;
+
+       if (g_cancellable_set_error_if_cancelled (cancellable, error)) {
+               return NULL;
+       }
+
+       /* We ignore the TRACKER_DIRECTORY_FLAG_NO_STAT here, it makes
+        * no sense to be at this point with that flag. So we warn
+        * about it...
+        */
+       if ((flags & TRACKER_DIRECTORY_FLAG_NO_STAT) != 0) {
+               g_warning ("Did not expect to have TRACKER_DIRECTORY_FLAG_NO_STAT "
+                          "flag in %s(), continuing anyway...",
+                          __FUNCTION__);
+       }
+
+       file_flags = G_FILE_QUERY_INFO_NOFOLLOW_SYMLINKS;
+
+       fe = g_file_enumerate_children (url,
+                                       attributes,
+                                       file_flags,
+                                       cancellable,
+                                       &local_error);
+
+       if (local_error) {
+               gchar *uri;
+
+               uri = g_file_get_uri (url);
+
+               g_warning ("Could not open directory '%s': %s",
+                          uri, local_error->message);
+
+               g_propagate_error (error, local_error);
+               g_free (uri);
+
+               return NULL;
+       }
+
+       return fe;
+}
+
+static void
+file_data_provider_begin_thread (GTask        *task,
+                                 gpointer      source_object,
+                                 gpointer      task_data,
+                                 GCancellable *cancellable)
+{
+       TrackerDataProvider *data_provider = source_object;
+       GFileEnumerator *enumerator = NULL;
+       BeginData *data = task_data;
+       GError *error = NULL;
+
+       if (g_cancellable_set_error_if_cancelled (cancellable, &error)) {
+               enumerator = NULL;
+       } else {
+               enumerator = file_data_provider_begin (data_provider,
+                                                      data->url,
+                                                      data->attributes,
+                                                      data->flags,
+                                                      cancellable,
+                                                      &error);
+       }
+
+       if (error) {
+               g_task_return_error (task, error);
+       } else {
+               g_task_return_pointer (task, enumerator, (GDestroyNotify) g_object_unref);
+       }
+}
+
+static void
+file_data_provider_begin_async (TrackerDataProvider   *data_provider,
+                                GFile                 *dir,
+                                const gchar           *attributes,
+                                TrackerDirectoryFlags  flags,
+                                int                    io_priority,
+                                GCancellable          *cancellable,
+                                GAsyncReadyCallback    callback,
+                                gpointer               user_data)
+{
+       GTask *task;
+
+       task = g_task_new (data_provider, cancellable, callback, user_data);
+       g_task_set_task_data (task, begin_data_new (dir, attributes, flags), (GDestroyNotify) 
begin_data_free);
+       g_task_set_priority (task, io_priority);
+       g_task_run_in_thread (task, file_data_provider_begin_thread);
+       g_object_unref (task);
+}
+
+static GFileEnumerator *
+file_data_provider_begin_finish (TrackerDataProvider  *data_provider,
+                                 GAsyncResult         *result,
+                                 GError              **error)
+{
+       g_return_val_if_fail (g_task_is_valid (result, data_provider), NULL);
+
+       return g_task_propagate_pointer (G_TASK (result), error);
+}
+
+static void
+tracker_file_data_provider_file_iface_init (TrackerDataProviderIface *iface)
+{
+       iface->begin = file_data_provider_begin;
+       iface->begin_async = file_data_provider_begin_async;
+       iface->begin_finish = file_data_provider_begin_finish;
+}
+
+/**
+ * tracker_file_data_provider_new:
+ *
+ * Creates a new TrackerDataProvider which can be used to create new
+ * #TrackerMinerFS classes. See #TrackerMinerFS for an example of how
+ * to use your #TrackerDataProvider.
+ *
+ * Returns: (transfer full): a #TrackerDataProvider which must be
+ * unreferenced with g_object_unref().
+ *
+ * Since: 1.2:
+ **/
+TrackerDataProvider *
+tracker_file_data_provider_new (void)
+{
+       TrackerFileDataProvider *tfdp;
+
+       tfdp = g_object_new (TRACKER_TYPE_FILE_DATA_PROVIDER, NULL);
+
+       return TRACKER_DATA_PROVIDER (tfdp);
+}
diff --git a/src/libtracker-miner/tracker-file-data-provider.h 
b/src/libtracker-miner/tracker-file-data-provider.h
new file mode 100644
index 000000000..945eaa0ab
--- /dev/null
+++ b/src/libtracker-miner/tracker-file-data-provider.h
@@ -0,0 +1,62 @@
+/*
+ * Copyright (C) 2014, Softathome <contact softathome com>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ * Boston, MA  02110-1301, USA.
+ *
+ * Author: Martyn Russell <martyn lanedo com>
+ */
+
+#ifndef __LIBTRACKER_MINER_FILE_DATA_PROVIDER_H__
+#define __LIBTRACKER_MINER_FILE_DATA_PROVIDER_H__
+
+#include <gio/gio.h>
+
+#include "tracker-data-provider.h"
+
+G_BEGIN_DECLS
+
+#define TRACKER_TYPE_FILE_DATA_PROVIDER         (tracker_file_data_provider_get_type ())
+#define TRACKER_FILE_DATA_PROVIDER(o)           (G_TYPE_CHECK_INSTANCE_CAST ((o), 
TRACKER_TYPE_FILE_DATA_PROVIDER, TrackerFileDataProvider))
+#define TRACKER_FILE_DATA_PROVIDER_CLASS(k)     (G_TYPE_CHECK_CLASS_CAST((k), 
TRACKER_TYPE_FILE_DATA_PROVIDER, TrackerFileDataProviderClass))
+#define TRACKER_IS_FILE_DATA_PROVIDER(o)        (G_TYPE_CHECK_INSTANCE_TYPE ((o), 
TRACKER_TYPE_FILE_DATA_PROVIDER))
+#define TRACKER_IS_FILE_DATA_PROVIDER_CLASS(k)  (G_TYPE_CHECK_CLASS_TYPE ((k), 
TRACKER_TYPE_FILE_DATA_PROVIDER))
+#define TRACKER_FILE_DATA_PROVIDER_GET_CLASS(o) (G_TYPE_INSTANCE_GET_CLASS ((o), 
TRACKER_TYPE_FILE_DATA_PROVIDER, TrackerFileDataProviderClass))
+
+/**
+ * TrackerFileDataProvider:
+ *
+ * An implementation of the #TrackerDataProvider interface.
+ **/
+typedef struct _TrackerFileDataProvider        TrackerFileDataProvider;
+typedef struct _TrackerFileDataProviderClass   TrackerFileDataProviderClass;
+typedef struct _TrackerFileDataProviderPrivate TrackerFileDataProviderPrivate;
+
+/**
+ * TrackerFileDataProviderClass:
+ * @parent_class: Parent object class.
+ *
+ * Prototype for the class implementation.
+ **/
+struct _TrackerFileDataProviderClass {
+       GObjectClass parent_class;
+};
+
+GType                 tracker_file_data_provider_get_type (void) G_GNUC_CONST;
+TrackerDataProvider * tracker_file_data_provider_new      (void);
+
+G_END_DECLS
+
+#endif /* __LIBTRACKERMINER_FILE_DATA_PROVIDER_H__ */
diff --git a/src/libtracker-miner/tracker-file-notifier.c b/src/libtracker-miner/tracker-file-notifier.c
new file mode 100644
index 000000000..ca75aff95
--- /dev/null
+++ b/src/libtracker-miner/tracker-file-notifier.c
@@ -0,0 +1,2116 @@
+/*
+ * Copyright (C) 2011, Nokia <ivan frade nokia com>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ * Boston, MA  02110-1301, USA.
+ *
+ * Author: Carlos Garnacho  <carlos lanedo com>
+ */
+
+#include "config-miners.h"
+
+#include <libtracker-miners-common/tracker-common.h>
+#include <libtracker-sparql/tracker-sparql.h>
+
+#include "tracker-file-notifier.h"
+#include "tracker-file-system.h"
+#include "tracker-crawler.h"
+#include "tracker-monitor.h"
+
+static GQuark quark_property_iri = 0;
+static GQuark quark_property_store_mtime = 0;
+static GQuark quark_property_filesystem_mtime = 0;
+static gboolean force_check_updated = FALSE;
+
+#define MAX_DEPTH 1
+
+enum {
+       PROP_0,
+       PROP_INDEXING_TREE,
+       PROP_DATA_PROVIDER,
+       PROP_CONNECTION
+};
+
+enum {
+       FILE_CREATED,
+       FILE_UPDATED,
+       FILE_DELETED,
+       FILE_MOVED,
+       DIRECTORY_STARTED,
+       DIRECTORY_FINISHED,
+       FINISHED,
+       LAST_SIGNAL
+};
+
+static guint signals[LAST_SIGNAL] = { 0 };
+
+typedef struct {
+       GFile *root;
+       GFile *current_dir;
+       GQueue *pending_dirs;
+       GPtrArray *query_files;
+       guint flags;
+       guint directories_found;
+       guint directories_ignored;
+       guint files_found;
+       guint files_ignored;
+       guint current_dir_content_filtered : 1;
+} RootData;
+
+typedef struct {
+       TrackerIndexingTree *indexing_tree;
+       TrackerFileSystem *file_system;
+
+       TrackerSparqlConnection *connection;
+       GCancellable *cancellable;
+
+       TrackerCrawler *crawler;
+       TrackerMonitor *monitor;
+       TrackerDataProvider *data_provider;
+
+       GTimer *timer;
+
+       /* List of pending directory
+        * trees to get data from
+        */
+       GList *pending_index_roots;
+       RootData *current_index_root;
+
+       guint stopped : 1;
+} TrackerFileNotifierPrivate;
+
+typedef struct {
+       TrackerFileNotifier *notifier;
+       GNode *cur_parent_node;
+
+       /* Canonical copy from priv->file_system */
+       GFile *cur_parent;
+} DirectoryCrawledData;
+
+typedef struct {
+       TrackerFileNotifier *notifier;
+       gint max_depth;
+} SparqlStartData;
+
+static gboolean crawl_directories_start (TrackerFileNotifier *notifier);
+static void     sparql_files_query_start (TrackerFileNotifier  *notifier,
+                                          GFile               **files,
+                                          guint                 n_files,
+                                          gint                  max_depth);
+
+G_DEFINE_TYPE (TrackerFileNotifier, tracker_file_notifier, G_TYPE_OBJECT)
+
+static void
+tracker_file_notifier_set_property (GObject      *object,
+                                    guint         prop_id,
+                                    const GValue *value,
+                                    GParamSpec   *pspec)
+{
+       TrackerFileNotifierPrivate *priv;
+
+       priv = TRACKER_FILE_NOTIFIER (object)->priv;
+
+       switch (prop_id) {
+       case PROP_INDEXING_TREE:
+               priv->indexing_tree = g_value_dup_object (value);
+               tracker_monitor_set_indexing_tree (priv->monitor,
+                                                  priv->indexing_tree);
+               break;
+       case PROP_DATA_PROVIDER:
+               priv->data_provider = g_value_dup_object (value);
+               break;
+       case PROP_CONNECTION:
+               priv->connection = g_value_dup_object (value);
+               break;
+       default:
+               G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+               break;
+       }
+}
+
+static void
+tracker_file_notifier_get_property (GObject    *object,
+                                    guint       prop_id,
+                                    GValue     *value,
+                                    GParamSpec *pspec)
+{
+       TrackerFileNotifierPrivate *priv;
+
+       priv = TRACKER_FILE_NOTIFIER (object)->priv;
+
+       switch (prop_id) {
+       case PROP_INDEXING_TREE:
+               g_value_set_object (value, priv->indexing_tree);
+               break;
+       case PROP_DATA_PROVIDER:
+               g_value_set_object (value, priv->data_provider);
+               break;
+       case PROP_CONNECTION:
+               g_value_set_object (value, priv->connection);
+               break;
+       default:
+               G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+               break;
+       }
+}
+
+static RootData *
+root_data_new (TrackerFileNotifier *notifier,
+               GFile               *file,
+               guint                flags)
+{
+       RootData *data;
+
+       data = g_new0 (RootData, 1);
+       data->root = g_object_ref (file);
+       data->pending_dirs = g_queue_new ();
+       data->query_files = g_ptr_array_new_with_free_func (g_object_unref);
+       data->flags = flags;
+
+       g_queue_push_tail (data->pending_dirs, g_object_ref (file));
+
+       return data;
+}
+
+static void
+root_data_free (RootData *data)
+{
+       g_queue_free_full (data->pending_dirs, (GDestroyNotify) g_object_unref);
+       g_ptr_array_unref (data->query_files);
+       if (data->current_dir) {
+               g_object_unref (data->current_dir);
+       }
+       g_object_unref (data->root);
+       g_free (data);
+}
+
+/* Crawler signal handlers */
+static gboolean
+crawler_check_file_cb (TrackerCrawler *crawler,
+                       GFile          *file,
+                       gpointer        user_data)
+{
+       TrackerFileNotifierPrivate *priv;
+
+       priv = TRACKER_FILE_NOTIFIER (user_data)->priv;
+
+       return tracker_indexing_tree_file_is_indexable (priv->indexing_tree,
+                                                       file,
+                                                       G_FILE_TYPE_REGULAR);
+}
+
+static gboolean
+crawler_check_directory_cb (TrackerCrawler *crawler,
+                            GFile          *directory,
+                            gpointer        user_data)
+{
+       TrackerFileNotifierPrivate *priv;
+       GFile *root, *canonical;
+
+       priv = TRACKER_FILE_NOTIFIER (user_data)->priv;
+       g_assert (priv->current_index_root != NULL);
+
+       canonical = tracker_file_system_peek_file (priv->file_system, directory);
+       root = tracker_indexing_tree_get_root (priv->indexing_tree, directory, NULL);
+
+       /* If it's a config root itself, other than the one
+        * currently processed, bypass it, it will be processed
+        * when the time arrives.
+        */
+       if (canonical && root == canonical &&
+           root != priv->current_index_root->root) {
+               return FALSE;
+       }
+
+       return tracker_indexing_tree_file_is_indexable (priv->indexing_tree,
+                                                       directory,
+                                                       G_FILE_TYPE_DIRECTORY);
+}
+
+static gboolean
+crawler_check_directory_contents_cb (TrackerCrawler *crawler,
+                                     GFile          *parent,
+                                     GList          *children,
+                                     gpointer        user_data)
+{
+       TrackerFileNotifierPrivate *priv;
+       gboolean process;
+
+       priv = TRACKER_FILE_NOTIFIER (user_data)->priv;
+       process = tracker_indexing_tree_parent_is_indexable (priv->indexing_tree,
+                                                            parent, children);
+       if (process) {
+               TrackerDirectoryFlags parent_flags;
+               GFile *canonical;
+               gboolean add_monitor;
+
+               canonical = tracker_file_system_get_file (priv->file_system,
+                                                         parent,
+                                                         G_FILE_TYPE_DIRECTORY,
+                                                         NULL);
+               tracker_indexing_tree_get_root (priv->indexing_tree,
+                                               canonical, &parent_flags);
+
+               add_monitor = (parent_flags & TRACKER_DIRECTORY_FLAG_MONITOR) != 0;
+
+               if (add_monitor) {
+                       tracker_monitor_add (priv->monitor, canonical);
+               } else {
+                       tracker_monitor_remove (priv->monitor, canonical);
+               }
+       } else {
+               priv->current_index_root->current_dir_content_filtered = TRUE;
+       }
+
+       return process;
+}
+
+static gboolean
+file_notifier_traverse_tree_foreach (GFile    *file,
+                                     gpointer  user_data)
+{
+       TrackerFileNotifier *notifier;
+       TrackerFileNotifierPrivate *priv;
+       guint64 *store_mtime, *disk_mtime;
+       GFile *current_root;
+
+       notifier = user_data;
+       priv = notifier->priv;
+       current_root = priv->current_index_root->current_dir;
+
+       /* If we're crawling over a subdirectory of a root index, it's been
+        * already notified in the crawling op that made it processed, so avoid
+        * it here again.
+        */
+       if (current_root == file &&
+           current_root != priv->current_index_root->root) {
+               tracker_file_system_unset_property (priv->file_system, file,
+                                                   quark_property_filesystem_mtime);
+               tracker_file_system_unset_property (priv->file_system, file,
+                                                   quark_property_store_mtime);
+               return FALSE;
+       }
+
+       store_mtime = tracker_file_system_steal_property (priv->file_system, file,
+                                                         quark_property_store_mtime);
+       disk_mtime = tracker_file_system_steal_property (priv->file_system, file,
+                                                        quark_property_filesystem_mtime);
+
+       if (store_mtime && !disk_mtime) {
+               /* In store but not in disk, delete */
+               g_signal_emit (notifier, signals[FILE_DELETED], 0, file);
+
+               g_free (store_mtime);
+               g_free (disk_mtime);
+               return TRUE;
+       } else if (disk_mtime && !store_mtime) {
+               /* In disk but not in store, create */
+               g_signal_emit (notifier, signals[FILE_CREATED], 0, file);
+       } else if (store_mtime && disk_mtime && *disk_mtime != *store_mtime) {
+               /* Mtime changed, update */
+               g_signal_emit (notifier, signals[FILE_UPDATED], 0, file, FALSE);
+       } else if (!store_mtime && !disk_mtime) {
+               /* what are we doing with such file? should happen rarely,
+                * only with files that we've queried, but we decided not
+                * to crawl (i.e. embedded root directories, that would
+                * be processed when that root is being crawled).
+                */
+               if (file != priv->current_index_root->root &&
+                   !tracker_indexing_tree_file_is_root (priv->indexing_tree, file)) {
+                       gchar *uri;
+
+                       uri = g_file_get_uri (file);
+                       g_debug ("File '%s' has no disk nor store mtime",
+                                uri);
+                       g_free (uri);
+               }
+       }
+
+       g_free (store_mtime);
+       g_free (disk_mtime);
+
+       return FALSE;
+}
+
+static gboolean
+notifier_check_next_root (TrackerFileNotifier *notifier)
+{
+       TrackerFileNotifierPrivate *priv;
+
+       priv = notifier->priv;
+       g_assert (priv->current_index_root == NULL);
+
+       if (priv->pending_index_roots) {
+               return crawl_directories_start (notifier);
+       } else {
+               g_signal_emit (notifier, signals[FINISHED], 0);
+               return FALSE;
+       }
+}
+
+static void
+file_notifier_traverse_tree (TrackerFileNotifier *notifier, gint max_depth)
+{
+       TrackerFileNotifierPrivate *priv;
+       GFile *config_root, *directory;
+       TrackerDirectoryFlags flags;
+
+       priv = notifier->priv;
+       g_assert (priv->current_index_root != NULL);
+
+       directory = priv->current_index_root->current_dir;
+       config_root = tracker_indexing_tree_get_root (priv->indexing_tree,
+                                                     directory, &flags);
+
+       /* The max_depth parameter is usually '1', which would cause only the
+        * directory itself to be processed. We want the directory and its contents
+        * to be processed so we need to go to (max_depth + 1) here.
+        */
+
+       if (config_root != directory ||
+           flags & TRACKER_DIRECTORY_FLAG_CHECK_MTIME) {
+               tracker_file_system_traverse (priv->file_system,
+                                             directory,
+                                             G_LEVEL_ORDER,
+                                             file_notifier_traverse_tree_foreach,
+                                             max_depth + 1,
+                                             notifier);
+       }
+}
+
+static gboolean
+file_notifier_is_directory_modified (TrackerFileNotifier *notifier,
+                                     GFile               *file)
+{
+       TrackerFileNotifierPrivate *priv;
+       guint64 *store_mtime, *disk_mtime;
+
+       if (G_UNLIKELY (force_check_updated))
+               return TRUE;
+
+       priv = notifier->priv;
+       store_mtime = tracker_file_system_get_property (priv->file_system, file,
+                                                       quark_property_store_mtime);
+       disk_mtime = tracker_file_system_get_property (priv->file_system, file,
+                                                      quark_property_filesystem_mtime);
+
+       return (store_mtime && disk_mtime && *disk_mtime != *store_mtime);
+}
+
+static gboolean
+file_notifier_add_node_foreach (GNode    *node,
+                                gpointer  user_data)
+{
+       DirectoryCrawledData *data = user_data;
+       TrackerFileNotifierPrivate *priv;
+       GFileInfo *file_info;
+       GFile *canonical, *file;
+
+       priv = data->notifier->priv;
+       file = node->data;
+
+       if (node->parent &&
+           node->parent != data->cur_parent_node) {
+               data->cur_parent_node = node->parent;
+               data->cur_parent = tracker_file_system_peek_file (priv->file_system,
+                                                                 node->parent->data);
+       } else {
+               data->cur_parent_node = NULL;
+               data->cur_parent = NULL;
+       }
+
+       file_info = tracker_crawler_get_file_info (priv->crawler, file);
+
+       if (file_info) {
+               GFileType file_type;
+               guint64 time, *time_ptr;
+               gint depth;
+
+               file_type = g_file_info_get_file_type (file_info);
+               depth = g_node_depth (node);
+
+               /* Intern file in filesystem */
+               canonical = tracker_file_system_get_file (priv->file_system,
+                                                         file, file_type,
+                                                         data->cur_parent);
+
+               time = g_file_info_get_attribute_uint64 (file_info,
+                                                        G_FILE_ATTRIBUTE_TIME_MODIFIED);
+
+               time_ptr = g_new (guint64, 1);
+               *time_ptr = time;
+
+               tracker_file_system_set_property (priv->file_system, canonical,
+                                                 quark_property_filesystem_mtime,
+                                                 time_ptr);
+               g_object_unref (file_info);
+
+               if (file_type == G_FILE_TYPE_DIRECTORY && depth == MAX_DEPTH + 1) {
+                       /* If the max crawling depth is reached,
+                        * queue dirs for later processing
+                        */
+                       g_assert (node->children == NULL);
+                       g_queue_push_tail (priv->current_index_root->pending_dirs,
+                                          g_object_ref (canonical));
+               }
+
+               if (file == priv->current_index_root->root ||
+                   (depth != 0 &&
+                    !tracker_indexing_tree_file_is_root (priv->indexing_tree, file))) {
+                       g_ptr_array_add (priv->current_index_root->query_files,
+                                        g_object_ref (canonical));
+               }
+       }
+
+       return FALSE;
+}
+
+static void
+crawler_directory_crawled_cb (TrackerCrawler *crawler,
+                              GFile          *directory,
+                              GNode          *tree,
+                              guint           directories_found,
+                              guint           directories_ignored,
+                              guint           files_found,
+                              guint           files_ignored,
+                              gpointer        user_data)
+{
+       TrackerFileNotifier *notifier;
+       TrackerFileNotifierPrivate *priv;
+       DirectoryCrawledData data = { 0 };
+
+       notifier = data.notifier = user_data;
+       priv = notifier->priv;
+
+       g_node_traverse (tree,
+                        G_PRE_ORDER,
+                        G_TRAVERSE_ALL,
+                        -1,
+                        file_notifier_add_node_foreach,
+                        &data);
+
+       priv->current_index_root->directories_found += directories_found;
+       priv->current_index_root->directories_ignored += directories_ignored;
+       priv->current_index_root->files_found += files_found;
+       priv->current_index_root->files_ignored += files_ignored;
+}
+
+static GFile *
+_insert_store_info (TrackerFileNotifier *notifier,
+                    GFile               *file,
+                    GFileType            file_type,
+                    const gchar         *iri,
+                    guint64              _time)
+{
+       TrackerFileNotifierPrivate *priv;
+       GFile *canonical;
+
+       priv = notifier->priv;
+       canonical = tracker_file_system_get_file (priv->file_system,
+                                                 file, file_type,
+                                                 NULL);
+       tracker_file_system_set_property (priv->file_system, canonical,
+                                         quark_property_iri,
+                                         g_strdup (iri));
+       tracker_file_system_set_property (priv->file_system, canonical,
+                                         quark_property_store_mtime,
+                                         g_memdup (&_time, sizeof (guint64)));
+       return canonical;
+}
+
+static void
+sparql_files_query_populate (TrackerFileNotifier *notifier,
+                            TrackerSparqlCursor *cursor,
+                            gboolean             check_root)
+{
+       TrackerFileNotifierPrivate *priv;
+
+       priv = notifier->priv;
+
+       while (tracker_sparql_cursor_next (cursor, NULL, NULL)) {
+               GFile *file, *canonical, *root;
+               const gchar *time_str, *iri;
+               GError *error = NULL;
+               guint64 _time;
+
+               file = g_file_new_for_uri (tracker_sparql_cursor_get_string (cursor, 0, NULL));
+
+               if (check_root) {
+                       /* If it's a config root itself, other than the one
+                        * currently processed, bypass it, it will be processed
+                        * when the time arrives.
+                        */
+                       canonical = tracker_file_system_peek_file (priv->file_system, file);
+                       root = tracker_indexing_tree_get_root (priv->indexing_tree, file, NULL);
+
+                       if (canonical && root == file && priv->current_index_root &&
+                           root != priv->current_index_root->root) {
+                               g_object_unref (file);
+                               continue;
+                       }
+               }
+
+               iri = tracker_sparql_cursor_get_string (cursor, 1, NULL);
+               time_str = tracker_sparql_cursor_get_string (cursor, 2, NULL);
+               _time = tracker_string_to_date (time_str, NULL, &error);
+
+               if (error) {
+                       /* This should never happen. Assume that file was modified. */
+                       g_critical ("Getting store mtime: %s", error->message);
+                       g_clear_error (&error);
+                       _time = 0;
+               }
+
+               _insert_store_info (notifier, file,
+                                   G_FILE_TYPE_UNKNOWN,
+                                   iri, _time);
+               g_object_unref (file);
+       }
+}
+
+static void
+sparql_contents_check_deleted (TrackerFileNotifier *notifier,
+                               TrackerSparqlCursor *cursor)
+{
+       TrackerFileNotifierPrivate *priv;
+       GFile *file, *canonical;
+       const gchar *iri;
+
+       priv = notifier->priv;
+
+       while (tracker_sparql_cursor_next (cursor, NULL, NULL)) {
+               const gchar *uri;
+               gboolean is_folder;
+               GFileType file_type;
+
+               /* Sometimes URI can be NULL when nie:url and
+                * nfo:belongsToContainer does not have a strictly 1:1
+                * relationship, e.g. data containers where there is
+                * only one nie:url but many nfo:belongsToContainer
+                * cases.
+                */
+               uri = tracker_sparql_cursor_get_string (cursor, 0, NULL);
+               if (!uri) {
+                       continue;
+               }
+
+               file = g_file_new_for_uri (uri);
+               iri = tracker_sparql_cursor_get_string (cursor, 1, NULL);
+               is_folder = tracker_sparql_cursor_get_boolean (cursor, 3);
+               file_type = is_folder ? G_FILE_TYPE_DIRECTORY : G_FILE_TYPE_UNKNOWN;
+               canonical = tracker_file_system_peek_file (priv->file_system, file);
+
+               if (!canonical) {
+                       /* The file exists on the store, but not on the
+                        * crawled content, insert temporarily to handle
+                        * the delete event.
+                        */
+                       canonical = _insert_store_info (notifier, file,
+                                                       file_type,
+                                                       iri, 0);
+                       g_signal_emit (notifier, signals[FILE_DELETED], 0, canonical);
+               } else if (priv->current_index_root->current_dir_content_filtered ||
+                          !tracker_indexing_tree_file_is_indexable (priv->indexing_tree,
+                                                                    canonical,
+                                                                    file_type)) {
+                       /* File is there, but is not indexable anymore, remove too */
+                       g_signal_emit (notifier, signals[FILE_DELETED], 0, canonical);
+               }
+
+               g_object_unref (file);
+       }
+}
+
+static gboolean
+crawl_directory_in_current_root (TrackerFileNotifier *notifier)
+{
+       TrackerFileNotifierPrivate *priv = notifier->priv;
+       gint depth;
+       GFile *directory;
+
+       if (!priv->current_index_root)
+               return FALSE;
+
+       directory = g_queue_pop_head (priv->current_index_root->pending_dirs);
+
+       if (!directory)
+               return FALSE;
+
+       priv->current_index_root->current_dir = directory;
+
+       if (priv->cancellable)
+               g_object_unref (priv->cancellable);
+       priv->cancellable = g_cancellable_new ();
+
+       if ((priv->current_index_root->flags & TRACKER_DIRECTORY_FLAG_RECURSE) == 0) {
+               /* Don't recurse */
+               depth = 1;
+       } else {
+               /* Recurse */
+               depth = MAX_DEPTH;
+       }
+
+       if (!tracker_crawler_start (priv->crawler,
+                                   directory,
+                                   priv->current_index_root->flags,
+                                   depth)) {
+               sparql_files_query_start (notifier, &directory, 1, depth);
+       }
+
+       return TRUE;
+}
+
+static void
+finish_current_directory (TrackerFileNotifier *notifier,
+                          gboolean             interrupted)
+{
+       TrackerFileNotifierPrivate *priv;
+       GFile *directory;
+
+       priv = notifier->priv;
+       directory = priv->current_index_root->current_dir;
+       priv->current_index_root->current_dir = NULL;
+       priv->current_index_root->current_dir_content_filtered = FALSE;
+
+       /* If crawling was interrupted, we take all collected info as invalid.
+        * Otherwise we dispose regular files here, only directories are
+        * cached once crawling has completed.
+        */
+       tracker_file_system_forget_files (priv->file_system,
+                                         directory,
+                                         interrupted ?
+                                         G_FILE_TYPE_UNKNOWN :
+                                         G_FILE_TYPE_REGULAR);
+
+       if (interrupted || !crawl_directory_in_current_root (notifier)) {
+               /* No more directories left to be crawled in the current
+                * root, jump to the next one.
+                */
+               g_signal_emit (notifier, signals[DIRECTORY_FINISHED], 0,
+                              priv->current_index_root->root,
+                              priv->current_index_root->directories_found,
+                              priv->current_index_root->directories_ignored,
+                              priv->current_index_root->files_found,
+                              priv->current_index_root->files_ignored);
+
+               g_info ("  Notified files after %2.2f seconds",
+                       g_timer_elapsed (priv->timer, NULL));
+               g_info ("  Found %d directories, ignored %d directories",
+                       priv->current_index_root->directories_found,
+                       priv->current_index_root->directories_ignored);
+               g_info ("  Found %d files, ignored %d files",
+                       priv->current_index_root->files_found,
+                       priv->current_index_root->files_ignored);
+
+               if (!interrupted) {
+                       root_data_free (priv->current_index_root);
+                       priv->current_index_root = NULL;
+
+                       notifier_check_next_root (notifier);
+               }
+       }
+
+       g_object_unref (directory);
+}
+
+static gboolean
+root_data_remove_directory (RootData *data,
+                           GFile    *directory)
+{
+       GList *l = data->pending_dirs->head, *next;
+       GFile *file;
+
+       while (l) {
+               file = l->data;
+               next = l->next;
+
+               if (g_file_equal (file, directory) ||
+                   g_file_has_prefix (file, directory)) {
+                       g_queue_remove (data->pending_dirs, file);
+                       g_object_unref (file);
+               }
+
+               l = next;
+       }
+
+       return (g_file_equal (data->current_dir, directory) ||
+               g_file_has_prefix (data->current_dir, directory));
+}
+
+static void
+file_notifier_current_root_check_remove_directory (TrackerFileNotifier *notifier,
+                                                  GFile               *file)
+{
+       TrackerFileNotifierPrivate *priv;
+
+       priv = notifier->priv;
+
+       if (priv->current_index_root &&
+           root_data_remove_directory (priv->current_index_root, file)) {
+               g_cancellable_cancel (priv->cancellable);
+               tracker_crawler_stop (priv->crawler);
+
+               if (!crawl_directory_in_current_root (notifier)) {
+                       if (priv->current_index_root) {
+                               root_data_free (priv->current_index_root);
+                               priv->current_index_root = NULL;
+                       }
+
+                       notifier_check_next_root (notifier);
+               }
+       }
+}
+
+/* Query for directory contents, used to look for deleted contents in those */
+static void
+sparql_contents_query_cb (GObject      *object,
+                          GAsyncResult *result,
+                          gpointer      user_data)
+{
+       TrackerFileNotifier *notifier = TRACKER_FILE_NOTIFIER (user_data);
+       TrackerSparqlCursor *cursor;
+       GError *error = NULL;
+
+       cursor = tracker_sparql_connection_query_finish (TRACKER_SPARQL_CONNECTION (object),
+                                                        result, &error);
+       if (error) {
+               if (!g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED)) {
+                       g_warning ("Could not query directory contents: %s\n", error->message);
+                       finish_current_directory (notifier, TRUE);
+               }
+               goto out;
+       }
+
+       if (cursor) {
+               sparql_contents_check_deleted (notifier, cursor);
+               g_object_unref (cursor);
+       }
+
+       finish_current_directory (notifier, FALSE);
+
+out:
+       if (error) {
+               g_error_free (error);
+       }
+}
+
+static gchar *
+sparql_contents_compose_query (GFile **directories,
+                               guint   n_dirs)
+{
+       GString *str;
+       gchar *uri;
+       gint i;
+       gboolean first = TRUE;
+
+       str = g_string_new ("SELECT nie:url(?u) ?u nfo:fileLastModified(?u) "
+                           "       IF (nie:mimeType(?u) = \"inode/directory\", true, false) {"
+                           " ?u nfo:belongsToContainer ?f . ?f nie:url ?url ."
+                           " FILTER (?url IN (");
+       for (i = 0; i < n_dirs; i++) {
+               if (!first) {
+                       g_string_append_c (str, ',');
+               }
+
+               first = FALSE;
+               uri = g_file_get_uri (directories[i]);
+               g_string_append_printf (str, "\"%s\"", uri);
+               g_free (uri);
+       }
+
+       g_string_append (str, "))}");
+
+       return g_string_free (str, FALSE);
+}
+
+static void
+sparql_contents_query_start (TrackerFileNotifier  *notifier,
+                             GFile               **directories,
+                             guint                 n_dirs)
+{
+       TrackerFileNotifierPrivate *priv;
+       gchar *sparql;
+
+       priv = notifier->priv;
+
+       if (G_UNLIKELY (priv->connection == NULL)) {
+               return;
+       }
+
+       sparql = sparql_contents_compose_query (directories, n_dirs);
+       tracker_sparql_connection_query_async (priv->connection,
+                                              sparql,
+                                              priv->cancellable,
+                                              sparql_contents_query_cb,
+                                              notifier);
+       g_free (sparql);
+}
+
+/* Query for file information, used on all elements found during crawling */
+static void
+sparql_files_query_cb (GObject      *object,
+                      GAsyncResult *result,
+                      gpointer      user_data)
+{
+       SparqlStartData *data = user_data;
+       TrackerFileNotifierPrivate *priv;
+       TrackerFileNotifier *notifier;
+       TrackerSparqlCursor *cursor;
+       GError *error = NULL;
+       GFile *directory;
+       guint flags;
+
+       notifier = data->notifier;
+       priv = notifier->priv;
+
+       cursor = tracker_sparql_connection_query_finish (TRACKER_SPARQL_CONNECTION (object),
+                                                        result, &error);
+       if (error) {
+               if (!g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED)) {
+                       g_warning ("Could not query indexed files: %s\n", error->message);
+                       finish_current_directory (notifier, TRUE);
+               }
+               goto out;
+       }
+
+       if (cursor) {
+               sparql_files_query_populate (notifier, cursor, TRUE);
+               g_object_unref (cursor);
+       }
+
+       file_notifier_traverse_tree (notifier, data->max_depth);
+       directory = priv->current_index_root->current_dir;
+       flags = priv->current_index_root->flags;
+
+       if ((flags & TRACKER_DIRECTORY_FLAG_CHECK_DELETED) != 0 ||
+           priv->current_index_root->current_dir_content_filtered ||
+           file_notifier_is_directory_modified (notifier, directory)) {
+               /* The directory has updated its mtime, this means something
+                * was either added or removed in the mean time. Crawling
+                * will always find all newly added files. But still, we
+                * must check the contents in the store to handle contents
+                * having been deleted in the directory.
+                */
+               sparql_contents_query_start (notifier, &directory, 1);
+       } else {
+               finish_current_directory (notifier, FALSE);
+       }
+
+out:
+       if (error) {
+               g_error_free (error);
+       }
+       g_free (data);
+}
+
+static gchar *
+sparql_files_compose_query (GFile **files,
+                           guint   n_files)
+{
+       GString *str;
+       gchar *uri;
+       gint i = 0;
+
+       str = g_string_new ("SELECT ?url ?u nfo:fileLastModified(?u) {"
+                           "  ?u a rdfs:Resource ; nie:url ?url . "
+                           "FILTER (?url IN (");
+       for (i = 0; i < n_files; i++) {
+               if (i != 0)
+                       g_string_append_c (str, ',');
+
+               uri = g_file_get_uri (files[i]);
+               g_string_append_printf (str, "\"%s\"", uri);
+               g_free (uri);
+       }
+
+       g_string_append (str, "))}");
+
+       return g_string_free (str, FALSE);
+}
+
+static void
+sparql_files_query_start (TrackerFileNotifier  *notifier,
+                          GFile               **files,
+                          guint                 n_files,
+                          gint                  max_depth)
+{
+       TrackerFileNotifierPrivate *priv;
+       gchar *sparql;
+       SparqlStartData *data = g_new (SparqlStartData, 1);
+
+       data->notifier = notifier;
+       data->max_depth = max_depth;
+
+       priv = notifier->priv;
+
+       if (G_UNLIKELY (priv->connection == NULL)) {
+               return;
+       }
+
+       sparql = sparql_files_compose_query (files, n_files);
+       tracker_sparql_connection_query_async (priv->connection,
+                                              sparql,
+                                              priv->cancellable,
+                                              sparql_files_query_cb,
+                                              data);
+       g_free (sparql);
+}
+
+static gboolean
+crawl_directories_start (TrackerFileNotifier *notifier)
+{
+       TrackerFileNotifierPrivate *priv = notifier->priv;
+       TrackerDirectoryFlags flags;
+       GFile *directory;
+
+       if (priv->current_index_root) {
+               return FALSE;
+       }
+
+       if (!priv->pending_index_roots) {
+               return FALSE;
+       }
+
+       if (priv->stopped) {
+               return FALSE;
+       }
+
+       while (priv->pending_index_roots) {
+               priv->current_index_root = priv->pending_index_roots->data;
+               priv->pending_index_roots = g_list_delete_link (priv->pending_index_roots,
+                                                               priv->pending_index_roots);
+               directory = priv->current_index_root->root;
+               flags = priv->current_index_root->flags;
+
+               if ((flags & TRACKER_DIRECTORY_FLAG_IGNORE) == 0 &&
+                   crawl_directory_in_current_root (notifier)) {
+                       gchar *uri;
+
+                       uri = g_file_get_uri (directory);
+                       g_info ("Processing location: '%s'", uri);
+                       g_free (uri);
+
+                       g_timer_reset (priv->timer);
+                       g_signal_emit (notifier, signals[DIRECTORY_STARTED], 0, directory);
+
+                       return TRUE;
+               } else {
+                       /* Emit both signals for consistency */
+                       g_signal_emit (notifier, signals[DIRECTORY_STARTED], 0, directory);
+
+                       if ((flags & TRACKER_DIRECTORY_FLAG_PRESERVE) == 0) {
+                               g_signal_emit (notifier, signals[FILE_DELETED], 0, directory);
+                       }
+
+                       g_signal_emit (notifier, signals[DIRECTORY_FINISHED], 0,
+                                      directory, 0, 0, 0, 0);
+               }
+
+               root_data_free (priv->current_index_root);
+               priv->current_index_root = NULL;
+       }
+
+       g_signal_emit (notifier, signals[FINISHED], 0);
+
+       return FALSE;
+}
+
+static void
+crawler_finished_cb (TrackerCrawler *crawler,
+                     gboolean        was_interrupted,
+                     gpointer        user_data)
+{
+       TrackerFileNotifier *notifier = user_data;
+       TrackerFileNotifierPrivate *priv = notifier->priv;
+       GFile *directory;
+       gint max_depth = -1;
+
+       g_assert (priv->current_index_root != NULL);
+
+       if (was_interrupted) {
+               finish_current_directory (notifier, TRUE);
+               return;
+       }
+
+       max_depth = tracker_crawler_get_max_depth (crawler);
+
+       directory = priv->current_index_root->current_dir;
+
+       if (priv->current_index_root->query_files->len > 0 &&
+           (directory == priv->current_index_root->root ||
+            tracker_file_system_get_property (priv->file_system,
+                                              directory, quark_property_iri))) {
+               sparql_files_query_start (notifier,
+                                  (GFile**) priv->current_index_root->query_files->pdata,
+                                         priv->current_index_root->query_files->len, max_depth);
+               g_ptr_array_set_size (priv->current_index_root->query_files, 0);
+       } else {
+               g_ptr_array_set_size (priv->current_index_root->query_files, 0);
+               file_notifier_traverse_tree (notifier, max_depth);
+               finish_current_directory (notifier, FALSE);
+       }
+}
+
+static gint
+find_directory_root (RootData *data,
+                     GFile    *file)
+{
+       if (data->root == file)
+               return 0;
+       return -1;
+}
+
+static void
+notifier_queue_file (TrackerFileNotifier   *notifier,
+                     GFile                 *file,
+                     TrackerDirectoryFlags  flags)
+{
+       TrackerFileNotifierPrivate *priv = notifier->priv;
+       RootData *data = root_data_new (notifier, file, flags);
+
+       if (priv->current_index_root &&
+           priv->current_index_root->root == file)
+               return;
+
+       if (g_list_find_custom (priv->pending_index_roots, file,
+                               (GCompareFunc) find_directory_root))
+               return;
+
+       if (flags & TRACKER_DIRECTORY_FLAG_PRIORITY) {
+               priv->pending_index_roots = g_list_prepend (priv->pending_index_roots, data);
+       } else {
+               priv->pending_index_roots = g_list_append (priv->pending_index_roots, data);
+       }
+}
+
+/* Monitor signal handlers */
+static void
+monitor_item_created_cb (TrackerMonitor *monitor,
+                         GFile          *file,
+                         gboolean        is_directory,
+                         gpointer        user_data)
+{
+       TrackerFileNotifier *notifier = user_data;
+       TrackerFileNotifierPrivate *priv = notifier->priv;
+       GFileType file_type;
+       GFile *canonical;
+
+       file_type = (is_directory) ? G_FILE_TYPE_DIRECTORY : G_FILE_TYPE_REGULAR;
+
+       if (!tracker_indexing_tree_file_is_indexable (priv->indexing_tree,
+                                                     file, file_type)) {
+               /* File should not be indexed */
+               return ;
+       }
+
+       if (!is_directory) {
+               gboolean indexable;
+               GList *children;
+               GFile *parent;
+
+               parent = g_file_get_parent (file);
+
+               if (parent) {
+                       children = g_list_prepend (NULL, file);
+                       indexable = tracker_indexing_tree_parent_is_indexable (priv->indexing_tree,
+                                                                              parent,
+                                                                              children);
+                       g_list_free (children);
+
+                       if (!indexable) {
+                               /* New file triggered a directory content
+                                * filter, remove parent directory altogether
+                                */
+                               g_signal_emit (notifier, signals[FILE_DELETED], 0, parent);
+                               file_notifier_current_root_check_remove_directory (notifier, parent);
+                               g_object_unref (parent);
+                               return;
+                       }
+
+                       g_object_unref (parent);
+               }
+       } else {
+               TrackerDirectoryFlags flags;
+
+               /* If config for the directory is recursive,
+                * Crawl new entire directory and add monitors
+                */
+               tracker_indexing_tree_get_root (priv->indexing_tree,
+                                               file, &flags);
+
+               if (flags & TRACKER_DIRECTORY_FLAG_RECURSE) {
+                       canonical = tracker_file_system_get_file (priv->file_system,
+                                                                 file,
+                                                                 file_type,
+                                                                 NULL);
+                       notifier_queue_file (notifier, canonical, flags);
+                       crawl_directories_start (notifier);
+                       return;
+               }
+       }
+
+       /* Fetch the interned copy */
+       canonical = tracker_file_system_get_file (priv->file_system,
+                                                 file, file_type, NULL);
+
+       g_signal_emit (notifier, signals[FILE_CREATED], 0, canonical);
+
+       if (!is_directory) {
+               tracker_file_system_forget_files (priv->file_system, canonical,
+                                                 G_FILE_TYPE_REGULAR);
+       }
+}
+
+static void
+monitor_item_updated_cb (TrackerMonitor *monitor,
+                         GFile          *file,
+                         gboolean        is_directory,
+                         gpointer        user_data)
+{
+       TrackerFileNotifier *notifier = user_data;
+       TrackerFileNotifierPrivate *priv = notifier->priv;
+       GFileType file_type;
+       GFile *canonical;
+
+       file_type = (is_directory) ? G_FILE_TYPE_DIRECTORY : G_FILE_TYPE_REGULAR;
+
+       if (!tracker_indexing_tree_file_is_indexable (priv->indexing_tree,
+                                                     file, file_type)) {
+               /* File should not be indexed */
+               return;
+       }
+
+       /* Fetch the interned copy */
+       canonical = tracker_file_system_get_file (priv->file_system,
+                                                 file, file_type, NULL);
+       g_signal_emit (notifier, signals[FILE_UPDATED], 0, canonical, FALSE);
+
+       if (!is_directory) {
+               tracker_file_system_forget_files (priv->file_system, canonical,
+                                                 G_FILE_TYPE_REGULAR);
+       }
+}
+
+static void
+monitor_item_attribute_updated_cb (TrackerMonitor *monitor,
+                                   GFile          *file,
+                                   gboolean        is_directory,
+                                   gpointer        user_data)
+{
+       TrackerFileNotifier *notifier = user_data;
+       TrackerFileNotifierPrivate *priv = notifier->priv;
+       GFile *canonical;
+       GFileType file_type;
+
+       file_type = (is_directory) ? G_FILE_TYPE_DIRECTORY : G_FILE_TYPE_REGULAR;
+
+       if (!tracker_indexing_tree_file_is_indexable (priv->indexing_tree,
+                                                     file, file_type)) {
+               /* File should not be indexed */
+               return;
+       }
+
+       /* Fetch the interned copy */
+       canonical = tracker_file_system_get_file (priv->file_system,
+                                                 file, file_type, NULL);
+       g_signal_emit (notifier, signals[FILE_UPDATED], 0, canonical, TRUE);
+
+       if (!is_directory) {
+               tracker_file_system_forget_files (priv->file_system, canonical,
+                                                 G_FILE_TYPE_REGULAR);
+       }
+}
+
+static void
+monitor_item_deleted_cb (TrackerMonitor *monitor,
+                         GFile          *file,
+                         gboolean        is_directory,
+                         gpointer        user_data)
+{
+       TrackerFileNotifier *notifier = user_data;
+       TrackerFileNotifierPrivate *priv = notifier->priv;
+       GFile *canonical;
+       GFileType file_type;
+
+       file_type = (is_directory) ? G_FILE_TYPE_DIRECTORY : G_FILE_TYPE_REGULAR;
+
+       /* Remove monitors if any */
+       if (is_directory &&
+           tracker_indexing_tree_file_is_root (priv->indexing_tree, file)) {
+               tracker_monitor_remove_children_recursively (priv->monitor,
+                                                            file);
+       } else if (is_directory) {
+               tracker_monitor_remove_recursively (priv->monitor, file);
+       }
+
+       if (!tracker_indexing_tree_file_is_indexable (priv->indexing_tree,
+                                                     file, file_type)) {
+               /* File was not indexed */
+               return ;
+       }
+
+       if (!is_directory) {
+               TrackerDirectoryFlags flags;
+               gboolean indexable;
+               GList *children;
+               GFile *parent;
+
+               children = g_list_prepend (NULL, file);
+               parent = g_file_get_parent (file);
+
+               indexable = tracker_indexing_tree_parent_is_indexable (priv->indexing_tree,
+                                                                      parent, children);
+               g_object_unref (parent);
+               g_list_free (children);
+
+               /* note: This supposedly works, but in practice
+                * won't ever happen as we don't get monitor events
+                * from directories triggering a filter of type
+                * TRACKER_FILTER_PARENT_DIRECTORY.
+                */
+               if (!indexable) {
+                       /* New file was triggering a directory content
+                        * filter, reindex parent directory altogether
+                        */
+                       file = tracker_file_system_get_file (priv->file_system,
+                                                            file,
+                                                            G_FILE_TYPE_DIRECTORY,
+                                                            NULL);
+                       tracker_indexing_tree_get_root (priv->indexing_tree,
+                                                       file, &flags);
+                       notifier_queue_file (notifier, file, flags);
+                       crawl_directories_start (notifier);
+                       return;
+               }
+       }
+
+       /* Fetch the interned copy */
+       canonical = tracker_file_system_get_file (priv->file_system,
+                                                 file, file_type, NULL);
+
+       /* tracker_file_system_forget_files() might already have been
+        * called on this file. In this case, the object might become
+        * invalid when returning from g_signal_emit(). Take a
+        * reference in order to prevent that.
+        */
+       g_object_ref (canonical);
+       g_signal_emit (notifier, signals[FILE_DELETED], 0, canonical);
+
+       file_notifier_current_root_check_remove_directory (notifier, canonical);
+
+       /* Remove the file from the cache (works recursively for directories) */
+       tracker_file_system_forget_files (priv->file_system,
+                                         canonical,
+                                         G_FILE_TYPE_UNKNOWN);
+       g_object_unref (canonical);
+}
+
+static void
+monitor_item_moved_cb (TrackerMonitor *monitor,
+                       GFile          *file,
+                       GFile          *other_file,
+                       gboolean        is_directory,
+                       gboolean        is_source_monitored,
+                       gpointer        user_data)
+{
+       TrackerFileNotifier *notifier;
+       TrackerFileNotifierPrivate *priv;
+       TrackerDirectoryFlags flags;
+
+       notifier = user_data;
+       priv = notifier->priv;
+       tracker_indexing_tree_get_root (priv->indexing_tree, other_file, &flags);
+
+       if (!is_source_monitored) {
+               if (is_directory) {
+                       /* Remove monitors if any */
+                       tracker_monitor_remove_recursively (priv->monitor, file);
+
+                       /* If should recurse, crawl other_file, as content is "new" */
+                       file = tracker_file_system_get_file (priv->file_system,
+                                                            other_file,
+                                                            G_FILE_TYPE_DIRECTORY,
+                                                            NULL);
+                       notifier_queue_file (notifier, file, flags);
+                       crawl_directories_start (notifier);
+               }
+               /* else, file, do nothing */
+       } else {
+               gboolean source_stored, should_process_other;
+               GFileType file_type;
+               GFile *check_file;
+
+               if (is_directory) {
+                       check_file = g_object_ref (file);
+               } else {
+                       check_file = g_file_get_parent (file);
+               }
+
+               file_type = (is_directory) ? G_FILE_TYPE_DIRECTORY : G_FILE_TYPE_REGULAR;
+
+               /* If the (parent) directory is in
+                * the filesystem, file is stored
+                */
+               source_stored = (tracker_file_system_peek_file (priv->file_system,
+                                                               check_file) != NULL);
+               should_process_other = tracker_indexing_tree_file_is_indexable (priv->indexing_tree,
+                                                                               other_file,
+                                                                               file_type);
+               g_object_unref (check_file);
+
+               if (!source_stored) {
+                       /* Destination location should be indexed as if new */
+                       /* Remove monitors if any */
+                       if (is_directory) {
+                               tracker_monitor_remove_recursively (priv->monitor,
+                                                                   file);
+                       }
+
+                       if (should_process_other) {
+                               gboolean dest_is_recursive;
+                               TrackerDirectoryFlags flags;
+
+                               tracker_indexing_tree_get_root (priv->indexing_tree, other_file, &flags);
+                               dest_is_recursive = (flags & TRACKER_DIRECTORY_FLAG_RECURSE) != 0;
+
+                               /* Source file was not stored, check dest file as new */
+                               if (!is_directory || !dest_is_recursive) {
+                                       g_signal_emit (notifier, signals[FILE_CREATED], 0, other_file);
+                               } else if (is_directory) {
+                                       /* Crawl dest directory */
+                                       other_file = tracker_file_system_get_file (priv->file_system,
+                                                                                  other_file,
+                                                                                  G_FILE_TYPE_DIRECTORY,
+                                                                                  NULL);
+                                       notifier_queue_file (notifier, other_file, flags);
+                                       crawl_directories_start (notifier);
+                               }
+                       }
+                       /* Else, do nothing else */
+               } else if (!should_process_other) {
+                       /* Delete original location as it moves to be non indexable */
+                       if (is_directory) {
+                               tracker_monitor_remove_recursively (priv->monitor,
+                                                                   file);
+                       }
+
+                       g_signal_emit (notifier, signals[FILE_DELETED], 0, file);
+                       file_notifier_current_root_check_remove_directory (notifier, file);
+               } else {
+                       /* Handle move */
+                       if (is_directory) {
+                               gboolean dest_is_recursive, source_is_recursive;
+                               TrackerDirectoryFlags source_flags;
+
+                               tracker_monitor_move (priv->monitor,
+                                                     file, other_file);
+
+                               tracker_indexing_tree_get_root (priv->indexing_tree,
+                                                               file, &source_flags);
+                               source_is_recursive = (source_flags & TRACKER_DIRECTORY_FLAG_RECURSE) != 0;
+                               dest_is_recursive = (flags & TRACKER_DIRECTORY_FLAG_RECURSE) != 0;
+
+                               if (source_is_recursive && !dest_is_recursive) {
+                                       /* A directory is being moved from a
+                                        * recursive location to a non-recursive
+                                        * one, don't do anything here, and let
+                                        * TrackerMinerFS handle it, see item_move().
+                                        */
+                               } else if (!source_is_recursive && dest_is_recursive) {
+                                       /* crawl the folder */
+                                       file = tracker_file_system_get_file (priv->file_system,
+                                                                            other_file,
+                                                                            G_FILE_TYPE_DIRECTORY,
+                                                                            NULL);
+                                       notifier_queue_file (notifier, file, flags);
+                                       crawl_directories_start (notifier);
+                               }
+                       }
+
+                       g_signal_emit (notifier, signals[FILE_MOVED], 0, file, other_file);
+               }
+       }
+}
+
+/* Indexing tree signal handlers */
+static void
+indexing_tree_directory_added (TrackerIndexingTree *indexing_tree,
+                               GFile               *directory,
+                               gpointer             user_data)
+{
+       TrackerFileNotifier *notifier = user_data;
+       TrackerFileNotifierPrivate *priv = notifier->priv;
+       TrackerDirectoryFlags flags;
+
+       tracker_indexing_tree_get_root (indexing_tree, directory, &flags);
+
+       directory = tracker_file_system_get_file (priv->file_system, directory,
+                                                 G_FILE_TYPE_DIRECTORY, NULL);
+       notifier_queue_file (notifier, directory, flags);
+       crawl_directories_start (notifier);
+}
+
+static void
+indexing_tree_directory_updated (TrackerIndexingTree *indexing_tree,
+                                 GFile               *directory,
+                                 gpointer             user_data)
+{
+       TrackerFileNotifier *notifier = user_data;
+       TrackerFileNotifierPrivate *priv = notifier->priv;
+       TrackerDirectoryFlags flags;
+
+       tracker_indexing_tree_get_root (indexing_tree, directory, &flags);
+       flags |= TRACKER_DIRECTORY_FLAG_CHECK_DELETED;
+
+       directory = tracker_file_system_get_file (priv->file_system, directory,
+                                                 G_FILE_TYPE_DIRECTORY, NULL);
+       notifier_queue_file (notifier, directory, flags);
+       crawl_directories_start (notifier);
+}
+
+static void
+indexing_tree_directory_removed (TrackerIndexingTree *indexing_tree,
+                                 GFile               *directory,
+                                 gpointer             user_data)
+{
+       TrackerFileNotifier *notifier = user_data;
+       TrackerFileNotifierPrivate *priv = notifier->priv;
+       TrackerDirectoryFlags flags;
+       GList *elem;
+
+       /* Flags are still valid at the moment of deletion */
+       tracker_indexing_tree_get_root (indexing_tree, directory, &flags);
+       directory = tracker_file_system_peek_file (priv->file_system, directory);
+
+       if (!directory) {
+               /* If the dir has no canonical copy,
+                * it wasn't even told to be indexed.
+                */
+               return;
+       }
+
+       /* If the folder was being ignored, index/crawl it from scratch */
+       if (flags & TRACKER_DIRECTORY_FLAG_IGNORE) {
+               GFile *parent;
+
+               parent = g_file_get_parent (directory);
+
+               if (parent) {
+                       TrackerDirectoryFlags parent_flags;
+
+                       tracker_indexing_tree_get_root (indexing_tree,
+                                                       parent,
+                                                       &parent_flags);
+
+                       if (parent_flags & TRACKER_DIRECTORY_FLAG_RECURSE) {
+                               notifier_queue_file (notifier, directory, parent_flags);
+                               crawl_directories_start (notifier);
+                       } else if (tracker_indexing_tree_file_is_root (indexing_tree,
+                                                                      parent)) {
+                               g_signal_emit (notifier, signals[FILE_CREATED],
+                                              0, directory);
+                       }
+
+                       g_object_unref (parent);
+               }
+               return;
+       }
+
+       if ((flags & TRACKER_DIRECTORY_FLAG_PRESERVE) == 0) {
+               /* Directory needs to be deleted from the store too */
+               g_signal_emit (notifier, signals[FILE_DELETED], 0, directory);
+       }
+
+       elem = g_list_find_custom (priv->pending_index_roots, directory,
+                                  (GCompareFunc) find_directory_root);
+
+       if (elem) {
+               root_data_free (elem->data);
+               priv->pending_index_roots =
+                       g_list_delete_link (priv->pending_index_roots, elem);
+       }
+
+       if (priv->current_index_root &&
+           directory == priv->current_index_root->root) {
+               /* Directory being currently processed */
+               tracker_crawler_stop (priv->crawler);
+               g_cancellable_cancel (priv->cancellable);
+
+               /* If the crawler was already stopped (eg. we're at the querying
+                * phase), the current index root won't be cleared.
+                */
+               if (priv->current_index_root) {
+                       root_data_free (priv->current_index_root);
+                       priv->current_index_root = NULL;
+               }
+
+               notifier_check_next_root (notifier);
+       }
+
+       /* Remove monitors if any */
+       /* FIXME: How do we handle this with 3rd party data_providers? */
+       tracker_monitor_remove_recursively (priv->monitor, directory);
+
+       /* Remove all files from cache */
+       tracker_file_system_forget_files (priv->file_system, directory,
+                                         G_FILE_TYPE_UNKNOWN);
+}
+
+static void
+indexing_tree_child_updated (TrackerIndexingTree *indexing_tree,
+                             GFile               *root,
+                             GFile               *child,
+                             gpointer             user_data)
+{
+       TrackerFileNotifier *notifier = user_data;
+       TrackerFileNotifierPrivate *priv = notifier->priv;
+       TrackerDirectoryFlags flags;
+       GFileType child_type;
+       GFile *canonical;
+
+       child_type = g_file_query_file_type (child,
+                                            G_FILE_QUERY_INFO_NOFOLLOW_SYMLINKS,
+                                            NULL);
+
+       if (child_type == G_FILE_TYPE_UNKNOWN)
+               return;
+
+       canonical = tracker_file_system_get_file (priv->file_system,
+                                                 child, child_type, NULL);
+       tracker_indexing_tree_get_root (indexing_tree, child, &flags);
+
+       if (child_type == G_FILE_TYPE_DIRECTORY &&
+           (flags & TRACKER_DIRECTORY_FLAG_RECURSE)) {
+               flags |= TRACKER_DIRECTORY_FLAG_CHECK_DELETED;
+
+               notifier_queue_file (notifier, canonical, flags);
+               crawl_directories_start (notifier);
+       } else if (tracker_indexing_tree_file_is_indexable (priv->indexing_tree,
+                                                           canonical, child_type)) {
+               g_signal_emit (notifier, signals[FILE_UPDATED], 0, canonical, FALSE);
+       }
+}
+
+static void
+tracker_file_notifier_finalize (GObject *object)
+{
+       TrackerFileNotifierPrivate *priv;
+
+       priv = TRACKER_FILE_NOTIFIER (object)->priv;
+
+       if (priv->indexing_tree) {
+               g_object_unref (priv->indexing_tree);
+       }
+
+       if (priv->data_provider) {
+               g_object_unref (priv->data_provider);
+       }
+
+       if (priv->cancellable) {
+               g_cancellable_cancel (priv->cancellable);
+               g_object_unref (priv->cancellable);
+       }
+
+       g_object_unref (priv->crawler);
+       g_object_unref (priv->monitor);
+       g_object_unref (priv->file_system);
+       g_clear_object (&priv->connection);
+
+       if (priv->current_index_root)
+               root_data_free (priv->current_index_root);
+
+       g_list_foreach (priv->pending_index_roots, (GFunc) root_data_free, NULL);
+       g_list_free (priv->pending_index_roots);
+       g_timer_destroy (priv->timer);
+
+       G_OBJECT_CLASS (tracker_file_notifier_parent_class)->finalize (object);
+}
+
+static void
+check_disable_monitor (TrackerFileNotifier *notifier)
+{
+       TrackerFileNotifierPrivate *priv;
+       TrackerSparqlCursor *cursor;
+       gint64 folder_count = 0;
+       GError *error = NULL;
+
+       priv = notifier->priv;
+       cursor = tracker_sparql_connection_query (priv->connection,
+                                                 "SELECT COUNT(?f) { ?f a nfo:Folder }",
+                                                 NULL, &error);
+
+       if (!error && tracker_sparql_cursor_next (cursor, NULL, &error)) {
+               folder_count = tracker_sparql_cursor_get_integer (cursor, 0);
+               tracker_sparql_cursor_close (cursor);
+       }
+
+       if (error) {
+               g_warning ("Could not get folder count: %s\n", error->message);
+               g_error_free (error);
+       } else if (folder_count > tracker_monitor_get_limit (priv->monitor)) {
+               /* If the folder count exceeds the monitor limit, there's
+                * nothing we can do anyway to prevent possibly out of date
+                * content. As it is the case no matter what we try, fully
+                * embrace it instead, and disable monitors until after crawling
+                * has been performed. This dramatically improves crawling time
+                * as monitors are inherently expensive.
+                */
+               g_info ("Temporarily disabling monitors until crawling is "
+                       "completed. Too many folders to monitor anyway");
+               tracker_monitor_set_enabled (priv->monitor, FALSE);
+       }
+
+       g_clear_object (&cursor);
+}
+
+static void
+tracker_file_notifier_constructed (GObject *object)
+{
+       TrackerFileNotifierPrivate *priv;
+       GFile *root;
+
+       G_OBJECT_CLASS (tracker_file_notifier_parent_class)->constructed (object);
+
+       priv = TRACKER_FILE_NOTIFIER (object)->priv;
+       g_assert (priv->indexing_tree);
+
+       /* Initialize filesystem and register properties */
+       root = tracker_indexing_tree_get_master_root (priv->indexing_tree);
+       priv->file_system = tracker_file_system_new (root);
+
+       g_signal_connect (priv->indexing_tree, "directory-added",
+                         G_CALLBACK (indexing_tree_directory_added), object);
+       g_signal_connect (priv->indexing_tree, "directory-updated",
+                         G_CALLBACK (indexing_tree_directory_updated), object);
+       g_signal_connect (priv->indexing_tree, "directory-removed",
+                         G_CALLBACK (indexing_tree_directory_removed), object);
+       g_signal_connect (priv->indexing_tree, "child-updated",
+                         G_CALLBACK (indexing_tree_child_updated), object);
+
+       /* Set up crawler */
+       priv->crawler = tracker_crawler_new (priv->data_provider);
+       tracker_crawler_set_file_attributes (priv->crawler,
+                                            G_FILE_ATTRIBUTE_TIME_MODIFIED ","
+                                            G_FILE_ATTRIBUTE_STANDARD_TYPE);
+
+       g_signal_connect (priv->crawler, "check-file",
+                         G_CALLBACK (crawler_check_file_cb),
+                         object);
+       g_signal_connect (priv->crawler, "check-directory",
+                         G_CALLBACK (crawler_check_directory_cb),
+                         object);
+       g_signal_connect (priv->crawler, "check-directory-contents",
+                         G_CALLBACK (crawler_check_directory_contents_cb),
+                         object);
+       g_signal_connect (priv->crawler, "directory-crawled",
+                         G_CALLBACK (crawler_directory_crawled_cb),
+                         object);
+       g_signal_connect (priv->crawler, "finished",
+                         G_CALLBACK (crawler_finished_cb),
+                         object);
+
+       check_disable_monitor (TRACKER_FILE_NOTIFIER (object));
+}
+
+static void
+tracker_file_notifier_real_finished (TrackerFileNotifier *notifier)
+{
+       TrackerFileNotifierPrivate *priv;
+
+       priv = notifier->priv;
+
+       if (!tracker_monitor_get_enabled (priv->monitor)) {
+               /* If the monitor was disabled on ::constructed (see
+                * check_disable_monitor()), enable it back again.
+                * This will lazily create all missing directory
+                * monitors.
+                */
+               g_info ("Re-enabling directory monitors");
+               tracker_monitor_set_enabled (priv->monitor, TRUE);
+       }
+}
+
+static void
+tracker_file_notifier_class_init (TrackerFileNotifierClass *klass)
+{
+       GObjectClass *object_class = G_OBJECT_CLASS (klass);
+
+       object_class->finalize = tracker_file_notifier_finalize;
+       object_class->set_property = tracker_file_notifier_set_property;
+       object_class->get_property = tracker_file_notifier_get_property;
+       object_class->constructed = tracker_file_notifier_constructed;
+
+       klass->finished = tracker_file_notifier_real_finished;
+
+       signals[FILE_CREATED] =
+               g_signal_new ("file-created",
+                             G_TYPE_FROM_CLASS (klass),
+                             G_SIGNAL_RUN_LAST,
+                             G_STRUCT_OFFSET (TrackerFileNotifierClass,
+                                              file_created),
+                             NULL, NULL,
+                             NULL,
+                             G_TYPE_NONE,
+                             1, G_TYPE_FILE);
+       signals[FILE_UPDATED] =
+               g_signal_new ("file-updated",
+                             G_TYPE_FROM_CLASS (klass),
+                             G_SIGNAL_RUN_LAST,
+                             G_STRUCT_OFFSET (TrackerFileNotifierClass,
+                                              file_updated),
+                             NULL, NULL,
+                             NULL,
+                             G_TYPE_NONE,
+                             2, G_TYPE_FILE, G_TYPE_BOOLEAN);
+       signals[FILE_DELETED] =
+               g_signal_new ("file-deleted",
+                             G_TYPE_FROM_CLASS (klass),
+                             G_SIGNAL_RUN_LAST,
+                             G_STRUCT_OFFSET (TrackerFileNotifierClass,
+                                              file_deleted),
+                             NULL, NULL,
+                             NULL,
+                             G_TYPE_NONE,
+                             1, G_TYPE_FILE);
+       signals[FILE_MOVED] =
+               g_signal_new ("file-moved",
+                             G_TYPE_FROM_CLASS (klass),
+                             G_SIGNAL_RUN_LAST,
+                             G_STRUCT_OFFSET (TrackerFileNotifierClass,
+                                              file_moved),
+                             NULL, NULL,
+                             NULL,
+                             G_TYPE_NONE,
+                             2, G_TYPE_FILE, G_TYPE_FILE);
+       signals[DIRECTORY_STARTED] =
+               g_signal_new ("directory-started",
+                             G_TYPE_FROM_CLASS (klass),
+                             G_SIGNAL_RUN_LAST,
+                             G_STRUCT_OFFSET (TrackerFileNotifierClass,
+                                              directory_started),
+                             NULL, NULL,
+                             NULL,
+                             G_TYPE_NONE,
+                             1, G_TYPE_FILE);
+       signals[DIRECTORY_FINISHED] =
+               g_signal_new ("directory-finished",
+                             G_TYPE_FROM_CLASS (klass),
+                             G_SIGNAL_RUN_LAST,
+                             G_STRUCT_OFFSET (TrackerFileNotifierClass,
+                                              directory_finished),
+                             NULL, NULL,
+                             NULL,
+                             G_TYPE_NONE,
+                             5, G_TYPE_FILE, G_TYPE_UINT,
+                             G_TYPE_UINT, G_TYPE_UINT, G_TYPE_UINT);
+       signals[FINISHED] =
+               g_signal_new ("finished",
+                             G_TYPE_FROM_CLASS (klass),
+                             G_SIGNAL_RUN_LAST,
+                             G_STRUCT_OFFSET (TrackerFileNotifierClass,
+                                              finished),
+                             NULL, NULL,
+                             NULL,
+                             G_TYPE_NONE, 0, G_TYPE_NONE);
+
+       g_object_class_install_property (object_class,
+                                        PROP_INDEXING_TREE,
+                                        g_param_spec_object ("indexing-tree",
+                                                             "Indexing tree",
+                                                             "Indexing tree",
+                                                             TRACKER_TYPE_INDEXING_TREE,
+                                                             G_PARAM_READWRITE |
+                                                             G_PARAM_CONSTRUCT_ONLY));
+       g_object_class_install_property (object_class,
+                                        PROP_DATA_PROVIDER,
+                                        g_param_spec_object ("data-provider",
+                                                             "Data provider",
+                                                             "Data provider to use to crawl structures 
populating data, e.g. like GFileEnumerator",
+                                                             TRACKER_TYPE_DATA_PROVIDER,
+                                                             G_PARAM_READWRITE |
+                                                             G_PARAM_CONSTRUCT_ONLY));
+       g_object_class_install_property (object_class,
+                                        PROP_CONNECTION,
+                                        g_param_spec_object ("connection",
+                                                             "Connection",
+                                                             "Connection to use for queries",
+                                                             TRACKER_SPARQL_TYPE_CONNECTION,
+                                                             G_PARAM_READWRITE |
+                                                             G_PARAM_CONSTRUCT_ONLY));
+
+       g_type_class_add_private (object_class,
+                                 sizeof (TrackerFileNotifierClass));
+
+       /* Initialize property quarks */
+       quark_property_iri = g_quark_from_static_string ("tracker-property-iri");
+       tracker_file_system_register_property (quark_property_iri, g_free);
+
+       quark_property_store_mtime = g_quark_from_static_string ("tracker-property-store-mtime");
+       tracker_file_system_register_property (quark_property_store_mtime,
+                                              g_free);
+
+       quark_property_filesystem_mtime = g_quark_from_static_string ("tracker-property-filesystem-mtime");
+       tracker_file_system_register_property (quark_property_filesystem_mtime,
+                                              g_free);
+
+       force_check_updated = g_getenv ("TRACKER_MINER_FORCE_CHECK_UPDATED") != NULL;
+}
+
+static void
+tracker_file_notifier_init (TrackerFileNotifier *notifier)
+{
+       TrackerFileNotifierPrivate *priv;
+
+       priv = notifier->priv =
+               G_TYPE_INSTANCE_GET_PRIVATE (notifier,
+                                            TRACKER_TYPE_FILE_NOTIFIER,
+                                            TrackerFileNotifierPrivate);
+
+       priv->timer = g_timer_new ();
+       priv->stopped = TRUE;
+
+       /* Set up monitor */
+       priv->monitor = tracker_monitor_new ();
+
+       g_signal_connect (priv->monitor, "item-created",
+                         G_CALLBACK (monitor_item_created_cb),
+                         notifier);
+       g_signal_connect (priv->monitor, "item-updated",
+                         G_CALLBACK (monitor_item_updated_cb),
+                         notifier);
+       g_signal_connect (priv->monitor, "item-attribute-updated",
+                         G_CALLBACK (monitor_item_attribute_updated_cb),
+                         notifier);
+       g_signal_connect (priv->monitor, "item-deleted",
+                         G_CALLBACK (monitor_item_deleted_cb),
+                         notifier);
+       g_signal_connect (priv->monitor, "item-moved",
+                         G_CALLBACK (monitor_item_moved_cb),
+                         notifier);
+}
+
+TrackerFileNotifier *
+tracker_file_notifier_new (TrackerIndexingTree     *indexing_tree,
+                           TrackerDataProvider     *data_provider,
+                           TrackerSparqlConnection *connection)
+{
+       g_return_val_if_fail (TRACKER_IS_INDEXING_TREE (indexing_tree), NULL);
+
+       return g_object_new (TRACKER_TYPE_FILE_NOTIFIER,
+                            "indexing-tree", indexing_tree,
+                            "data-provider", data_provider,
+                            "connection", connection,
+                            NULL);
+}
+
+gboolean
+tracker_file_notifier_start (TrackerFileNotifier *notifier)
+{
+       TrackerFileNotifierPrivate *priv;
+
+       g_return_val_if_fail (TRACKER_IS_FILE_NOTIFIER (notifier), FALSE);
+
+       priv = notifier->priv;
+
+       if (priv->stopped) {
+               priv->stopped = FALSE;
+
+               if (priv->pending_index_roots) {
+                       crawl_directories_start (notifier);
+               } else {
+                       g_signal_emit (notifier, signals[FINISHED], 0);
+               }
+       }
+
+       return TRUE;
+}
+
+void
+tracker_file_notifier_stop (TrackerFileNotifier *notifier)
+{
+       TrackerFileNotifierPrivate *priv;
+
+       g_return_if_fail (TRACKER_IS_FILE_NOTIFIER (notifier));
+
+       priv = notifier->priv;
+
+       if (!priv->stopped) {
+               tracker_crawler_stop (priv->crawler);
+
+               if (priv->current_index_root) {
+                       root_data_free (priv->current_index_root);
+                       priv->current_index_root = NULL;
+               }
+
+               g_cancellable_cancel (priv->cancellable);
+               priv->stopped = TRUE;
+       }
+}
+
+gboolean
+tracker_file_notifier_is_active (TrackerFileNotifier *notifier)
+{
+       TrackerFileNotifierPrivate *priv;
+
+       g_return_val_if_fail (TRACKER_IS_FILE_NOTIFIER (notifier), FALSE);
+
+       priv = notifier->priv;
+       return priv->pending_index_roots || priv->current_index_root;
+}
+
+const gchar *
+tracker_file_notifier_get_file_iri (TrackerFileNotifier *notifier,
+                                    GFile               *file,
+                                    gboolean             force)
+{
+       TrackerFileNotifierPrivate *priv;
+       GFile *canonical;
+       gchar *iri = NULL;
+       gboolean found;
+
+       g_return_val_if_fail (TRACKER_IS_FILE_NOTIFIER (notifier), NULL);
+       g_return_val_if_fail (G_IS_FILE (file), NULL);
+
+       priv = notifier->priv;
+
+       if (G_UNLIKELY (priv->connection == NULL)) {
+               return NULL;
+       }
+
+       canonical = tracker_file_system_get_file (priv->file_system,
+                                                 file,
+                                                 G_FILE_TYPE_REGULAR,
+                                                 NULL);
+       if (!canonical) {
+               return NULL;
+       }
+
+       found = tracker_file_system_get_property_full (priv->file_system,
+                                                      canonical,
+                                                      quark_property_iri,
+                                                      (gpointer *) &iri);
+
+       if (found && !iri) {
+               /* NULL here mean the file iri was "invalidated", the file
+                * was inserted by a previous event, so it has an unknown iri,
+                * and further updates are keeping the file object alive.
+                *
+                * When these updates are processed, they'll need fetching the
+                * file IRI again, so we force here extraction for these cases.
+                */
+               force = TRUE;
+       }
+
+       if (!iri && force) {
+               TrackerSparqlCursor *cursor;
+               const gchar *str;
+               gchar *sparql;
+
+               /* Fetch data for this file synchronously */
+               sparql = sparql_files_compose_query (&file, 1);
+               cursor = tracker_sparql_connection_query (priv->connection,
+                                                         sparql, NULL, NULL);
+               g_free (sparql);
+
+               if (!cursor)
+                       return NULL;
+
+               if (!tracker_sparql_cursor_next (cursor, NULL, NULL)) {
+                       g_object_unref (cursor);
+                       return NULL;
+               }
+
+               str = tracker_sparql_cursor_get_string (cursor, 1, NULL);
+               iri = g_strdup (str);
+               tracker_file_system_set_property (priv->file_system, canonical,
+                                                 quark_property_iri, iri);
+               g_object_unref (cursor);
+       }
+
+       return iri;
+}
+
+static gboolean
+file_notifier_invalidate_file_iri_foreach (GFile    *file,
+                                           gpointer  user_data)
+{
+       TrackerFileSystem *file_system = user_data;
+
+       tracker_file_system_set_property (file_system,
+                                         file,
+                                         quark_property_iri,
+                                         NULL);
+
+       return FALSE;
+}
+
+void
+tracker_file_notifier_invalidate_file_iri (TrackerFileNotifier *notifier,
+                                           GFile               *file,
+                                           gboolean             recursive)
+{
+       TrackerFileNotifierPrivate *priv;
+       GFile *canonical;
+
+       g_return_if_fail (TRACKER_IS_FILE_NOTIFIER (notifier));
+       g_return_if_fail (G_IS_FILE (file));
+
+       priv = notifier->priv;
+       canonical = tracker_file_system_get_file (priv->file_system,
+                                                 file,
+                                                 G_FILE_TYPE_REGULAR,
+                                                 NULL);
+       if (!canonical) {
+               return;
+       }
+
+       if (!recursive) {
+               /* Set a NULL iri, so we make sure to look it up afterwards */
+               tracker_file_system_set_property (priv->file_system,
+                                                 canonical,
+                                                 quark_property_iri,
+                                                 NULL);
+               return;
+       }
+
+       tracker_file_system_traverse (priv->file_system,
+                                     canonical,
+                                     G_PRE_ORDER,
+                                     file_notifier_invalidate_file_iri_foreach,
+                                     -1,
+                                     priv->file_system);
+}
+
+GFileType
+tracker_file_notifier_get_file_type (TrackerFileNotifier *notifier,
+                                     GFile               *file)
+{
+       TrackerFileNotifierPrivate *priv;
+       GFile *canonical;
+
+       g_return_val_if_fail (TRACKER_IS_FILE_NOTIFIER (notifier), G_FILE_TYPE_UNKNOWN);
+       g_return_val_if_fail (G_IS_FILE (file), G_FILE_TYPE_UNKNOWN);
+
+       priv = notifier->priv;
+       canonical = tracker_file_system_get_file (priv->file_system,
+                                                 file,
+                                                 G_FILE_TYPE_REGULAR,
+                                                 NULL);
+       if (!canonical) {
+               return G_FILE_TYPE_UNKNOWN;
+       }
+
+       return tracker_file_system_get_file_type (priv->file_system, canonical);
+}
diff --git a/src/libtracker-miner/tracker-file-notifier.h b/src/libtracker-miner/tracker-file-notifier.h
new file mode 100644
index 000000000..921c2a65e
--- /dev/null
+++ b/src/libtracker-miner/tracker-file-notifier.h
@@ -0,0 +1,102 @@
+/*
+ * Copyright (C) 2011, Nokia <ivan frade nokia com>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ * Boston, MA  02110-1301, USA.
+ *
+ * Author: Carlos Garnacho  <carlos lanedo com>
+ */
+
+#ifndef __TRACKER_FILE_NOTIFIER_H__
+#define __TRACKER_FILE_NOTIFIER_H__
+
+#if !defined (__LIBTRACKER_MINER_H_INSIDE__) && !defined (TRACKER_COMPILATION)
+#error "Only <libtracker-miner/tracker-miner.h> can be included directly."
+#endif
+
+#include <gio/gio.h>
+#include "tracker-indexing-tree.h"
+#include "tracker-miner-fs.h"
+
+G_BEGIN_DECLS
+
+#define TRACKER_TYPE_FILE_NOTIFIER         (tracker_file_notifier_get_type ())
+#define TRACKER_FILE_NOTIFIER(o)           (G_TYPE_CHECK_INSTANCE_CAST ((o), TRACKER_TYPE_FILE_NOTIFIER, 
TrackerFileNotifier))
+#define TRACKER_FILE_NOTIFIER_CLASS(c)     (G_TYPE_CHECK_CLASS_CAST ((c),    TRACKER_TYPE_FILE_NOTIFIER, 
TrackerFileNotifierClass))
+#define TRACKER_IS_FILE_NOTIFIER(o)        (G_TYPE_CHECK_INSTANCE_TYPE ((o), TRACKER_TYPE_FILE_NOTIFIER))
+#define TRACKER_IS_FILE_NOTIFIER_CLASS(c)  (G_TYPE_CHECK_CLASS_TYPE ((c),    TRACKER_TYPE_FILE_NOTIFIER))
+#define TRACKER_FILE_NOTIFIER_GET_CLASS(o) (G_TYPE_INSTANCE_GET_CLASS ((o),  TRACKER_TYPE_FILE_NOTIFIER, 
TrackerFileNotifierClass))
+
+typedef struct _TrackerFileNotifier TrackerFileNotifier;
+typedef struct _TrackerFileNotifierClass TrackerFileNotifierClass;
+typedef enum _TrackerFileNotifierType TrackerFileNotifierType;
+
+struct _TrackerFileNotifier {
+       GObject parent_instance;
+       gpointer priv;
+};
+
+struct _TrackerFileNotifierClass {
+       GObjectClass parent_class;
+
+       void (* file_created) (TrackerFileNotifier *notifier,
+                              GFile               *file);
+       void (* file_updated) (TrackerFileNotifier *notifier,
+                              GFile               *file,
+                              gboolean             attributes_only);
+       void (* file_deleted) (TrackerFileNotifier *notifier,
+                              GFile               *file);
+       void (* file_moved)   (TrackerFileNotifier *notifier,
+                              GFile               *from,
+                              GFile               *to);
+
+       /* Directory notifications */
+       void (* directory_started)  (TrackerFileNotifier *notifier,
+                                    GFile               *directory);
+       void (* directory_finished) (TrackerFileNotifier *notifier,
+                                    GFile               *directory,
+                                    guint                directories_found,
+                                    guint                directories_ignored,
+                                    guint                files_found,
+                                    guint                files_ignored);
+
+       void (* finished)           (TrackerFileNotifier *notifier);
+};
+
+GType         tracker_file_notifier_get_type     (void) G_GNUC_CONST;
+
+TrackerFileNotifier *
+              tracker_file_notifier_new          (TrackerIndexingTree     *indexing_tree,
+                                                  TrackerDataProvider     *data_provider,
+                                                  TrackerSparqlConnection *connection);
+
+gboolean      tracker_file_notifier_start        (TrackerFileNotifier     *notifier);
+void          tracker_file_notifier_stop         (TrackerFileNotifier     *notifier);
+gboolean      tracker_file_notifier_is_active    (TrackerFileNotifier     *notifier);
+
+const gchar * tracker_file_notifier_get_file_iri (TrackerFileNotifier     *notifier,
+                                                  GFile                   *file,
+                                                  gboolean                 force);
+
+void          tracker_file_notifier_invalidate_file_iri (TrackerFileNotifier *notifier,
+                                                         GFile               *file,
+                                                         gboolean             recursive);
+
+GFileType     tracker_file_notifier_get_file_type (TrackerFileNotifier *notifier,
+                                                   GFile               *file);
+
+G_END_DECLS
+
+#endif /* __TRACKER_FILE_SYSTEM_H__ */
diff --git a/src/libtracker-miner/tracker-file-system.c b/src/libtracker-miner/tracker-file-system.c
new file mode 100644
index 000000000..5573e1ef2
--- /dev/null
+++ b/src/libtracker-miner/tracker-file-system.c
@@ -0,0 +1,1062 @@
+/*
+ * Copyright (C) 2011, Nokia <ivan frade nokia com>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ * Boston, MA  02110-1301, USA.
+ *
+ * Author: Carlos Garnacho  <carlos lanedo com>
+ */
+
+#include <string.h>
+#include <stdlib.h>
+
+#include "tracker-file-system.h"
+
+typedef struct _TrackerFileSystemPrivate TrackerFileSystemPrivate;
+typedef struct _FileNodeProperty FileNodeProperty;
+typedef struct _FileNodeData FileNodeData;
+typedef struct _NodeLookupData NodeLookupData;
+
+static GHashTable *properties = NULL;
+
+struct _TrackerFileSystemPrivate {
+       GNode *file_tree;
+       GFile *root;
+};
+
+struct _FileNodeProperty {
+       GQuark prop_quark;
+       gpointer value;
+};
+
+struct _FileNodeData {
+       GFile *file;
+       gchar *uri_prefix;
+       GArray *properties;
+       guint shallow   : 1;
+       guint unowned : 1;
+       guint file_type : 4;
+};
+
+struct _NodeLookupData {
+       TrackerFileSystem *file_system;
+       GNode *node;
+};
+
+enum {
+       PROP_0,
+       PROP_ROOT,
+};
+
+static GQuark quark_file_node = 0;
+
+static void file_weak_ref_notify (gpointer    user_data,
+                                  GObject    *prev_location);
+
+G_DEFINE_TYPE (TrackerFileSystem, tracker_file_system, G_TYPE_OBJECT)
+
+/*
+ * TrackerFileSystem is a filesystem abstraction, it mainly serves 2 purposes:
+ *   - Canonicalizes GFiles, so it is possible later to perform pointer
+ *     comparisons on them.
+ *   - Stores data for the GFile lifetime, so it may be used as cache store
+ *     as long as some file is needed.
+ *
+ * The TrackerFileSystem holds a reference on each GFile. There are two cases
+ * when we want to force a cached GFile to be freed: when it no longer exists
+ * on disk, and once crawling a directory has completed and we only need to
+ * remember the directories. Objects may persist in the cache even after
+ * tracker_file_system_forget_files() is called to delete them if there are
+ * references held on them elsewhere, and they will stay until all references
+ * are dropped.
+ */
+
+
+static void
+file_node_data_free (FileNodeData *data,
+                     GNode        *node)
+{
+       guint i;
+
+       if (data->file) {
+               if (!data->shallow) {
+                       g_object_weak_unref (G_OBJECT (data->file),
+                                            file_weak_ref_notify,
+                                            node);
+               }
+
+               if (!data->unowned) {
+                       g_object_unref (data->file);
+               }
+       }
+
+       data->file = NULL;
+       g_free (data->uri_prefix);
+
+       for (i = 0; i < data->properties->len; i++) {
+               FileNodeProperty *property;
+               GDestroyNotify destroy_notify;
+
+               property = &g_array_index (data->properties,
+                                          FileNodeProperty, i);
+
+               destroy_notify = g_hash_table_lookup (properties,
+                                                     GUINT_TO_POINTER (property->prop_quark));
+
+               if (destroy_notify) {
+                       (destroy_notify) (property->value);
+               }
+       }
+
+       g_array_free (data->properties, TRUE);
+       g_slice_free (FileNodeData, data);
+}
+
+static FileNodeData *
+file_node_data_new (TrackerFileSystem *file_system,
+                    GFile             *file,
+                    GFileType          file_type,
+                    GNode             *node)
+{
+       FileNodeData *data;
+       NodeLookupData lookup_data;
+       GArray *node_data;
+
+       data = g_slice_new0 (FileNodeData);
+       data->file = g_object_ref (file);
+       data->file_type = file_type;
+       data->properties = g_array_new (FALSE, TRUE, sizeof (FileNodeProperty));
+
+       /* We use weak refs to keep track of files */
+       g_object_weak_ref (G_OBJECT (data->file), file_weak_ref_notify, node);
+
+       node_data = g_object_get_qdata (G_OBJECT (data->file),
+                                       quark_file_node);
+
+       if (!node_data) {
+               node_data = g_array_new (FALSE, FALSE, sizeof (NodeLookupData));
+               g_object_set_qdata_full (G_OBJECT (data->file),
+                                        quark_file_node,
+                                        node_data,
+                                        (GDestroyNotify) g_array_unref);
+       }
+
+       lookup_data.file_system = file_system;
+       lookup_data.node = node;
+       g_array_append_val (node_data, lookup_data);
+
+       g_assert (node->data == NULL);
+       node->data = data;
+
+       return data;
+}
+
+static FileNodeData *
+file_node_data_root_new (GFile *root)
+{
+       FileNodeData *data;
+
+       data = g_slice_new0 (FileNodeData);
+       data->uri_prefix = g_file_get_uri (root);
+       data->file = g_object_ref (root);
+       data->properties = g_array_new (FALSE, TRUE, sizeof (FileNodeProperty));
+       data->file_type = G_FILE_TYPE_DIRECTORY;
+       data->shallow = TRUE;
+
+       return data;
+}
+
+static gboolean
+file_node_data_equal_or_child (GNode  *node,
+                               gchar  *uri_prefix,
+                               gchar **uri_remainder)
+{
+       FileNodeData *data;
+       gsize len;
+
+       data = node->data;
+       len = strlen (data->uri_prefix);
+
+       if (strncmp (uri_prefix, data->uri_prefix, len) == 0) {
+               uri_prefix += len;
+
+               if (uri_prefix[0] == '/') {
+                       uri_prefix++;
+               } else if (uri_prefix[0] != '\0' &&
+                          (len < 4 ||
+                           strcmp (data->uri_prefix + len - 4, ":///") != 0)) {
+                       /* If the first char isn't an uri separator
+                        * nor \0, node represents a similarly named
+                        * file, but not a parent after all.
+                        */
+                       return FALSE;
+               }
+
+               if (uri_remainder) {
+                       *uri_remainder = uri_prefix;
+               }
+
+               return TRUE;
+       } else {
+               return FALSE;
+       }
+}
+
+static GNode *
+file_tree_lookup (GNode     *tree,
+                  GFile     *file,
+                  GNode    **parent_node,
+                  gchar    **uri_remainder)
+{
+       GNode *parent, *node_found, *parent_found;
+       FileNodeData *data;
+       gchar *uri, *ptr;
+
+       uri = ptr = g_file_get_uri (file);
+       node_found = parent_found = NULL;
+
+       /* Run through the filesystem tree, comparing chunks of
+        * uri with the uri prefix in the file nodes, this would
+        * get us to the closest registered parent, or the file
+        * itself.
+        */
+
+       if (parent_node) {
+               *parent_node = NULL;
+       }
+
+       if (uri_remainder) {
+               *uri_remainder = NULL;
+       }
+
+       if (!tree) {
+               return NULL;
+       }
+
+       if (!G_NODE_IS_ROOT (tree)) {
+               FileNodeData *parent_data;
+               gchar *parent_uri;
+
+               parent_data = tree->data;
+               parent_uri = g_file_get_uri (parent_data->file);
+
+               /* Sanity check */
+               if (!g_str_has_prefix (uri, parent_uri)) {
+                       g_free (parent_uri);
+                       return NULL;
+               }
+
+               ptr += strlen (parent_uri);
+
+               g_assert (ptr[0] == '/');
+               ptr++;
+
+               g_free (parent_uri);
+       } else {
+               /* First check the root node */
+               if (!file_node_data_equal_or_child (tree, uri, &ptr)) {
+                       g_free (uri);
+                       return NULL;
+               }
+
+               /* Second check there is no basename and if there isn't,
+                * then this node MUST be the closest registered node
+                * we can use for the uri. The difference here is that
+                * we return tree not NULL.
+                */
+               else if (ptr[0] == '\0') {
+                       g_free (uri);
+                       return tree;
+                }
+       }
+
+       parent = tree;
+
+       while (parent) {
+               GNode *child, *next = NULL;
+               gchar *ret_ptr;
+
+               for (child = g_node_first_child (parent);
+                    child != NULL;
+                    child = g_node_next_sibling (child)) {
+                       data = child->data;
+
+                       if (data->uri_prefix[0] != ptr[0])
+                               continue;
+
+                       if (file_node_data_equal_or_child (child, ptr, &ret_ptr)) {
+                               ptr = ret_ptr;
+                               next = child;
+                               break;
+                       }
+               }
+
+               if (next) {
+                       if (ptr[0] == '\0') {
+                               /* Exact match */
+                               node_found = next;
+                               parent_found = parent;
+                               break;
+                       } else {
+                               /* Descent down the child */
+                               parent = next;
+                       }
+               } else {
+                       parent_found = parent;
+                       break;
+               }
+       }
+
+       if (parent_node) {
+               *parent_node = parent_found;
+       }
+
+       if (ptr && *ptr && uri_remainder) {
+               *uri_remainder = g_strdup (ptr);
+       }
+
+       g_free (uri);
+
+       return node_found;
+}
+
+static gboolean
+file_tree_free_node_foreach (GNode    *node,
+                             gpointer  user_data)
+{
+       file_node_data_free (node->data, node);
+       return FALSE;
+}
+
+/* TrackerFileSystem implementation */
+
+static void
+file_system_finalize (GObject *object)
+{
+       TrackerFileSystemPrivate *priv;
+
+       priv = TRACKER_FILE_SYSTEM (object)->priv;
+
+       g_node_traverse (priv->file_tree,
+                        G_POST_ORDER,
+                        G_TRAVERSE_ALL, -1,
+                        file_tree_free_node_foreach,
+                        NULL);
+       g_node_destroy (priv->file_tree);
+
+       if (priv->root) {
+               g_object_unref (priv->root);
+       }
+
+       G_OBJECT_CLASS (tracker_file_system_parent_class)->finalize (object);
+}
+
+static void
+file_system_constructed (GObject *object)
+{
+       TrackerFileSystemPrivate *priv;
+       FileNodeData *root_data;
+
+       G_OBJECT_CLASS (tracker_file_system_parent_class)->constructed (object);
+
+       priv = TRACKER_FILE_SYSTEM (object)->priv;
+
+       if (priv->root == NULL) {
+               priv->root = g_file_new_for_uri ("file:///");
+       }
+
+       root_data = file_node_data_root_new (priv->root);
+       priv->file_tree = g_node_new (root_data);
+}
+
+static void
+file_system_get_property (GObject    *object,
+                          guint       prop_id,
+                          GValue     *value,
+                          GParamSpec *pspec)
+{
+       TrackerFileSystemPrivate *priv;
+
+       priv = TRACKER_FILE_SYSTEM (object)->priv;
+
+       switch (prop_id) {
+       case PROP_ROOT:
+               g_value_set_object (value, priv->root);
+               break;
+       default:
+               G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+               break;
+       }
+}
+
+static void
+file_system_set_property (GObject      *object,
+                          guint         prop_id,
+                          const GValue *value,
+                          GParamSpec   *pspec)
+{
+       TrackerFileSystemPrivate *priv;
+
+       priv = TRACKER_FILE_SYSTEM (object)->priv;
+
+       switch (prop_id) {
+       case PROP_ROOT:
+               priv->root = g_value_dup_object (value);
+               break;
+       default:
+               G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+               break;
+       }
+}
+
+static void
+tracker_file_system_class_init (TrackerFileSystemClass *klass)
+{
+       GObjectClass *object_class = G_OBJECT_CLASS (klass);
+
+       object_class->finalize = file_system_finalize;
+       object_class->constructed = file_system_constructed;
+       object_class->get_property = file_system_get_property;
+       object_class->set_property = file_system_set_property;
+
+       g_object_class_install_property (object_class,
+                                        PROP_ROOT,
+                                        g_param_spec_object ("root",
+                                                             "Root URL",
+                                                             "The root GFile for the indexing tree",
+                                                             G_TYPE_FILE,
+                                                             G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY));
+
+       g_type_class_add_private (object_class,
+                                 sizeof (TrackerFileSystemPrivate));
+
+       quark_file_node =
+               g_quark_from_static_string ("tracker-quark-file-node");
+}
+static void
+tracker_file_system_init (TrackerFileSystem *file_system)
+{
+       file_system->priv =
+               G_TYPE_INSTANCE_GET_PRIVATE (file_system,
+                                            TRACKER_TYPE_FILE_SYSTEM,
+                                            TrackerFileSystemPrivate);
+}
+
+TrackerFileSystem *
+tracker_file_system_new (GFile *root)
+{
+       return g_object_new (TRACKER_TYPE_FILE_SYSTEM,
+                            "root", root,
+                            NULL);
+}
+
+static void
+reparent_child_nodes_to_parent (GNode *node)
+{
+       FileNodeData *node_data;
+       GNode *child, *parent;
+
+       if (!node->parent) {
+               return;
+       }
+
+       parent = node->parent;
+       node_data = node->data;
+       child = g_node_first_child (node);
+
+       while (child) {
+               FileNodeData *data;
+               gchar *uri_prefix;
+               GNode *cur;
+
+               cur = child;
+               data = cur->data;
+               child = g_node_next_sibling (child);
+
+               uri_prefix = g_strdup_printf ("%s/%s",
+                                             node_data->uri_prefix,
+                                             data->uri_prefix);
+
+               g_free (data->uri_prefix);
+               data->uri_prefix = uri_prefix;
+
+               g_node_unlink (cur);
+               g_node_prepend (parent, cur);
+       }
+}
+
+static void
+file_weak_ref_notify (gpointer  user_data,
+                      GObject  *prev_location)
+{
+       FileNodeData *data;
+       GNode *node;
+
+       node = user_data;
+       data = node->data;
+
+       g_assert (data->file == (GFile *) prev_location);
+
+       data->file = NULL;
+       reparent_child_nodes_to_parent (node);
+
+       /* Delete node tree here */
+       file_node_data_free (data, NULL);
+       g_node_destroy (node);
+}
+
+static GNode *
+file_system_get_node (TrackerFileSystem *file_system,
+                      GFile             *file)
+{
+       TrackerFileSystemPrivate *priv;
+       GArray *node_data;
+       GNode *node = NULL;
+
+       node_data = g_object_get_qdata (G_OBJECT (file), quark_file_node);
+
+       if (node_data) {
+               NodeLookupData *cur;
+               guint i;
+
+               for (i = 0; i < node_data->len; i++) {
+                       cur = &g_array_index (node_data, NodeLookupData, i);
+
+                       if (cur->file_system == file_system) {
+                               node = cur->node;
+                       }
+               }
+       }
+
+       if (!node) {
+               priv = file_system->priv;
+               node = file_tree_lookup (priv->file_tree, file,
+                                        NULL, NULL);
+       }
+
+       return node;
+}
+
+GFile *
+tracker_file_system_get_file (TrackerFileSystem *file_system,
+                              GFile             *file,
+                              GFileType          file_type,
+                              GFile             *parent)
+{
+       TrackerFileSystemPrivate *priv;
+       FileNodeData *data;
+       GNode *node, *parent_node;
+       gchar *uri_prefix = NULL;
+
+       g_return_val_if_fail (G_IS_FILE (file), NULL);
+       g_return_val_if_fail (TRACKER_IS_FILE_SYSTEM (file_system), NULL);
+
+       priv = file_system->priv;
+       node = NULL;
+       parent_node = NULL;
+
+       if (parent) {
+               parent_node = file_system_get_node (file_system, parent);
+               node = file_tree_lookup (parent_node, file,
+                                        NULL, &uri_prefix);
+       } else {
+               node = file_tree_lookup (priv->file_tree, file,
+                                        &parent_node, &uri_prefix);
+       }
+
+       if (!node) {
+               if (!parent_node) {
+                       gchar *uri;
+
+                       uri = g_file_get_uri (file);
+                       g_warning ("Could not find parent node for URI:'%s'", uri);
+                       g_warning ("NOTE: URI theme may be outside scheme expected, for example, expecting 
'file://' when given 'http://' prefix.");
+                       g_free (uri);
+
+                       return NULL;
+               }
+
+               node = g_node_new (NULL);
+
+               /* Parent was found, add file as child */
+               data = file_node_data_new (file_system, file,
+                                          file_type, node);
+               data->uri_prefix = uri_prefix;
+
+               g_node_append (parent_node, node);
+       } else {
+               data = node->data;
+               g_free (uri_prefix);
+
+               /* Update file type if it was unknown */
+               if (data->file_type == G_FILE_TYPE_UNKNOWN) {
+                       data->file_type = file_type;
+               }
+       }
+
+       return data->file;
+}
+
+GFile *
+tracker_file_system_peek_file (TrackerFileSystem *file_system,
+                               GFile             *file)
+{
+       GNode *node;
+
+       g_return_val_if_fail (G_IS_FILE (file), NULL);
+       g_return_val_if_fail (TRACKER_IS_FILE_SYSTEM (file_system), NULL);
+
+       node = file_system_get_node (file_system, file);
+
+       if (node) {
+               FileNodeData *data;
+
+               data = node->data;
+               return data->file;
+       }
+
+       return NULL;
+}
+
+GFile *
+tracker_file_system_peek_parent (TrackerFileSystem *file_system,
+                                 GFile             *file)
+{
+       GNode *node;
+
+       g_return_val_if_fail (file != NULL, NULL);
+       g_return_val_if_fail (TRACKER_IS_FILE_SYSTEM (file_system), NULL);
+
+       node = file_system_get_node (file_system, file);
+
+       if (node) {
+               FileNodeData *parent_data;
+               GNode *parent;
+
+               parent = node->parent;
+               parent_data = parent->data;
+
+               return parent_data->file;
+       }
+
+       return NULL;
+}
+
+typedef struct {
+       TrackerFileSystemTraverseFunc func;
+       gpointer user_data;
+       GSList *ignore_children;
+} TraverseData;
+
+static gint
+node_is_child_of_ignored (gconstpointer a,
+                          gconstpointer b)
+{
+       if (g_node_is_ancestor ((GNode *) a, (GNode *) b))
+               return 0;
+
+       return 1;
+}
+
+static gboolean
+traverse_filesystem_func (GNode    *node,
+                          gpointer  user_data)
+{
+       TraverseData *data = user_data;
+       FileNodeData *node_data;
+       gboolean retval = FALSE;
+
+       node_data = node->data;
+
+       if (!data->ignore_children ||
+           !g_slist_find_custom (data->ignore_children,
+                                 node, node_is_child_of_ignored)) {
+               /* This node isn't a child of an
+                * ignored one, execute callback
+                */
+               retval = data->func (node_data->file, data->user_data);
+       }
+
+       /* Avoid recursing within the children of this node */
+       if (retval) {
+               data->ignore_children = g_slist_prepend (data->ignore_children,
+                                                        node);
+       }
+
+       return FALSE;
+}
+
+void
+tracker_file_system_traverse (TrackerFileSystem             *file_system,
+                              GFile                         *root,
+                              GTraverseType                  order,
+                              TrackerFileSystemTraverseFunc  func,
+                              gint                           max_depth,
+                              gpointer                       user_data)
+{
+       TrackerFileSystemPrivate *priv;
+       TraverseData data;
+       GNode *node;
+
+       g_return_if_fail (TRACKER_IS_FILE_SYSTEM (file_system));
+       g_return_if_fail (func != NULL);
+
+       priv = file_system->priv;
+
+       if (root) {
+               node = file_system_get_node (file_system, root);
+       } else {
+               node = priv->file_tree;
+       }
+
+       data.func = func;
+       data.user_data = user_data;
+       data.ignore_children = NULL;
+
+       g_node_traverse (node,
+                        order,
+                        G_TRAVERSE_ALL,
+                        max_depth,
+                        traverse_filesystem_func,
+                        &data);
+
+       g_slist_free (data.ignore_children);
+}
+
+void
+tracker_file_system_register_property (GQuark             prop,
+                                       GDestroyNotify     destroy_notify)
+{
+       g_return_if_fail (prop != 0);
+
+       if (!properties) {
+               properties = g_hash_table_new (NULL, NULL);
+       }
+
+       if (g_hash_table_contains (properties, GUINT_TO_POINTER (prop))) {
+               g_warning ("FileSystem: property '%s' has been already registered",
+                          g_quark_to_string (prop));
+               return;
+       }
+
+       g_hash_table_insert (properties,
+                            GUINT_TO_POINTER (prop),
+                            destroy_notify);
+}
+
+static int
+search_property_node (gconstpointer key,
+                      gconstpointer item)
+{
+       const FileNodeProperty *key_prop, *prop;
+
+       key_prop = key;
+       prop = item;
+
+       if (key_prop->prop_quark < prop->prop_quark)
+               return -1;
+       else if (key_prop->prop_quark > prop->prop_quark)
+               return 1;
+
+       return 0;
+}
+
+void
+tracker_file_system_set_property (TrackerFileSystem *file_system,
+                                  GFile             *file,
+                                  GQuark             prop,
+                                  gpointer           prop_data)
+{
+       FileNodeProperty property, *match;
+       GDestroyNotify destroy_notify;
+       FileNodeData *data;
+       GNode *node;
+
+       g_return_if_fail (TRACKER_IS_FILE_SYSTEM (file_system));
+       g_return_if_fail (file != NULL);
+       g_return_if_fail (prop != 0);
+
+       if (!properties ||
+           !g_hash_table_lookup_extended (properties,
+                                          GUINT_TO_POINTER (prop),
+                                          NULL, (gpointer *) &destroy_notify)) {
+               g_warning ("FileSystem: property '%s' is not registered",
+                          g_quark_to_string (prop));
+               return;
+       }
+
+       node = file_system_get_node (file_system, file);
+       g_return_if_fail (node != NULL);
+
+       data = node->data;
+
+       property.prop_quark = prop;
+       match = bsearch (&property, data->properties->data,
+                        data->properties->len, sizeof (FileNodeProperty),
+                        search_property_node);
+
+       if (match) {
+               if (destroy_notify) {
+                       (destroy_notify) (match->value);
+               }
+
+               match->value = prop_data;
+       } else {
+               FileNodeProperty *item;
+               guint i;
+
+               /* No match, insert new element */
+               for (i = 0; i < data->properties->len; i++) {
+                       item = &g_array_index (data->properties,
+                                              FileNodeProperty, i);
+
+                       if (item->prop_quark > prop) {
+                               break;
+                       }
+               }
+
+               property.value = prop_data;
+
+               if (i >= data->properties->len) {
+                       g_array_append_val (data->properties, property);
+               } else {
+                       g_array_insert_val (data->properties, i, property);
+               }
+       }
+}
+
+gboolean
+tracker_file_system_get_property_full (TrackerFileSystem *file_system,
+                                       GFile             *file,
+                                       GQuark             prop,
+                                       gpointer          *prop_data)
+{
+       FileNodeData *data;
+       FileNodeProperty property, *match;
+       GNode *node;
+
+       g_return_val_if_fail (TRACKER_IS_FILE_SYSTEM (file_system), FALSE);
+       g_return_val_if_fail (file != NULL, FALSE);
+       g_return_val_if_fail (prop > 0, FALSE);
+
+       node = file_system_get_node (file_system, file);
+       g_return_val_if_fail (node != NULL, FALSE);
+
+       data = node->data;
+       property.prop_quark = prop;
+
+       match = bsearch (&property, data->properties->data,
+                        data->properties->len, sizeof (FileNodeProperty),
+                        search_property_node);
+
+       if (prop_data)
+               *prop_data = (match) ? match->value : NULL;
+
+       return match != NULL;
+}
+
+gpointer
+tracker_file_system_get_property (TrackerFileSystem *file_system,
+                                  GFile             *file,
+                                  GQuark             prop)
+{
+       gpointer data;
+
+       g_return_val_if_fail (TRACKER_IS_FILE_SYSTEM (file_system), NULL);
+       g_return_val_if_fail (file != NULL, NULL);
+       g_return_val_if_fail (prop > 0, NULL);
+
+       tracker_file_system_get_property_full (file_system, file, prop, &data);
+
+       return data;
+}
+
+void
+tracker_file_system_unset_property (TrackerFileSystem *file_system,
+                                    GFile             *file,
+                                    GQuark             prop)
+{
+       FileNodeData *data;
+       FileNodeProperty property, *match;
+       GDestroyNotify destroy_notify = NULL;
+       GNode *node;
+       guint index;
+
+       g_return_if_fail (TRACKER_IS_FILE_SYSTEM (file_system));
+       g_return_if_fail (file != NULL);
+       g_return_if_fail (prop > 0);
+
+       if (!properties ||
+           !g_hash_table_lookup_extended (properties,
+                                          GUINT_TO_POINTER (prop),
+                                          NULL,
+                                          (gpointer *) &destroy_notify)) {
+               g_warning ("FileSystem: property '%s' is not registered",
+                          g_quark_to_string (prop));
+       }
+
+       node = file_system_get_node (file_system, file);
+       g_return_if_fail (node != NULL);
+
+       data = node->data;
+       property.prop_quark = prop;
+
+       match = bsearch (&property, data->properties->data,
+                        data->properties->len, sizeof (FileNodeProperty),
+                        search_property_node);
+
+       if (!match) {
+               return;
+       }
+
+       if (destroy_notify) {
+               (destroy_notify) (match->value);
+       }
+
+       /* Find out the index from memory positions */
+       index = (guint) ((FileNodeProperty *) match -
+                        (FileNodeProperty *) data->properties->data);
+       g_assert (index < data->properties->len);
+
+       g_array_remove_index (data->properties, index);
+}
+
+gpointer
+tracker_file_system_steal_property (TrackerFileSystem *file_system,
+                                    GFile             *file,
+                                    GQuark             prop)
+{
+       FileNodeData *data;
+       FileNodeProperty property, *match;
+       GNode *node;
+       guint index;
+       gpointer prop_value;
+
+       g_return_val_if_fail (TRACKER_IS_FILE_SYSTEM (file_system), NULL);
+       g_return_val_if_fail (file != NULL, NULL);
+       g_return_val_if_fail (prop > 0, NULL);
+
+       node = file_system_get_node (file_system, file);
+       g_return_val_if_fail (node != NULL, NULL);
+
+       data = node->data;
+       property.prop_quark = prop;
+
+       match = bsearch (&property, data->properties->data,
+                        data->properties->len, sizeof (FileNodeProperty),
+                        search_property_node);
+
+       if (!match) {
+               return NULL;
+       }
+
+       prop_value = match->value;
+
+       /* Find out the index from memory positions */
+       index = (guint) ((FileNodeProperty *) match -
+                        (FileNodeProperty *) data->properties->data);
+       g_assert (index < data->properties->len);
+
+       g_array_remove_index (data->properties, index);
+
+       return prop_value;
+}
+
+typedef struct {
+       TrackerFileSystem *file_system;
+       GList *list;
+       GFileType file_type;
+} ForgetFilesData;
+
+static gboolean
+append_deleted_files (GNode    *node,
+                     gpointer  user_data)
+{
+       ForgetFilesData *data;
+       FileNodeData *node_data;
+
+       data = user_data;
+       node_data = node->data;
+
+       if (data->file_type == G_FILE_TYPE_UNKNOWN ||
+           node_data->file_type == data->file_type) {
+               data->list = g_list_prepend (data->list, node_data);
+       }
+
+       return FALSE;
+}
+
+static void
+forget_file (FileNodeData *node_data)
+{
+       if (!node_data->unowned) {
+               node_data->unowned = TRUE;
+
+               /* Weak reference handler will remove the file from the tree and
+                * clean up node_data if this is the final reference.
+                */
+               g_object_unref (node_data->file);
+       }
+}
+
+void
+tracker_file_system_forget_files (TrackerFileSystem *file_system,
+                                 GFile             *root,
+                                 GFileType          file_type)
+{
+       ForgetFilesData data = { file_system, NULL, file_type };
+       GNode *node;
+
+       g_return_if_fail (TRACKER_IS_FILE_SYSTEM (file_system));
+       g_return_if_fail (G_IS_FILE (root));
+
+       node = file_system_get_node (file_system, root);
+       g_return_if_fail (node != NULL);
+
+       /* We need to get the files to delete into a list, so
+        * the node tree isn't modified during traversal.
+        */
+       g_node_traverse (node,
+                        G_PRE_ORDER,
+                        (file_type == G_FILE_TYPE_REGULAR) ?
+                          G_TRAVERSE_LEAVES : G_TRAVERSE_ALL,
+                        -1, append_deleted_files,
+                        &data);
+
+       g_list_foreach (data.list, (GFunc) forget_file, NULL);
+       g_list_free (data.list);
+}
+
+GFileType
+tracker_file_system_get_file_type (TrackerFileSystem *file_system,
+                                   GFile             *file)
+{
+       GFileType file_type = G_FILE_TYPE_UNKNOWN;
+       GNode *node;
+
+       g_return_val_if_fail (TRACKER_IS_FILE_SYSTEM (file_system), file_type);
+       g_return_val_if_fail (G_IS_FILE (file), file_type);
+
+       node = file_system_get_node (file_system, file);
+
+       if (node) {
+               FileNodeData *node_data;
+
+               node_data = node->data;
+               file_type = node_data->file_type;
+       }
+
+       return file_type;
+}
diff --git a/src/libtracker-miner/tracker-file-system.h b/src/libtracker-miner/tracker-file-system.h
new file mode 100644
index 000000000..dc3e72474
--- /dev/null
+++ b/src/libtracker-miner/tracker-file-system.h
@@ -0,0 +1,107 @@
+/*
+ * Copyright (C) 2011, Nokia <ivan frade nokia com>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ * Boston, MA  02110-1301, USA.
+ *
+ * Author: Carlos Garnacho  <carlos lanedo com>
+ */
+
+#ifndef __TRACKER_FILE_SYSTEM_H__
+#define __TRACKER_FILE_SYSTEM_H__
+
+#if !defined (__LIBTRACKER_MINER_H_INSIDE__) && !defined (TRACKER_COMPILATION)
+#error "Only <libtracker-miner/tracker-miner.h> can be included directly."
+#endif
+
+#include <gio/gio.h>
+
+G_BEGIN_DECLS
+
+#define TRACKER_TYPE_FILE_SYSTEM         (tracker_file_system_get_type())
+#define TRACKER_FILE_SYSTEM(o)           (G_TYPE_CHECK_INSTANCE_CAST ((o), TRACKER_TYPE_FILE_SYSTEM, 
TrackerFileSystem))
+#define TRACKER_FILE_SYSTEM_CLASS(c)     (G_TYPE_CHECK_CLASS_CAST ((c),    TRACKER_TYPE_FILE_SYSTEM, 
TrackerFileSystemClass))
+#define TRACKER_IS_FILE_SYSTEM(o)        (G_TYPE_CHECK_INSTANCE_TYPE ((o), TRACKER_TYPE_FILE_SYSTEM))
+#define TRACKER_IS_FILE_SYSTEM_CLASS(c)  (G_TYPE_CHECK_CLASS_TYPE ((c),    TRACKER_TYPE_FILE_SYSTEM))
+#define TRACKER_FILE_SYSTEM_GET_CLASS(o) (G_TYPE_INSTANCE_GET_CLASS ((o),  TRACKER_TYPE_FILE_SYSTEM, 
TrackerFileSystemClass))
+
+typedef struct _TrackerFileSystem TrackerFileSystem;
+typedef struct _TrackerFileSystemClass TrackerFileSystemClass;
+
+struct _TrackerFileSystem {
+       GObject parent_instance;
+       gpointer priv;
+};
+
+struct _TrackerFileSystemClass {
+       GObjectClass parent_class;
+};
+
+typedef gboolean (* TrackerFileSystemTraverseFunc) (GFile    *file,
+                                                    gpointer  user_data);
+
+GType      tracker_file_system_get_type      (void) G_GNUC_CONST;
+
+TrackerFileSystem *
+              tracker_file_system_new            (GFile              *root);
+
+GFile *       tracker_file_system_get_file       (TrackerFileSystem  *file_system,
+                                                  GFile              *file,
+                                                  GFileType           file_type,
+                                                  GFile              *parent);
+GFile *       tracker_file_system_peek_file      (TrackerFileSystem  *file_system,
+                                                  GFile              *file);
+GFile *       tracker_file_system_peek_parent    (TrackerFileSystem  *file_system,
+                                                  GFile              *file);
+
+void          tracker_file_system_traverse       (TrackerFileSystem             *file_system,
+                                                  GFile                         *root,
+                                                  GTraverseType                  order,
+                                                  TrackerFileSystemTraverseFunc  func,
+                                                  gint                           max_depth,
+                                                  gpointer                       user_data);
+
+void          tracker_file_system_forget_files   (TrackerFileSystem *file_system,
+                                                 GFile             *root,
+                                                 GFileType          file_type);
+
+GFileType     tracker_file_system_get_file_type  (TrackerFileSystem  *file_system,
+                                                  GFile              *file);
+/* properties */
+void      tracker_file_system_register_property (GQuark             prop,
+                                                 GDestroyNotify     destroy_notify);
+
+void      tracker_file_system_set_property   (TrackerFileSystem  *file_system,
+                                              GFile              *file,
+                                              GQuark              prop,
+                                              gpointer            prop_data);
+gpointer  tracker_file_system_get_property   (TrackerFileSystem  *file_system,
+                                              GFile              *file,
+                                              GQuark              prop);
+void      tracker_file_system_unset_property (TrackerFileSystem  *file_system,
+                                              GFile              *file,
+                                              GQuark              prop);
+gpointer  tracker_file_system_steal_property (TrackerFileSystem *file_system,
+                                              GFile             *file,
+                                              GQuark             prop);
+
+gboolean  tracker_file_system_get_property_full (TrackerFileSystem *file_system,
+                                                 GFile             *file,
+                                                 GQuark             prop,
+                                                 gpointer          *data);
+
+G_END_DECLS
+
+#endif /* __TRACKER_FILE_SYSTEM_H__ */
diff --git a/src/libtracker-miner/tracker-indexing-tree.c b/src/libtracker-miner/tracker-indexing-tree.c
new file mode 100644
index 000000000..d5a098a1b
--- /dev/null
+++ b/src/libtracker-miner/tracker-indexing-tree.c
@@ -0,0 +1,1218 @@
+/*
+ * Copyright (C) 2011, Nokia <ivan frade nokia com>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ * Boston, MA  02110-1301, USA.
+ *
+ * Author: Carlos Garnacho  <carlos lanedo com>
+ */
+
+#include <libtracker-miners-common/tracker-file-utils.h>
+#include "tracker-indexing-tree.h"
+
+/**
+ * SECTION:tracker-indexing-tree
+ * @short_description: Indexing tree handling
+ *
+ * #TrackerIndexingTree handles the tree of directories configured to be indexed
+ * by the #TrackerMinerFS.
+ **/
+
+G_DEFINE_TYPE (TrackerIndexingTree, tracker_indexing_tree, G_TYPE_OBJECT)
+
+typedef struct _TrackerIndexingTreePrivate TrackerIndexingTreePrivate;
+typedef struct _NodeData NodeData;
+typedef struct _PatternData PatternData;
+typedef struct _FindNodeData FindNodeData;
+
+struct _NodeData
+{
+       GFile *file;
+       guint flags;
+       guint shallow : 1;
+       guint removing : 1;
+};
+
+struct _PatternData
+{
+       GPatternSpec *pattern;
+       TrackerFilterType type;
+       GFile *file; /* Only filled in in absolute paths */
+};
+
+struct _FindNodeData
+{
+       GEqualFunc func;
+       GNode *node;
+       GFile *file;
+};
+
+struct _TrackerIndexingTreePrivate
+{
+       GNode *config_tree;
+       GList *filter_patterns;
+       TrackerFilterPolicy policies[TRACKER_FILTER_PARENT_DIRECTORY + 1];
+
+       GFile *root;
+       guint filter_hidden : 1;
+};
+
+enum {
+       PROP_0,
+       PROP_ROOT,
+       PROP_FILTER_HIDDEN
+};
+
+enum {
+       DIRECTORY_ADDED,
+       DIRECTORY_REMOVED,
+       DIRECTORY_UPDATED,
+       CHILD_UPDATED,
+       LAST_SIGNAL
+};
+
+static guint signals[LAST_SIGNAL] = { 0 };
+
+static NodeData *
+node_data_new (GFile *file,
+               guint  flags)
+{
+       NodeData *data;
+
+       data = g_slice_new0 (NodeData);
+       data->file = g_object_ref (file);
+       data->flags = flags;
+
+       return data;
+}
+
+static void
+node_data_free (NodeData *data)
+{
+       g_object_unref (data->file);
+       g_slice_free (NodeData, data);
+}
+
+static gboolean
+node_free (GNode    *node,
+           gpointer  user_data)
+{
+       node_data_free (node->data);
+       return FALSE;
+}
+
+static PatternData *
+pattern_data_new (const gchar *glob_string,
+                  guint        type)
+{
+       PatternData *data;
+
+       data = g_slice_new0 (PatternData);
+       data->pattern = g_pattern_spec_new (glob_string);
+       data->type = type;
+
+       if (g_path_is_absolute (glob_string)) {
+               data->file = g_file_new_for_path (glob_string);
+       }
+
+       return data;
+}
+
+static void
+pattern_data_free (PatternData *data)
+{
+       if (data->file) {
+               g_object_unref (data->file);
+       }
+
+       g_pattern_spec_free (data->pattern);
+       g_slice_free (PatternData, data);
+}
+
+static void
+tracker_indexing_tree_get_property (GObject    *object,
+                                    guint       prop_id,
+                                    GValue     *value,
+                                    GParamSpec *pspec)
+{
+       TrackerIndexingTreePrivate *priv;
+
+       priv = TRACKER_INDEXING_TREE (object)->priv;
+
+       switch (prop_id) {
+       case PROP_ROOT:
+               g_value_set_object (value, priv->root);
+               break;
+       case PROP_FILTER_HIDDEN:
+               g_value_set_boolean (value, priv->filter_hidden);
+               break;
+       default:
+               G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+               break;
+       }
+}
+
+static void
+tracker_indexing_tree_set_property (GObject      *object,
+                                    guint         prop_id,
+                                    const GValue *value,
+                                    GParamSpec   *pspec)
+{
+       TrackerIndexingTree *tree;
+       TrackerIndexingTreePrivate *priv;
+
+       tree = TRACKER_INDEXING_TREE (object);
+       priv = tree->priv;
+
+       switch (prop_id) {
+       case PROP_ROOT:
+               priv->root = g_value_dup_object (value);
+               break;
+       case PROP_FILTER_HIDDEN:
+               tracker_indexing_tree_set_filter_hidden (tree,
+                                                        g_value_get_boolean (value));
+               break;
+       default:
+               G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+               break;
+       }
+}
+
+static void
+tracker_indexing_tree_constructed (GObject *object)
+{
+       TrackerIndexingTree *tree;
+       TrackerIndexingTreePrivate *priv;
+       NodeData *data;
+
+       G_OBJECT_CLASS (tracker_indexing_tree_parent_class)->constructed (object);
+
+       tree = TRACKER_INDEXING_TREE (object);
+       priv = tree->priv;
+
+       /* Add a shallow root node */
+       if (priv->root == NULL) {
+               priv->root = g_file_new_for_uri ("file:///");
+       }
+
+       data = node_data_new (priv->root, 0);
+       data->shallow = TRUE;
+
+       priv->config_tree = g_node_new (data);
+}
+
+static void
+tracker_indexing_tree_finalize (GObject *object)
+{
+       TrackerIndexingTreePrivate *priv;
+       TrackerIndexingTree *tree;
+
+       tree = TRACKER_INDEXING_TREE (object);
+       priv = tree->priv;
+
+       g_list_foreach (priv->filter_patterns, (GFunc) pattern_data_free, NULL);
+       g_list_free (priv->filter_patterns);
+
+       g_node_traverse (priv->config_tree,
+                        G_POST_ORDER,
+                        G_TRAVERSE_ALL,
+                        -1,
+                        (GNodeTraverseFunc) node_free,
+                        NULL);
+       g_node_destroy (priv->config_tree);
+
+       if (priv->root) {
+               g_object_unref (priv->root);
+       }
+
+       G_OBJECT_CLASS (tracker_indexing_tree_parent_class)->finalize (object);
+}
+
+static void
+tracker_indexing_tree_class_init (TrackerIndexingTreeClass *klass)
+{
+       GObjectClass *object_class = G_OBJECT_CLASS (klass);
+
+       object_class->finalize = tracker_indexing_tree_finalize;
+       object_class->constructed = tracker_indexing_tree_constructed;
+       object_class->set_property = tracker_indexing_tree_set_property;
+       object_class->get_property = tracker_indexing_tree_get_property;
+
+       g_object_class_install_property (object_class,
+                                        PROP_ROOT,
+                                        g_param_spec_object ("root",
+                                                             "Root URL",
+                                                             "The root GFile for the indexing tree",
+                                                             G_TYPE_FILE,
+                                                             G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY));
+
+       g_object_class_install_property (object_class,
+                                        PROP_FILTER_HIDDEN,
+                                        g_param_spec_boolean ("filter-hidden",
+                                                              "Filter hidden",
+                                                              "Whether hidden resources are filtered",
+                                                              FALSE,
+                                                              G_PARAM_READWRITE));
+       /**
+        * TrackerIndexingTree::directory-added:
+        * @indexing_tree: a #TrackerIndexingTree
+        * @directory: a #GFile
+        *
+        * the ::directory-added signal is emitted when a new
+        * directory is added to the list of other directories which
+        * are to be considered for indexing. Typically this is
+        * signalled when the tracker_indexing_tree_add() API is
+        * called.
+        *
+        * Since: 0.14.0
+        **/
+       signals[DIRECTORY_ADDED] =
+               g_signal_new ("directory-added",
+                             G_OBJECT_CLASS_TYPE (object_class),
+                             G_SIGNAL_RUN_LAST,
+                             G_STRUCT_OFFSET (TrackerIndexingTreeClass,
+                                              directory_added),
+                             NULL, NULL,
+                             NULL,
+                             G_TYPE_NONE, 1, G_TYPE_FILE);
+
+       /**
+        * TrackerIndexingTree::directory-removed:
+        * @indexing_tree: a #TrackerIndexingTree
+        * @directory: a #GFile
+        *
+        * the ::directory-removed signal is emitted when a
+        * directory is removed from the list of other directories
+        * which are to be considered for indexing. Typically this is
+        * signalled when the tracker_indexing_tree_remove() API is
+        * called.
+        *
+        * Since: 0.14.0
+        **/
+       signals[DIRECTORY_REMOVED] =
+               g_signal_new ("directory-removed",
+                             G_OBJECT_CLASS_TYPE (object_class),
+                             G_SIGNAL_RUN_LAST,
+                             G_STRUCT_OFFSET (TrackerIndexingTreeClass,
+                                              directory_removed),
+                             NULL, NULL,
+                             NULL,
+                             G_TYPE_NONE, 1, G_TYPE_FILE);
+
+       /**
+        * TrackerIndexingTree::directory-updated:
+        * @indexing_tree: a #TrackerIndexingTree
+        * @directory: a #GFile
+        *
+        * The ::directory-updated signal is emitted on a root
+        * when either its indexing flags change (e.g. due to consecutive
+        * calls to tracker_indexing_tree_add()), or anytime an update is
+        * requested through tracker_indexing_tree_notify_update().
+        *
+        * Since: 0.14.0
+        **/
+       signals[DIRECTORY_UPDATED] =
+               g_signal_new ("directory-updated",
+                             G_OBJECT_CLASS_TYPE (object_class),
+                             G_SIGNAL_RUN_LAST,
+                             G_STRUCT_OFFSET (TrackerIndexingTreeClass,
+                                              directory_updated),
+                             NULL, NULL,
+                             NULL,
+                             G_TYPE_NONE, 1, G_TYPE_FILE);
+
+       /**
+        * TrackerIndexingTree::child-updated:
+        * @indexing_tree: a #TrackerIndexingTree
+        * @root: the root of this child
+        * @child: the updated child
+        *
+        * The ::child-updated signal may be emitted to notify
+        * about possible changes on children of a root.
+        *
+        * #TrackerIndexingTree does not emit those by itself,
+        * those may be triggered through tracker_indexing_tree_notify_update().
+        *
+        * Since: 1.10
+        **/
+       signals[CHILD_UPDATED] =
+               g_signal_new ("child-updated",
+                             G_OBJECT_CLASS_TYPE (object_class),
+                             G_SIGNAL_RUN_LAST,
+                             G_STRUCT_OFFSET (TrackerIndexingTreeClass,
+                                              child_updated),
+                             NULL, NULL,
+                             NULL,
+                             G_TYPE_NONE, 2, G_TYPE_FILE, G_TYPE_FILE);
+
+       g_type_class_add_private (object_class,
+                                 sizeof (TrackerIndexingTreePrivate));
+}
+
+static void
+tracker_indexing_tree_init (TrackerIndexingTree *tree)
+{
+       TrackerIndexingTreePrivate *priv;
+       gint i;
+
+       priv = tree->priv = G_TYPE_INSTANCE_GET_PRIVATE (tree,
+                                                        TRACKER_TYPE_INDEXING_TREE,
+                                                        TrackerIndexingTreePrivate);
+
+       for (i = TRACKER_FILTER_FILE; i <= TRACKER_FILTER_PARENT_DIRECTORY; i++) {
+               priv->policies[i] = TRACKER_FILTER_POLICY_ACCEPT;
+       }
+}
+
+/**
+ * tracker_indexing_tree_new:
+ *
+ * Returns a newly created #TrackerIndexingTree
+ *
+ * Returns: a newly allocated #TrackerIndexingTree
+ *
+ * Since: 0.14.0
+ **/
+TrackerIndexingTree *
+tracker_indexing_tree_new (void)
+{
+       return g_object_new (TRACKER_TYPE_INDEXING_TREE, NULL);
+}
+
+/**
+ * tracker_indexing_tree_new_with_root:
+ * @root: The top level URL
+ *
+ * If @root is %NULL, the default value is 'file:///'. Using %NULL
+ * here is the equivalent to calling tracker_indexing_tree_new() which
+ * takes no @root argument.
+ *
+ * Returns: a newly allocated #TrackerIndexingTree
+ *
+ * Since: 1.2.2
+ **/
+TrackerIndexingTree *
+tracker_indexing_tree_new_with_root (GFile *root)
+{
+       return g_object_new (TRACKER_TYPE_INDEXING_TREE,
+                            "root", root,
+                            NULL);
+}
+
+#ifdef PRINT_INDEXING_TREE
+static gboolean
+print_node_foreach (GNode    *node,
+                    gpointer  user_data)
+{
+       NodeData *node_data = node->data;
+       gchar *uri;
+
+       uri = g_file_get_uri (node_data->file);
+       g_debug ("%*s %s", g_node_depth (node), "-", uri);
+       g_free (uri);
+
+       return FALSE;
+}
+
+static void
+print_tree (GNode *node)
+{
+       g_debug ("Printing modified tree...");
+       g_node_traverse (node,
+                        G_PRE_ORDER,
+                        G_TRAVERSE_ALL,
+                        -1,
+                        print_node_foreach,
+                        NULL);
+}
+
+#endif /* PRINT_INDEXING_TREE */
+
+static gboolean
+find_node_foreach (GNode    *node,
+                   gpointer  user_data)
+{
+       FindNodeData *data = user_data;
+       NodeData *node_data = node->data;
+
+       if ((data->func) (data->file, node_data->file)) {
+               data->node = node;
+               return TRUE;
+       }
+
+       return FALSE;
+}
+
+static GNode *
+find_directory_node (GNode      *node,
+                     GFile      *file,
+                     GEqualFunc  func)
+{
+       FindNodeData data;
+
+       data.file = file;
+       data.node = NULL;
+       data.func = func;
+
+       g_node_traverse (node,
+                        G_POST_ORDER,
+                        G_TRAVERSE_ALL,
+                        -1,
+                        find_node_foreach,
+                        &data);
+
+       return data.node;
+}
+
+static void
+check_reparent_node (GNode    *node,
+                     gpointer  user_data)
+{
+       GNode *new_node = user_data;
+       NodeData *new_node_data, *node_data;
+
+       new_node_data = new_node->data;
+       node_data = node->data;
+
+       if (g_file_has_prefix (node_data->file,
+                              new_node_data->file)) {
+               g_node_unlink (node);
+               g_node_append (new_node, node);
+       }
+}
+
+/**
+ * tracker_indexing_tree_add:
+ * @tree: a #TrackerIndexingTree
+ * @directory: #GFile pointing to a directory
+ * @flags: Configuration flags for the directory
+ *
+ * Adds a directory to the indexing tree with the
+ * given configuration flags.
+ **/
+void
+tracker_indexing_tree_add (TrackerIndexingTree   *tree,
+                           GFile                 *directory,
+                           TrackerDirectoryFlags  flags)
+{
+       TrackerIndexingTreePrivate *priv;
+       GNode *parent, *node;
+       NodeData *data;
+
+       g_return_if_fail (TRACKER_IS_INDEXING_TREE (tree));
+       g_return_if_fail (G_IS_FILE (directory));
+
+       priv = tree->priv;
+       node = find_directory_node (priv->config_tree, directory,
+                                   (GEqualFunc) g_file_equal);
+
+       if (node) {
+               /* Node already existed */
+               data = node->data;
+               data->shallow = FALSE;
+
+               /* Overwrite flags if they are different */
+               if (data->flags != flags) {
+                       gchar *uri;
+
+                       uri = g_file_get_uri (directory);
+                       g_message ("Overwriting flags for directory '%s'", uri);
+                       g_free (uri);
+
+                       data->flags = flags;
+                       g_signal_emit (tree, signals[DIRECTORY_UPDATED], 0,
+                                      data->file);
+               }
+               return;
+       }
+
+       /* Find out the parent */
+       parent = find_directory_node (priv->config_tree, directory,
+                                     (GEqualFunc) g_file_has_prefix);
+
+       /* Create node, move children of parent that
+        * could be children of this new node now.
+        */
+       data = node_data_new (directory, flags);
+       node = g_node_new (data);
+
+       g_node_children_foreach (parent, G_TRAVERSE_ALL,
+                                check_reparent_node, node);
+
+       /* Add the new node underneath the parent */
+       g_node_append (parent, node);
+
+       g_signal_emit (tree, signals[DIRECTORY_ADDED], 0, directory);
+
+#ifdef PRINT_INDEXING_TREE
+       /* Print tree */
+       print_tree (priv->config_tree);
+#endif /* PRINT_INDEXING_TREE */
+}
+
+/**
+ * tracker_indexing_tree_remove:
+ * @tree: a #TrackerIndexingTree
+ * @directory: #GFile pointing to a directory
+ *
+ * Removes @directory from the indexing tree, note that
+ * only directories previously added with tracker_indexing_tree_add()
+ * can be effectively removed.
+ **/
+void
+tracker_indexing_tree_remove (TrackerIndexingTree *tree,
+                              GFile               *directory)
+{
+       TrackerIndexingTreePrivate *priv;
+       GNode *node, *parent;
+       NodeData *data;
+
+       g_return_if_fail (TRACKER_IS_INDEXING_TREE (tree));
+       g_return_if_fail (G_IS_FILE (directory));
+
+       priv = tree->priv;
+       node = find_directory_node (priv->config_tree, directory,
+                                   (GEqualFunc) g_file_equal);
+       if (!node) {
+               return;
+       }
+
+       data = node->data;
+
+       if (data->removing) {
+               return;
+       }
+
+       data->removing = TRUE;
+
+       if (!node->parent) {
+               /* Node is the config tree
+                * root, mark as shallow again
+                */
+               data->shallow = TRUE;
+               return;
+       }
+
+       g_signal_emit (tree, signals[DIRECTORY_REMOVED], 0, data->file);
+
+       parent = node->parent;
+       g_node_unlink (node);
+
+       /* Move children to parent */
+       g_node_children_foreach (node, G_TRAVERSE_ALL,
+                                check_reparent_node, parent);
+
+       node_data_free (node->data);
+       g_node_destroy (node);
+}
+
+/**
+ * tracker_indexing_tree_notify_update:
+ * @tree: a #TrackerIndexingTree
+ * @file: a #GFile
+ * @recursive: Whether contained indexing roots are affected by the update
+ *
+ * Signals either #TrackerIndexingTree::directory-updated or
+ * #TrackerIndexingTree::child-updated on the given file and
+ * returns #TRUE. If @file is not indexed according to the
+ * #TrackerIndexingTree, #FALSE is returned.
+ *
+ * If @recursive is #TRUE, #TrackerIndexingTree::directory-updated
+ * will be emitted on the indexing roots that are contained in @file.
+ *
+ * Returns: #TRUE if a signal is emitted.
+ *
+ * Since: 1.10
+ **/
+gboolean
+tracker_indexing_tree_notify_update (TrackerIndexingTree *tree,
+                                     GFile               *file,
+                                     gboolean             recursive)
+{
+       TrackerDirectoryFlags flags;
+       gboolean emitted = FALSE;
+       GFile *root;
+
+       g_return_val_if_fail (TRACKER_IS_INDEXING_TREE (tree), FALSE);
+       g_return_val_if_fail (G_IS_FILE (file), FALSE);
+
+       root = tracker_indexing_tree_get_root (tree, file, &flags);
+
+       if (tracker_indexing_tree_file_is_root (tree, file)) {
+               g_signal_emit (tree, signals[DIRECTORY_UPDATED], 0, root);
+               emitted = TRUE;
+       } else if (root &&
+                  ((flags & TRACKER_DIRECTORY_FLAG_RECURSE) ||
+                   g_file_has_parent (file, root))) {
+               g_signal_emit (tree, signals[CHILD_UPDATED], 0, root, file);
+               emitted = TRUE;
+       }
+
+       if (recursive) {
+               GList *roots, *l;
+
+               roots = tracker_indexing_tree_list_roots (tree);
+
+               for (l = roots; l; l = l->next) {
+                       if (!g_file_has_prefix (l->data, file))
+                               continue;
+
+                       g_signal_emit (tree, signals[DIRECTORY_UPDATED], 0, l->data);
+                       emitted = TRUE;
+               }
+
+               g_list_free (roots);
+       }
+
+       return emitted;
+}
+
+/**
+ * tracker_indexing_tree_add_filter:
+ * @tree: a #TrackerIndexingTree
+ * @filter: filter type
+ * @glob_string: glob-style string for the filter
+ *
+ * Adds a new filter for basenames.
+ **/
+void
+tracker_indexing_tree_add_filter (TrackerIndexingTree *tree,
+                                  TrackerFilterType    filter,
+                                  const gchar         *glob_string)
+{
+       TrackerIndexingTreePrivate *priv;
+       PatternData *data;
+
+       g_return_if_fail (TRACKER_IS_INDEXING_TREE (tree));
+       g_return_if_fail (glob_string != NULL);
+
+       priv = tree->priv;
+
+       data = pattern_data_new (glob_string, filter);
+       priv->filter_patterns = g_list_prepend (priv->filter_patterns, data);
+}
+
+/**
+ * tracker_indexing_tree_clear_filters:
+ * @tree: a #TrackerIndexingTree
+ * @type: filter type to clear
+ *
+ * Clears all filters of a given type.
+ **/
+void
+tracker_indexing_tree_clear_filters (TrackerIndexingTree *tree,
+                                     TrackerFilterType    type)
+{
+       TrackerIndexingTreePrivate *priv;
+       GList *l;
+
+       g_return_if_fail (TRACKER_IS_INDEXING_TREE (tree));
+
+       priv = tree->priv;
+
+       for (l = priv->filter_patterns; l; l = l->next) {
+               PatternData *data = l->data;
+
+               if (data->type == type) {
+                       /* When we delete the link 'l', we point back
+                        * to the beginning of the list to make sure
+                        * we don't miss anything.
+                        */
+                       l = priv->filter_patterns = g_list_delete_link (priv->filter_patterns, l);
+                       pattern_data_free (data);
+               }
+       }
+}
+
+/**
+ * tracker_indexing_tree_file_matches_filter:
+ * @tree: a #TrackerIndexingTree
+ * @type: filter type
+ * @file: a #GFile
+ *
+ * Returns %TRUE if @file matches any filter of the given filter type.
+ *
+ * Returns: %TRUE if @file is filtered.
+ **/
+gboolean
+tracker_indexing_tree_file_matches_filter (TrackerIndexingTree *tree,
+                                           TrackerFilterType    type,
+                                           GFile               *file)
+{
+       TrackerIndexingTreePrivate *priv;
+       GList *filters;
+       gchar *basename;
+
+       g_return_val_if_fail (TRACKER_IS_INDEXING_TREE (tree), FALSE);
+       g_return_val_if_fail (G_IS_FILE (file), FALSE);
+
+       priv = tree->priv;
+       filters = priv->filter_patterns;
+       basename = g_file_get_basename (file);
+
+       while (filters) {
+               PatternData *data = filters->data;
+
+               filters = filters->next;
+
+               if (data->type != type)
+                       continue;
+
+               if (data->file &&
+                   (g_file_equal (file, data->file) ||
+                    g_file_has_prefix (file, data->file))) {
+                       g_free (basename);
+                       return TRUE;
+               }
+
+               if (g_pattern_match_string (data->pattern, basename)) {
+                       g_free (basename);
+                       return TRUE;
+               }
+       }
+
+       g_free (basename);
+       return FALSE;
+}
+
+static gboolean
+parent_or_equals (GFile *file1,
+                  GFile *file2)
+{
+       return (file1 == file2 ||
+               g_file_equal (file1, file2) ||
+               g_file_has_prefix (file1, file2));
+}
+
+static gboolean
+indexing_tree_file_is_filtered (TrackerIndexingTree *tree,
+                                TrackerFilterType    filter,
+                                GFile               *file)
+{
+       TrackerIndexingTreePrivate *priv;
+
+       priv = tree->priv;
+
+       if (tracker_indexing_tree_file_matches_filter (tree, filter, file)) {
+               if (priv->policies[filter] == TRACKER_FILTER_POLICY_ACCEPT) {
+                       /* Filter blocks otherwise accepted
+                        * (by the default policy) file
+                        */
+                       return TRUE;
+               }
+       } else {
+               if (priv->policies[filter] == TRACKER_FILTER_POLICY_DENY) {
+                       /* No match, and the default policy denies it */
+                       return TRUE;
+               }
+       }
+
+       return FALSE;
+}
+
+/**
+ * tracker_indexing_tree_file_is_indexable:
+ * @tree: a #TrackerIndexingTree
+ * @file: a #GFile
+ * @file_type: a #GFileType
+ *
+ * returns %TRUE if @file should be indexed according to the
+ * parameters given through tracker_indexing_tree_add() and
+ * tracker_indexing_tree_add_filter().
+ *
+ * If @file_type is #G_FILE_TYPE_UNKNOWN, file type will be queried to the
+ * file system.
+ *
+ * Returns: %TRUE if @file should be indexed.
+ **/
+gboolean
+tracker_indexing_tree_file_is_indexable (TrackerIndexingTree *tree,
+                                         GFile               *file,
+                                         GFileType            file_type)
+{
+       TrackerFilterType filter;
+       TrackerDirectoryFlags config_flags;
+       GFile *config_file;
+
+       g_return_val_if_fail (TRACKER_IS_INDEXING_TREE (tree), FALSE);
+       g_return_val_if_fail (G_IS_FILE (file), FALSE);
+
+       config_file = tracker_indexing_tree_get_root (tree, file, &config_flags);
+       if (!config_file) {
+               /* Not under an added dir */
+               return FALSE;
+       }
+
+       /* Don't check file type if _NO_STAT is given in flags */
+       if (file_type == G_FILE_TYPE_UNKNOWN &&
+           (config_flags & TRACKER_DIRECTORY_FLAG_NO_STAT) != 0) {
+               GFileQueryInfoFlags file_flags;
+
+               file_flags = G_FILE_QUERY_INFO_NOFOLLOW_SYMLINKS;
+
+               file_type = g_file_query_file_type (file, file_flags, NULL);
+
+               filter = (file_type == G_FILE_TYPE_DIRECTORY) ?
+                       TRACKER_FILTER_DIRECTORY : TRACKER_FILTER_FILE;
+
+               if (indexing_tree_file_is_filtered (tree, filter, file)) {
+                       return FALSE;
+               }
+       } else if (file_type != G_FILE_TYPE_UNKNOWN) {
+               filter = (file_type == G_FILE_TYPE_DIRECTORY) ?
+                       TRACKER_FILTER_DIRECTORY : TRACKER_FILTER_FILE;
+
+               if (indexing_tree_file_is_filtered (tree, filter, file)) {
+                       return FALSE;
+               }
+       }
+
+       /* FIXME: Shouldn't we only do this for file_type == G_FILE_TYPE_DIRECTORY ? */
+       if (config_flags & TRACKER_DIRECTORY_FLAG_IGNORE) {
+               return FALSE;
+       }
+
+       if (g_file_equal (file, config_file)) {
+               return TRUE;
+       } else {
+               if ((config_flags & TRACKER_DIRECTORY_FLAG_RECURSE) == 0 &&
+                   !g_file_has_parent (file, config_file)) {
+                       /* Non direct child in a non-recursive dir, ignore */
+                       return FALSE;
+               }
+
+               if (tracker_indexing_tree_get_filter_hidden (tree) &&
+                   tracker_file_is_hidden (file)) {
+                       return FALSE;
+               }
+
+               return TRUE;
+       }
+}
+
+/**
+ * tracker_indexing_tree_parent_is_indexable:
+ * @tree: a #TrackerIndexingTree
+ * @parent: parent directory
+ * @children: (element-type GFile): children within @parent
+ *
+ * returns %TRUE if @parent should be indexed based on its contents.
+ *
+ * Returns: %TRUE if @parent should be indexed.
+ **/
+gboolean
+tracker_indexing_tree_parent_is_indexable (TrackerIndexingTree *tree,
+                                           GFile               *parent,
+                                           GList               *children)
+{
+       if (!tracker_indexing_tree_file_is_indexable (tree,
+                                                     parent,
+                                                     G_FILE_TYPE_DIRECTORY)) {
+               return FALSE;
+       }
+
+       while (children) {
+               if (indexing_tree_file_is_filtered (tree,
+                                                   TRACKER_FILTER_PARENT_DIRECTORY,
+                                                   children->data)) {
+                       return FALSE;
+               }
+
+               children = children->next;
+       }
+
+       return TRUE;
+}
+
+/**
+ * tracker_indexing_tree_get_filter_hidden:
+ * @tree: a #TrackerIndexingTree
+ *
+ * Describes if the @tree should index hidden content. To change this
+ * setting, see tracker_indexing_tree_set_filter_hidden().
+ *
+ * Returns: %FALSE if hidden files are indexed, otherwise %TRUE.
+ *
+ * Since: 0.18.
+ **/
+gboolean
+tracker_indexing_tree_get_filter_hidden (TrackerIndexingTree *tree)
+{
+       TrackerIndexingTreePrivate *priv;
+
+       g_return_val_if_fail (TRACKER_IS_INDEXING_TREE (tree), FALSE);
+
+       priv = tree->priv;
+       return priv->filter_hidden;
+}
+
+/**
+ * tracker_indexing_tree_set_filter_hidden:
+ * @tree: a #TrackerIndexingTree
+ * @filter_hidden: a boolean
+ *
+ * When indexing content, sometimes it is preferable to ignore hidden
+ * content, for example, files prefixed with &quot;.&quot;. This is
+ * common for files in a home directory which are usually config
+ * files.
+ *
+ * Sets the indexing policy for @tree with hidden files and content.
+ * To ignore hidden files, @filter_hidden should be %TRUE, otherwise
+ * %FALSE.
+ *
+ * Since: 0.18.
+ **/
+void
+tracker_indexing_tree_set_filter_hidden (TrackerIndexingTree *tree,
+                                         gboolean             filter_hidden)
+{
+       TrackerIndexingTreePrivate *priv;
+
+       g_return_if_fail (TRACKER_IS_INDEXING_TREE (tree));
+
+       priv = tree->priv;
+       priv->filter_hidden = filter_hidden;
+
+       g_object_notify (G_OBJECT (tree), "filter-hidden");
+}
+
+/**
+ * tracker_indexing_tree_set_default_policy:
+ * @tree: a #TrackerIndexingTree
+ * @filter: a #TrackerFilterType
+ * @policy: a #TrackerFilterPolicy
+ *
+ * Set the default @policy (to allow or deny) for content in @tree
+ * based on the type - in this case @filter. Here, @filter is a file
+ * or directory and there are some other options too.
+ *
+ * For example, you can (by default), disable indexing all directories
+ * using this function.
+ *
+ * Since: 0.18.
+ **/
+void
+tracker_indexing_tree_set_default_policy (TrackerIndexingTree *tree,
+                                          TrackerFilterType    filter,
+                                          TrackerFilterPolicy  policy)
+{
+       TrackerIndexingTreePrivate *priv;
+
+       g_return_if_fail (TRACKER_IS_INDEXING_TREE (tree));
+       g_return_if_fail (filter >= TRACKER_FILTER_FILE && filter <= TRACKER_FILTER_PARENT_DIRECTORY);
+
+       priv = tree->priv;
+       priv->policies[filter] = policy;
+}
+
+/**
+ * tracker_indexing_tree_get_default_policy:
+ * @tree: a #TrackerIndexingTree
+ * @filter: a #TrackerFilterType
+ *
+ * Get the default filtering policies for @tree when indexing content.
+ * Some content is black listed or white listed and the default policy
+ * for that is returned here. The @filter allows specific type of
+ * policies to be returned, for example, the default policy for files
+ * (#TRACKER_FILTER_FILE).
+ *
+ * Returns: Either #TRACKER_FILTER_POLICY_DENY or
+ * #TRACKER_FILTER_POLICY_ALLOW.
+ *
+ * Since: 0.18.
+ **/
+TrackerFilterPolicy
+tracker_indexing_tree_get_default_policy (TrackerIndexingTree *tree,
+                                          TrackerFilterType    filter)
+{
+       TrackerIndexingTreePrivate *priv;
+
+       g_return_val_if_fail (TRACKER_IS_INDEXING_TREE (tree),
+                             TRACKER_FILTER_POLICY_DENY);
+       g_return_val_if_fail (filter >= TRACKER_FILTER_FILE &&
+                             filter <= TRACKER_FILTER_PARENT_DIRECTORY,
+                             TRACKER_FILTER_POLICY_DENY);
+
+       priv = tree->priv;
+       return priv->policies[filter];
+}
+
+/**
+ * tracker_indexing_tree_get_root:
+ * @tree: a #TrackerIndexingTree
+ * @file: a #GFile
+ * @directory_flags: (out): return location for the applying #TrackerDirectoryFlags
+ *
+ * Returns the #GFile that was previously added through tracker_indexing_tree_add()
+ * and would equal or contain @file, or %NULL if none applies.
+ *
+ * If the return value is non-%NULL, @directory_flags would contain the
+ * #TrackerDirectoryFlags applying to @file.
+ *
+ * Returns: (transfer none): the effective parent in @tree, or %NULL
+ **/
+GFile *
+tracker_indexing_tree_get_root (TrackerIndexingTree   *tree,
+                                GFile                 *file,
+                                TrackerDirectoryFlags *directory_flags)
+{
+       TrackerIndexingTreePrivate *priv;
+       NodeData *data;
+       GNode *parent;
+
+       if (directory_flags) {
+               *directory_flags = TRACKER_DIRECTORY_FLAG_NONE;
+       }
+
+       g_return_val_if_fail (TRACKER_IS_INDEXING_TREE (tree), NULL);
+       g_return_val_if_fail (G_IS_FILE (file), NULL);
+
+       priv = tree->priv;
+       parent = find_directory_node (priv->config_tree, file,
+                                     (GEqualFunc) parent_or_equals);
+       if (!parent) {
+               return NULL;
+       }
+
+       data = parent->data;
+
+       if (!data->shallow &&
+           (file == data->file ||
+            g_file_equal (file, data->file) ||
+            g_file_has_prefix (file, data->file))) {
+               if (directory_flags) {
+                       *directory_flags = data->flags;
+               }
+
+               return data->file;
+       }
+
+       return NULL;
+}
+
+/**
+ * tracker_indexing_tree_get_master_root:
+ * @tree: a #TrackerIndexingTree
+ *
+ * Returns the #GFile that represents the master root location for all
+ * indexing locations. For example, if
+ * <filename>file:///etc</filename> is an indexed path and so was
+ * <filename>file:///home/user</filename>, the master root is
+ * <filename>file:///</filename>. Only one scheme per @tree can be
+ * used, so you can not mix <filename>http</filename> and
+ * <filename>file</filename> roots in @tree.
+ *
+ * The return value should <emphasis>NEVER</emphasis> be %NULL. In
+ * cases where no root is given, we fallback to
+ * <filename>file:///</filename>.
+ *
+ * Roots explained:
+ *
+ * - master root = top most level root node,
+ *   e.g. file:///
+ *
+ * - config root = a root node from GSettings,
+ *   e.g. file:///home/martyn/Documents
+ *
+ * - root = ANY root, normally config root, but it can also apply to
+ *   roots added for devices, which technically are not a config root or a
+ *   master root.
+ *
+ * Returns: (transfer none): the effective root for all locations, or
+ * %NULL on error. The root is owned by @tree and should not be freed.
+ * It can be referenced using g_object_ref().
+ *
+ * Since: 1.2.
+ **/
+GFile *
+tracker_indexing_tree_get_master_root (TrackerIndexingTree *tree)
+{
+       TrackerIndexingTreePrivate *priv;
+
+       g_return_val_if_fail (TRACKER_IS_INDEXING_TREE (tree), NULL);
+
+       priv = tree->priv;
+
+       return priv->root;
+}
+
+/**
+ * tracker_indexing_tree_file_is_root:
+ * @tree: a #TrackerIndexingTree
+ * @file: a #GFile to compare
+ *
+ * Evaluates if the URL represented by @file is the same of that for
+ * the root of the @tree.
+ *
+ * Returns: %TRUE if @file matches the URL canonically, otherwise %FALSE.
+ *
+ * Since: 1.2.
+ **/
+gboolean
+tracker_indexing_tree_file_is_root (TrackerIndexingTree *tree,
+                                    GFile               *file)
+{
+       TrackerIndexingTreePrivate *priv;
+       GNode *node;
+
+       g_return_val_if_fail (TRACKER_IS_INDEXING_TREE (tree), FALSE);
+       g_return_val_if_fail (G_IS_FILE (file), FALSE);
+
+       priv = tree->priv;
+       node = find_directory_node (priv->config_tree, file,
+                                   (GEqualFunc) g_file_equal);
+       return node != NULL;
+}
+
+static gboolean
+prepend_config_root (GNode    *node,
+                     gpointer  user_data)
+{
+       GList **list = user_data;
+       NodeData *data = node->data;
+
+       if (!data->shallow && !data->removing)
+               *list = g_list_prepend (*list, data->file);
+       return FALSE;
+}
+
+/**
+ * tracker_indexing_tree_list_roots:
+ * @tree: a #TrackerIndexingTree
+ *
+ * Returns the list of indexing roots in @tree
+ *
+ * Returns: (transfer container) (element-type GFile): The list
+ *          of roots, the list itself must be freed with g_list_free(),
+ *          the list elements are owned by @tree and should not be
+ *          freed.
+ **/
+GList *
+tracker_indexing_tree_list_roots (TrackerIndexingTree *tree)
+{
+       TrackerIndexingTreePrivate *priv;
+       GList *nodes = NULL;
+
+       g_return_val_if_fail (TRACKER_IS_INDEXING_TREE (tree), NULL);
+
+       priv = tree->priv;
+       g_node_traverse (priv->config_tree,
+                        G_POST_ORDER,
+                        G_TRAVERSE_ALL,
+                        -1,
+                        prepend_config_root,
+                        &nodes);
+       return nodes;
+}
diff --git a/src/libtracker-miner/tracker-indexing-tree.h b/src/libtracker-miner/tracker-indexing-tree.h
new file mode 100644
index 000000000..3a3731f51
--- /dev/null
+++ b/src/libtracker-miner/tracker-indexing-tree.h
@@ -0,0 +1,134 @@
+/*
+ * Copyright (C) 2011, Nokia <ivan frade nokia com>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ * Boston, MA  02110-1301, USA.
+ *
+ * Author: Carlos Garnacho  <carlos lanedo com>
+ */
+
+#ifndef __TRACKER_INDEXING_TREE_H__
+#define __TRACKER_INDEXING_TREE_H__
+
+#if !defined (__LIBTRACKER_MINER_H_INSIDE__) && !defined (TRACKER_COMPILATION)
+#error "Only <libtracker-miner/tracker-miner.h> can be included directly."
+#endif
+
+#include <gio/gio.h>
+#include "tracker-miner-enums.h"
+
+G_BEGIN_DECLS
+
+#define TRACKER_TYPE_INDEXING_TREE         (tracker_indexing_tree_get_type())
+#define TRACKER_INDEXING_TREE(o)           (G_TYPE_CHECK_INSTANCE_CAST ((o), TRACKER_TYPE_INDEXING_TREE, 
TrackerIndexingTree))
+#define TRACKER_INDEXING_TREE_CLASS(c)     (G_TYPE_CHECK_CLASS_CAST ((c),    TRACKER_TYPE_INDEXING_TREE, 
TrackerIndexingTreeClass))
+#define TRACKER_IS_INDEXING_TREE(o)        (G_TYPE_CHECK_INSTANCE_TYPE ((o), TRACKER_TYPE_INDEXING_TREE))
+#define TRACKER_IS_INDEXING_TREE_CLASS(c)  (G_TYPE_CHECK_CLASS_TYPE ((c),    TRACKER_TYPE_INDEXING_TREE))
+#define TRACKER_INDEXING_TREE_GET_CLASS(o) (G_TYPE_INSTANCE_GET_CLASS ((o),  TRACKER_TYPE_INDEXING_TREE, 
TrackerIndexingTreeClass))
+
+/**
+ * TrackerIndexingTree:
+ *
+ * Base object used to configure indexing within #TrackerMinerFS items.
+ */
+
+typedef struct _TrackerIndexingTree TrackerIndexingTree;
+
+struct _TrackerIndexingTree {
+       GObject parent_instance;
+       gpointer priv;
+};
+
+/**
+ * TrackerIndexingTreeClass:
+ * @parent_class: parent object class
+ * @directory_added: Called when a directory is added.
+ * @directory_removed: Called when a directory is removed.
+ * @directory_updated: Called when a directory is updated.
+ * @padding: Reserved for future API improvements.
+ *
+ * Class for the #TrackerIndexingTree.
+ */
+typedef struct {
+       GObjectClass parent_class;
+
+       void (* directory_added)   (TrackerIndexingTree *indexing_tree,
+                                   GFile               *directory);
+       void (* directory_removed) (TrackerIndexingTree *indexing_tree,
+                                   GFile               *directory);
+       void (* directory_updated) (TrackerIndexingTree *indexing_tree,
+                                   GFile               *directory);
+       void (* child_updated)     (TrackerIndexingTree *indexing_tree,
+                                   GFile               *root,
+                                   GFile               *child);
+       /* <Private> */
+       gpointer padding[9];
+} TrackerIndexingTreeClass;
+
+GType                 tracker_indexing_tree_get_type (void) G_GNUC_CONST;
+
+TrackerIndexingTree * tracker_indexing_tree_new      (void);
+
+TrackerIndexingTree * tracker_indexing_tree_new_with_root (GFile            *root);
+
+void      tracker_indexing_tree_add                  (TrackerIndexingTree   *tree,
+                                                      GFile                 *directory,
+                                                      TrackerDirectoryFlags  flags);
+void      tracker_indexing_tree_remove               (TrackerIndexingTree   *tree,
+                                                      GFile                 *directory);
+gboolean  tracker_indexing_tree_notify_update        (TrackerIndexingTree   *tree,
+                                                      GFile                 *file,
+                                                      gboolean               recursive);
+
+void      tracker_indexing_tree_add_filter           (TrackerIndexingTree  *tree,
+                                                      TrackerFilterType     filter,
+                                                      const gchar          *glob_string);
+void      tracker_indexing_tree_clear_filters        (TrackerIndexingTree  *tree,
+                                                      TrackerFilterType     type);
+gboolean  tracker_indexing_tree_file_matches_filter  (TrackerIndexingTree  *tree,
+                                                      TrackerFilterType     type,
+                                                      GFile                *file);
+
+gboolean  tracker_indexing_tree_file_is_indexable    (TrackerIndexingTree  *tree,
+                                                      GFile                *file,
+                                                      GFileType             file_type);
+gboolean  tracker_indexing_tree_parent_is_indexable  (TrackerIndexingTree  *tree,
+                                                      GFile                *parent,
+                                                      GList                *children);
+
+gboolean  tracker_indexing_tree_get_filter_hidden    (TrackerIndexingTree  *tree);
+void      tracker_indexing_tree_set_filter_hidden    (TrackerIndexingTree  *tree,
+                                                      gboolean              filter_hidden);
+
+TrackerFilterPolicy tracker_indexing_tree_get_default_policy (TrackerIndexingTree *tree,
+                                                              TrackerFilterType    filter);
+void                tracker_indexing_tree_set_default_policy (TrackerIndexingTree *tree,
+                                                              TrackerFilterType    filter,
+                                                              TrackerFilterPolicy  policy);
+
+GFile *   tracker_indexing_tree_get_root             (TrackerIndexingTree   *tree,
+                                                      GFile                 *file,
+                                                      TrackerDirectoryFlags *directory_flags);
+GFile *   tracker_indexing_tree_get_master_root      (TrackerIndexingTree   *tree);
+
+gboolean  tracker_indexing_tree_file_is_root         (TrackerIndexingTree   *tree,
+                                                      GFile                 *file);
+
+GList *   tracker_indexing_tree_list_roots           (TrackerIndexingTree   *tree);
+
+
+G_END_DECLS
+
+#endif /* __TRACKER_INDEXING_TREE_H__ */
diff --git a/src/libtracker-miner/tracker-miner-enum-types.c.template 
b/src/libtracker-miner/tracker-miner-enum-types.c.template
new file mode 100644
index 000000000..2b9a9be80
--- /dev/null
+++ b/src/libtracker-miner/tracker-miner-enum-types.c.template
@@ -0,0 +1,44 @@
+/*** BEGIN file-header ***/
+#include <config-miners.h>
+
+#include "tracker-miner-enum-types.h"
+
+/*** END file-header ***/
+
+/*** BEGIN file-production ***/
+/* enumerations from "@filename@" */
+#include "@filename@"
+/*** END file-production ***/
+
+
+/*** BEGIN value-header ***/
+GType
+@enum_name@_get_type (void)
+{
+  static volatile gsize g_define_type_id__volatile = 0;
+ 
+  if (g_once_init_enter (&g_define_type_id__volatile)) {
+    static const G@Type@Value values[] = {
+/*** END value-header ***/
+
+/*** BEGIN value-production ***/
+      { @VALUENAME@, "@VALUENAME@", "@valuenick@" },
+/*** END value-production ***/
+
+/*** BEGIN value-tail ***/
+      { 0, NULL, NULL }
+    };
+    GType g_define_type_id = 
+       g_@type@_register_static (g_intern_static_string ("@EnumName@"), values);
+      
+    g_once_init_leave (&g_define_type_id__volatile, g_define_type_id);
+  }
+    
+  return g_define_type_id__volatile;
+}
+
+/*** END value-tail ***/
+
+/*** BEGIN file-tail ***/
+
+/*** END file-tail ***/
diff --git a/src/libtracker-miner/tracker-miner-enum-types.h.template 
b/src/libtracker-miner/tracker-miner-enum-types.h.template
new file mode 100644
index 000000000..a2180968e
--- /dev/null
+++ b/src/libtracker-miner/tracker-miner-enum-types.h.template
@@ -0,0 +1,26 @@
+/*** BEGIN file-header ***/
+
+#ifndef __TRACKER_MINER_ENUM_TYPES_H__
+#define __TRACKER_MINER_ENUM_TYPES_H__
+
+#include <glib-object.h>
+#include <libtracker-miner/tracker-miner-enums.h>
+
+G_BEGIN_DECLS
+/*** END file-header ***/
+
+/*** BEGIN file-production ***/
+
+/* enumerations from "@filename@" */
+/*** END file-production ***/
+
+/*** BEGIN value-header ***/
+GType @enum_name@_get_type (void) G_GNUC_CONST;
+#define TRACKER_TYPE_@ENUMSHORT@ (@enum_name@_get_type ())
+/*** END value-header ***/
+
+/*** BEGIN file-tail ***/
+G_END_DECLS
+
+#endif /* __TRACKER_ENUMS_TYPES_H__ */
+/*** END file-tail ***/
diff --git a/src/libtracker-miner/tracker-miner-enums.h b/src/libtracker-miner/tracker-miner-enums.h
new file mode 100644
index 000000000..172a83f4d
--- /dev/null
+++ b/src/libtracker-miner/tracker-miner-enums.h
@@ -0,0 +1,131 @@
+/*
+ * Copyright (C) 2011, Nokia <ivan frade nokia com>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ * Boston, MA  02110-1301, USA.
+ *
+ * Author: Carlos Garnacho  <carlos lanedo com>
+ */
+
+#ifndef __TRACKER_MINER_ENUMS_H__
+#define __TRACKER_MINER_ENUMS_H__
+
+G_BEGIN_DECLS
+
+/**
+ * SECTION:tracker-miner-enums
+ * @title: Enumerations
+ * @short_description: Common enumerations
+ * @include: libtracker-miner/tracker-miner-enums.h
+ *
+ * Common enumeration types used in libtracker-miner.
+ **/
+
+/**
+ * TrackerDirectoryFlags:
+ * @TRACKER_DIRECTORY_FLAG_NONE: No flags.
+ * @TRACKER_DIRECTORY_FLAG_RECURSE: Should recurse in the directory.
+ * @TRACKER_DIRECTORY_FLAG_CHECK_MTIME: Should check mtimes of items
+ * in the directory.
+ * @TRACKER_DIRECTORY_FLAG_MONITOR: Should setup monitors in the items
+ * found in the directory.
+ * @TRACKER_DIRECTORY_FLAG_IGNORE: Should ignore the directory
+ * contents.
+ * @TRACKER_DIRECTORY_FLAG_PRESERVE: Should preserve items in the
+ * directory even if the directory gets removed.
+ * @TRACKER_DIRECTORY_FLAG_PRIORITY: Internally a priority queue is
+ * used and this flag makes sure the directory is given a priority
+ * over other directories queued.
+ * @TRACKER_DIRECTORY_FLAG_NO_STAT: For cases where the content being
+ * crawled by the #TrackerEnumerator is not local (e.g. it's on a
+ * server somewhere), use the #TRACKER_DIRECTORY_FLAG_NO_STAT flag.
+ * The default is to use stat() and assume we're mining a local or
+ * mounted file system.
+ * @TRACKER_DIRECTORY_FLAG_CHECK_DELETED: Forces checks on deleted
+ * contents. This is most usually optimized away unless directory
+ * mtime changes indicate there could be deleted content.
+ *
+ * Flags used when adding a new directory to be indexed in the
+ * #TrackerIndexingTree and #TrackerDataProvider.
+ */
+typedef enum {
+       TRACKER_DIRECTORY_FLAG_NONE            = 0,
+       TRACKER_DIRECTORY_FLAG_RECURSE         = 1 << 1,
+       TRACKER_DIRECTORY_FLAG_CHECK_MTIME     = 1 << 2,
+       TRACKER_DIRECTORY_FLAG_MONITOR         = 1 << 3,
+       TRACKER_DIRECTORY_FLAG_IGNORE          = 1 << 4,
+       TRACKER_DIRECTORY_FLAG_PRESERVE        = 1 << 5,
+       TRACKER_DIRECTORY_FLAG_PRIORITY        = 1 << 6,
+       TRACKER_DIRECTORY_FLAG_NO_STAT         = 1 << 7,
+       TRACKER_DIRECTORY_FLAG_CHECK_DELETED   = 1 << 8,
+} TrackerDirectoryFlags;
+
+/**
+ * TrackerFilterType:
+ * @TRACKER_FILTER_FILE: All files matching this filter will be filtered out.
+ * @TRACKER_FILTER_DIRECTORY: All directories matching this filter will be filtered out.
+ * @TRACKER_FILTER_PARENT_DIRECTORY: All files in directories matching this filter will be filtered out.
+ *
+ * Flags used when adding a new filter in the #TrackerIndexingTree.
+ */
+typedef enum {
+       TRACKER_FILTER_FILE,
+       TRACKER_FILTER_DIRECTORY,
+       TRACKER_FILTER_PARENT_DIRECTORY
+} TrackerFilterType;
+
+/**
+ * TrackerFilterPolicy:
+ * @TRACKER_FILTER_POLICY_DENY: Items matching the filter will be skipped.
+ * @TRACKER_FILTER_POLICY_ACCEPT: Items matching the filter will be accepted.
+ *
+ * Flags used when defining default filter policy in the #TrackerIndexingTree.
+ */
+typedef enum {
+       TRACKER_FILTER_POLICY_DENY,
+       TRACKER_FILTER_POLICY_ACCEPT
+} TrackerFilterPolicy;
+
+/**
+ * TrackerNetworkType:
+ * @TRACKER_NETWORK_TYPE_NONE: Network is disconnected
+ * @TRACKER_NETWORK_TYPE_UNKNOWN: Network status is unknown
+ * @TRACKER_NETWORK_TYPE_GPRS: Network is connected over a GPRS
+ * connection
+ * @TRACKER_NETWORK_TYPE_EDGE: Network is connected over an EDGE
+ * connection
+ * @TRACKER_NETWORK_TYPE_3G: Network is connected over a 3G or
+ * faster (HSDPA, UMTS, ...) connection
+ * @TRACKER_NETWORK_TYPE_LAN: Network is connected over a local
+ * network connection. This can be ethernet, wifi, etc.
+ *
+ * Enumerates the different types of connections that the device might
+ * use when connected to internet. Note that not all providers might
+ * provide this information.
+ *
+ * Since: 0.18
+ **/
+typedef enum {
+       TRACKER_NETWORK_TYPE_NONE,
+       TRACKER_NETWORK_TYPE_UNKNOWN,
+       TRACKER_NETWORK_TYPE_GPRS,
+       TRACKER_NETWORK_TYPE_EDGE,
+       TRACKER_NETWORK_TYPE_3G,
+       TRACKER_NETWORK_TYPE_LAN
+} TrackerNetworkType;
+
+G_END_DECLS
+
+#endif /* __TRACKER_MINER_ENUMS_H__ */
diff --git a/src/libtracker-miner/tracker-miner-fs.c b/src/libtracker-miner/tracker-miner-fs.c
new file mode 100644
index 000000000..aba005039
--- /dev/null
+++ b/src/libtracker-miner/tracker-miner-fs.c
@@ -0,0 +1,2973 @@
+/*
+ * Copyright (C) 2009, Nokia <ivan frade nokia com>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ * Boston, MA  02110-1301, USA.
+ */
+
+#include "config-miners.h"
+
+#include <libtracker-miners-common/tracker-common.h>
+
+#include "tracker-crawler.h"
+#include "tracker-miner-fs.h"
+#include "tracker-monitor.h"
+#include "tracker-utils.h"
+#include "tracker-priority-queue.h"
+#include "tracker-task-pool.h"
+#include "tracker-sparql-buffer.h"
+#include "tracker-file-notifier.h"
+
+/* If defined will print the tree from GNode while running */
+#ifdef CRAWLED_TREE_ENABLE_TRACE
+#warning Tree debugging traces enabled
+#endif /* CRAWLED_TREE_ENABLE_TRACE */
+
+/* If defined will print push/pop actions on queues */
+#ifdef EVENT_QUEUE_ENABLE_TRACE
+#warning Event Queue traces enabled
+#define EVENT_QUEUE_LOG_PREFIX "[Event Queues] "
+#define EVENT_QUEUE_STATUS_TIMEOUT_SECS 30
+#define trace_eq(message, ...) g_debug (EVENT_QUEUE_LOG_PREFIX message, ##__VA_ARGS__)
+#define trace_eq_action(pushed, queue_name, position, gfile1, gfile2, reason) \
+       do { \
+               gchar *uri1 = g_file_get_uri (gfile1); \
+               gchar *uri2 = gfile2 ? g_file_get_uri (gfile2) : NULL; \
+               g_debug ("%s%s '%s%s%s' %s %s of queue '%s'%s%s", \
+                        EVENT_QUEUE_LOG_PREFIX, \
+                        pushed ? "Pushed" : "Popped", \
+                        uri1, \
+                        uri2 ? "->" : "", \
+                        uri2 ? uri2 : "", \
+                        pushed ? "to" : "from", \
+                        position, \
+                        queue_name, \
+                        reason ? ": " : "", \
+                        reason ? reason : ""); \
+               g_free (uri1); \
+               g_free (uri2); \
+       } while (0)
+#define trace_eq_push_tail(queue_name, gfile, reason)    \
+       trace_eq_action (TRUE, queue_name, "tail", gfile, NULL, reason)
+#define trace_eq_push_head(queue_name, gfile, reason)    \
+       trace_eq_action (TRUE, queue_name, "head", gfile, NULL, reason)
+#define trace_eq_push_tail_2(queue_name, gfile1, gfile2, reason)         \
+       trace_eq_action (TRUE, queue_name, "tail", gfile1, gfile2, reason)
+#define trace_eq_push_head_2(queue_name, gfile1, gfile2, reason)         \
+       trace_eq_action (TRUE, queue_name, "head", gfile1, gfile2, reason)
+#define trace_eq_pop_head(queue_name, gfile)     \
+       trace_eq_action (FALSE, queue_name, "head", gfile, NULL, NULL)
+#define trace_eq_pop_head_2(queue_name, gfile1, gfile2)          \
+       trace_eq_action (FALSE, queue_name, "head", gfile1, gfile2, NULL)
+static gboolean miner_fs_queues_status_trace_timeout_cb (gpointer data);
+#else
+#define trace_eq(...)
+#define trace_eq_push_tail(...)
+#define trace_eq_push_head(...)
+#define trace_eq_push_tail_2(...)
+#define trace_eq_push_head_2(...)
+#define trace_eq_pop_head(...)
+#define trace_eq_pop_head_2(...)
+#endif /* EVENT_QUEUE_ENABLE_TRACE */
+
+/* Number of times a GFile can be re-queued before it's dropped for
+ * whatever reason to avoid infinite loops.
+*/
+#define REENTRY_MAX 2
+
+/* Default processing pool limits to be set */
+#define DEFAULT_WAIT_POOL_LIMIT 1
+#define DEFAULT_READY_POOL_LIMIT 1
+
+/* Put tasks processing at a lower priority so other events
+ * (timeouts, monitor events, etc...) are guaranteed to be
+ * dispatched promptly.
+ */
+#define TRACKER_TASK_PRIORITY G_PRIORITY_DEFAULT_IDLE + 10
+
+#define MAX_SIMULTANEOUS_ITEMS 64
+
+/**
+ * SECTION:tracker-miner-fs
+ * @short_description: Abstract base class for filesystem miners
+ * @include: libtracker-miner/tracker-miner.h
+ *
+ * #TrackerMinerFS is an abstract base class for miners that collect data
+ * from a filesystem where parent/child relationships need to be
+ * inserted into the database correctly with queue management.
+ *
+ * All the filesystem crawling and monitoring is abstracted away,
+ * leaving to implementations the decisions of what directories/files
+ * should it process, and the actual data extraction.
+ *
+ * Example creating a TrackerMinerFS with our own file system root and
+ * data provider.
+ *
+ * First create our class and base it on TrackerMinerFS:
+ * |[
+ * G_DEFINE_TYPE_WITH_CODE (MyMinerFiles, my_miner_files, TRACKER_TYPE_MINER_FS,
+ *                          G_IMPLEMENT_INTERFACE (G_TYPE_INITABLE,
+ *                                                 my_miner_files_initable_iface_init))
+ * ]|
+ *
+ * Later in our class creation function, we are supplying the
+ * arguments we want. In this case, the 'root' is a #GFile pointing to
+ * a root URI location (for example 'file:///') and 'data_provider' is a
+ * #TrackerDataProvider used to enumerate 'root' and return children it
+ * finds. If 'data_provider' is %NULL (the default), then a
+ * #TrackerFileDataProvider is created automatically.
+ * |[
+ * // Note that only 'name' is mandatory
+ * miner = g_initable_new (MY_TYPE_MINER_FILES,
+ *                         NULL,
+ *                         error,
+ *                         "name", "MyMinerFiles",
+ *                         "root", root,
+ *                         "data-provider", data_provider,
+ *                         "processing-pool-wait-limit", 10,
+ *                         "processing-pool-ready-limit", 100,
+ *                         NULL);
+ * ]|
+ **/
+
+#define TRACKER_MINER_FS_GET_PRIVATE(o) (G_TYPE_INSTANCE_GET_PRIVATE ((o), TRACKER_TYPE_MINER_FS, 
TrackerMinerFSPrivate))
+
+typedef struct {
+       GFile *file;
+       GFile *source_file;
+} ItemMovedData;
+
+typedef struct {
+       GFile *file;
+       gchar *urn;
+       gint priority;
+       GCancellable *cancellable;
+       TrackerMiner *miner;
+} UpdateProcessingTaskContext;
+
+struct _TrackerMinerFSPrivate {
+       /* File queues for indexer */
+       TrackerPriorityQueue *items_created;
+       TrackerPriorityQueue *items_updated;
+       TrackerPriorityQueue *items_deleted;
+       TrackerPriorityQueue *items_moved;
+
+       guint item_queues_handler_id;
+       GFile *item_queue_blocker;
+
+#ifdef EVENT_QUEUE_ENABLE_TRACE
+       guint queue_status_timeout_id;
+#endif /* EVENT_QUEUE_ENABLE_TRACE */
+
+       /* Root / tree / index */
+       GFile *root;
+       TrackerIndexingTree *indexing_tree;
+       TrackerFileNotifier *file_notifier;
+       TrackerDataProvider *data_provider;
+
+       /* Sparql insertion tasks */
+       TrackerTaskPool *task_pool;
+       TrackerSparqlBuffer *sparql_buffer;
+       guint sparql_buffer_limit;
+
+       /* File properties */
+       GQuark quark_ignore_file;
+       GQuark quark_recursive_removal;
+       GQuark quark_attribute_updated;
+       GQuark quark_directory_found_crawling;
+       GQuark quark_reentry_counter;
+
+       /* Properties */
+       gdouble throttle;
+
+       /* Status */
+       GTimer *timer;
+       GTimer *extraction_timer;
+
+       guint been_started : 1;     /* TRUE if miner has been started */
+       guint been_crawled : 1;     /* TRUE if initial crawling has been
+                                    * done */
+       guint shown_totals : 1;     /* TRUE if totals have been shown */
+       guint is_paused : 1;        /* TRUE if miner is paused */
+
+       guint timer_stopped : 1;    /* TRUE if main timer is stopped */
+       guint extraction_timer_stopped : 1; /* TRUE if the extraction
+                                            * timer is stopped */
+
+       GHashTable *roots_to_notify;        /* Used to signal indexing
+                                            * trees finished */
+
+       /*
+        * Statistics
+        */
+
+       /* How many we found during crawling and how many were black
+        * listed (ignored). Reset to 0 when processing stops. */
+       guint total_directories_found;
+       guint total_directories_ignored;
+       guint total_files_found;
+       guint total_files_ignored;
+
+       /* How many we indexed and how many had errors indexing. */
+       guint total_files_processed;
+       guint total_files_notified;
+       guint total_files_notified_error;
+};
+
+typedef enum {
+       QUEUE_NONE,
+       QUEUE_CREATED,
+       QUEUE_UPDATED,
+       QUEUE_DELETED,
+       QUEUE_MOVED,
+       QUEUE_WAIT,
+} QueueState;
+
+enum {
+       PROCESS_FILE,
+       PROCESS_FILE_ATTRIBUTES,
+       FINISHED,
+       FINISHED_ROOT,
+       REMOVE_FILE,
+       REMOVE_CHILDREN,
+       MOVE_FILE,
+       LAST_SIGNAL
+};
+
+enum {
+       PROP_0,
+       PROP_THROTTLE,
+       PROP_ROOT,
+       PROP_WAIT_POOL_LIMIT,
+       PROP_READY_POOL_LIMIT,
+       PROP_DATA_PROVIDER
+};
+
+static void           miner_fs_initable_iface_init        (GInitableIface       *iface);
+
+static void           fs_finalize                         (GObject              *object);
+static void           fs_constructed                      (GObject              *object);
+static void           fs_set_property                     (GObject              *object,
+                                                           guint                 prop_id,
+                                                           const GValue         *value,
+                                                           GParamSpec           *pspec);
+static void           fs_get_property                     (GObject              *object,
+                                                           guint                 prop_id,
+                                                           GValue               *value,
+                                                           GParamSpec           *pspec);
+
+static void           miner_started                       (TrackerMiner         *miner);
+static void           miner_stopped                       (TrackerMiner         *miner);
+static void           miner_paused                        (TrackerMiner         *miner);
+static void           miner_resumed                       (TrackerMiner         *miner);
+static ItemMovedData *item_moved_data_new                 (GFile                *file,
+                                                           GFile                *source_file);
+static void           item_moved_data_free                (ItemMovedData        *data);
+
+static void           indexing_tree_directory_removed     (TrackerIndexingTree  *indexing_tree,
+                                                           GFile                *directory,
+                                                           gpointer              user_data);
+static void           file_notifier_file_created          (TrackerFileNotifier  *notifier,
+                                                           GFile                *file,
+                                                           gpointer              user_data);
+static void           file_notifier_file_deleted          (TrackerFileNotifier  *notifier,
+                                                           GFile                *file,
+                                                           gpointer              user_data);
+static void           file_notifier_file_updated          (TrackerFileNotifier  *notifier,
+                                                           GFile                *file,
+                                                           gboolean              attributes_only,
+                                                           gpointer              user_data);
+static void           file_notifier_file_moved            (TrackerFileNotifier  *notifier,
+                                                           GFile                *source,
+                                                           GFile                *dest,
+                                                           gpointer              user_data);
+static void           file_notifier_directory_started     (TrackerFileNotifier *notifier,
+                                                           GFile               *directory,
+                                                           gpointer             user_data);
+static void           file_notifier_directory_finished    (TrackerFileNotifier *notifier,
+                                                           GFile               *directory,
+                                                           guint                directories_found,
+                                                           guint                directories_ignored,
+                                                           guint                files_found,
+                                                           guint                files_ignored,
+                                                           gpointer             user_data);
+static void           file_notifier_finished              (TrackerFileNotifier *notifier,
+                                                           gpointer             user_data);
+
+static void           item_queue_handlers_set_up          (TrackerMinerFS       *fs);
+
+static void           task_pool_cancel_foreach                (gpointer        data,
+                                                               gpointer        user_data);
+static void           task_pool_limit_reached_notify_cb       (GObject        *object,
+                                                               GParamSpec     *pspec,
+                                                               gpointer        user_data);
+
+static GQuark quark_file_iri = 0;
+static GInitableIface* miner_fs_initable_parent_iface;
+static guint signals[LAST_SIGNAL] = { 0, };
+
+/**
+ * tracker_miner_fs_error_quark:
+ *
+ * Gives the caller the #GQuark used to identify #TrackerMinerFS errors
+ * in #GError structures. The #GQuark is used as the domain for the error.
+ *
+ * Returns: the #GQuark used for the domain of a #GError.
+ *
+ * Since: 1.2.
+ **/
+G_DEFINE_QUARK (TrackerMinerFSError, tracker_miner_fs_error)
+
+G_DEFINE_ABSTRACT_TYPE_WITH_CODE (TrackerMinerFS, tracker_miner_fs, TRACKER_TYPE_MINER,
+                                  G_IMPLEMENT_INTERFACE (G_TYPE_INITABLE,
+                                                         miner_fs_initable_iface_init));
+
+static void
+tracker_miner_fs_class_init (TrackerMinerFSClass *klass)
+{
+       GObjectClass *object_class = G_OBJECT_CLASS (klass);
+       TrackerMinerClass *miner_class = TRACKER_MINER_CLASS (klass);
+
+       object_class->finalize = fs_finalize;
+       object_class->constructed = fs_constructed;
+       object_class->set_property = fs_set_property;
+       object_class->get_property = fs_get_property;
+
+       miner_class->started = miner_started;
+       miner_class->stopped = miner_stopped;
+       miner_class->paused  = miner_paused;
+       miner_class->resumed = miner_resumed;
+
+       g_object_class_install_property (object_class,
+                                        PROP_THROTTLE,
+                                        g_param_spec_double ("throttle",
+                                                             "Throttle",
+                                                             "Modifier for the indexing speed, 0 is max 
speed",
+                                                             0, 1, 0,
+                                                             G_PARAM_READWRITE));
+       g_object_class_install_property (object_class,
+                                        PROP_ROOT,
+                                        g_param_spec_object ("root",
+                                                             "Root",
+                                                             "Top level URI for our indexing tree and file 
notify clases",
+                                                             G_TYPE_FILE,
+                                                             G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY));
+       g_object_class_install_property (object_class,
+                                        PROP_WAIT_POOL_LIMIT,
+                                        g_param_spec_uint ("processing-pool-wait-limit",
+                                                           "Processing pool limit for WAIT tasks",
+                                                           "Maximum number of files that can be concurrently 
"
+                                                           "processed by the upper layer",
+                                                           1, G_MAXUINT, DEFAULT_WAIT_POOL_LIMIT,
+                                                           G_PARAM_READWRITE | G_PARAM_CONSTRUCT));
+       g_object_class_install_property (object_class,
+                                        PROP_READY_POOL_LIMIT,
+                                        g_param_spec_uint ("processing-pool-ready-limit",
+                                                           "Processing pool limit for READY tasks",
+                                                           "Maximum number of SPARQL updates that can be 
merged "
+                                                           "in a single connection to the store",
+                                                           1, G_MAXUINT, DEFAULT_READY_POOL_LIMIT,
+                                                           G_PARAM_READWRITE | G_PARAM_CONSTRUCT));
+       g_object_class_install_property (object_class,
+                                        PROP_DATA_PROVIDER,
+                                        g_param_spec_object ("data-provider",
+                                                             "Data provider",
+                                                             "Data provider populating data, e.g. like 
GFileEnumerator",
+                                                             TRACKER_TYPE_DATA_PROVIDER,
+                                                             G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY));
+
+       /**
+        * TrackerMinerFS::process-file:
+        * @miner_fs: the #TrackerMinerFS
+        * @file: a #GFile
+        * @builder: a #TrackerSparqlBuilder
+        * @cancellable: a #GCancellable
+        *
+        * The ::process-file signal is emitted whenever a file should
+        * be processed, and it's metadata extracted.
+        *
+        * @builder is the #TrackerSparqlBuilder where all sparql updates
+        * to be performed for @file will be appended.
+        *
+        * This signal allows both synchronous and asynchronous extraction,
+        * in the synchronous case @cancellable can be safely ignored. In
+        * either case, on successful metadata extraction, implementations
+        * must call tracker_miner_fs_notify_finish() to indicate that
+        * processing has finished on @file, so the miner can execute
+        * the SPARQL updates and continue processing other files.
+        *
+        * Returns: %TRUE if the file is accepted for processing,
+        *          %FALSE if the file should be ignored.
+        *
+        * Since: 0.8
+        **/
+       signals[PROCESS_FILE] =
+               g_signal_new ("process-file",
+                             G_OBJECT_CLASS_TYPE (object_class),
+                             G_SIGNAL_RUN_LAST,
+                             G_STRUCT_OFFSET (TrackerMinerFSClass, process_file),
+                             NULL, NULL,
+                             NULL,
+                             G_TYPE_BOOLEAN,
+                             2, G_TYPE_FILE, G_TYPE_TASK);
+
+       /**
+        * TrackerMinerFS::process-file-attributes:
+        * @miner_fs: the #TrackerMinerFS
+        * @file: a #GFile
+        * @builder: a #TrackerSparqlBuilder
+        * @cancellable: a #GCancellable
+        *
+        * The ::process-file-attributes signal is emitted whenever a file should
+        * be processed, but only the attribute-related metadata extracted.
+        *
+        * @builder is the #TrackerSparqlBuilder where all sparql updates
+        * to be performed for @file will be appended. For the properties being
+        * updated, the DELETE statements should be included as well.
+        *
+        * This signal allows both synchronous and asynchronous extraction,
+        * in the synchronous case @cancellable can be safely ignored. In
+        * either case, on successful metadata extraction, implementations
+        * must call tracker_miner_fs_notify_finish() to indicate that
+        * processing has finished on @file, so the miner can execute
+        * the SPARQL updates and continue processing other files.
+        *
+        * Returns: %TRUE if the file is accepted for processing,
+        *          %FALSE if the file should be ignored.
+        *
+        * Since: 0.10
+        **/
+       signals[PROCESS_FILE_ATTRIBUTES] =
+               g_signal_new ("process-file-attributes",
+                             G_OBJECT_CLASS_TYPE (object_class),
+                             G_SIGNAL_RUN_LAST,
+                             G_STRUCT_OFFSET (TrackerMinerFSClass, process_file_attributes),
+                             NULL, NULL,
+                             NULL,
+                             G_TYPE_BOOLEAN,
+                             2, G_TYPE_FILE, G_TYPE_TASK);
+
+       /**
+        * TrackerMinerFS::finished:
+        * @miner_fs: the #TrackerMinerFS
+        * @elapsed: elapsed time since mining was started
+        * @directories_found: number of directories found
+        * @directories_ignored: number of ignored directories
+        * @files_found: number of files found
+        * @files_ignored: number of ignored files
+        *
+        * The ::finished signal is emitted when @miner_fs has finished
+        * all pending processing.
+        *
+        * Since: 0.8
+        **/
+       signals[FINISHED] =
+               g_signal_new ("finished",
+                             G_TYPE_FROM_CLASS (object_class),
+                             G_SIGNAL_RUN_LAST,
+                             G_STRUCT_OFFSET (TrackerMinerFSClass, finished),
+                             NULL, NULL,
+                             NULL,
+                             G_TYPE_NONE,
+                             5,
+                             G_TYPE_DOUBLE,
+                             G_TYPE_UINT,
+                             G_TYPE_UINT,
+                             G_TYPE_UINT,
+                             G_TYPE_UINT);
+
+       /**
+        * TrackerMinerFS::finished-root:
+        * @miner_fs: the #TrackerMinerFS
+        * @file: a #GFile
+        *
+        * The ::finished-crawl signal is emitted when @miner_fs has
+        * finished finding all resources that need to be indexed
+        * with the root location of @file. At this point, it's likely
+        * many are still in the queue to be added to the database,
+        * but this gives some indication that a location is
+        * processed.
+        *
+        * Since: 1.2
+        **/
+       signals[FINISHED_ROOT] =
+               g_signal_new ("finished-root",
+                             G_TYPE_FROM_CLASS (object_class),
+                             G_SIGNAL_RUN_LAST,
+                             G_STRUCT_OFFSET (TrackerMinerFSClass, finished_root),
+                             NULL, NULL,
+                             NULL,
+                             G_TYPE_NONE,
+                             1,
+                             G_TYPE_FILE);
+
+       /**
+        * TrackerMinerFS::remove-file:
+        * @miner_fs: the #TrackerMinerFS
+        * @file: a #GFile
+        * @children_only: #TRUE if only the children of @file are to be deleted
+        * @builder: a #TrackerSparqlBuilder
+        *
+        * The ::remove-file signal will be emitted on files that need removal
+        * according to the miner configuration (either the files themselves are
+        * deleted, or the directory/contents no longer need inspection according
+        * to miner configuration and their location.
+        *
+        * This operation is always assumed to be recursive, the @children_only
+        * argument will be %TRUE if for any reason the topmost directory needs
+        * to stay (e.g. moved from a recursively indexed directory tree to a
+        * non-recursively indexed location).
+        *
+        * The @builder argument can be used to provide additional SPARQL
+        * deletes and updates necessary around the deletion of those items. If
+        * the return value of this signal is %TRUE, @builder is expected to
+        * contain all relevant deletes for this operation.
+        *
+        * If the return value of this signal is %FALSE, the miner will apply
+        * its default behavior, which is deleting all triples that correspond
+        * to the affected URIs.
+        *
+        * Returns: %TRUE if @builder contains all the necessary operations to
+        *          delete the affected resources, %FALSE to let the miner
+        *          implicitly handle the deletion.
+        *
+        * Since: 1.8
+        **/
+       signals[REMOVE_FILE] =
+               g_signal_new ("remove-file",
+                             G_TYPE_FROM_CLASS (object_class),
+                             G_SIGNAL_RUN_LAST,
+                             G_STRUCT_OFFSET (TrackerMinerFSClass, remove_file),
+                             NULL, NULL, NULL,
+                             G_TYPE_STRING,
+                             1, G_TYPE_FILE);
+
+       signals[REMOVE_CHILDREN] =
+               g_signal_new ("remove-children",
+                             G_TYPE_FROM_CLASS (object_class),
+                             G_SIGNAL_RUN_LAST,
+                             G_STRUCT_OFFSET (TrackerMinerFSClass, remove_children),
+                             NULL, NULL, NULL,
+                             G_TYPE_STRING,
+                             1, G_TYPE_FILE);
+
+       signals[MOVE_FILE] =
+               g_signal_new ("move-file",
+                             G_TYPE_FROM_CLASS (object_class),
+                             G_SIGNAL_RUN_LAST,
+                             G_STRUCT_OFFSET (TrackerMinerFSClass, move_file),
+                             NULL, NULL, NULL,
+                             G_TYPE_STRING,
+                             3, G_TYPE_FILE, G_TYPE_FILE, G_TYPE_BOOLEAN);
+
+       g_type_class_add_private (object_class, sizeof (TrackerMinerFSPrivate));
+
+       quark_file_iri = g_quark_from_static_string ("tracker-miner-file-iri");
+}
+
+static void
+tracker_miner_fs_init (TrackerMinerFS *object)
+{
+       TrackerMinerFSPrivate *priv;
+
+       object->priv = TRACKER_MINER_FS_GET_PRIVATE (object);
+
+       priv = object->priv;
+
+       priv->timer = g_timer_new ();
+       priv->extraction_timer = g_timer_new ();
+
+       g_timer_stop (priv->timer);
+       g_timer_stop (priv->extraction_timer);
+
+       priv->timer_stopped = TRUE;
+       priv->extraction_timer_stopped = TRUE;
+
+       priv->items_created = tracker_priority_queue_new ();
+       priv->items_updated = tracker_priority_queue_new ();
+       priv->items_deleted = tracker_priority_queue_new ();
+       priv->items_moved = tracker_priority_queue_new ();
+
+#ifdef EVENT_QUEUE_ENABLE_TRACE
+       priv->queue_status_timeout_id = g_timeout_add_seconds (EVENT_QUEUE_STATUS_TIMEOUT_SECS,
+                                                              miner_fs_queues_status_trace_timeout_cb,
+                                                              object);
+#endif /* PROCESSING_POOL_ENABLE_TRACE */
+
+       /* Create processing pools */
+       priv->task_pool = tracker_task_pool_new (DEFAULT_WAIT_POOL_LIMIT);
+       g_signal_connect (priv->task_pool, "notify::limit-reached",
+                         G_CALLBACK (task_pool_limit_reached_notify_cb), object);
+
+       priv->quark_ignore_file = g_quark_from_static_string ("tracker-ignore-file");
+       priv->quark_recursive_removal = g_quark_from_static_string ("tracker-recursive-removal");
+       priv->quark_directory_found_crawling = g_quark_from_static_string 
("tracker-directory-found-crawling");
+       priv->quark_attribute_updated = g_quark_from_static_string ("tracker-attribute-updated");
+       priv->quark_reentry_counter = g_quark_from_static_string ("tracker-reentry-counter");
+
+       priv->roots_to_notify = g_hash_table_new_full (g_file_hash,
+                                                      (GEqualFunc) g_file_equal,
+                                                      g_object_unref,
+                                                      NULL);
+}
+
+static gboolean
+miner_fs_initable_init (GInitable     *initable,
+                        GCancellable  *cancellable,
+                        GError       **error)
+{
+       TrackerMinerFSPrivate *priv;
+       guint limit;
+
+       if (!miner_fs_initable_parent_iface->init (initable, cancellable, error)) {
+               return FALSE;
+       }
+
+       priv = TRACKER_MINER_FS_GET_PRIVATE (initable);
+
+       g_object_get (initable, "processing-pool-ready-limit", &limit, NULL);
+       priv->sparql_buffer = tracker_sparql_buffer_new (tracker_miner_get_connection (TRACKER_MINER 
(initable)),
+                                                        limit);
+
+       if (!priv->sparql_buffer) {
+               g_set_error (error,
+                            tracker_miner_fs_error_quark (),
+                            TRACKER_MINER_FS_ERROR_INIT,
+                            "Could not create TrackerSparqlBuffer needed to process resources");
+               return FALSE;
+       }
+
+       g_signal_connect (priv->sparql_buffer, "notify::limit-reached",
+                         G_CALLBACK (task_pool_limit_reached_notify_cb),
+                         initable);
+
+       if (!priv->indexing_tree) {
+               g_set_error (error,
+                            tracker_miner_fs_error_quark (),
+                            TRACKER_MINER_FS_ERROR_INIT,
+                            "Could not create TrackerIndexingTree needed to manage content indexed");
+               return FALSE;
+       }
+
+       g_signal_connect (priv->indexing_tree, "directory-removed",
+                         G_CALLBACK (indexing_tree_directory_removed),
+                         initable);
+
+       /* Create the file notifier */
+       priv->file_notifier = tracker_file_notifier_new (priv->indexing_tree,
+                                                        priv->data_provider,
+                                                        tracker_miner_get_connection (TRACKER_MINER 
(initable)));
+
+       if (!priv->file_notifier) {
+               g_set_error (error,
+                            tracker_miner_fs_error_quark (),
+                            TRACKER_MINER_FS_ERROR_INIT,
+                            "Could not create TrackerFileNotifier needed to signal new resources to be 
indexed");
+               return FALSE;
+       }
+
+       g_signal_connect (priv->file_notifier, "file-created",
+                         G_CALLBACK (file_notifier_file_created),
+                         initable);
+       g_signal_connect (priv->file_notifier, "file-updated",
+                         G_CALLBACK (file_notifier_file_updated),
+                         initable);
+       g_signal_connect (priv->file_notifier, "file-deleted",
+                         G_CALLBACK (file_notifier_file_deleted),
+                         initable);
+       g_signal_connect (priv->file_notifier, "file-moved",
+                         G_CALLBACK (file_notifier_file_moved),
+                         initable);
+       g_signal_connect (priv->file_notifier, "directory-started",
+                         G_CALLBACK (file_notifier_directory_started),
+                         initable);
+       g_signal_connect (priv->file_notifier, "directory-finished",
+                         G_CALLBACK (file_notifier_directory_finished),
+                         initable);
+       g_signal_connect (priv->file_notifier, "finished",
+                         G_CALLBACK (file_notifier_finished),
+                         initable);
+
+       return TRUE;
+}
+
+static void
+miner_fs_initable_iface_init (GInitableIface *iface)
+{
+       miner_fs_initable_parent_iface = g_type_interface_peek_parent (iface);
+       iface->init = miner_fs_initable_init;
+}
+
+static void
+fs_finalize (GObject *object)
+{
+       TrackerMinerFSPrivate *priv;
+
+       priv = TRACKER_MINER_FS_GET_PRIVATE (object);
+
+       g_timer_destroy (priv->timer);
+       g_timer_destroy (priv->extraction_timer);
+
+       if (priv->item_queues_handler_id) {
+               g_source_remove (priv->item_queues_handler_id);
+               priv->item_queues_handler_id = 0;
+       }
+
+       if (priv->item_queue_blocker) {
+               g_object_unref (priv->item_queue_blocker);
+       }
+
+       if (priv->file_notifier) {
+               tracker_file_notifier_stop (priv->file_notifier);
+       }
+
+       /* Cancel every pending task */
+       tracker_task_pool_foreach (priv->task_pool,
+                                  task_pool_cancel_foreach,
+                                  NULL);
+       g_object_unref (priv->task_pool);
+
+       if (priv->sparql_buffer) {
+               g_object_unref (priv->sparql_buffer);
+       }
+
+       tracker_priority_queue_foreach (priv->items_moved,
+                                       (GFunc) item_moved_data_free,
+                                       NULL);
+       tracker_priority_queue_unref (priv->items_moved);
+
+       tracker_priority_queue_foreach (priv->items_deleted,
+                                       (GFunc) g_object_unref,
+                                       NULL);
+       tracker_priority_queue_unref (priv->items_deleted);
+
+       tracker_priority_queue_foreach (priv->items_updated,
+                                       (GFunc) g_object_unref,
+                                       NULL);
+       tracker_priority_queue_unref (priv->items_updated);
+
+       tracker_priority_queue_foreach (priv->items_created,
+                                       (GFunc) g_object_unref,
+                                       NULL);
+       tracker_priority_queue_unref (priv->items_created);
+
+       if (priv->indexing_tree) {
+               g_object_unref (priv->indexing_tree);
+       }
+
+       if (priv->file_notifier) {
+               g_object_unref (priv->file_notifier);
+       }
+
+       if (priv->roots_to_notify) {
+               g_hash_table_unref (priv->roots_to_notify);
+
+               /* Just in case we end up using this AFTER finalize, not expected */
+               priv->roots_to_notify = NULL;
+       }
+
+#ifdef EVENT_QUEUE_ENABLE_TRACE
+       if (priv->queue_status_timeout_id)
+               g_source_remove (priv->queue_status_timeout_id);
+#endif /* PROCESSING_POOL_ENABLE_TRACE */
+
+       G_OBJECT_CLASS (tracker_miner_fs_parent_class)->finalize (object);
+}
+
+static void
+fs_constructed (GObject *object)
+{
+       TrackerMinerFSPrivate *priv;
+
+       /* NOTE: We have to do this in this order because initables
+        * are called _AFTER_ constructed and for subclasses that are
+        * not initables we don't have any other way than to chain
+        * constructed and root/indexing tree must exist at that
+        * point.
+        *
+        * If priv->indexing_tree is NULL after this function, the
+        * initiable functions will fail and this class will not be
+        * created anyway.
+        */
+       G_OBJECT_CLASS (tracker_miner_fs_parent_class)->constructed (object);
+
+       priv = TRACKER_MINER_FS_GET_PRIVATE (object);
+
+       /* Create root if one didn't exist */
+       if (priv->root == NULL) {
+               /* We default to file:/// */
+               priv->root = g_file_new_for_uri ("file:///");
+       }
+
+       /* Create indexing tree */
+       priv->indexing_tree = tracker_indexing_tree_new_with_root (priv->root);
+}
+
+static void
+fs_set_property (GObject      *object,
+                 guint         prop_id,
+                 const GValue *value,
+                 GParamSpec   *pspec)
+{
+       TrackerMinerFS *fs = TRACKER_MINER_FS (object);
+
+       switch (prop_id) {
+       case PROP_THROTTLE:
+               tracker_miner_fs_set_throttle (TRACKER_MINER_FS (object),
+                                              g_value_get_double (value));
+               break;
+       case PROP_ROOT:
+               /* We expect this to only occur once, on object construct */
+               fs->priv->root = g_value_dup_object (value);
+               break;
+       case PROP_WAIT_POOL_LIMIT:
+               tracker_task_pool_set_limit (fs->priv->task_pool,
+                                            g_value_get_uint (value));
+               break;
+       case PROP_READY_POOL_LIMIT:
+               fs->priv->sparql_buffer_limit = g_value_get_uint (value);
+
+               if (fs->priv->sparql_buffer) {
+                       tracker_task_pool_set_limit (TRACKER_TASK_POOL (fs->priv->sparql_buffer),
+                                                    fs->priv->sparql_buffer_limit);
+               }
+               break;
+       case PROP_DATA_PROVIDER:
+               fs->priv->data_provider = g_value_dup_object (value);
+               break;
+       default:
+               G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+               break;
+       }
+}
+
+static void
+fs_get_property (GObject    *object,
+                 guint       prop_id,
+                 GValue     *value,
+                 GParamSpec *pspec)
+{
+       TrackerMinerFS *fs;
+
+       fs = TRACKER_MINER_FS (object);
+
+       switch (prop_id) {
+       case PROP_THROTTLE:
+               g_value_set_double (value, fs->priv->throttle);
+               break;
+       case PROP_ROOT:
+               g_value_set_object (value, fs->priv->root);
+               break;
+       case PROP_WAIT_POOL_LIMIT:
+               g_value_set_uint (value, tracker_task_pool_get_limit (fs->priv->task_pool));
+               break;
+       case PROP_READY_POOL_LIMIT:
+               g_value_set_uint (value, fs->priv->sparql_buffer_limit);
+               break;
+       case PROP_DATA_PROVIDER:
+               g_value_set_object (value, fs->priv->data_provider);
+               break;
+       default:
+               G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+               break;
+       }
+}
+
+static void
+task_pool_limit_reached_notify_cb (GObject    *object,
+                                  GParamSpec *pspec,
+                                  gpointer    user_data)
+{
+       if (!tracker_task_pool_limit_reached (TRACKER_TASK_POOL (object))) {
+               item_queue_handlers_set_up (TRACKER_MINER_FS (user_data));
+       }
+}
+
+static void
+miner_started (TrackerMiner *miner)
+{
+       TrackerMinerFS *fs;
+
+       fs = TRACKER_MINER_FS (miner);
+
+       fs->priv->been_started = TRUE;
+
+       g_info ("Initializing");
+
+       g_object_set (miner,
+                     "progress", 0.0,
+                     "status", "Initializing",
+                     "remaining-time", 0,
+                     NULL);
+
+       tracker_file_notifier_start (fs->priv->file_notifier);
+}
+
+static void
+miner_stopped (TrackerMiner *miner)
+{
+       g_info ("Idle");
+
+       g_object_set (miner,
+                     "progress", 1.0,
+                     "status", "Idle",
+                     "remaining-time", -1,
+                     NULL);
+}
+
+static void
+miner_paused (TrackerMiner *miner)
+{
+       TrackerMinerFS *fs;
+
+       fs = TRACKER_MINER_FS (miner);
+
+       fs->priv->is_paused = TRUE;
+
+       tracker_file_notifier_stop (fs->priv->file_notifier);
+
+       if (fs->priv->item_queues_handler_id) {
+               g_source_remove (fs->priv->item_queues_handler_id);
+               fs->priv->item_queues_handler_id = 0;
+       }
+}
+
+static void
+miner_resumed (TrackerMiner *miner)
+{
+       TrackerMinerFS *fs;
+
+       fs = TRACKER_MINER_FS (miner);
+
+       fs->priv->is_paused = FALSE;
+
+       tracker_file_notifier_start (fs->priv->file_notifier);
+
+       /* Only set up queue handler if we have items waiting to be
+        * processed.
+        */
+       if (tracker_miner_fs_has_items_to_process (fs)) {
+               item_queue_handlers_set_up (fs);
+       }
+}
+
+static gboolean
+item_moved_data_has_prefix (gpointer data,
+                           gpointer user_data)
+{
+       ItemMovedData *moved_item = data;
+       GFile *prefix = user_data;
+
+       return g_file_has_prefix (moved_item->file, prefix);
+}
+
+static void
+notify_roots_finished (TrackerMinerFS *fs,
+                       gboolean        check_queues)
+{
+       GHashTableIter iter;
+       gpointer key, value;
+
+       if (check_queues &&
+           fs->priv->roots_to_notify &&
+           g_hash_table_size (fs->priv->roots_to_notify) < 2) {
+               /* Technically, if there is only one root, it's
+                * pointless to do anything before the FINISHED (not
+                * FINISHED_ROOT) signal is emitted. In that
+                * situation we calls function first anyway with
+                * check_queues=FALSE so we still notify roots. This
+                * is really just for efficiency.
+                */
+               return;
+       } else if (fs->priv->roots_to_notify == NULL ||
+                  g_hash_table_size (fs->priv->roots_to_notify) < 1) {
+               /* Nothing to do */
+               return;
+       }
+
+       g_hash_table_iter_init (&iter, fs->priv->roots_to_notify);
+       while (g_hash_table_iter_next (&iter, &key, &value)) {
+               GFile *root = key;
+
+               /* Check if any content for root is still in the queue
+                * to be processed. This is only called each time a
+                * container/folder has been added to Tracker (so not
+                * too frequently)
+                */
+               if (check_queues &&
+                   (tracker_priority_queue_find (fs->priv->items_created, NULL, (GEqualFunc) 
g_file_has_prefix, root) ||
+                    tracker_priority_queue_find (fs->priv->items_updated, NULL, (GEqualFunc) 
g_file_has_prefix, root) ||
+                    tracker_priority_queue_find (fs->priv->items_deleted, NULL, (GEqualFunc) 
g_file_has_prefix, root) ||
+                    tracker_priority_queue_find (fs->priv->items_moved, NULL, (GEqualFunc) 
item_moved_data_has_prefix, root))) {
+                       continue;
+               }
+
+               /* Signal root is finished */
+               g_signal_emit (fs, signals[FINISHED_ROOT], 0, root);
+
+               /* Remove from hash table */
+               g_hash_table_iter_remove (&iter);
+       }
+}
+
+static void
+process_print_stats (TrackerMinerFS *fs)
+{
+       /* Only do this the first time, otherwise the results are
+        * likely to be inaccurate. Devices can be added or removed so
+        * we can't assume stats are correct.
+        */
+       if (!fs->priv->shown_totals) {
+               fs->priv->shown_totals = TRUE;
+
+               g_info ("--------------------------------------------------");
+               g_info ("Total directories : %d (%d ignored)",
+                       fs->priv->total_directories_found,
+                       fs->priv->total_directories_ignored);
+               g_info ("Total files       : %d (%d ignored)",
+                       fs->priv->total_files_found,
+                       fs->priv->total_files_ignored);
+#if 0
+               g_info ("Total monitors    : %d",
+                       tracker_monitor_get_count (fs->priv->monitor));
+#endif
+               g_info ("Total processed   : %d (%d notified, %d with error)",
+                       fs->priv->total_files_processed,
+                       fs->priv->total_files_notified,
+                       fs->priv->total_files_notified_error);
+               g_info ("--------------------------------------------------\n");
+       }
+}
+
+static void
+process_stop (TrackerMinerFS *fs)
+{
+       /* Now we have finished crawling, print stats and enable monitor events */
+       process_print_stats (fs);
+
+       g_timer_stop (fs->priv->timer);
+       g_timer_stop (fs->priv->extraction_timer);
+
+       fs->priv->timer_stopped = TRUE;
+       fs->priv->extraction_timer_stopped = TRUE;
+
+       g_info ("Idle");
+
+       g_object_set (fs,
+                     "progress", 1.0,
+                     "status", "Idle",
+                     "remaining-time", 0,
+                     NULL);
+
+       /* Make sure we signal _ALL_ roots as finished before the
+        * main FINISHED signal
+        */
+       notify_roots_finished (fs, FALSE);
+
+       g_signal_emit (fs, signals[FINISHED], 0,
+                      g_timer_elapsed (fs->priv->timer, NULL),
+                      fs->priv->total_directories_found,
+                      fs->priv->total_directories_ignored,
+                      fs->priv->total_files_found,
+                      fs->priv->total_files_ignored);
+
+       g_timer_stop (fs->priv->timer);
+       g_timer_stop (fs->priv->extraction_timer);
+
+       fs->priv->total_directories_found = 0;
+       fs->priv->total_directories_ignored = 0;
+       fs->priv->total_files_found = 0;
+       fs->priv->total_files_ignored = 0;
+
+       fs->priv->been_crawled = TRUE;
+}
+
+static ItemMovedData *
+item_moved_data_new (GFile *file,
+                     GFile *source_file)
+{
+       ItemMovedData *data;
+
+       data = g_slice_new (ItemMovedData);
+       data->file = g_object_ref (file);
+       data->source_file = g_object_ref (source_file);
+
+       return data;
+}
+
+static void
+item_moved_data_free (ItemMovedData *data)
+{
+       g_object_unref (data->file);
+       g_object_unref (data->source_file);
+       g_slice_free (ItemMovedData, data);
+}
+
+static gboolean
+item_queue_is_blocked_by_file (TrackerMinerFS *fs,
+                               GFile *file)
+{
+       g_return_val_if_fail (G_IS_FILE (file), FALSE);
+
+       if (fs->priv->item_queue_blocker != NULL &&
+           (fs->priv->item_queue_blocker == file ||
+            g_file_equal (fs->priv->item_queue_blocker, file))) {
+               return TRUE;
+       }
+
+       return FALSE;
+}
+
+static void
+sparql_buffer_task_finished_cb (GObject      *object,
+                                GAsyncResult *result,
+                                gpointer      user_data)
+{
+       TrackerMinerFS *fs;
+       TrackerMinerFSPrivate *priv;
+       TrackerTask *task;
+       GFile *task_file;
+       gboolean recursive;
+       GError *error = NULL;
+
+       fs = user_data;
+       priv = fs->priv;
+
+       task = tracker_sparql_buffer_push_finish (TRACKER_SPARQL_BUFFER (object),
+                                                 result, &error);
+
+       if (error) {
+               g_critical ("Could not execute sparql: %s", error->message);
+               priv->total_files_notified_error++;
+               g_error_free (error);
+       }
+
+       task_file = tracker_task_get_file (task);
+
+       recursive = GPOINTER_TO_INT (g_object_steal_qdata (G_OBJECT (task_file),
+                                                            priv->quark_recursive_removal));
+       tracker_file_notifier_invalidate_file_iri (priv->file_notifier, task_file, recursive);
+
+       if (item_queue_is_blocked_by_file (fs, task_file)) {
+               g_object_unref (priv->item_queue_blocker);
+               priv->item_queue_blocker = NULL;
+       }
+
+       if (priv->item_queue_blocker != NULL) {
+               if (tracker_task_pool_get_size (TRACKER_TASK_POOL (object)) > 0) {
+                       tracker_sparql_buffer_flush (TRACKER_SPARQL_BUFFER (object),
+                                                    "Item queue still blocked after flush");
+
+                       /* Check if we've finished inserting for given prefixes ... */
+                       notify_roots_finished (fs, TRUE);
+               }
+       } else {
+               item_queue_handlers_set_up (fs);
+       }
+
+       tracker_task_unref (task);
+}
+
+static UpdateProcessingTaskContext *
+update_processing_task_context_new (TrackerMiner         *miner,
+                                    gint                  priority,
+                                    const gchar          *urn,
+                                    GCancellable         *cancellable)
+{
+       UpdateProcessingTaskContext *ctxt;
+
+       ctxt = g_slice_new0 (UpdateProcessingTaskContext);
+       ctxt->miner = miner;
+       ctxt->urn = g_strdup (urn);
+       ctxt->priority = priority;
+
+       if (cancellable) {
+               ctxt->cancellable = g_object_ref (cancellable);
+       }
+
+       return ctxt;
+}
+
+static void
+update_processing_task_context_free (UpdateProcessingTaskContext *ctxt)
+{
+       g_free (ctxt->urn);
+
+       if (ctxt->cancellable) {
+               g_object_unref (ctxt->cancellable);
+       }
+
+       g_slice_free (UpdateProcessingTaskContext, ctxt);
+}
+
+static void
+on_signal_gtask_complete (GObject      *source,
+                         GAsyncResult *res,
+                         gpointer      user_data)
+{
+       TrackerMinerFS *fs = TRACKER_MINER_FS (source);
+       TrackerTask *task, *sparql_task = NULL;
+       UpdateProcessingTaskContext *ctxt;
+       GError *error = NULL;
+       GFile *file = user_data;
+       gchar *uri, *sparql;
+
+       sparql = g_task_propagate_pointer (G_TASK (res), &error);
+       g_object_unref (res);
+
+       task = tracker_task_pool_find (fs->priv->task_pool, file);
+       g_assert (task != NULL);
+
+       ctxt = tracker_task_get_data (task);
+       uri = g_file_get_uri (file);
+
+       if (error) {
+               g_message ("Could not process '%s': %s", uri, error->message);
+               g_error_free (error);
+
+               fs->priv->total_files_notified_error++;
+       } else {
+               fs->priv->total_files_notified++;
+
+               if (ctxt->urn) {
+                       gboolean attribute_update_only;
+
+                       /* The SPARQL builder will already contain the necessary
+                        * DELETE statements for the properties being updated */
+                       attribute_update_only = GPOINTER_TO_INT (g_object_steal_qdata (G_OBJECT (file), 
fs->priv->quark_attribute_updated));
+                       g_debug ("Updating item '%s' with urn '%s'%s",
+                                uri,
+                                ctxt->urn,
+                                attribute_update_only ? " (attributes only)" : "");
+
+               } else {
+                       g_debug ("Creating new item '%s'", uri);
+               }
+
+               sparql_task = tracker_sparql_task_new_take_sparql_str (file, sparql);
+       }
+
+       if (sparql_task) {
+               tracker_sparql_buffer_push (fs->priv->sparql_buffer,
+                                           sparql_task,
+                                           ctxt->priority,
+                                           sparql_buffer_task_finished_cb,
+                                           fs);
+
+               if (item_queue_is_blocked_by_file (fs, file)) {
+                       tracker_sparql_buffer_flush (fs->priv->sparql_buffer, "Current file is blocking item 
queue");
+
+                       /* Check if we've finished inserting for given prefixes ... */
+                       notify_roots_finished (fs, TRUE);
+               }
+
+               /* We can let go of our reference here because the
+                * sparql buffer takes its own reference when adding
+                * it to the task pool.
+                */
+               tracker_task_unref (sparql_task);
+       } else {
+               if (item_queue_is_blocked_by_file (fs, file)) {
+                       /* Make sure that we don't stall the item queue, although we could
+                        * expect the file to be reenqueued until the loop detector makes
+                        * us drop it since we were specifically waiting for it to complete.
+                        */
+                       g_object_unref (fs->priv->item_queue_blocker);
+                       fs->priv->item_queue_blocker = NULL;
+                       item_queue_handlers_set_up (fs);
+               }
+       }
+
+       /* Last reference is kept by the pool, removing the task from
+        * the pool cleans up the task too!
+        *
+        * NOTE that calling this any earlier actually causes invalid
+        * reads because the task frees up the
+        * UpdateProcessingTaskContext and GFile.
+        */
+       tracker_task_pool_remove (fs->priv->task_pool, task);
+
+       if (tracker_miner_fs_has_items_to_process (fs) == FALSE &&
+           tracker_task_pool_get_size (TRACKER_TASK_POOL (fs->priv->task_pool)) == 0) {
+               /* We need to run this one more time to trigger process_stop() */
+               item_queue_handlers_set_up (fs);
+       }
+
+       g_free (uri);
+}
+
+static const gchar *
+lookup_file_urn (TrackerMinerFS *fs,
+                 GFile          *file,
+                 gboolean        force)
+{
+       const gchar *urn;
+
+       g_return_val_if_fail (TRACKER_IS_MINER_FS (fs), NULL);
+       g_return_val_if_fail (G_IS_FILE (file), NULL);
+
+       urn = g_object_get_qdata (G_OBJECT (file), quark_file_iri);
+
+       if (!urn)
+               urn = tracker_file_notifier_get_file_iri (fs->priv->file_notifier,
+                                                         file, force);
+       return urn;
+}
+
+static gboolean
+item_add_or_update (TrackerMinerFS *fs,
+                    GFile          *file,
+                    gint            priority)
+{
+       TrackerMinerFSPrivate *priv;
+       UpdateProcessingTaskContext *ctxt;
+       GCancellable *cancellable;
+       gboolean processing;
+       gboolean attribute_update_only;
+       TrackerTask *task;
+       const gchar *urn;
+       gchar *uri;
+       GTask *gtask;
+
+       priv = fs->priv;
+
+       cancellable = g_cancellable_new ();
+       g_object_ref (file);
+
+       /* Always query. No matter we are notified the file was just
+        * created, its meta data might already be in the store
+        * (possibly inserted by other application) - in such a case
+        * we have to UPDATE, not INSERT. */
+       urn = lookup_file_urn (fs, file, FALSE);
+
+       /* Create task and add it to the pool as a WAIT task (we need to extract
+        * the file metadata and such) */
+       ctxt = update_processing_task_context_new (TRACKER_MINER (fs),
+                                                  priority,
+                                                  urn,
+                                                  cancellable);
+       task = tracker_task_new (file, ctxt,
+                                (GDestroyNotify) update_processing_task_context_free);
+
+       tracker_task_pool_add (priv->task_pool, task);
+       tracker_task_unref (task);
+
+       /* Call ::process-file to see if we handle this resource or not */
+       uri = g_file_get_uri (file);
+
+       attribute_update_only = GPOINTER_TO_INT (g_object_get_qdata (G_OBJECT (file), 
priv->quark_attribute_updated));
+
+       gtask = g_task_new (fs, ctxt->cancellable, on_signal_gtask_complete, file);
+
+       if (!attribute_update_only) {
+               g_debug ("Processing file '%s'...", uri);
+               g_signal_emit (fs, signals[PROCESS_FILE], 0,
+                              file, gtask,
+                              &processing);
+       } else {
+               g_debug ("Processing attributes in file '%s'...", uri);
+               g_signal_emit (fs, signals[PROCESS_FILE_ATTRIBUTES], 0,
+                              file, gtask,
+                              &processing);
+       }
+
+       if (!processing) {
+               GError *error;
+
+               error = g_error_new (tracker_miner_fs_error_quark (),
+                                    TRACKER_MINER_FS_ERROR_INIT,
+                                    "TrackerMinerFS::%s returned FALSE",
+                                    attribute_update_only ? "process-file-attributes" : "process-file");
+               g_task_return_error (gtask, error);
+       } else {
+               fs->priv->total_files_processed++;
+       }
+
+       g_free (uri);
+       g_object_unref (file);
+       g_object_unref (cancellable);
+
+       return !tracker_task_pool_limit_reached (priv->task_pool);
+}
+
+static gboolean
+item_remove (TrackerMinerFS *fs,
+             GFile          *file,
+             gboolean        only_children)
+{
+       gchar *uri, *sparql;
+       TrackerTask *task;
+       guint signal_num;
+
+       uri = g_file_get_uri (file);
+
+       g_debug ("Removing item: '%s' (Deleted from filesystem or no longer monitored)",
+                uri);
+
+       g_object_set_qdata (G_OBJECT (file),
+                           fs->priv->quark_recursive_removal,
+                           GINT_TO_POINTER (TRUE));
+
+       signal_num = only_children ? REMOVE_CHILDREN : REMOVE_FILE;
+       g_signal_emit (fs, signals[signal_num], 0, file, &sparql);
+
+       if (sparql) {
+               task = tracker_sparql_task_new_take_sparql_str (file, sparql);
+               tracker_sparql_buffer_push (fs->priv->sparql_buffer,
+                                           task,
+                                           G_PRIORITY_DEFAULT,
+                                           sparql_buffer_task_finished_cb,
+                                           fs);
+               tracker_task_unref (task);
+       }
+
+       if (!tracker_task_pool_limit_reached (TRACKER_TASK_POOL (fs->priv->sparql_buffer))) {
+               item_queue_handlers_set_up (fs);
+       }
+
+       g_free (uri);
+
+       return TRUE;
+}
+
+static gboolean
+item_move (TrackerMinerFS *fs,
+           GFile          *file,
+           GFile          *source_file)
+{
+       gchar     *uri, *source_uri, *sparql;
+       GFileInfo *file_info;
+       TrackerTask *task;
+       const gchar *source_iri;
+       gboolean source_exists;
+       TrackerDirectoryFlags source_flags, flags;
+       gboolean recursive;
+
+       uri = g_file_get_uri (file);
+       source_uri = g_file_get_uri (source_file);
+
+       /* FIXME: Should check the _NO_STAT on TrackerDirectoryFlags first! */
+       file_info = g_file_query_info (file,
+                                      G_FILE_ATTRIBUTE_STANDARD_TYPE,
+                                      G_FILE_QUERY_INFO_NOFOLLOW_SYMLINKS,
+                                      NULL, NULL);
+
+       /* Get 'source' ID */
+       source_iri = lookup_file_urn (fs, source_file, TRUE);
+       source_exists = (source_iri != NULL);
+
+       if (!file_info) {
+               gboolean retval;
+
+               if (source_exists) {
+                       /* Destination file has gone away, ignore dest file and remove source if any */
+                       retval = item_remove (fs, source_file, FALSE);
+               } else {
+                       /* Destination file went away, and source wasn't indexed either */
+                       retval = TRUE;
+               }
+
+               g_free (source_uri);
+               g_free (uri);
+
+               return retval;
+       } else if (!source_exists) {
+               gboolean retval;
+
+               /* The source file might not be indexed yet (eg. temporary save
+                * files that are immediately renamed to the definitive path).
+                * Deal with those as newly added items.
+                */
+               g_debug ("Source file '%s' not yet in store, indexing '%s' "
+                        "from scratch", source_uri, uri);
+
+               retval = item_add_or_update (fs, file, G_PRIORITY_DEFAULT);
+
+               g_free (source_uri);
+               g_free (uri);
+               g_object_unref (file_info);
+
+               return retval;
+       }
+
+       g_debug ("Moving item from '%s' to '%s'",
+                source_uri,
+                uri);
+
+       tracker_indexing_tree_get_root (fs->priv->indexing_tree, source_file, &source_flags);
+       tracker_indexing_tree_get_root (fs->priv->indexing_tree, file, &flags);
+       recursive = ((source_flags & TRACKER_DIRECTORY_FLAG_RECURSE) != 0 &&
+                    (flags & TRACKER_DIRECTORY_FLAG_RECURSE) != 0 &&
+                    g_file_info_get_file_type (file_info) == G_FILE_TYPE_DIRECTORY);
+
+       /* Delete destination item from store if any */
+       item_remove (fs, file, FALSE);
+
+       /* If the original location is recursive, but the destination location
+        * is not, remove all children.
+        */
+       if (!recursive &&
+           (source_flags & TRACKER_DIRECTORY_FLAG_RECURSE) != 0)
+               item_remove (fs, source_file, TRUE);
+
+       g_signal_emit (fs, signals[MOVE_FILE], 0, file, source_file, recursive, &sparql);
+
+       if (sparql) {
+               /* Add new task to processing pool */
+               task = tracker_sparql_task_new_take_sparql_str (file, sparql);
+               tracker_sparql_buffer_push (fs->priv->sparql_buffer,
+                                           task,
+                                           G_PRIORITY_DEFAULT,
+                                           sparql_buffer_task_finished_cb,
+                                           fs);
+               tracker_task_unref (task);
+       }
+
+       if (!tracker_task_pool_limit_reached (TRACKER_TASK_POOL (fs->priv->sparql_buffer))) {
+               item_queue_handlers_set_up (fs);
+       }
+
+       g_free (uri);
+       g_free (source_uri);
+       g_object_unref (file_info);
+
+       return TRUE;
+}
+
+static gboolean
+should_wait (TrackerMinerFS *fs,
+             GFile          *file)
+{
+       GFile *parent;
+
+       /* Is the item already being processed? */
+       if (tracker_task_pool_find (fs->priv->task_pool, file) ||
+           tracker_task_pool_find (TRACKER_TASK_POOL (fs->priv->sparql_buffer), file)) {
+               /* Yes, a previous event on same item currently
+                * being processed */
+               fs->priv->item_queue_blocker = g_object_ref (file);
+               return TRUE;
+       }
+
+       /* Is the item's parent being processed right now? */
+       parent = g_file_get_parent (file);
+       if (parent) {
+               if (tracker_task_pool_find (fs->priv->task_pool, parent) ||
+                   tracker_task_pool_find (TRACKER_TASK_POOL (fs->priv->sparql_buffer), parent)) {
+                       /* Yes, a previous event on the parent of this item
+                        * currently being processed */
+                       fs->priv->item_queue_blocker = parent;
+                       return TRUE;
+               }
+
+               g_object_unref (parent);
+       }
+       return FALSE;
+}
+
+static gboolean
+item_enqueue_again (TrackerMinerFS       *fs,
+                    TrackerPriorityQueue *item_queue,
+                    GFile                *queue_file,
+                    gint                  priority)
+{
+       gint reentry_counter;
+       gchar *uri;
+       gboolean should_wait;
+
+       reentry_counter = GPOINTER_TO_INT (g_object_get_qdata (G_OBJECT (queue_file),
+                                                              fs->priv->quark_reentry_counter));
+
+       if (reentry_counter <= REENTRY_MAX) {
+               g_object_set_qdata (G_OBJECT (queue_file),
+                                   fs->priv->quark_reentry_counter,
+                                   GINT_TO_POINTER (reentry_counter + 1));
+               tracker_priority_queue_add (item_queue, g_object_ref (queue_file), priority);
+
+               should_wait = TRUE;
+       } else {
+               uri = g_file_get_uri (queue_file);
+               g_warning ("File '%s' has been reenqueued more than %d times. It will not be indexed.", uri, 
REENTRY_MAX);
+               g_free (uri);
+
+               /* We must be careful not to return QUEUE_WAIT when there's actually
+                * nothing left to wait for, or the crawling might never complete.
+                */
+               if (tracker_miner_fs_has_items_to_process (fs)) {
+                       should_wait = TRUE;
+               } else {
+                       should_wait = FALSE;
+               }
+       }
+
+       return should_wait;
+}
+
+static QueueState
+item_queue_get_next_file (TrackerMinerFS  *fs,
+                          GFile          **file,
+                          GFile          **source_file,
+                          gint            *priority_out)
+{
+       ItemMovedData *data;
+       GFile *queue_file;
+       gint priority;
+
+       /* Deleted items second */
+       queue_file = tracker_priority_queue_peek (fs->priv->items_deleted,
+                                                 &priority);
+       if (queue_file) {
+               *source_file = NULL;
+
+               trace_eq_pop_head ("DELETED", queue_file);
+
+               /* Do not ignore DELETED event. We should never see DELETED on update
+                  (atomic rename or in-place update) but we may see DELETED
+                  due to actual file deletion right after update. */
+
+               /* If the same item OR its first parent is currently being processed,
+                * we need to wait for this event */
+               if (should_wait (fs, queue_file)) {
+                       *file = NULL;
+
+                       trace_eq_push_head ("DELETED", queue_file, "Should wait");
+                       return QUEUE_WAIT;
+               }
+
+               tracker_priority_queue_pop (fs->priv->items_deleted, NULL);
+               *file = queue_file;
+               *priority_out = priority;
+               return QUEUE_DELETED;
+       }
+
+       /* Created items next */
+       queue_file = tracker_priority_queue_peek (fs->priv->items_created,
+                                                 &priority);
+       if (queue_file) {
+               *source_file = NULL;
+
+               trace_eq_pop_head ("CREATED", queue_file);
+
+               /* If the same item OR its first parent is currently being processed,
+                * we need to wait for this event */
+               if (should_wait (fs, queue_file)) {
+                       *file = NULL;
+
+                       trace_eq_push_head ("CREATED", queue_file, "Should wait");
+                       return QUEUE_WAIT;
+               }
+
+               tracker_priority_queue_pop (fs->priv->items_created, NULL);
+               *file = queue_file;
+               *priority_out = priority;
+               return QUEUE_CREATED;
+       }
+
+       /* Updated items next */
+       queue_file = tracker_priority_queue_peek (fs->priv->items_updated,
+                                                 &priority);
+       if (queue_file) {
+               *file = queue_file;
+               *source_file = NULL;
+
+               trace_eq_pop_head ("UPDATED", queue_file);
+
+               /* If the same item OR its first parent is currently being processed,
+                * we need to wait for this event */
+               if (should_wait (fs, queue_file)) {
+                       *file = NULL;
+
+                       trace_eq_push_head ("UPDATED", queue_file, "Should wait");
+                       return QUEUE_WAIT;
+               }
+
+               tracker_priority_queue_pop (fs->priv->items_updated, NULL);
+               *priority_out = priority;
+
+               return QUEUE_UPDATED;
+       }
+
+       /* Moved items next */
+       data = tracker_priority_queue_peek (fs->priv->items_moved,
+                                           &priority);
+       if (data) {
+               trace_eq_pop_head_2 ("MOVED", data->file, data->source_file);
+
+               /* If the same item OR its first parent is currently being processed,
+                * we need to wait for this event */
+               if (should_wait (fs, data->file) ||
+                   should_wait (fs, data->source_file)) {
+                       *file = NULL;
+                       *source_file = NULL;
+
+                       trace_eq_push_head_2 ("MOVED", data->source_file, data->file, "Should wait");
+                       return QUEUE_WAIT;
+               }
+
+               tracker_priority_queue_pop (fs->priv->items_moved, NULL);
+               *file = g_object_ref (data->file);
+               *source_file = g_object_ref (data->source_file);
+               *priority_out = priority;
+               item_moved_data_free (data);
+               return QUEUE_MOVED;
+       }
+
+       *file = NULL;
+       *source_file = NULL;
+
+       if (tracker_file_notifier_is_active (fs->priv->file_notifier) ||
+           tracker_task_pool_limit_reached (fs->priv->task_pool) ||
+           tracker_task_pool_limit_reached (TRACKER_TASK_POOL (fs->priv->sparql_buffer))) {
+               if (tracker_task_pool_get_size (fs->priv->task_pool) == 0) {
+                       fs->priv->extraction_timer_stopped = TRUE;
+                       g_timer_stop (fs->priv->extraction_timer);
+               }
+
+               /* There are still pending items to crawl,
+                * or extract pool limit is reached
+                */
+               return QUEUE_WAIT;
+       }
+
+       return QUEUE_NONE;
+}
+
+static gdouble
+item_queue_get_progress (TrackerMinerFS *fs,
+                         guint          *n_items_processed,
+                         guint          *n_items_remaining)
+{
+       guint items_to_process = 0;
+       guint items_total = 0;
+
+       items_to_process += tracker_priority_queue_get_length (fs->priv->items_deleted);
+       items_to_process += tracker_priority_queue_get_length (fs->priv->items_created);
+       items_to_process += tracker_priority_queue_get_length (fs->priv->items_updated);
+       items_to_process += tracker_priority_queue_get_length (fs->priv->items_moved);
+
+       items_total += fs->priv->total_directories_found;
+       items_total += fs->priv->total_files_found;
+
+       if (n_items_processed) {
+               *n_items_processed = ((items_total >= items_to_process) ?
+                                     (items_total - items_to_process) : 0);
+       }
+
+       if (n_items_remaining) {
+               *n_items_remaining = items_to_process;
+       }
+
+       if (items_total == 0 ||
+           items_to_process == 0 ||
+           items_to_process > items_total) {
+               return 1.0;
+       }
+
+       return (gdouble) (items_total - items_to_process) / items_total;
+}
+
+static gboolean
+miner_handle_next_item (TrackerMinerFS *fs)
+{
+       GFile *file = NULL;
+       GFile *source_file = NULL;
+       GFile *parent;
+       QueueState queue;
+       GTimeVal time_now;
+       static GTimeVal time_last = { 0 };
+       gboolean keep_processing = TRUE;
+       gint priority = 0;
+
+       if (fs->priv->timer_stopped) {
+               g_timer_start (fs->priv->timer);
+               fs->priv->timer_stopped = FALSE;
+       }
+
+       if (tracker_task_pool_limit_reached (TRACKER_TASK_POOL (fs->priv->sparql_buffer))) {
+               /* Task pool is full, give it a break */
+               return FALSE;
+       }
+
+       queue = item_queue_get_next_file (fs, &file, &source_file, &priority);
+
+       if (queue == QUEUE_WAIT) {
+               /* We should flush the processing pool buffer here, because
+                * if there was a previous task on the same file we want to
+                * process now, we want it to get finished before we can go
+                * on with the queues... */
+               tracker_sparql_buffer_flush (fs->priv->sparql_buffer,
+                                            "Queue handlers WAIT");
+
+               /* Check if we've finished inserting for given prefixes ... */
+               notify_roots_finished (fs, TRUE);
+
+               /* Items are still being processed, so wait until
+                * the processing pool is cleared before starting with
+                * the next directories batch.
+                */
+               return FALSE;
+       }
+
+       if (queue == QUEUE_NONE) {
+               g_timer_stop (fs->priv->extraction_timer);
+               fs->priv->extraction_timer_stopped = TRUE;
+       } else if (fs->priv->extraction_timer_stopped) {
+               g_timer_continue (fs->priv->extraction_timer);
+               fs->priv->extraction_timer_stopped = FALSE;
+       }
+
+       /* Update progress, but don't spam it. */
+       g_get_current_time (&time_now);
+
+       if ((time_now.tv_sec - time_last.tv_sec) >= 1) {
+               guint items_processed, items_remaining;
+               gdouble progress_now;
+               static gdouble progress_last = 0.0;
+               static gint info_last = 0;
+               gdouble seconds_elapsed, extraction_elapsed;
+
+               time_last = time_now;
+
+               /* Update progress? */
+               progress_now = item_queue_get_progress (fs,
+                                                       &items_processed,
+                                                       &items_remaining);
+               seconds_elapsed = g_timer_elapsed (fs->priv->timer, NULL);
+               extraction_elapsed = g_timer_elapsed (fs->priv->extraction_timer, NULL);
+
+               if (!tracker_file_notifier_is_active (fs->priv->file_notifier)) {
+                       gchar *status;
+                       gint remaining_time;
+
+                       g_object_get (fs, "status", &status, NULL);
+
+                       /* Compute remaining time */
+                       remaining_time = (gint)tracker_seconds_estimate (extraction_elapsed,
+                                                                        items_processed,
+                                                                        items_remaining);
+
+                       /* CLAMP progress so it doesn't go back below
+                        * 2% (which we use for crawling)
+                        */
+                       if (g_strcmp0 (status, "Processing…") != 0) {
+                               /* Don't spam this */
+                               g_info ("Processing…");
+                               g_object_set (fs,
+                                             "status", "Processing…",
+                                             "progress", CLAMP (progress_now, 0.02, 1.00),
+                                             "remaining-time", remaining_time,
+                                             NULL);
+                       } else {
+                               g_object_set (fs,
+                                             "progress", CLAMP (progress_now, 0.02, 1.00),
+                                             "remaining-time", remaining_time,
+                                             NULL);
+                       }
+
+                       g_free (status);
+               }
+
+               if (++info_last >= 5 &&
+                   (gint) (progress_last * 100) != (gint) (progress_now * 100)) {
+                       gchar *str1, *str2;
+
+                       info_last = 0;
+                       progress_last = progress_now;
+
+                       /* Log estimated remaining time */
+                       str1 = tracker_seconds_estimate_to_string (extraction_elapsed,
+                                                                  TRUE,
+                                                                  items_processed,
+                                                                  items_remaining);
+                       str2 = tracker_seconds_to_string (seconds_elapsed, TRUE);
+
+                       g_info ("Processed %u/%u, estimated %s left, %s elapsed",
+                               items_processed,
+                               items_processed + items_remaining,
+                               str1,
+                               str2);
+
+                       g_free (str2);
+                       g_free (str1);
+               }
+       }
+
+       /* Handle queues */
+       switch (queue) {
+       case QUEUE_NONE:
+               if (!tracker_file_notifier_is_active (fs->priv->file_notifier) &&
+                   tracker_task_pool_get_size (fs->priv->task_pool) == 0) {
+                       if (tracker_task_pool_get_size (TRACKER_TASK_POOL (fs->priv->sparql_buffer)) == 0) {
+                               /* Print stats and signal finished */
+                               process_stop (fs);
+                       } else {
+                               /* Flush any possible pending update here */
+                               tracker_sparql_buffer_flush (fs->priv->sparql_buffer,
+                                                            "Queue handlers NONE");
+
+                               /* Check if we've finished inserting for given prefixes ... */
+                               notify_roots_finished (fs, TRUE);
+                       }
+               }
+
+               /* No more files left to process */
+               keep_processing = FALSE;
+               break;
+       case QUEUE_MOVED:
+               keep_processing = item_move (fs, file, source_file);
+               break;
+       case QUEUE_DELETED:
+               keep_processing = item_remove (fs, file, FALSE);
+               break;
+       case QUEUE_CREATED:
+       case QUEUE_UPDATED:
+               parent = g_file_get_parent (file);
+
+               if (!parent ||
+                   tracker_indexing_tree_file_is_root (fs->priv->indexing_tree, file) ||
+                   !tracker_indexing_tree_get_root (fs->priv->indexing_tree, file, NULL) ||
+                   lookup_file_urn (fs, parent, TRUE)) {
+                       keep_processing = item_add_or_update (fs, file, priority);
+               } else {
+                       TrackerPriorityQueue *item_queue;
+                       gchar *uri;
+
+                       uri = g_file_get_uri (parent);
+                       g_message ("Parent '%s' not indexed yet", uri);
+                       g_free (uri);
+
+                       if (queue == QUEUE_CREATED) {
+                               item_queue = fs->priv->items_created;
+                       } else {
+                               item_queue = fs->priv->items_updated;
+                       }
+
+                       /* Parent isn't indexed yet, reinsert the task into the queue,
+                        * but forcily prepended by its parent so its indexing is
+                        * ensured, tasks are inserted at a higher priority so they
+                        * are processed promptly anyway.
+                        */
+                       item_enqueue_again (fs, item_queue, parent, priority - 1);
+                       item_enqueue_again (fs, item_queue, file, priority);
+
+                       keep_processing = TRUE;
+               }
+
+               if (parent) {
+                       g_object_unref (parent);
+               }
+
+               break;
+       default:
+               g_assert_not_reached ();
+       }
+
+       if (file) {
+               g_object_unref (file);
+       }
+
+       if (source_file) {
+               g_object_unref (source_file);
+       }
+
+       return keep_processing;
+}
+
+static gboolean
+item_queue_handlers_cb (gpointer user_data)
+{
+       TrackerMinerFS *fs = user_data;
+       gboolean retval = FALSE;
+       gint i;
+
+       for (i = 0; i < MAX_SIMULTANEOUS_ITEMS; i++) {
+               retval = miner_handle_next_item (fs);
+               if (retval == FALSE)
+                       break;
+       }
+
+       if (retval == FALSE) {
+               fs->priv->item_queues_handler_id = 0;
+       }
+
+       return retval;
+}
+
+static guint
+_tracker_idle_add (TrackerMinerFS *fs,
+                   GSourceFunc     func,
+                   gpointer        user_data)
+{
+       guint interval;
+
+       interval = TRACKER_CRAWLER_MAX_TIMEOUT_INTERVAL * fs->priv->throttle;
+
+       if (interval == 0) {
+               return g_idle_add_full (TRACKER_TASK_PRIORITY, func, user_data, NULL);
+       } else {
+               return g_timeout_add_full (TRACKER_TASK_PRIORITY, interval, func, user_data, NULL);
+       }
+}
+
+static void
+item_queue_handlers_set_up (TrackerMinerFS *fs)
+{
+       trace_eq ("Setting up queue handlers...");
+       if (fs->priv->item_queues_handler_id != 0) {
+               trace_eq ("   cancelled: already one active");
+               return;
+       }
+
+       if (fs->priv->is_paused) {
+               trace_eq ("   cancelled: paused");
+               return;
+       }
+
+       if (fs->priv->item_queue_blocker) {
+               trace_eq ("   cancelled: item queue blocked waiting for file '%s'",
+                         g_file_get_uri (fs->priv->item_queue_blocker));
+               return;
+       }
+
+       /* Already processing max number of sparql updates */
+       if (tracker_task_pool_limit_reached (fs->priv->task_pool)) {
+               trace_eq ("   cancelled: pool limit reached (tasks: %u (max %u)",
+                         tracker_task_pool_get_size (fs->priv->task_pool),
+                         tracker_task_pool_get_limit (fs->priv->task_pool));
+               return;
+       }
+
+       if (tracker_task_pool_limit_reached (TRACKER_TASK_POOL (fs->priv->sparql_buffer))) {
+               trace_eq ("   cancelled: pool limit reached (sparql buffer: %u)",
+                         tracker_task_pool_get_limit (TRACKER_TASK_POOL (fs->priv->sparql_buffer)));
+               return;
+       }
+
+       if (!tracker_file_notifier_is_active (fs->priv->file_notifier)) {
+               gchar *status;
+               gdouble progress;
+
+               g_object_get (fs,
+                             "progress", &progress,
+                             "status", &status,
+                             NULL);
+
+               /* Don't spam this */
+               if (progress > 0.01 && g_strcmp0 (status, "Processing…") != 0) {
+                       g_info ("Processing…");
+                       g_object_set (fs, "status", "Processing…", NULL);
+               }
+
+               g_free (status);
+       }
+
+       trace_eq ("   scheduled in idle");
+       fs->priv->item_queues_handler_id =
+               _tracker_idle_add (fs,
+                                  item_queue_handlers_cb,
+                                  fs);
+}
+
+static gboolean
+should_check_file (TrackerMinerFS *fs,
+                   GFile          *file,
+                   gboolean        is_dir)
+{
+       GFileType file_type;
+
+       file_type = (is_dir) ? G_FILE_TYPE_DIRECTORY : G_FILE_TYPE_REGULAR;
+       return tracker_indexing_tree_file_is_indexable (fs->priv->indexing_tree,
+                                                       file, file_type);
+}
+
+static gboolean
+moved_files_equal (gconstpointer a,
+                   gconstpointer b)
+{
+       const ItemMovedData *data = a;
+       GFile *file = G_FILE (b);
+
+       /* Compare with dest file */
+       return g_file_equal (data->file, file);
+}
+
+static gint
+miner_fs_get_queue_priority (TrackerMinerFS *fs,
+                             GFile          *file)
+{
+       TrackerDirectoryFlags flags;
+
+       tracker_indexing_tree_get_root (fs->priv->indexing_tree,
+                                       file, &flags);
+
+       return (flags & TRACKER_DIRECTORY_FLAG_PRIORITY) ?
+               G_PRIORITY_HIGH : G_PRIORITY_DEFAULT;
+}
+
+static void
+miner_fs_cache_file_urn (TrackerMinerFS *fs,
+                         GFile          *file,
+                         gboolean        query_urn)
+{
+       const gchar *urn;
+
+       /* Store urn as qdata */
+       urn = tracker_file_notifier_get_file_iri (fs->priv->file_notifier, file, query_urn);
+       g_object_set_qdata_full (G_OBJECT (file), quark_file_iri,
+                                g_strdup (urn), (GDestroyNotify) g_free);
+}
+
+static void
+miner_fs_queue_file (TrackerMinerFS       *fs,
+                     TrackerPriorityQueue *item_queue,
+                     GFile                *file,
+                     gboolean              query_urn)
+{
+       gint priority;
+
+       miner_fs_cache_file_urn (fs, file, query_urn);
+       priority = miner_fs_get_queue_priority (fs, file);
+       tracker_priority_queue_add (item_queue, g_object_ref (file), priority);
+}
+
+/* Checks previous created/updated/deleted/moved queues for
+ * monitor events. Returns TRUE if the item should still
+ * be added to the queue.
+ */
+static gboolean
+check_item_queues (TrackerMinerFS *fs,
+                   QueueState      queue,
+                   GFile          *file,
+                   GFile          *other_file)
+{
+       ItemMovedData *move_data;
+
+       if (!fs->priv->been_crawled) {
+               /* Only do this after initial crawling, so
+                * we are mostly sure that we won't be doing
+                * checks on huge lists.
+                */
+               return TRUE;
+       }
+
+       switch (queue) {
+       case QUEUE_CREATED:
+               /* Created items aren't likely to have
+                * anything in other queues for the same
+                * file.
+                */
+               return TRUE;
+       case QUEUE_UPDATED:
+               /* No further updates after a previous created/updated event */
+               if (tracker_priority_queue_find (fs->priv->items_created, NULL,
+                                                (GEqualFunc) g_file_equal, file) ||
+                   tracker_priority_queue_find (fs->priv->items_updated, NULL,
+                                                (GEqualFunc) g_file_equal, file)) {
+                       g_debug ("  Found previous unhandled CREATED/UPDATED event");
+                       return FALSE;
+               }
+               return TRUE;
+       case QUEUE_DELETED:
+               if (tracker_file_notifier_get_file_type (fs->priv->file_notifier,
+                                                        file) == G_FILE_TYPE_DIRECTORY) {
+                       if (tracker_priority_queue_foreach_remove (fs->priv->items_updated,
+                                                                  (GEqualFunc) g_file_has_prefix,
+                                                                  file,
+                                                                  (GDestroyNotify) g_object_unref)) {
+                               g_debug ("  Deleting previous unhandled UPDATED events on children");
+                       }
+
+                       if (tracker_priority_queue_foreach_remove (fs->priv->items_created,
+                                                                  (GEqualFunc) g_file_has_prefix,
+                                                                  file,
+                                                                  (GDestroyNotify) g_object_unref)) {
+                               g_debug ("  Deleting previous unhandled CREATED events on children");
+                       }
+
+                       if (tracker_priority_queue_foreach_remove (fs->priv->items_deleted,
+                                                                  (GEqualFunc) g_file_has_prefix,
+                                                                  file,
+                                                                  (GDestroyNotify) g_object_unref)) {
+                               g_debug ("  Deleting previous unhandled DELETED events on children");
+                       }
+               }
+
+               /* Remove all previous updates */
+               if (tracker_priority_queue_foreach_remove (fs->priv->items_updated,
+                                                          (GEqualFunc) g_file_equal,
+                                                          file,
+                                                          (GDestroyNotify) g_object_unref)) {
+                       g_debug ("  Deleting previous unhandled UPDATED event");
+               }
+
+               if (tracker_priority_queue_foreach_remove (fs->priv->items_created,
+                                                          (GEqualFunc) g_file_equal,
+                                                          file,
+                                                          (GDestroyNotify) g_object_unref)) {
+                       /* Created event was still in the queue,
+                        * remove it and ignore the current event
+                        */
+                       g_debug ("  Found matching unhandled CREATED event, removing file altogether");
+                       return FALSE;
+               }
+
+               return TRUE;
+       case QUEUE_MOVED:
+               /* Kill any events on other_file (The dest one), since it will be rewritten anyway */
+               if (tracker_priority_queue_foreach_remove (fs->priv->items_created,
+                                                          (GEqualFunc) g_file_equal,
+                                                          other_file,
+                                                          (GDestroyNotify) g_object_unref)) {
+                       g_debug ("  Removing previous unhandled CREATED event for dest file, will be 
rewritten anyway");
+               }
+
+               if (tracker_priority_queue_foreach_remove (fs->priv->items_updated,
+                                                          (GEqualFunc) g_file_equal,
+                                                          other_file,
+                                                          (GDestroyNotify) g_object_unref)) {
+                       g_debug ("  Removing previous unhandled UPDATED event for dest file, will be 
rewritten anyway");
+               }
+
+               /* Now check file (Origin one) */
+               if (tracker_priority_queue_foreach_remove (fs->priv->items_created,
+                                                          (GEqualFunc) g_file_equal,
+                                                          file,
+                                                          (GDestroyNotify) g_object_unref)) {
+                       /* If source file was created, replace it with
+                        * a create event for the destination file, and
+                        * discard this event.
+                        *
+                        * We assume all posterior updates
+                        * have been merged together previously by this
+                        * same function.
+                        */
+                       g_debug ("  Found matching unhandled CREATED event "
+                                "for source file, merging both events together");
+                       miner_fs_queue_file (fs, fs->priv->items_created, other_file, FALSE);
+
+                       return FALSE;
+               }
+
+               move_data = tracker_priority_queue_find (fs->priv->items_moved, NULL,
+                                                        (GEqualFunc) moved_files_equal, file);
+               if (move_data) {
+                       /* Origin file was the dest of a previous
+                        * move operation, merge these together.
+                        */
+                       g_debug ("  Source file is the destination of a previous "
+                                "unhandled MOVED event, merging both events together");
+                       g_object_unref (move_data->file);
+                       move_data->file = g_object_ref (other_file);
+                       return FALSE;
+               }
+
+               return TRUE;
+               break;
+       default:
+               g_assert_not_reached ();
+       }
+
+       return TRUE;
+}
+
+static gboolean
+filter_event (TrackerMinerFS          *fs,
+              TrackerMinerFSEventType  type,
+              GFile                   *file,
+              GFile                   *source_file)
+{
+       TrackerMinerFSClass *klass = TRACKER_MINER_FS_GET_CLASS (fs);
+
+       if (!klass->filter_event)
+               return FALSE;
+
+       return klass->filter_event (fs, type, file, source_file);
+}
+
+static void
+file_notifier_file_created (TrackerFileNotifier  *notifier,
+                            GFile                *file,
+                            gpointer              user_data)
+{
+       TrackerMinerFS *fs = user_data;
+
+       if (filter_event (fs, TRACKER_MINER_FS_EVENT_CREATED, file, NULL))
+               return;
+
+       if (check_item_queues (fs, QUEUE_CREATED, file, NULL)) {
+               miner_fs_queue_file (fs, fs->priv->items_created, file, FALSE);
+               item_queue_handlers_set_up (fs);
+       }
+}
+
+static void
+file_notifier_file_deleted (TrackerFileNotifier  *notifier,
+                            GFile                *file,
+                            gpointer              user_data)
+{
+       TrackerMinerFS *fs = user_data;
+
+       if (filter_event (fs, TRACKER_MINER_FS_EVENT_DELETED, file, NULL))
+               return;
+
+       if (tracker_file_notifier_get_file_type (notifier, file) == G_FILE_TYPE_DIRECTORY) {
+               /* Cancel all pending tasks on files inside the path given by file */
+               tracker_task_pool_foreach (fs->priv->task_pool,
+                                          task_pool_cancel_foreach,
+                                          file);
+       }
+
+       if (check_item_queues (fs, QUEUE_DELETED, file, NULL)) {
+               miner_fs_queue_file (fs, fs->priv->items_deleted, file, FALSE);
+               item_queue_handlers_set_up (fs);
+       }
+}
+
+static void
+file_notifier_file_updated (TrackerFileNotifier  *notifier,
+                            GFile                *file,
+                            gboolean              attributes_only,
+                            gpointer              user_data)
+{
+       TrackerMinerFS *fs = user_data;
+
+       if (!attributes_only &&
+           filter_event (fs, TRACKER_MINER_FS_EVENT_UPDATED, file, NULL))
+               return;
+
+       if (check_item_queues (fs, QUEUE_UPDATED, file, NULL)) {
+               if (attributes_only) {
+                       g_object_set_qdata (G_OBJECT (file),
+                                           fs->priv->quark_attribute_updated,
+                                           GINT_TO_POINTER (TRUE));
+               }
+
+               miner_fs_queue_file (fs, fs->priv->items_updated, file, TRUE);
+               item_queue_handlers_set_up (fs);
+       }
+}
+
+static void
+file_notifier_file_moved (TrackerFileNotifier *notifier,
+                          GFile               *source,
+                          GFile               *dest,
+                          gpointer             user_data)
+{
+       TrackerMinerFS *fs = user_data;
+
+       if (filter_event (fs, TRACKER_MINER_FS_EVENT_MOVED, dest, source))
+               return;
+
+       if (check_item_queues (fs, QUEUE_MOVED, source, dest)) {
+               gint priority;
+
+               priority = miner_fs_get_queue_priority (fs, dest);
+               tracker_priority_queue_add (fs->priv->items_moved,
+                                           item_moved_data_new (dest, source),
+                                           priority);
+               item_queue_handlers_set_up (fs);
+       }
+}
+
+static void
+file_notifier_directory_started (TrackerFileNotifier *notifier,
+                                 GFile               *directory,
+                                 gpointer             user_data)
+{
+       TrackerMinerFS *fs = user_data;
+       TrackerDirectoryFlags flags;
+       gchar *str, *uri;
+
+       uri = g_file_get_uri (directory);
+       tracker_indexing_tree_get_root (fs->priv->indexing_tree,
+                                       directory, &flags);
+
+       if ((flags & TRACKER_DIRECTORY_FLAG_RECURSE) != 0) {
+                str = g_strdup_printf ("Crawling recursively directory '%s'", uri);
+        } else {
+                str = g_strdup_printf ("Crawling single directory '%s'", uri);
+        }
+
+       if (fs->priv->timer_stopped) {
+               g_timer_start (fs->priv->timer);
+               fs->priv->timer_stopped = FALSE;
+       }
+
+       if (fs->priv->extraction_timer_stopped) {
+               g_timer_start (fs->priv->timer);
+               fs->priv->extraction_timer_stopped = FALSE;
+       }
+
+       /* Always set the progress here to at least 1%, and the remaining time
+         * to -1 as we cannot guess during crawling (we don't know how many directories
+         * we will find) */
+        g_object_set (fs,
+                      "progress", 0.01,
+                      "status", str,
+                      "remaining-time", -1,
+                      NULL);
+       g_free (str);
+       g_free (uri);
+}
+
+static void
+file_notifier_directory_finished (TrackerFileNotifier *notifier,
+                                  GFile               *directory,
+                                  guint                directories_found,
+                                  guint                directories_ignored,
+                                  guint                files_found,
+                                  guint                files_ignored,
+                                  gpointer             user_data)
+{
+       TrackerMinerFS *fs = user_data;
+       gchar *str, *uri;
+
+       /* Update stats */
+       fs->priv->total_directories_found += directories_found;
+       fs->priv->total_directories_ignored += directories_ignored;
+       fs->priv->total_files_found += files_found;
+       fs->priv->total_files_ignored += files_ignored;
+
+       uri = g_file_get_uri (directory);
+       str = g_strdup_printf ("Crawl finished for directory '%s'", uri);
+
+        g_object_set (fs,
+                      "progress", 0.01,
+                      "status", str,
+                      "remaining-time", -1,
+                      NULL);
+
+       g_free (str);
+       g_free (uri);
+
+       if (directories_found == 0 &&
+           files_found == 0) {
+               /* Signal now because we have nothing to index */
+               g_signal_emit (fs, signals[FINISHED_ROOT], 0, directory);
+       } else {
+               /* Add root to list we want to be notified about when
+                * finished indexing! */
+               g_hash_table_replace (fs->priv->roots_to_notify,
+                                     g_object_ref (directory),
+                                     GUINT_TO_POINTER(time(NULL)));
+       }
+}
+
+static void
+file_notifier_finished (TrackerFileNotifier *notifier,
+                        gpointer             user_data)
+{
+       TrackerMinerFS *fs = user_data;
+
+       if (!tracker_miner_fs_has_items_to_process (fs)) {
+               g_info ("Finished all tasks");
+               process_stop (fs);
+       }
+}
+
+
+#ifdef CRAWLED_TREE_ENABLE_TRACE
+
+static gboolean
+print_file_tree (GNode    *node,
+                 gpointer  user_data)
+{
+       gchar *name;
+       gint i;
+
+       name = g_file_get_basename (node->data);
+
+       /* Indentation */
+       for (i = g_node_depth (node) - 1; i > 0; i--) {
+               g_print ("  ");
+       }
+
+       g_print ("%s\n", name);
+       g_free (name);
+
+       return FALSE;
+}
+
+#endif /* CRAWLED_TREE_ENABLE_TRACE */
+
+/* Returns TRUE if file equals to
+ * other_file, or is a child of it
+ */
+static gboolean
+file_equal_or_descendant (GFile *file,
+                          GFile *prefix)
+{
+       if (g_file_equal (file, prefix) ||
+           g_file_has_prefix (file, prefix)) {
+               return TRUE;
+       }
+
+       return FALSE;
+}
+
+static void
+task_pool_cancel_foreach (gpointer data,
+                          gpointer user_data)
+{
+       TrackerTask *task = data;
+       GFile *file = user_data;
+       GFile *task_file;
+       UpdateProcessingTaskContext *ctxt;
+
+       ctxt = tracker_task_get_data (task);
+       task_file = tracker_task_get_file (task);
+
+       if (ctxt &&
+           ctxt->cancellable &&
+           (!file ||
+            (g_file_equal (task_file, file) ||
+             g_file_has_prefix (task_file, file)))) {
+               g_cancellable_cancel (ctxt->cancellable);
+       }
+}
+
+static void
+indexing_tree_directory_removed (TrackerIndexingTree *indexing_tree,
+                                 GFile               *directory,
+                                 gpointer             user_data)
+{
+       TrackerMinerFS *fs = user_data;
+       TrackerMinerFSPrivate *priv = fs->priv;
+       GTimer *timer = g_timer_new ();
+
+       /* Cancel all pending tasks on files inside the path given by file */
+       tracker_task_pool_foreach (priv->task_pool,
+                                  task_pool_cancel_foreach,
+                                  directory);
+
+       g_debug ("  Cancelled processing pool tasks at %f\n", g_timer_elapsed (timer, NULL));
+
+       /* Remove anything contained in the removed directory
+        * from all relevant processing queues.
+        */
+       tracker_priority_queue_foreach_remove (priv->items_updated,
+                                              (GEqualFunc) file_equal_or_descendant,
+                                              directory,
+                                              (GDestroyNotify) g_object_unref);
+       tracker_priority_queue_foreach_remove (priv->items_created,
+                                              (GEqualFunc) file_equal_or_descendant,
+                                              directory,
+                                              (GDestroyNotify) g_object_unref);
+
+       g_debug ("  Removed files at %f\n", g_timer_elapsed (timer, NULL));
+
+       g_message ("Finished remove directory operation in %f\n", g_timer_elapsed (timer, NULL));
+       g_timer_destroy (timer);
+}
+
+static gboolean
+check_file_parents (TrackerMinerFS *fs,
+                    GFile          *file)
+{
+       GFile *parent, *root;
+       GList *parents = NULL, *p;
+
+       parent = g_file_get_parent (file);
+
+       if (!parent) {
+               return FALSE;
+       }
+
+       root = tracker_indexing_tree_get_root (fs->priv->indexing_tree,
+                                              parent, NULL);
+       if (!root) {
+               g_object_unref (parent);
+               return FALSE;
+       }
+
+       /* Add parent directories until we're past the config dir */
+       while (parent &&
+              !g_file_has_prefix (root, parent)) {
+               parents = g_list_prepend (parents, parent);
+               parent = g_file_get_parent (parent);
+       }
+
+       /* Last parent fetched is not added to the list */
+       if (parent) {
+               g_object_unref (parent);
+       }
+
+       for (p = parents; p; p = p->next) {
+               trace_eq_push_tail ("UPDATED", p->data, "checking file parents");
+               miner_fs_queue_file (fs, fs->priv->items_updated, p->data, TRUE);
+               g_object_unref (p->data);
+       }
+
+       g_list_free (parents);
+
+       return TRUE;
+}
+
+/**
+ * tracker_miner_fs_check_file:
+ * @fs: a #TrackerMinerFS
+ * @file: #GFile for the file to check
+ * @priority: the priority of the check task
+ * @check_parents: whether to check parents and eligibility or not
+ *
+ * Tells the filesystem miner to check and index a file at
+ * a given priority, this file must be part of the usual
+ * crawling directories of #TrackerMinerFS. See
+ * tracker_miner_fs_directory_add().
+ *
+ * Since: 0.10
+ **/
+void
+tracker_miner_fs_check_file (TrackerMinerFS *fs,
+                             GFile          *file,
+                             gint            priority,
+                             gboolean        check_parents)
+{
+       gboolean should_process = TRUE;
+       gchar *uri;
+
+       g_return_if_fail (TRACKER_IS_MINER_FS (fs));
+       g_return_if_fail (G_IS_FILE (file));
+
+       if (check_parents) {
+               should_process = should_check_file (fs, file, FALSE);
+       }
+
+       uri = g_file_get_uri (file);
+
+       g_debug ("%s:'%s' (FILE) (requested by application)",
+                should_process ? "Found " : "Ignored",
+                uri);
+
+       if (should_process) {
+               if (check_parents && !check_file_parents (fs, file)) {
+                       return;
+               }
+
+               trace_eq_push_tail ("UPDATED", file, "Requested by application");
+               miner_fs_cache_file_urn (fs, file, TRUE);
+               tracker_priority_queue_add (fs->priv->items_updated,
+                                           g_object_ref (file),
+                                           priority);
+
+               item_queue_handlers_set_up (fs);
+       }
+
+       g_free (uri);
+}
+
+/**
+ * tracker_miner_fs_notify_finish:
+ * @fs: a #TrackerMinerFS
+ * @task: a #GTask obtained in a #TrackerMinerFS signal/vmethod
+ * @sparql: (nullable): Resulting sparql for the given operation, or %NULL if
+ *   there is an error
+ * @error: a #GError with the error that happened during processing, or %NULL.
+ *
+ * Notifies @fs that all processing on @file has been finished, if any error
+ * happened during file data processing, it should be passed in @error, else
+ * @sparql should contain correct SPARQL representing the operation in
+ * particular.
+ *
+ * This function is expected to be called in reaction to all #TrackerMinerFS
+ * signals
+ **/
+void
+tracker_miner_fs_notify_finish (TrackerMinerFS *fs,
+                                GTask          *task,
+                                const gchar    *sparql,
+                                GError         *error)
+{
+       g_return_if_fail (TRACKER_IS_MINER_FS (fs));
+       g_return_if_fail (G_IS_TASK (task));
+       g_return_if_fail (sparql || error);
+
+       if (error)
+               g_task_return_error (task, error);
+       else
+               g_task_return_pointer (task, g_strdup (sparql), g_free);
+}
+
+/**
+ * tracker_miner_fs_set_throttle:
+ * @fs: a #TrackerMinerFS
+ * @throttle: a double between 0.0 and 1.0
+ *
+ * Tells the filesystem miner to throttle its operations. A value of
+ * 0.0 means no throttling at all, so the miner will perform
+ * operations at full speed, 1.0 is the slowest value. With a value of
+ * 1.0, the @fs is typically waiting one full second before handling
+ * the next batch of queued items to be processed.
+ *
+ * Since: 0.8
+ **/
+void
+tracker_miner_fs_set_throttle (TrackerMinerFS *fs,
+                               gdouble         throttle)
+{
+       g_return_if_fail (TRACKER_IS_MINER_FS (fs));
+
+       throttle = CLAMP (throttle, 0, 1);
+
+       if (fs->priv->throttle == throttle) {
+               return;
+       }
+
+       fs->priv->throttle = throttle;
+
+       /* Update timeouts */
+       if (fs->priv->item_queues_handler_id != 0) {
+               g_source_remove (fs->priv->item_queues_handler_id);
+
+               fs->priv->item_queues_handler_id =
+                       _tracker_idle_add (fs,
+                                          item_queue_handlers_cb,
+                                          fs);
+       }
+}
+
+/**
+ * tracker_miner_fs_get_throttle:
+ * @fs: a #TrackerMinerFS
+ *
+ * Gets the current throttle value, see
+ * tracker_miner_fs_set_throttle() for more details.
+ *
+ * Returns: a double representing a value between 0.0 and 1.0.
+ *
+ * Since: 0.8
+ **/
+gdouble
+tracker_miner_fs_get_throttle (TrackerMinerFS *fs)
+{
+       g_return_val_if_fail (TRACKER_IS_MINER_FS (fs), 0);
+
+       return fs->priv->throttle;
+}
+
+/**
+ * tracker_miner_fs_get_urn:
+ * @fs: a #TrackerMinerFS
+ * @file: a #GFile obtained in #TrackerMinerFS::process-file
+ *
+ * If the item exists in the store, this function retrieves
+ * the URN for a #GFile being currently processed.
+
+ * If @file is not being currently processed by @fs, or doesn't
+ * exist in the store yet, %NULL will be returned.
+ *
+ * Returns: (transfer none) (nullable): The URN containing the data associated to @file,
+ *          or %NULL.
+ *
+ * Since: 0.8
+ **/
+const gchar *
+tracker_miner_fs_get_urn (TrackerMinerFS *fs,
+                          GFile          *file)
+{
+       TrackerTask *task;
+
+       g_return_val_if_fail (TRACKER_IS_MINER_FS (fs), NULL);
+       g_return_val_if_fail (G_IS_FILE (file), NULL);
+
+       /* Check if found in currently processed data */
+       task = tracker_task_pool_find (fs->priv->task_pool, file);
+
+       if (!task) {
+               gchar *uri;
+
+               uri = g_file_get_uri (file);
+
+               g_critical ("File '%s' is not being currently processed, "
+                           "so the URN cannot be retrieved.", uri);
+               g_free (uri);
+
+               return NULL;
+       } else {
+               UpdateProcessingTaskContext *ctxt;
+
+               /* We are only storing the URN in the created/updated tasks */
+               ctxt = tracker_task_get_data (task);
+
+               if (!ctxt) {
+                       gchar *uri;
+
+                       uri = g_file_get_uri (file);
+                       g_critical ("File '%s' is being processed, but not as a "
+                                   "CREATED/UPDATED task, so cannot get URN",
+                                   uri);
+                       g_free (uri);
+                       return NULL;
+               }
+
+               return ctxt->urn;
+       }
+}
+
+/**
+ * tracker_miner_fs_query_urn:
+ * @fs: a #TrackerMinerFS
+ * @file: a #GFile
+ *
+ * If the item exists in the store, this function retrieves
+ * the URN of the given #GFile
+
+ * If @file doesn't exist in the store yet, %NULL will be returned.
+ *
+ * Returns: (transfer full): A newly allocated string with the URN containing the data associated
+ *          to @file, or %NULL.
+ *
+ * Since: 0.10
+ **/
+gchar *
+tracker_miner_fs_query_urn (TrackerMinerFS *fs,
+                            GFile          *file)
+{
+       g_return_val_if_fail (TRACKER_IS_MINER_FS (fs), NULL);
+       g_return_val_if_fail (G_IS_FILE (file), NULL);
+
+       return g_strdup (lookup_file_urn (fs, file, TRUE));
+}
+
+/**
+ * tracker_miner_fs_has_items_to_process:
+ * @fs: a #TrackerMinerFS
+ *
+ * The @fs keeps many priority queus for content it is processing.
+ * This function returns %TRUE if the sum of all (or any) priority
+ * queues is more than 0. This includes items deleted, created,
+ * updated, moved or being written back.
+ *
+ * Returns: %TRUE if there are items to process in the internal
+ * queues, otherwise %FALSE.
+ *
+ * Since: 0.10
+ **/
+gboolean
+tracker_miner_fs_has_items_to_process (TrackerMinerFS *fs)
+{
+       g_return_val_if_fail (TRACKER_IS_MINER_FS (fs), FALSE);
+
+       if (tracker_file_notifier_is_active (fs->priv->file_notifier) ||
+           !tracker_priority_queue_is_empty (fs->priv->items_deleted) ||
+           !tracker_priority_queue_is_empty (fs->priv->items_created) ||
+           !tracker_priority_queue_is_empty (fs->priv->items_updated) ||
+           !tracker_priority_queue_is_empty (fs->priv->items_moved)) {
+               return TRUE;
+       }
+
+       return FALSE;
+}
+
+/**
+ * tracker_miner_fs_get_indexing_tree:
+ * @fs: a #TrackerMinerFS
+ *
+ * Returns the #TrackerIndexingTree which determines
+ * what files/directories are indexed by @fs
+ *
+ * Returns: (transfer none): The #TrackerIndexingTree
+ *          holding the indexing configuration
+ **/
+TrackerIndexingTree *
+tracker_miner_fs_get_indexing_tree (TrackerMinerFS *fs)
+{
+       g_return_val_if_fail (TRACKER_IS_MINER_FS (fs), NULL);
+
+       return fs->priv->indexing_tree;
+}
+
+/**
+ * tracker_miner_fs_get_data_provider:
+ * @fs: a #TrackerMinerFS
+ *
+ * Returns the #TrackerDataProvider implementation, which is being used
+ * to supply #GFile and #GFileInfo content to Tracker.
+ *
+ * Returns: (transfer none): The #TrackerDataProvider supplying content
+ *
+ * Since: 1.2
+ **/
+TrackerDataProvider *
+tracker_miner_fs_get_data_provider (TrackerMinerFS *fs)
+{
+       g_return_val_if_fail (TRACKER_IS_MINER_FS (fs), NULL);
+
+       return fs->priv->data_provider;
+}
+
+#ifdef EVENT_QUEUE_ENABLE_TRACE
+
+static void
+trace_files_foreach (gpointer file,
+                     gpointer fs)
+{
+       gchar *uri;
+
+       uri = g_file_get_uri (G_FILE (file));
+       trace_eq ("(%s)     '%s'",
+                 G_OBJECT_TYPE_NAME (G_OBJECT (fs)),
+                 uri);
+       g_free (uri);
+}
+
+static void
+trace_moved_foreach (gpointer moved_data,
+                     gpointer fs)
+{
+       ItemMovedData *data = moved_data;
+       gchar *source_uri;
+       gchar *dest_uri;
+
+       source_uri = g_file_get_uri (data->source_file);
+       dest_uri = g_file_get_uri (data->file);
+       trace_eq ("(%s)     '%s->%s'",
+                 G_OBJECT_TYPE_NAME (G_OBJECT (fs)),
+                 source_uri,
+                 dest_uri);
+       g_free (source_uri);
+       g_free (dest_uri);
+}
+
+static void
+miner_fs_trace_queue (TrackerMinerFS       *fs,
+                      const gchar          *queue_name,
+                      TrackerPriorityQueue *queue,
+                      GFunc                 foreach_cb)
+{
+       trace_eq ("(%s) Queue '%s' has %u elements:",
+                 G_OBJECT_TYPE_NAME (fs),
+                 queue_name,
+                 tracker_priority_queue_get_length (queue));
+       tracker_priority_queue_foreach (queue,
+                                       foreach_cb,
+                                       fs);
+}
+
+static gboolean
+miner_fs_queues_status_trace_timeout_cb (gpointer data)
+{
+       TrackerMinerFS *fs = data;
+
+       trace_eq ("(%s) ------------", G_OBJECT_TYPE_NAME (fs));
+       miner_fs_trace_queue (fs, "CREATED",   fs->priv->items_created,   trace_files_foreach);
+       miner_fs_trace_queue (fs, "UPDATED",   fs->priv->items_updated,   trace_files_foreach);
+       miner_fs_trace_queue (fs, "DELETED",   fs->priv->items_deleted,   trace_files_foreach);
+       miner_fs_trace_queue (fs, "MOVED",     fs->priv->items_moved,     trace_moved_foreach);
+
+       return TRUE;
+}
+
+#endif /* EVENT_QUEUE_ENABLE_TRACE */
diff --git a/src/libtracker-miner/tracker-miner-fs.h b/src/libtracker-miner/tracker-miner-fs.h
new file mode 100644
index 000000000..47d7dd9f5
--- /dev/null
+++ b/src/libtracker-miner/tracker-miner-fs.h
@@ -0,0 +1,168 @@
+/*
+ * Copyright (C) 2009, Nokia <ivan frade nokia com>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ * Boston, MA  02110-1301, USA.
+ */
+
+#ifndef __LIBTRACKER_MINER_MINER_FS_H__
+#define __LIBTRACKER_MINER_MINER_FS_H__
+
+#if !defined (__LIBTRACKER_MINER_H_INSIDE__) && !defined (TRACKER_COMPILATION)
+#error "Only <libtracker-miner/tracker-miner.h> can be included directly."
+#endif
+
+#include <glib-object.h>
+#include <gio/gio.h>
+
+#include <libtracker-sparql/tracker-sparql.h>
+
+#include "tracker-miner-object.h"
+#include "tracker-data-provider.h"
+#include "tracker-indexing-tree.h"
+
+G_BEGIN_DECLS
+
+#define TRACKER_TYPE_MINER_FS         (tracker_miner_fs_get_type())
+#define TRACKER_MINER_FS(o)           (G_TYPE_CHECK_INSTANCE_CAST ((o), TRACKER_TYPE_MINER_FS, 
TrackerMinerFS))
+#define TRACKER_MINER_FS_CLASS(c)     (G_TYPE_CHECK_CLASS_CAST ((c), TRACKER_TYPE_MINER_FS, 
TrackerMinerFSClass))
+#define TRACKER_IS_MINER_FS(o)        (G_TYPE_CHECK_INSTANCE_TYPE ((o), TRACKER_TYPE_MINER_FS))
+#define TRACKER_IS_MINER_FS_CLASS(c)  (G_TYPE_CHECK_CLASS_TYPE ((c),  TRACKER_TYPE_MINER_FS))
+#define TRACKER_MINER_FS_GET_CLASS(o) (G_TYPE_INSTANCE_GET_CLASS ((o), TRACKER_TYPE_MINER_FS, 
TrackerMinerFSClass))
+
+typedef enum {
+       TRACKER_MINER_FS_EVENT_CREATED,
+       TRACKER_MINER_FS_EVENT_UPDATED,
+       TRACKER_MINER_FS_EVENT_DELETED,
+       TRACKER_MINER_FS_EVENT_MOVED,
+} TrackerMinerFSEventType;
+
+typedef struct _TrackerMinerFS        TrackerMinerFS;
+typedef struct _TrackerMinerFSPrivate TrackerMinerFSPrivate;
+
+/**
+ * TrackerMinerFS:
+ *
+ * Abstract miner implementation to get data from the filesystem.
+ **/
+struct _TrackerMinerFS {
+       TrackerMiner parent;
+       TrackerMinerFSPrivate *priv;
+};
+
+/**
+ * TrackerMinerFSClass:
+ * @parent: parent object class
+ * @process_file: Called when the metadata associated to a file is
+ * requested.
+ * @finished: Called when all processing has been performed.
+ * @process_file_attributes: Called when the metadata associated with
+ * a file's attributes changes, for example, the mtime.
+ * @finished_root: Called when all resources on a particular root URI
+ * have been processed.
+ * @padding: Reserved for future API improvements.
+ *
+ * Prototype for the abstract class, @process_file must be implemented
+ * in the deriving class in order to actually extract data.
+ **/
+typedef struct {
+       TrackerMinerClass parent;
+
+       gboolean (* process_file)             (TrackerMinerFS       *fs,
+                                              GFile                *file,
+                                              GTask                *task);
+       void     (* finished)                 (TrackerMinerFS       *fs,
+                                              gdouble               elapsed,
+                                              gint                  directories_found,
+                                              gint                  directories_ignored,
+                                              gint                  files_found,
+                                              gint                  files_ignored);
+       gboolean (* process_file_attributes)  (TrackerMinerFS       *fs,
+                                              GFile                *file,
+                                              GTask                *task);
+       void     (* finished_root)            (TrackerMinerFS       *fs,
+                                              GFile                *root,
+                                              gint                  directories_found,
+                                              gint                  directories_ignored,
+                                              gint                  files_found,
+                                              gint                  files_ignored);
+       gchar *  (* remove_file)              (TrackerMinerFS       *fs,
+                                              GFile                *file);
+       gchar *  (* remove_children)          (TrackerMinerFS       *fs,
+                                              GFile                *file);
+       gchar *  (* move_file)                (TrackerMinerFS       *fs,
+                                              GFile                *source,
+                                              GFile                *dest,
+                                              gboolean              recursive);
+
+       gboolean (* filter_event)             (TrackerMinerFS          *fs,
+                                              TrackerMinerFSEventType  type,
+                                              GFile                   *file,
+                                              GFile                   *source_file);
+
+       /* <Private> */
+       gpointer padding[20];
+} TrackerMinerFSClass;
+
+/**
+ * TrackerMinerFSError:
+ * @TRACKER_MINER_FS_ERROR_INIT: There was an error during
+ * initialization of the object. The specific details are in the
+ * message.
+ *
+ * Possible errors returned when calling creating new objects based on
+ * the #TrackerMinerFS type and other APIs available with this class.
+ *
+ * Since: 1.2.
+ **/
+typedef enum {
+       TRACKER_MINER_FS_ERROR_INIT,
+} TrackerMinerFSError;
+
+GType                 tracker_miner_fs_get_type              (void) G_GNUC_CONST;
+GQuark                tracker_miner_fs_error_quark           (void);
+
+/* Properties */
+TrackerIndexingTree * tracker_miner_fs_get_indexing_tree     (TrackerMinerFS  *fs);
+TrackerDataProvider * tracker_miner_fs_get_data_provider     (TrackerMinerFS  *fs);
+gdouble               tracker_miner_fs_get_throttle          (TrackerMinerFS  *fs);
+void                  tracker_miner_fs_set_throttle          (TrackerMinerFS  *fs,
+                                                              gdouble          throttle);
+
+/* Queueing files to be processed AFTER checking rules in IndexingTree */
+void                  tracker_miner_fs_check_file            (TrackerMinerFS  *fs,
+                                                              GFile           *file,
+                                                              gint             priority,
+                                                              gboolean         check_parents);
+
+/* Continuation for async vmethods */
+void                  tracker_miner_fs_notify_finish         (TrackerMinerFS  *fs,
+                                                             GTask           *task,
+                                                             const gchar     *sparql,
+                                                             GError          *error);
+
+/* URNs */
+const gchar          *tracker_miner_fs_get_urn               (TrackerMinerFS  *fs,
+                                                              GFile           *file);
+gchar                *tracker_miner_fs_query_urn             (TrackerMinerFS  *fs,
+                                                              GFile           *file);
+
+
+/* Progress */
+gboolean              tracker_miner_fs_has_items_to_process  (TrackerMinerFS  *fs);
+
+G_END_DECLS
+
+#endif /* __LIBTRACKER_MINER_MINER_FS_H__ */
diff --git a/src/libtracker-miner/tracker-miner-object.c b/src/libtracker-miner/tracker-miner-object.c
new file mode 100644
index 000000000..a7aa4cdaf
--- /dev/null
+++ b/src/libtracker-miner/tracker-miner-object.c
@@ -0,0 +1,631 @@
+/*
+ * Copyright (C) 2009, Nokia <ivan frade nokia com>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ * Boston, MA  02110-1301, USA.
+ */
+
+#include "config-miners.h"
+
+#include <math.h>
+
+#include <glib/gi18n.h>
+
+#include <libtracker-miners-common/tracker-dbus.h>
+#include <libtracker-miners-common/tracker-type-utils.h>
+
+#include "tracker-miner-object.h"
+
+/* Here we use ceil() to eliminate decimal points beyond what we're
+ * interested in, which is 2 decimal places for the progress. The
+ * ceil() call will also round up the last decimal place.
+ *
+ * The 0.49 value is used for rounding correctness, because ceil()
+ * rounds up if the number is > 0.0.
+ */
+#define PROGRESS_ROUNDED(x) ((x) < 0.01 ? 0.00 : (ceil (((x) * 100) - 0.49) / 100))
+
+#ifdef MINER_STATUS_ENABLE_TRACE
+#warning Miner status traces are enabled
+#define trace(message, ...) g_debug (message, ##__VA_ARGS__)
+#else
+#define trace(...)
+#endif /* MINER_STATUS_ENABLE_TRACE */
+
+/**
+ * SECTION:tracker-miner-object
+ * @short_description: Abstract base class for data miners
+ * @include: libtracker-miner/tracker-miner.h
+ *
+ * #TrackerMiner is an abstract base class to help developing data miners
+ * for tracker-store, being an abstract class it doesn't do much by itself,
+ * but provides the basic signaling and operation control so the miners
+ * implementing this class are properly recognized by Tracker, and can be
+ * controlled properly by external means such as #TrackerMinerManager.
+ *
+ * #TrackerMiner implements the #GInitable interface, and thus, all objects of
+ * types inheriting from #TrackerMiner must be initialized with g_initable_init()
+ * just after creation (or directly created with g_initable_new()).
+ **/
+
+#define TRACKER_MINER_GET_PRIVATE(o) (G_TYPE_INSTANCE_GET_PRIVATE ((o), TRACKER_TYPE_MINER, 
TrackerMinerPrivate))
+
+struct _TrackerMinerPrivate {
+       TrackerSparqlConnection *connection;
+       gboolean started;
+       gint n_pauses;
+       gchar *status;
+       gdouble progress;
+       gint remaining_time;
+       gint availability_cookie;
+       guint update_id;
+};
+
+enum {
+       PROP_0,
+       PROP_STATUS,
+       PROP_PROGRESS,
+       PROP_REMAINING_TIME,
+       PROP_CONNECTION
+};
+
+enum {
+       STARTED,
+       STOPPED,
+       PAUSED,
+       RESUMED,
+       PROGRESS,
+       LAST_SIGNAL
+};
+
+static guint signals[LAST_SIGNAL] = { 0 };
+
+static void       miner_set_property           (GObject                *object,
+                                                guint                   param_id,
+                                                const GValue           *value,
+                                                GParamSpec             *pspec);
+static void       miner_get_property           (GObject                *object,
+                                                guint                   param_id,
+                                                GValue                 *value,
+                                                GParamSpec             *pspec);
+static void       miner_finalize               (GObject                *object);
+static void       miner_initable_iface_init    (GInitableIface         *iface);
+static gboolean   miner_initable_init          (GInitable              *initable,
+                                                GCancellable           *cancellable,
+                                                GError                **error);
+
+/**
+ * tracker_miner_error_quark:
+ *
+ * Gives the caller the #GQuark used to identify #TrackerMiner errors
+ * in #GError structures. The #GQuark is used as the domain for the error.
+ *
+ * Returns: the #GQuark used for the domain of a #GError.
+ *
+ * Since: 0.8
+ **/
+G_DEFINE_QUARK (TrackerMinerError, tracker_miner_error)
+
+G_DEFINE_ABSTRACT_TYPE_WITH_CODE (TrackerMiner, tracker_miner, G_TYPE_OBJECT,
+                                  G_IMPLEMENT_INTERFACE (G_TYPE_INITABLE,
+                                                         miner_initable_iface_init));
+
+static void
+tracker_miner_class_init (TrackerMinerClass *klass)
+{
+       GObjectClass *object_class = G_OBJECT_CLASS (klass);
+
+       object_class->set_property = miner_set_property;
+       object_class->get_property = miner_get_property;
+       object_class->finalize     = miner_finalize;
+
+       /**
+        * TrackerMiner::started:
+        * @miner: the #TrackerMiner
+        *
+        * the ::started signal is emitted in the miner
+        * right after it has been started through
+        * tracker_miner_start().
+        *
+        * Since: 0.8
+        **/
+       signals[STARTED] =
+               g_signal_new ("started",
+                             G_OBJECT_CLASS_TYPE (object_class),
+                             G_SIGNAL_RUN_LAST,
+                             G_STRUCT_OFFSET (TrackerMinerClass, started),
+                             NULL, NULL,
+                             NULL,
+                             G_TYPE_NONE, 0);
+       /**
+        * TrackerMiner::stopped:
+        * @miner: the #TrackerMiner
+        *
+        * the ::stopped signal is emitted in the miner
+        * right after it has been stopped through
+        * tracker_miner_stop().
+        *
+        * Since: 0.8
+        **/
+       signals[STOPPED] =
+               g_signal_new ("stopped",
+                             G_OBJECT_CLASS_TYPE (object_class),
+                             G_SIGNAL_RUN_LAST,
+                             G_STRUCT_OFFSET (TrackerMinerClass, stopped),
+                             NULL, NULL,
+                             NULL,
+                             G_TYPE_NONE, 0);
+       /**
+        * TrackerMiner::paused:
+        * @miner: the #TrackerMiner
+        *
+        * the ::paused signal is emitted whenever
+        * there is any reason to pause, either
+        * internal (through tracker_miner_pause()) or
+        * external (through DBus, see #TrackerMinerManager).
+        *
+        * Since: 0.8
+        **/
+       signals[PAUSED] =
+               g_signal_new ("paused",
+                             G_OBJECT_CLASS_TYPE (object_class),
+                             G_SIGNAL_RUN_LAST,
+                             G_STRUCT_OFFSET (TrackerMinerClass, paused),
+                             NULL, NULL,
+                             NULL,
+                             G_TYPE_NONE, 0);
+       /**
+        * TrackerMiner::resumed:
+        * @miner: the #TrackerMiner
+        *
+        * the ::resumed signal is emitted whenever
+        * all reasons to pause have disappeared, see
+        * tracker_miner_resume() and #TrackerMinerManager.
+        *
+        * Since: 0.8
+        **/
+       signals[RESUMED] =
+               g_signal_new ("resumed",
+                             G_OBJECT_CLASS_TYPE (object_class),
+                             G_SIGNAL_RUN_LAST,
+                             G_STRUCT_OFFSET (TrackerMinerClass, resumed),
+                             NULL, NULL,
+                             NULL,
+                             G_TYPE_NONE, 0);
+       /**
+        * TrackerMiner::progress:
+        * @miner: the #TrackerMiner
+        * @status: miner status
+        * @progress: a #gdouble indicating miner progress, from 0 to 1.
+        * @remaining_time: a #gint indicating the reamaining processing time, in
+        * seconds.
+        *
+        * the ::progress signal will be emitted by TrackerMiner implementations
+        * to indicate progress about the data mining process. @status will
+        * contain a translated string with the current miner status and @progress
+        * will indicate how much has been processed so far. @remaining_time will
+        * give the number expected of seconds to finish processing, 0 if the
+        * value cannot be estimated, and -1 if its not applicable.
+        *
+        * Since: 0.12
+        **/
+       signals[PROGRESS] =
+               g_signal_new ("progress",
+                             G_OBJECT_CLASS_TYPE (object_class),
+                             G_SIGNAL_RUN_LAST,
+                             G_STRUCT_OFFSET (TrackerMinerClass, progress),
+                             NULL, NULL,
+                             NULL,
+                             G_TYPE_NONE, 3,
+                             G_TYPE_STRING,
+                             G_TYPE_DOUBLE,
+                             G_TYPE_INT);
+
+       g_object_class_install_property (object_class,
+                                        PROP_STATUS,
+                                        g_param_spec_string ("status",
+                                                             "Status",
+                                                             "Translatable string with status description",
+                                                             "Idle",
+                                                             G_PARAM_READWRITE | G_PARAM_CONSTRUCT));
+       g_object_class_install_property (object_class,
+                                        PROP_PROGRESS,
+                                        g_param_spec_double ("progress",
+                                                             "Progress",
+                                                             "Miner progress",
+                                                             0.0,
+                                                             1.0,
+                                                             0.0,
+                                                             G_PARAM_READWRITE | G_PARAM_CONSTRUCT));
+
+       g_object_class_install_property (object_class,
+                                        PROP_REMAINING_TIME,
+                                        g_param_spec_int ("remaining-time",
+                                                          "Remaining time",
+                                                          "Estimated remaining time to finish processing",
+                                                          -1,
+                                                          G_MAXINT,
+                                                          -1,
+                                                          G_PARAM_READWRITE | G_PARAM_CONSTRUCT));
+       g_object_class_install_property (object_class,
+                                        PROP_CONNECTION,
+                                        g_param_spec_object ("connection",
+                                                             "Connection",
+                                                             "SPARQL Connection",
+                                                             TRACKER_SPARQL_TYPE_CONNECTION,
+                                                             G_PARAM_READWRITE |
+                                                             G_PARAM_CONSTRUCT_ONLY));
+
+       g_type_class_add_private (object_class, sizeof (TrackerMinerPrivate));
+}
+
+static void
+miner_initable_iface_init (GInitableIface *iface)
+{
+       iface->init = miner_initable_init;
+}
+
+static gboolean
+miner_initable_init (GInitable     *initable,
+                     GCancellable  *cancellable,
+                     GError       **error)
+{
+       TrackerMiner *miner = TRACKER_MINER (initable);
+       GError *inner_error = NULL;
+
+       if (!miner->priv->connection) {
+               /* Try to get SPARQL connection... */
+               miner->priv->connection = tracker_sparql_connection_get (NULL, &inner_error);
+       }
+
+       if (!miner->priv->connection) {
+               g_propagate_error (error, inner_error);
+               return FALSE;
+       }
+
+       return TRUE;
+}
+
+static void
+tracker_miner_init (TrackerMiner *miner)
+{
+       miner->priv = TRACKER_MINER_GET_PRIVATE (miner);
+}
+
+static gboolean
+miner_update_progress_cb (gpointer data)
+{
+       TrackerMiner *miner = data;
+
+       trace ("(Miner:'%s') UPDATE PROGRESS SIGNAL", G_OBJECT_TYPE_NAME (miner));
+
+       g_signal_emit (miner, signals[PROGRESS], 0,
+                      miner->priv->status,
+                      miner->priv->progress,
+                      miner->priv->remaining_time);
+
+       miner->priv->update_id = 0;
+
+       return FALSE;
+}
+
+static void
+miner_set_property (GObject      *object,
+                    guint         prop_id,
+                    const GValue *value,
+                    GParamSpec   *pspec)
+{
+       TrackerMiner *miner = TRACKER_MINER (object);
+
+       /* Quite often, we see status of 100% and still have
+        * status messages saying Processing... which is not
+        * true. So we use an idle timeout to help that situation.
+        * Additionally we can't force both properties are correct
+        * with the GObject API, so we have to do some checks our
+        * selves. The g_object_bind_property() API also isn't
+        * sufficient here.
+        */
+
+       switch (prop_id) {
+       case PROP_STATUS: {
+               const gchar *new_status;
+
+               new_status = g_value_get_string (value);
+
+               trace ("(Miner:'%s') Set property:'status' to '%s'",
+                      G_OBJECT_TYPE_NAME (miner),
+                      new_status);
+
+               if (miner->priv->status && new_status &&
+                   strcmp (miner->priv->status, new_status) == 0) {
+                       /* Same, do nothing */
+                       break;
+               }
+
+               g_free (miner->priv->status);
+               miner->priv->status = g_strdup (new_status);
+
+               /* Check progress matches special statuses */
+               if (new_status != NULL) {
+                       if (g_ascii_strcasecmp (new_status, "Initializing") == 0 &&
+                           miner->priv->progress != 0.0) {
+                               trace ("(Miner:'%s') Set progress to 0.0 from status:'Initializing'",
+                                      G_OBJECT_TYPE_NAME (miner));
+                               miner->priv->progress = 0.0;
+                       } else if (g_ascii_strcasecmp (new_status, "Idle") == 0 &&
+                                  miner->priv->progress != 1.0) {
+                               trace ("(Miner:'%s') Set progress to 1.0 from status:'Idle'",
+                                      G_OBJECT_TYPE_NAME (miner));
+                               miner->priv->progress = 1.0;
+                       }
+               }
+
+               if (miner->priv->update_id == 0) {
+                       miner->priv->update_id = g_idle_add_full (G_PRIORITY_HIGH_IDLE,
+                                                                 miner_update_progress_cb,
+                                                                 miner,
+                                                                 NULL);
+               }
+
+               break;
+       }
+       case PROP_PROGRESS: {
+               gdouble new_progress;
+
+               new_progress = PROGRESS_ROUNDED (g_value_get_double (value));
+               trace ("(Miner:'%s') Set property:'progress' to '%2.2f' (%2.2f before rounded)",
+                        G_OBJECT_TYPE_NAME (miner),
+                        new_progress,
+                        g_value_get_double (value));
+
+               /* NOTE: We don't round the current progress before
+                * comparison because we use the rounded value when
+                * we set it last.
+                *
+                * Only notify 1% changes
+                */
+               if (new_progress == miner->priv->progress) {
+                       /* Same, do nothing */
+                       break;
+               }
+
+               miner->priv->progress = new_progress;
+
+               /* Check status matches special progress values */
+               if (new_progress == 0.0) {
+                       if (miner->priv->status == NULL ||
+                           g_ascii_strcasecmp (miner->priv->status, "Initializing") != 0) {
+                               trace ("(Miner:'%s') Set status:'Initializing' from progress:0.0",
+                                      G_OBJECT_TYPE_NAME (miner));
+                               g_free (miner->priv->status);
+                               miner->priv->status = g_strdup ("Initializing");
+                       }
+               } else if (new_progress == 1.0) {
+                       if (miner->priv->status == NULL ||
+                           g_ascii_strcasecmp (miner->priv->status, "Idle") != 0) {
+                               trace ("(Miner:'%s') Set status:'Idle' from progress:1.0",
+                                      G_OBJECT_TYPE_NAME (miner));
+                               g_free (miner->priv->status);
+                               miner->priv->status = g_strdup ("Idle");
+                       }
+               }
+
+               if (miner->priv->update_id == 0) {
+                       miner->priv->update_id = g_idle_add_full (G_PRIORITY_HIGH_IDLE,
+                                                                 miner_update_progress_cb,
+                                                                 miner,
+                                                                 NULL);
+               }
+
+               break;
+       }
+       case PROP_REMAINING_TIME: {
+               gint new_remaining_time;
+
+               new_remaining_time = g_value_get_int (value);
+               if (new_remaining_time != miner->priv->remaining_time) {
+                       /* Just set the new remaining time, don't notify it */
+                       miner->priv->remaining_time = new_remaining_time;
+               }
+               break;
+       }
+       case PROP_CONNECTION: {
+               miner->priv->connection = g_value_dup_object (value);
+               break;
+       }
+       default:
+               G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+               break;
+       }
+}
+
+static void
+miner_get_property (GObject    *object,
+                    guint       prop_id,
+                    GValue     *value,
+                    GParamSpec *pspec)
+{
+       TrackerMiner *miner = TRACKER_MINER (object);
+
+       switch (prop_id) {
+       case PROP_STATUS:
+               g_value_set_string (value, miner->priv->status);
+               break;
+       case PROP_PROGRESS:
+               g_value_set_double (value, miner->priv->progress);
+               break;
+       case PROP_REMAINING_TIME:
+               g_value_set_int (value, miner->priv->remaining_time);
+               break;
+       case PROP_CONNECTION:
+               g_value_set_object (value, miner->priv->connection);
+       default:
+               G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+               break;
+       }
+}
+
+/**
+ * tracker_miner_start:
+ * @miner: a #TrackerMiner
+ *
+ * Tells the miner to start processing data.
+ *
+ * Since: 0.8
+ **/
+void
+tracker_miner_start (TrackerMiner *miner)
+{
+       g_return_if_fail (TRACKER_IS_MINER (miner));
+       g_return_if_fail (miner->priv->started == FALSE);
+
+       miner->priv->started = TRUE;
+       g_signal_emit (miner, signals[STARTED], 0);
+}
+
+/**
+ * tracker_miner_stop:
+ * @miner: a #TrackerMiner
+ *
+ * Tells the miner to stop processing data.
+ *
+ * Since: 0.8
+ **/
+void
+tracker_miner_stop (TrackerMiner *miner)
+{
+       g_return_if_fail (TRACKER_IS_MINER (miner));
+       g_return_if_fail (miner->priv->started == TRUE);
+
+       miner->priv->started = FALSE;
+       g_signal_emit (miner, signals[STOPPED], 0);
+}
+
+/**
+ * tracker_miner_is_started:
+ * @miner: a #TrackerMiner
+ *
+ * Returns #TRUE if the miner has been started.
+ *
+ * Returns: #TRUE if the miner is already started.
+ *
+ * Since: 0.8
+ **/
+gboolean
+tracker_miner_is_started (TrackerMiner *miner)
+{
+       g_return_val_if_fail (TRACKER_IS_MINER (miner), TRUE);
+
+       return miner->priv->started;
+}
+
+/**
+ * tracker_miner_is_paused:
+ * @miner: a #TrackerMiner
+ *
+ * Returns #TRUE if the miner is paused.
+ *
+ * Returns: #TRUE if the miner is paused.
+ *
+ * Since: 0.10
+ **/
+gboolean
+tracker_miner_is_paused (TrackerMiner *miner)
+{
+       g_return_val_if_fail (TRACKER_IS_MINER (miner), TRUE);
+
+       return miner->priv->n_pauses > 0;
+}
+
+/**
+ * tracker_miner_pause:
+ * @miner: a #TrackerMiner
+ *
+ * Asks @miner to pause. This call may be called multiple times,
+ * but #TrackerMiner::paused will only be emitted the first time.
+ * The same number of tracker_miner_resume() calls are expected
+ * in order to resume operations.
+ **/
+void
+tracker_miner_pause (TrackerMiner *miner)
+{
+       gint previous;
+
+       g_return_if_fail (TRACKER_IS_MINER (miner));
+
+       previous = g_atomic_int_add (&miner->priv->n_pauses, 1);
+
+       if (previous == 0)
+               g_signal_emit (miner, signals[PAUSED], 0);
+}
+
+/**
+ * tracker_miner_resume:
+ * @miner: a #TrackerMiner
+ *
+ * Asks the miner to resume processing. This needs to be called
+ * as many times as tracker_miner_pause() calls were done
+ * previously. This function will return #TRUE when the miner
+ * is actually resumed.
+ *
+ * Returns: #TRUE if the miner resumed its operations.
+ **/
+gboolean
+tracker_miner_resume (TrackerMiner *miner)
+{
+       g_return_val_if_fail (TRACKER_IS_MINER (miner), FALSE);
+       g_return_val_if_fail (miner->priv->n_pauses > 0, FALSE);
+
+       if (g_atomic_int_dec_and_test (&miner->priv->n_pauses)) {
+               g_signal_emit (miner, signals[RESUMED], 0);
+               return TRUE;
+       }
+
+       return FALSE;
+}
+
+/**
+ * tracker_miner_get_connection:
+ * @miner: a #TrackerMiner
+ *
+ * Gets the #TrackerSparqlConnection initialized by @miner
+ *
+ * Returns: (transfer none): a #TrackerSparqlConnection.
+ *
+ * Since: 0.10
+ **/
+TrackerSparqlConnection *
+tracker_miner_get_connection (TrackerMiner *miner)
+{
+       return miner->priv->connection;
+}
+
+static void
+miner_finalize (GObject *object)
+{
+       TrackerMiner *miner = TRACKER_MINER (object);
+
+       if (miner->priv->update_id != 0) {
+               g_source_remove (miner->priv->update_id);
+       }
+
+       g_free (miner->priv->status);
+
+       if (miner->priv->connection) {
+               g_object_unref (miner->priv->connection);
+       }
+
+       G_OBJECT_CLASS (tracker_miner_parent_class)->finalize (object);
+}
diff --git a/src/libtracker-miner/tracker-miner-object.h b/src/libtracker-miner/tracker-miner-object.h
new file mode 100644
index 000000000..180ac83a5
--- /dev/null
+++ b/src/libtracker-miner/tracker-miner-object.h
@@ -0,0 +1,185 @@
+/*
+ * Copyright (C) 2009, Nokia <ivan frade nokia com>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ * Boston, MA  02110-1301, USA.
+ */
+
+#ifndef __LIBTRACKER_MINER_OBJECT_H__
+#define __LIBTRACKER_MINER_OBJECT_H__
+
+#if !defined (__LIBTRACKER_MINER_H_INSIDE__) && !defined (TRACKER_COMPILATION)
+#error "Only <libtracker-miner/tracker-miner.h> can be included directly."
+#endif
+
+#include <glib-object.h>
+#include <gio/gio.h>
+
+#include <libtracker-sparql/tracker-sparql.h>
+
+G_BEGIN_DECLS
+
+/* Common definitions for all miners */
+/**
+ * TRACKER_MINER_DBUS_INTERFACE:
+ *
+ * The name of the D-Bus interface to use for all data miners that
+ * inter-operate with Tracker.
+ *
+ * Since: 0.8.
+ **/
+#define TRACKER_MINER_DBUS_INTERFACE   "org.freedesktop.Tracker1.Miner"
+
+/**
+ * TRACKER_MINER_DBUS_NAME_PREFIX:
+ *
+ * D-Bus name prefix to use for all data miners. This allows custom
+ * miners to be written using @TRACKER_MINER_DBUS_NAME_PREFIX + "Files" for
+ * example and would show up on D-Bus under
+ * &quot;org.freedesktop.Tracker1.Miner.Files&quot;.
+ *
+ * Since: 0.8.
+ **/
+#define TRACKER_MINER_DBUS_NAME_PREFIX "org.freedesktop.Tracker1.Miner."
+
+/**
+ * TRACKER_MINER_DBUS_PATH_PREFIX:
+ *
+ * D-Bus path prefix to use for all data miners. This allows custom
+ * miners to be written using @TRACKER_MINER_DBUS_PATH_PREFIX + "Files" for
+ * example and would show up on D-Bus under
+ * &quot;/org/freedesktop/Tracker1/Miner/Files&quot;.
+ *
+ * Since: 0.8.
+ **/
+#define TRACKER_MINER_DBUS_PATH_PREFIX "/org/freedesktop/Tracker1/Miner/"
+
+#define TRACKER_TYPE_MINER         (tracker_miner_get_type())
+#define TRACKER_MINER(o)           (G_TYPE_CHECK_INSTANCE_CAST ((o), TRACKER_TYPE_MINER, TrackerMiner))
+#define TRACKER_MINER_CLASS(c)     (G_TYPE_CHECK_CLASS_CAST ((c),    TRACKER_TYPE_MINER, TrackerMinerClass))
+#define TRACKER_IS_MINER(o)        (G_TYPE_CHECK_INSTANCE_TYPE ((o), TRACKER_TYPE_MINER))
+#define TRACKER_IS_MINER_CLASS(c)  (G_TYPE_CHECK_CLASS_TYPE ((c),    TRACKER_TYPE_MINER))
+#define TRACKER_MINER_GET_CLASS(o) (G_TYPE_INSTANCE_GET_CLASS ((o),  TRACKER_TYPE_MINER, TrackerMinerClass))
+
+/**
+ * TRACKER_MINER_ERROR_DOMAIN:
+ *
+ * Used as the domain for any #GErrors reported by @TrackerMiner objects.
+ *
+ * Since: 0.8.
+ **/
+#define TRACKER_MINER_ERROR_DOMAIN "TrackerMiner"
+
+/**
+ * TRACKER_MINER_ERROR:
+ *
+ * Returns the @GQuark used for #GErrors and for @TrackerMiner
+ * implementations. This calls tracker_miner_error_quark().
+ *
+ * Since: 0.8.
+ **/
+#define TRACKER_MINER_ERROR        tracker_miner_error_quark()
+
+typedef struct _TrackerMiner TrackerMiner;
+typedef struct _TrackerMinerPrivate TrackerMinerPrivate;
+
+/**
+ * TrackerMiner:
+ *
+ * Abstract miner object.
+ **/
+struct _TrackerMiner {
+       GObject parent_instance;
+       TrackerMinerPrivate *priv;
+};
+
+/**
+ * TrackerMinerClass:
+ * @parent_class: parent object class.
+ * @started: Called when the miner is told to start collecting data.
+ * @stopped: Called when the miner is told to stop collecting data.
+ * @paused: Called when the miner is told to pause.
+ * @resumed: Called when the miner is told to resume activity.
+ * @progress: progress.
+ * @padding: Reserved for future API improvements.
+ *
+ * Virtual methods left to implement.
+ **/
+typedef struct {
+       GObjectClass parent_class;
+
+       /* signals */
+       void (* started)            (TrackerMiner *miner);
+       void (* stopped)            (TrackerMiner *miner);
+
+       void (* paused)             (TrackerMiner *miner);
+       void (* resumed)            (TrackerMiner *miner);
+
+       void (* progress)           (TrackerMiner *miner,
+                                    const gchar  *status,
+                                    gdouble       progress,
+                                    gint          remaining_time);
+
+       /* <Private> */
+       gpointer padding[10];
+} TrackerMinerClass;
+
+/**
+ * TrackerMinerError:
+ * @TRACKER_MINER_ERROR_NAME_MISSING: No name was given when creating
+ * the miner. The name is crucial for D-Bus presence and a host of
+ * other things.
+ * @TRACKER_MINER_ERROR_NAME_UNAVAILABLE: The name trying to be used
+ * for the miner was not available, possibly because the miner is
+ * already running with the same name in another process.
+ * @TRACKER_MINER_ERROR_PAUSED: Given by miners when an API is used at
+ * the time the miner itself is paused and such actions should be avoided.
+ * @TRACKER_MINER_ERROR_PAUSED_ALREADY: The pause request has already
+ * been given by the same application with the same reason. Duplicate
+ * pause calls with the same reason by the same application can not
+ * be carried out.
+ * @TRACKER_MINER_ERROR_INVALID_COOKIE: When pausing a miner, a cookie
+ * (or @gint based ID) is given. That cookie must be used to resume a
+ * previous pause request. If the cookie is unrecognised, this error
+ * is given.
+ *
+ * Possible errors returned when calling #TrackerMiner APIs or
+ * subclassed miners where the error is generic to all miners.
+ **/
+typedef enum {
+       TRACKER_MINER_ERROR_NAME_MISSING,
+       TRACKER_MINER_ERROR_NAME_UNAVAILABLE,
+       TRACKER_MINER_ERROR_PAUSED,
+       TRACKER_MINER_ERROR_PAUSED_ALREADY,
+       TRACKER_MINER_ERROR_INVALID_COOKIE
+} TrackerMinerError;
+
+
+GType                    tracker_miner_get_type            (void) G_GNUC_CONST;
+GQuark                   tracker_miner_error_quark         (void);
+
+void                     tracker_miner_start               (TrackerMiner         *miner);
+void                     tracker_miner_stop                (TrackerMiner         *miner);
+gboolean                 tracker_miner_is_started          (TrackerMiner         *miner);
+gboolean                 tracker_miner_is_paused           (TrackerMiner         *miner);
+
+void                     tracker_miner_pause               (TrackerMiner         *miner);
+gboolean                 tracker_miner_resume              (TrackerMiner         *miner);
+
+TrackerSparqlConnection *tracker_miner_get_connection      (TrackerMiner         *miner);
+
+G_END_DECLS
+
+#endif /* __LIBTRACKER_MINER_OBJECT_H__ */
diff --git a/src/libtracker-miner/tracker-miner-online.c b/src/libtracker-miner/tracker-miner-online.c
new file mode 100644
index 000000000..507a5af61
--- /dev/null
+++ b/src/libtracker-miner/tracker-miner-online.c
@@ -0,0 +1,407 @@
+/*
+ * Copyright (C) 2009-2014, Adrien Bustany <abustany gnome org>
+ * Copyright (C) 2014, Carlos Garnacho <carlosg gnome org>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ * Boston, MA  02110-1301, USA.
+ */
+
+#include "config-miners.h"
+
+#include "tracker-miner-online.h"
+#include "tracker-miner-enum-types.h"
+
+#include <glib/gi18n.h>
+
+#ifdef HAVE_NETWORK_MANAGER
+#include <NetworkManager.h>
+#endif /* HAVE_NETWORK_MANAGER */
+
+/**
+ * SECTION:tracker-miner-online
+ * @short_description: Abstract base class for miners connecting to
+ *   online resources
+ * @include: libtracker-miner/tracker-miner.h
+ *
+ * #TrackerMinerOnline is an abstract base class for miners retrieving data
+ * from online resources. It's a very thin layer above #TrackerMiner that
+ * additionally handles network connection status.
+ *
+ * #TrackerMinerOnline implementations can implement the
+ * <literal>connected</literal> vmethod in order to tell the miner whether
+ * a connection is valid to retrieve data or not. The miner data extraction
+ * still must be dictated through the #TrackerMiner vmethods.
+ *
+ * Since: 0.18.
+ **/
+
+typedef struct _TrackerMinerOnlinePrivate TrackerMinerOnlinePrivate;
+
+struct _TrackerMinerOnlinePrivate {
+#ifdef HAVE_NETWORK_MANAGER
+       NMClient *client;
+#endif
+       TrackerNetworkType network_type;
+       gboolean paused;
+};
+
+enum {
+       PROP_NETWORK_TYPE = 1
+};
+
+enum {
+       CONNECTED,
+       DISCONNECTED,
+       N_SIGNALS
+};
+
+static void       miner_online_initable_iface_init (GInitableIface         *iface);
+
+static GInitableIface* miner_online_initable_parent_iface;
+static guint signals[N_SIGNALS] = { 0 };
+
+G_DEFINE_ABSTRACT_TYPE_WITH_CODE (TrackerMinerOnline, tracker_miner_online, TRACKER_TYPE_MINER,
+                                  G_ADD_PRIVATE (TrackerMinerOnline)
+                                  G_IMPLEMENT_INTERFACE (G_TYPE_INITABLE,
+                                                         miner_online_initable_iface_init));
+
+static void
+miner_online_finalize (GObject *object)
+{
+#ifdef HAVE_NETWORK_MANAGER
+       TrackerMinerOnlinePrivate *priv;
+       TrackerMinerOnline *miner;
+
+       miner = TRACKER_MINER_ONLINE (object);
+       priv = tracker_miner_online_get_instance_private (miner);
+
+       if (priv->client)
+               g_object_unref (priv->client);
+#endif /* HAVE_NETWORK_MANAGER */
+
+       G_OBJECT_CLASS (tracker_miner_online_parent_class)->finalize (object);
+}
+
+static void
+miner_online_set_property (GObject      *object,
+                           guint         param_id,
+                           const GValue *value,
+                           GParamSpec   *pspec)
+{
+       switch (param_id) {
+       default:
+               G_OBJECT_WARN_INVALID_PROPERTY_ID (object, param_id, pspec);
+               break;
+       }
+}
+
+static void
+miner_online_get_property (GObject    *object,
+                           guint       param_id,
+                           GValue     *value,
+                           GParamSpec *pspec)
+{
+       TrackerMinerOnlinePrivate *priv;
+       TrackerMinerOnline *miner;
+
+       miner = TRACKER_MINER_ONLINE (object);
+       priv = tracker_miner_online_get_instance_private (miner);
+
+       switch (param_id) {
+       case PROP_NETWORK_TYPE:
+               g_value_set_enum (value, priv->network_type);
+               break;
+       default:
+               G_OBJECT_WARN_INVALID_PROPERTY_ID (object, param_id, pspec);
+               break;
+       }
+}
+
+static void
+tracker_miner_online_class_init (TrackerMinerOnlineClass *klass)
+{
+       GObjectClass *object_class = G_OBJECT_CLASS (klass);
+
+       object_class->finalize     = miner_online_finalize;
+       object_class->set_property = miner_online_set_property;
+       object_class->get_property = miner_online_get_property;
+
+       g_object_class_install_property (object_class,
+                                        PROP_NETWORK_TYPE,
+                                        g_param_spec_enum ("network-type",
+                                                           "Network type",
+                                                           "Network type for the current connection",
+                                                           TRACKER_TYPE_NETWORK_TYPE,
+                                                           TRACKER_NETWORK_TYPE_NONE,
+                                                           G_PARAM_READABLE));
+
+       /**
+        * TrackerMinerOnline::connected:
+        * @miner: a #TrackerMinerOnline
+        * @type: a #TrackerNetworkType
+        *
+        * the ::connected signal is emitted when a specific @type of
+        * network becomes connected.
+        *
+        * Return values of #TRUE from this signal indicate whether a
+        * #TrackerMiner should resume indexing or not upon ::connected.
+        *
+        * Since: 0.18.0
+        **/
+       signals[CONNECTED] =
+               g_signal_new ("connected",
+                             G_OBJECT_CLASS_TYPE (object_class),
+                             G_SIGNAL_RUN_LAST,
+                             G_STRUCT_OFFSET (TrackerMinerOnlineClass, connected),
+                             NULL, NULL, NULL,
+                             G_TYPE_BOOLEAN, 1, TRACKER_TYPE_NETWORK_TYPE);
+
+       /**
+        * TrackerMinerOnline::disconnected:
+        * @miner: a #TrackerMinerOnline
+        * @type: a #TrackerNetworkType
+        *
+        * the ::disconnected signal is emitted when a specific @type of
+        * network becomes disconnected.
+        *
+        * Since: 0.18.0
+        **/
+       signals[DISCONNECTED] =
+               g_signal_new ("disconnected",
+                             G_OBJECT_CLASS_TYPE (object_class),
+                             G_SIGNAL_RUN_LAST,
+                             G_STRUCT_OFFSET (TrackerMinerOnlineClass, connected),
+                             NULL, NULL, NULL,
+                             G_TYPE_NONE, 0);
+}
+
+static void
+tracker_miner_online_init (TrackerMinerOnline *miner)
+{
+       TrackerMinerOnlinePrivate *priv;
+
+       priv = tracker_miner_online_get_instance_private (miner);
+       priv->network_type = TRACKER_NETWORK_TYPE_NONE;
+}
+
+#ifdef HAVE_NETWORK_MANAGER
+/*
+ * Returns the first NMActiveConnection with the "default" property set, or
+ * NULL if none is found.
+ */
+static NMActiveConnection*
+find_default_active_connection (NMClient *client)
+{
+       NMActiveConnection *active_connection = NULL;
+       const GPtrArray *active_connections;
+       gint i;
+
+       active_connections = nm_client_get_active_connections (client);
+
+       for (i = 0; i < active_connections->len; i++) {
+               active_connection = g_ptr_array_index (active_connections, i);
+
+               if (nm_active_connection_get_default (active_connection)) {
+                       break;
+               }
+       }
+
+       return active_connection;
+}
+
+static TrackerNetworkType
+_nm_client_get_network_type (NMClient *nm_client)
+{
+       NMActiveConnection *default_active_connection;
+       const GPtrArray *devices;
+       NMDevice *device;
+       NMState state;
+
+       if (!nm_client_get_nm_running (nm_client)) {
+               return TRACKER_NETWORK_TYPE_UNKNOWN;
+       }
+
+       state = nm_client_get_state (nm_client);
+       if (state == NM_STATE_UNKNOWN)
+               return TRACKER_NETWORK_TYPE_UNKNOWN;
+       if (state <= NM_STATE_DISCONNECTING)
+               return TRACKER_NETWORK_TYPE_UNKNOWN;
+
+       default_active_connection = find_default_active_connection (nm_client);
+
+       if (!default_active_connection) {
+               return TRACKER_NETWORK_TYPE_NONE;
+       }
+
+       switch (nm_active_connection_get_state (default_active_connection)) {
+       case NM_ACTIVE_CONNECTION_STATE_UNKNOWN:
+               return TRACKER_NETWORK_TYPE_UNKNOWN;
+       case NM_ACTIVE_CONNECTION_STATE_ACTIVATED:
+               break;
+       default:
+               return TRACKER_NETWORK_TYPE_NONE;
+       }
+
+       devices = nm_active_connection_get_devices (default_active_connection);
+
+       if (!devices->len) {
+               return TRACKER_NETWORK_TYPE_NONE;
+       }
+
+       /* Pick the first device, I don't know when there are more than one */
+       device = g_ptr_array_index (devices, 0);
+
+       switch (nm_device_get_state (device)) {
+       case NM_DEVICE_STATE_UNKNOWN:
+               return TRACKER_NETWORK_TYPE_UNKNOWN;
+               break;
+       case NM_DEVICE_STATE_ACTIVATED:
+               break;
+       default:
+               return TRACKER_NETWORK_TYPE_NONE;
+       }
+
+       if (NM_IS_DEVICE_ETHERNET (device) || NM_IS_DEVICE_WIFI (device)) {
+               return TRACKER_NETWORK_TYPE_LAN;
+       }
+
+#if (NM_CHECK_VERSION (0,8,992))
+       if (NM_IS_DEVICE_MODEM (device)) {
+               return TRACKER_NETWORK_TYPE_3G;
+       }
+#else
+       if (NM_IS_GSM_DEVICE (device) || NM_IS_CDMA_DEVICE (device)) {
+               return TRACKER_NETWORK_TYPE_3G;
+       }
+#endif
+
+       /* We know the device is activated, but we don't know the type of device */
+       return TRACKER_NETWORK_TYPE_UNKNOWN;
+}
+
+static void
+_tracker_miner_online_set_network_type (TrackerMinerOnline *miner,
+                                        TrackerNetworkType  type)
+{
+       TrackerMinerOnlinePrivate *priv;
+       gboolean cont = FALSE;
+       GError *error = NULL;
+
+       priv = tracker_miner_online_get_instance_private (miner);
+
+       if (type == priv->network_type) {
+               return;
+       }
+
+       priv->network_type = type;
+
+       if (type != TRACKER_NETWORK_TYPE_NONE) {
+               g_signal_emit (miner, signals[CONNECTED], 0, type, &cont);
+       } else {
+               g_signal_emit (miner, signals[DISCONNECTED], 0);
+       }
+
+       if (cont && priv->paused) {
+               tracker_miner_resume (TRACKER_MINER (miner));
+               priv->paused = FALSE;
+       } else if (!cont && !priv->paused) {
+               tracker_miner_pause (TRACKER_MINER (miner));
+               priv->paused = TRUE;
+       }
+
+       if (error) {
+               g_warning ("There was an error after getting network type %d: %s",
+                          type, error->message);
+               g_error_free (error);
+       }
+}
+
+static void
+_nm_client_state_notify_cb (GObject            *object,
+                            GParamSpec         *pspec,
+                            TrackerMinerOnline *miner)
+{
+       TrackerMinerOnlinePrivate *priv;
+       TrackerNetworkType type;
+
+       priv = tracker_miner_online_get_instance_private (miner);
+       type = _nm_client_get_network_type (priv->client);
+       _tracker_miner_online_set_network_type (miner, type);
+}
+#endif /* HAVE_NETWORK_MANAGER */
+
+static gboolean
+miner_online_initable_init (GInitable     *initable,
+                            GCancellable  *cancellable,
+                            GError       **error)
+{
+#ifdef HAVE_NETWORK_MANAGER
+       TrackerMinerOnlinePrivate *priv;
+       TrackerNetworkType network_type;
+       TrackerMinerOnline *miner;
+
+       miner = TRACKER_MINER_ONLINE (initable);
+
+       priv = tracker_miner_online_get_instance_private (miner);
+#endif /* HAVE_NETWORK_MANAGER */
+
+       if (!miner_online_initable_parent_iface->init (initable,
+                                                      cancellable, error)) {
+               return FALSE;
+       }
+
+#ifdef HAVE_NETWORK_MANAGER
+       priv->client = nm_client_new (NULL, error);
+       if (!priv->client) {
+               g_prefix_error (error, "Couldn't create NetworkManager client: ");
+               return FALSE;
+       }
+       g_signal_connect (priv->client, "notify::state",
+                         G_CALLBACK (_nm_client_state_notify_cb), miner);
+       network_type = _nm_client_get_network_type (priv->client);
+       _tracker_miner_online_set_network_type (miner, network_type);
+#endif
+
+       return TRUE;
+}
+
+static void
+miner_online_initable_iface_init (GInitableIface *iface)
+{
+       miner_online_initable_parent_iface = g_type_interface_peek_parent (iface);
+       iface->init = miner_online_initable_init;
+}
+
+/**
+ * tracker_miner_online_get_network_type:
+ * @miner: a #TrackerMinerOnline.
+ *
+ * Get the type of network this data @miner uses to index content.
+ *
+ * Returns: a #TrackerNetworkType on success or #TRACKER_NETWORK_TYPE_NONE on error.
+ *
+ * Since: 0.18.
+ **/
+TrackerNetworkType
+tracker_miner_online_get_network_type (TrackerMinerOnline *miner)
+{
+       TrackerMinerOnlinePrivate *priv;
+
+       g_return_val_if_fail (TRACKER_IS_MINER_ONLINE (miner), TRACKER_NETWORK_TYPE_NONE);
+
+       priv = tracker_miner_online_get_instance_private (miner);
+
+       return priv->network_type;
+}
diff --git a/src/libtracker-miner/tracker-miner-online.h b/src/libtracker-miner/tracker-miner-online.h
new file mode 100644
index 000000000..6a25cd529
--- /dev/null
+++ b/src/libtracker-miner/tracker-miner-online.h
@@ -0,0 +1,81 @@
+/*
+ * Copyright (C) 2009, Adrien Bustany <abustany gnome org>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ * Boston, MA  02110-1301, USA.
+ */
+
+#ifndef __LIBTRACKER_MINER_ONLINE_H__
+#define __LIBTRACKER_MINER_ONLINE_H__
+
+#if !defined (__LIBTRACKER_MINER_H_INSIDE__) && !defined (TRACKER_COMPILATION)
+#error "Only <libtracker-miner/tracker-miner.h> can be included directly."
+#endif
+
+#include <libtracker-miner/tracker-miner-object.h>
+#include <libtracker-miner/tracker-miner-enums.h>
+
+#define TRACKER_TYPE_MINER_ONLINE         (tracker_miner_online_get_type())
+#define TRACKER_MINER_ONLINE(o)           (G_TYPE_CHECK_INSTANCE_CAST ((o), TRACKER_TYPE_MINER_ONLINE, 
TrackerMinerOnline))
+#define TRACKER_MINER_ONLINE_CLASS(c)     (G_TYPE_CHECK_CLASS_CAST ((c),    TRACKER_TYPE_MINER_ONLINE, 
TrackerMinerOnlineClass))
+#define TRACKER_IS_MINER_ONLINE(o)        (G_TYPE_CHECK_INSTANCE_TYPE ((o), TRACKER_TYPE_MINER_ONLINE))
+#define TRACKER_IS_MINER_ONLINE_CLASS(c)  (G_TYPE_CHECK_CLASS_TYPE ((c),    TRACKER_TYPE_MINER_ONLINE))
+#define TRACKER_MINER_ONLINE_GET_CLASS(o) (G_TYPE_INSTANCE_GET_CLASS ((o),  TRACKER_TYPE_MINER_ONLINE, 
TrackerMinerOnlineClass))
+
+G_BEGIN_DECLS
+
+typedef struct _TrackerMinerOnline TrackerMinerOnline;
+typedef struct _TrackerMinerOnlineClass TrackerMinerOnlineClass;
+
+/**
+ * TrackerMinerOnline:
+ *
+ * Abstract miner object for data requiring connectivity.
+ **/
+struct _TrackerMinerOnline {
+       TrackerMiner parent_instance;
+};
+
+/**
+ * TrackerMinerOnlineClass:
+ * @parent_class: a #TrackerMinerClass
+ * @connected: called when there is a network connection, or a new
+ *   default route, returning #TRUE starts/resumes indexing.
+ * @disconnected: called when there is no network connection.
+ * @padding: Reserved for future API improvements.
+ *
+ * Virtual methods that can be overridden.
+ *
+ * Since: 0.18.
+ **/
+struct _TrackerMinerOnlineClass {
+       TrackerMinerClass parent_class;
+
+       /* vmethods */
+       gboolean (* connected)    (TrackerMinerOnline *miner,
+                                  TrackerNetworkType  network);
+       void     (* disconnected) (TrackerMinerOnline *miner);
+
+       /* <Private> */
+       gpointer padding[10];
+};
+
+GType               tracker_miner_online_get_type         (void) G_GNUC_CONST;
+
+TrackerNetworkType  tracker_miner_online_get_network_type (TrackerMinerOnline *miner);
+
+G_END_DECLS
+
+#endif /* __LIBTRACKER_MINER_ONLINE_H__ */
diff --git a/src/libtracker-miner/tracker-miner-proxy.c b/src/libtracker-miner/tracker-miner-proxy.c
new file mode 100644
index 000000000..b69e7b889
--- /dev/null
+++ b/src/libtracker-miner/tracker-miner-proxy.c
@@ -0,0 +1,851 @@
+/*
+ * Copyright (C) 2017, Red Hat, Inc.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ * Boston, MA  02110-1301, USA.
+ *
+ * Authors: Carlos Garnacho <carlosg gnome org>
+ */
+
+#include "config-miners.h"
+
+#include <glib/gi18n.h>
+#include <libtracker-miners-common/tracker-dbus.h>
+#include <libtracker-miners-common/tracker-type-utils.h>
+#include <libtracker-miners-common/tracker-domain-ontology.h>
+
+#include "tracker-miner-proxy.h"
+
+typedef struct {
+       TrackerMiner *miner;
+       GDBusConnection *d_connection;
+       GDBusNodeInfo *introspection_data;
+       gchar *dbus_path;
+       guint registration_id;
+       guint watch_name_id;
+       GHashTable *pauses;
+       gint availability_cookie;
+} TrackerMinerProxyPrivate;
+
+typedef struct {
+       gint cookie;
+       gchar *application;
+       gchar *reason;
+       gchar *watch_name;
+       guint watch_name_id;
+} PauseData;
+
+enum {
+       PROP_0,
+       PROP_MINER,
+       PROP_DBUS_CONNECTION,
+       PROP_DBUS_PATH,
+};
+
+static void tracker_miner_proxy_initable_iface_init (GInitableIface *iface);
+
+G_DEFINE_TYPE_WITH_CODE (TrackerMinerProxy, tracker_miner_proxy, G_TYPE_OBJECT,
+                         G_ADD_PRIVATE (TrackerMinerProxy)
+                         G_IMPLEMENT_INTERFACE (G_TYPE_INITABLE, tracker_miner_proxy_initable_iface_init))
+
+static const gchar introspection_xml[] =
+  "<node>"
+  "  <interface name='org.freedesktop.Tracker1.Miner'>"
+  "    <method name='Start'>"
+  "    </method>"
+  "    <method name='GetStatus'>"
+  "      <arg type='s' name='status' direction='out' />"
+  "    </method>"
+  "    <method name='GetProgress'>"
+  "      <arg type='d' name='progress' direction='out' />"
+  "    </method>"
+  "    <method name='GetRemainingTime'>"
+  "      <arg type='i' name='remaining_time' direction='out' />"
+  "    </method>"
+  "    <method name='GetPauseDetails'>"
+  "      <arg type='as' name='pause_applications' direction='out' />"
+  "      <arg type='as' name='pause_reasons' direction='out' />"
+  "    </method>"
+  "    <method name='Pause'>"
+  "      <arg type='s' name='application' direction='in' />"
+  "      <arg type='s' name='reason' direction='in' />"
+  "      <arg type='i' name='cookie' direction='out' />"
+  "    </method>"
+  "    <method name='PauseForProcess'>"
+  "      <arg type='s' name='application' direction='in' />"
+  "      <arg type='s' name='reason' direction='in' />"
+  "      <arg type='i' name='cookie' direction='out' />"
+  "    </method>"
+  "    <method name='Resume'>"
+  "      <arg type='i' name='cookie' direction='in' />"
+  "    </method>"
+  "    <signal name='Started' />"
+  "    <signal name='Stopped' />"
+  "    <signal name='Paused' />"
+  "    <signal name='Resumed' />"
+  "    <signal name='Progress'>"
+  "      <arg type='s' name='status' />"
+  "      <arg type='d' name='progress' />"
+  "      <arg type='i' name='remaining_time' />"
+  "    </signal>"
+  "  </interface>"
+  "</node>";
+
+#define TRACKER_SERVICE "org.freedesktop.Tracker1"
+
+static PauseData *
+pause_data_new (const gchar *application,
+                const gchar *reason,
+                const gchar *watch_name,
+                guint        watch_name_id)
+{
+       PauseData *data;
+       static gint cookie = 1;
+
+       data = g_slice_new0 (PauseData);
+
+       data->cookie = cookie++;
+       data->application = g_strdup (application);
+       data->reason = g_strdup (reason);
+       data->watch_name = g_strdup (watch_name);
+       data->watch_name_id = watch_name_id;
+
+       return data;
+}
+
+static void
+pause_data_destroy (gpointer data)
+{
+       PauseData *pd;
+
+       pd = data;
+
+       if (pd->watch_name_id) {
+               g_bus_unwatch_name (pd->watch_name_id);
+       }
+
+       g_free (pd->watch_name);
+
+       g_free (pd->reason);
+       g_free (pd->application);
+
+       g_slice_free (PauseData, pd);
+}
+
+static void
+tracker_miner_proxy_set_property (GObject      *object,
+                                  guint         prop_id,
+                                  const GValue *value,
+                                  GParamSpec   *pspec)
+{
+       TrackerMinerProxy *proxy = TRACKER_MINER_PROXY (object);
+       TrackerMinerProxyPrivate *priv = tracker_miner_proxy_get_instance_private (proxy);
+
+       switch (prop_id) {
+       case PROP_MINER:
+               priv->miner = g_value_dup_object (value);
+               break;
+       case PROP_DBUS_CONNECTION:
+               priv->d_connection = g_value_dup_object (value);
+               break;
+       case PROP_DBUS_PATH:
+               priv->dbus_path = g_value_dup_string (value);
+               break;
+       default:
+               G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+               break;
+       }
+}
+
+static void
+tracker_miner_proxy_get_property (GObject    *object,
+                                  guint       prop_id,
+                                  GValue     *value,
+                                  GParamSpec *pspec)
+{
+       TrackerMinerProxy *proxy = TRACKER_MINER_PROXY (object);
+       TrackerMinerProxyPrivate *priv = tracker_miner_proxy_get_instance_private (proxy);
+
+       switch (prop_id) {
+       case PROP_MINER:
+               g_value_set_object (value, priv->miner);
+               break;
+       case PROP_DBUS_CONNECTION:
+               g_value_set_object (value, priv->d_connection);
+               break;
+       case PROP_DBUS_PATH:
+               g_value_set_string (value, priv->dbus_path);
+               break;
+       default:
+               G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+               break;
+       }
+}
+
+static void
+tracker_miner_proxy_finalize (GObject *object)
+{
+       TrackerMinerProxy *proxy = TRACKER_MINER_PROXY (object);
+       TrackerMinerProxyPrivate *priv = tracker_miner_proxy_get_instance_private (proxy);
+
+       g_signal_handlers_disconnect_by_data (priv->miner, proxy);
+       g_clear_object (&priv->miner);
+       g_free (priv->dbus_path);
+       g_hash_table_unref (priv->pauses);
+
+       if (priv->watch_name_id != 0) {
+               g_bus_unwatch_name (priv->watch_name_id);
+       }
+
+       if (priv->registration_id != 0) {
+               g_dbus_connection_unregister_object (priv->d_connection,
+                                                    priv->registration_id);
+       }
+
+       if (priv->introspection_data) {
+               g_dbus_node_info_unref (priv->introspection_data);
+       }
+
+       if (priv->d_connection) {
+               g_object_unref (priv->d_connection);
+       }
+
+       G_OBJECT_CLASS (tracker_miner_proxy_parent_class)->finalize (object);
+}
+
+static void
+tracker_miner_proxy_class_init (TrackerMinerProxyClass *klass)
+{
+       GObjectClass *object_class = G_OBJECT_CLASS (klass);
+
+       object_class->set_property = tracker_miner_proxy_set_property;
+       object_class->get_property = tracker_miner_proxy_get_property;
+       object_class->finalize = tracker_miner_proxy_finalize;
+
+       g_object_class_install_property (object_class,
+                                        PROP_MINER,
+                                        g_param_spec_object ("miner",
+                                                             "Miner to manage",
+                                                             "Miner to manage",
+                                                             TRACKER_TYPE_MINER,
+                                                             G_PARAM_READWRITE |
+                                                             G_PARAM_CONSTRUCT_ONLY));
+       g_object_class_install_property (object_class,
+                                        PROP_DBUS_CONNECTION,
+                                        g_param_spec_object ("dbus-connection",
+                                                             "DBus connection",
+                                                             "DBus connection",
+                                                             G_TYPE_DBUS_CONNECTION,
+                                                             G_PARAM_READWRITE |
+                                                             G_PARAM_CONSTRUCT_ONLY));
+       g_object_class_install_property (object_class,
+                                        PROP_DBUS_PATH,
+                                        g_param_spec_string ("dbus-path",
+                                                             "DBus path",
+                                                             "DBus path for this miner",
+                                                             NULL,
+                                                             G_PARAM_READWRITE |
+                                                             G_PARAM_CONSTRUCT_ONLY));
+}
+
+static void
+tracker_miner_proxy_init (TrackerMinerProxy *proxy)
+{
+       TrackerMinerProxyPrivate *priv = tracker_miner_proxy_get_instance_private (proxy);
+
+       priv->pauses = g_hash_table_new_full (g_direct_hash,
+                                             g_direct_equal,
+                                             NULL,
+                                             pause_data_destroy);
+}
+
+static void
+handle_method_call_start (TrackerMinerProxy     *proxy,
+                          GDBusMethodInvocation *invocation,
+                          GVariant              *parameters)
+{
+       TrackerDBusRequest *request;
+       TrackerMinerProxyPrivate *priv;
+
+       priv = tracker_miner_proxy_get_instance_private (proxy);
+
+       request = tracker_g_dbus_request_begin (invocation,
+                                               "%s",
+                                               __PRETTY_FUNCTION__);
+
+       tracker_miner_start (priv->miner);
+
+       tracker_dbus_request_end (request, NULL);
+       g_dbus_method_invocation_return_value (invocation, NULL);
+}
+
+static void
+sync_miner_pause_state (TrackerMinerProxy *proxy)
+{
+       TrackerMinerProxyPrivate *priv;
+       guint n_pauses;
+       gboolean is_paused;
+
+       priv = tracker_miner_proxy_get_instance_private (proxy);
+       n_pauses = g_hash_table_size (priv->pauses);
+       is_paused = tracker_miner_is_paused (priv->miner);
+
+       if (!is_paused && n_pauses > 0) {
+               tracker_miner_pause (priv->miner);
+       } else if (is_paused && n_pauses == 0) {
+               tracker_miner_resume (priv->miner);
+       }
+}
+
+static void
+handle_method_call_resume (TrackerMinerProxy     *proxy,
+                           GDBusMethodInvocation *invocation,
+                           GVariant              *parameters)
+{
+       gint cookie;
+       TrackerDBusRequest *request;
+       TrackerMinerProxyPrivate *priv;
+
+       priv = tracker_miner_proxy_get_instance_private (proxy);
+
+       g_variant_get (parameters, "(i)", &cookie);
+
+       request = tracker_g_dbus_request_begin (invocation,
+                                               "%s(cookie:%d)",
+                                               __PRETTY_FUNCTION__,
+                                               cookie);
+
+       if (!g_hash_table_remove (priv->pauses, GINT_TO_POINTER (cookie))) {
+               tracker_dbus_request_end (request, NULL);
+               g_dbus_method_invocation_return_error (invocation,
+                                                      tracker_miner_error_quark (),
+                                                      TRACKER_MINER_ERROR_INVALID_COOKIE,
+                                                      _("Cookie not recognized to resume paused miner"));
+       } else {
+               sync_miner_pause_state (proxy);
+               tracker_dbus_request_end (request, NULL);
+               g_dbus_method_invocation_return_value (invocation, NULL);
+       }
+}
+
+static void
+pause_process_disappeared_cb (GDBusConnection *connection,
+                              const gchar     *name,
+                              gpointer         user_data)
+{
+       TrackerMinerProxy *proxy = user_data;
+       TrackerMinerProxyPrivate *priv = tracker_miner_proxy_get_instance_private (proxy);
+       GHashTableIter iter;
+       gpointer key, value;
+
+       g_message ("Process with name:'%s' has disappeared", name);
+
+       g_hash_table_iter_init (&iter, priv->pauses);
+       while (g_hash_table_iter_next (&iter, &key, &value)) {
+               PauseData *pd_iter = value;
+
+               if (g_strcmp0 (name, pd_iter->watch_name) == 0)
+                       g_hash_table_iter_remove (&iter);
+       }
+
+       sync_miner_pause_state (proxy);
+}
+
+static gint
+pause_miner (TrackerMinerProxy  *proxy,
+             const gchar        *application,
+             const gchar        *reason,
+             const gchar        *calling_name,
+             GError            **error)
+{
+       TrackerMinerProxyPrivate *priv;
+       PauseData *pd;
+       GHashTableIter iter;
+       gpointer key, value;
+       guint watch_name_id = 0;
+
+       priv = tracker_miner_proxy_get_instance_private (proxy);
+
+       /* Check this is not a duplicate pause */
+       g_hash_table_iter_init (&iter, priv->pauses);
+       while (g_hash_table_iter_next (&iter, &key, &value)) {
+               PauseData *pd = value;
+
+               if (g_strcmp0 (application, pd->application) == 0 &&
+                   g_strcmp0 (reason, pd->reason) == 0) {
+                       /* Can't use duplicate pauses */
+                       g_set_error_literal (error,
+                                            tracker_miner_error_quark (),
+                                            TRACKER_MINER_ERROR_PAUSED_ALREADY,
+                                            _("Pause application and reason match an already existing pause 
request"));
+                       return -1;
+               }
+       }
+
+       if (calling_name) {
+               g_message ("Watching process with name:'%s'", calling_name);
+               watch_name_id = g_bus_watch_name (TRACKER_IPC_BUS,
+                                                 calling_name,
+                                                 G_BUS_NAME_WATCHER_FLAGS_NONE,
+                                                 NULL,
+                                                 pause_process_disappeared_cb,
+                                                 proxy,
+                                                 NULL);
+       }
+
+       pd = pause_data_new (application, reason, calling_name, watch_name_id);
+
+       g_hash_table_insert (priv->pauses,
+                            GINT_TO_POINTER (pd->cookie),
+                            pd);
+
+       sync_miner_pause_state (proxy);
+
+       return pd->cookie;
+}
+
+static void
+handle_method_call_pause (TrackerMinerProxy     *proxy,
+                          GDBusMethodInvocation *invocation,
+                          GVariant              *parameters)
+{
+       GError *local_error = NULL;
+       gint cookie;
+       const gchar *application = NULL, *reason = NULL;
+       TrackerDBusRequest *request;
+
+       g_variant_get (parameters, "(&s&s)", &application, &reason);
+
+       tracker_gdbus_async_return_if_fail (application != NULL, invocation);
+       tracker_gdbus_async_return_if_fail (reason != NULL, invocation);
+
+       request = tracker_g_dbus_request_begin (invocation,
+                                               "%s(application:'%s', reason:'%s')",
+                                               __PRETTY_FUNCTION__,
+                                               application,
+                                               reason);
+
+       cookie = pause_miner (proxy, application, reason, NULL, &local_error);
+       if (cookie == -1) {
+               tracker_dbus_request_end (request, local_error);
+
+               g_dbus_method_invocation_return_gerror (invocation, local_error);
+
+               g_error_free (local_error);
+
+               return;
+       }
+
+       tracker_dbus_request_end (request, NULL);
+       g_dbus_method_invocation_return_value (invocation,
+                                              g_variant_new ("(i)", cookie));
+}
+
+static void
+handle_method_call_pause_for_process (TrackerMinerProxy     *proxy,
+                                      GDBusMethodInvocation *invocation,
+                                      GVariant              *parameters)
+{
+       GError *local_error = NULL;
+       gint cookie;
+       const gchar *application = NULL, *reason = NULL;
+       TrackerDBusRequest *request;
+
+       g_variant_get (parameters, "(&s&s)", &application, &reason);
+
+       tracker_gdbus_async_return_if_fail (application != NULL, invocation);
+       tracker_gdbus_async_return_if_fail (reason != NULL, invocation);
+
+       request = tracker_g_dbus_request_begin (invocation,
+                                               "%s(application:'%s', reason:'%s')",
+                                               __PRETTY_FUNCTION__,
+                                               application,
+                                               reason);
+
+       cookie = pause_miner (proxy,
+                             application,
+                             reason,
+                             g_dbus_method_invocation_get_sender (invocation),
+                             &local_error);
+       if (cookie == -1) {
+               tracker_dbus_request_end (request, local_error);
+
+               g_dbus_method_invocation_return_gerror (invocation, local_error);
+
+               g_error_free (local_error);
+
+               return;
+       }
+
+       tracker_dbus_request_end (request, NULL);
+       g_dbus_method_invocation_return_value (invocation,
+                                              g_variant_new ("(i)", cookie));
+}
+
+static void
+handle_method_call_get_pause_details (TrackerMinerProxy     *proxy,
+                                      GDBusMethodInvocation *invocation,
+                                      GVariant              *parameters)
+{
+       GSList *applications, *reasons;
+       GStrv applications_strv, reasons_strv;
+       GHashTableIter iter;
+       gpointer key, value;
+       TrackerDBusRequest *request;
+       TrackerMinerProxyPrivate *priv;
+
+       priv = tracker_miner_proxy_get_instance_private (proxy);
+       request = tracker_g_dbus_request_begin (invocation, "%s()", __PRETTY_FUNCTION__);
+
+       applications = NULL;
+       reasons = NULL;
+       g_hash_table_iter_init (&iter, priv->pauses);
+       while (g_hash_table_iter_next (&iter, &key, &value)) {
+               PauseData *pd = value;
+
+               applications = g_slist_prepend (applications, pd->application);
+               reasons = g_slist_prepend (reasons, pd->reason);
+       }
+       applications = g_slist_reverse (applications);
+       reasons = g_slist_reverse (reasons);
+       applications_strv = tracker_gslist_to_string_list (applications);
+       reasons_strv = tracker_gslist_to_string_list (reasons);
+
+       tracker_dbus_request_end (request, NULL);
+       g_dbus_method_invocation_return_value (invocation,
+                                              g_variant_new ("(^as^as)",
+                                                             applications_strv,
+                                                             reasons_strv));
+
+       g_strfreev (applications_strv);
+       g_strfreev (reasons_strv);
+       g_slist_free (applications);
+       g_slist_free (reasons);
+}
+
+static void
+handle_method_call_get_remaining_time (TrackerMinerProxy     *proxy,
+                                       GDBusMethodInvocation *invocation,
+                                       GVariant              *parameters)
+{
+       TrackerDBusRequest *request;
+       TrackerMinerProxyPrivate *priv;
+       gint remaining_time;
+
+       priv = tracker_miner_proxy_get_instance_private (proxy);
+
+       request = tracker_g_dbus_request_begin (invocation, "%s()", __PRETTY_FUNCTION__);
+
+       tracker_dbus_request_end (request, NULL);
+       g_object_get (G_OBJECT (priv->miner), "remaining-time", &remaining_time, NULL);
+       g_dbus_method_invocation_return_value (invocation,
+                                              g_variant_new ("(i)", remaining_time));
+}
+
+static void
+handle_method_call_get_progress (TrackerMinerProxy     *proxy,
+                                 GDBusMethodInvocation *invocation,
+                                 GVariant              *parameters)
+{
+       TrackerDBusRequest *request;
+       TrackerMinerProxyPrivate *priv;
+       gdouble progress;
+
+       priv = tracker_miner_proxy_get_instance_private (proxy);
+
+       request = tracker_g_dbus_request_begin (invocation, "%s()", __PRETTY_FUNCTION__);
+
+       tracker_dbus_request_end (request, NULL);
+       g_object_get (G_OBJECT (priv->miner), "progress", &progress, NULL);
+       g_dbus_method_invocation_return_value (invocation,
+                                              g_variant_new ("(d)", progress));
+}
+
+static void
+handle_method_call_get_status (TrackerMinerProxy     *proxy,
+                               GDBusMethodInvocation *invocation,
+                               GVariant              *parameters)
+{
+       TrackerDBusRequest *request;
+       TrackerMinerProxyPrivate *priv;
+       gchar *status;
+
+       priv = tracker_miner_proxy_get_instance_private (proxy);
+
+       request = tracker_g_dbus_request_begin (invocation, "%s()", __PRETTY_FUNCTION__);
+
+       tracker_dbus_request_end (request, NULL);
+       g_object_get (G_OBJECT (priv->miner), "status", &status, NULL);
+       g_dbus_method_invocation_return_value (invocation,
+                                              g_variant_new ("(s)",
+                                                             status ? status : ""));
+       g_free (status);
+}
+
+static void
+handle_method_call (GDBusConnection       *connection,
+                    const gchar           *sender,
+                    const gchar           *object_path,
+                    const gchar           *interface_name,
+                    const gchar           *method_name,
+                    GVariant              *parameters,
+                    GDBusMethodInvocation *invocation,
+                    gpointer               user_data)
+{
+       TrackerMinerProxy *proxy = user_data;
+
+       if (g_strcmp0 (method_name, "Start") == 0) {
+               handle_method_call_start (proxy, invocation, parameters);
+       } else if (g_strcmp0 (method_name, "Resume") == 0) {
+               handle_method_call_resume (proxy, invocation, parameters);
+       } else if (g_strcmp0 (method_name, "Pause") == 0) {
+               handle_method_call_pause (proxy, invocation, parameters);
+       } else if (g_strcmp0 (method_name, "PauseForProcess") == 0) {
+               handle_method_call_pause_for_process (proxy, invocation, parameters);
+       } else if (g_strcmp0 (method_name, "GetPauseDetails") == 0) {
+               handle_method_call_get_pause_details (proxy, invocation, parameters);
+       } else if (g_strcmp0 (method_name, "GetRemainingTime") == 0) {
+               handle_method_call_get_remaining_time (proxy, invocation, parameters);
+       } else if (g_strcmp0 (method_name, "GetProgress") == 0) {
+               handle_method_call_get_progress (proxy, invocation, parameters);
+       } else if (g_strcmp0 (method_name, "GetStatus") == 0) {
+               handle_method_call_get_status (proxy, invocation, parameters);
+       } else {
+               g_dbus_method_invocation_return_error (invocation,
+                                                      G_DBUS_ERROR,
+                                                      G_DBUS_ERROR_UNKNOWN_METHOD,
+                                                      "Unknown method %s",
+                                                      method_name);
+       }
+}
+
+static GVariant *
+handle_get_property (GDBusConnection  *connection,
+                     const gchar      *sender,
+                     const gchar      *object_path,
+                     const gchar      *interface_name,
+                     const gchar      *property_name,
+                     GError          **error,
+                     gpointer          user_data)
+{
+       return NULL;
+}
+
+static gboolean
+handle_set_property (GDBusConnection  *connection,
+                     const gchar      *sender,
+                     const gchar      *object_path,
+                     const gchar      *interface_name,
+                     const gchar      *property_name,
+                     GVariant         *value,
+                     GError          **error,
+                     gpointer          user_data)
+{
+       return FALSE;
+}
+
+static void
+emit_dbus_signal (TrackerMinerProxy *proxy,
+                  const gchar       *signal,
+                  GVariant          *variant)
+{
+       TrackerMinerProxyPrivate *priv;
+
+       priv = tracker_miner_proxy_get_instance_private (proxy);
+       g_dbus_connection_emit_signal (priv->d_connection,
+                                      NULL,
+                                      priv->dbus_path,
+                                      TRACKER_MINER_DBUS_INTERFACE,
+                                      signal,
+                                      variant,
+                                      NULL);
+}
+
+static void
+miner_started_cb (TrackerMiner      *miner,
+                  TrackerMinerProxy *proxy)
+{
+       emit_dbus_signal (proxy, "Started", NULL);
+}
+
+static void
+miner_stopped_cb (TrackerMiner      *miner,
+                  TrackerMinerProxy *proxy)
+{
+       emit_dbus_signal (proxy, "Stopped", NULL);
+}
+
+static void
+miner_paused_cb (TrackerMiner      *miner,
+                 TrackerMinerProxy *proxy)
+{
+       emit_dbus_signal (proxy, "Paused", NULL);
+}
+
+static void
+miner_resumed_cb (TrackerMiner      *miner,
+                  TrackerMinerProxy *proxy)
+{
+       emit_dbus_signal (proxy, "Resumed", NULL);
+}
+
+static void
+miner_progress_cb (TrackerMiner      *miner,
+                   const gchar       *status,
+                   gdouble            progress,
+                   gint               remaining_time,
+                   TrackerMinerProxy *proxy)
+{
+       GVariant *variant;
+
+       variant = g_variant_new ("(sdi)", status, progress, remaining_time);
+       /* variant reference is sunk here */
+       emit_dbus_signal (proxy, "Progress", variant);
+}
+
+static void
+on_tracker_store_appeared (GDBusConnection *connection,
+                           const gchar     *name,
+                           const gchar     *name_owner,
+                           gpointer         user_data)
+
+{
+       TrackerMinerProxy *proxy = user_data;
+       TrackerMinerProxyPrivate *priv = tracker_miner_proxy_get_instance_private (proxy);
+
+       g_debug ("Miner:'%s' noticed store availability has changed to AVAILABLE",
+                priv->dbus_path);
+
+       if (priv->availability_cookie != 0) {
+               g_hash_table_remove (priv->pauses,
+                                    GINT_TO_POINTER (priv->availability_cookie));
+               sync_miner_pause_state (proxy);
+               priv->availability_cookie = 0;
+       }
+}
+
+static void
+on_tracker_store_disappeared (GDBusConnection *connection,
+                              const gchar     *name,
+                              gpointer         user_data)
+{
+       TrackerMinerProxy *proxy = user_data;
+       TrackerMinerProxyPrivate *priv = tracker_miner_proxy_get_instance_private (proxy);
+
+       g_debug ("Miner:'%s' noticed store availability has changed to UNAVAILABLE",
+                priv->dbus_path);
+
+       if (priv->availability_cookie == 0) {
+               GError *error = NULL;
+               gint cookie_id;
+
+               cookie_id = pause_miner (proxy, NULL,
+                                        _("Data store is not available"),
+                                        NULL, &error);
+
+               if (error) {
+                       g_warning ("Could not pause, %s", error->message);
+                       g_error_free (error);
+               } else {
+                       priv->availability_cookie = cookie_id;
+               }
+       }
+}
+
+static gboolean
+tracker_miner_proxy_initable_init (GInitable     *initable,
+                                   GCancellable  *cancellable,
+                                   GError       **error)
+{
+       TrackerMinerProxy *proxy = TRACKER_MINER_PROXY (initable);
+       TrackerMinerProxyPrivate *priv = tracker_miner_proxy_get_instance_private (proxy);
+       GError *inner_error = NULL;
+       gchar *store_name;
+       GDBusInterfaceVTable interface_vtable = {
+               handle_method_call,
+               handle_get_property,
+               handle_set_property
+       };
+
+       priv->introspection_data = g_dbus_node_info_new_for_xml (introspection_xml,
+                                                                &inner_error);
+       if (inner_error) {
+               g_propagate_error (error, inner_error);
+               return FALSE;
+       }
+
+       priv->registration_id =
+               g_dbus_connection_register_object (priv->d_connection,
+                                                  priv->dbus_path,
+                                                  priv->introspection_data->interfaces[0],
+                                                  &interface_vtable,
+                                                  proxy,
+                                                  NULL,
+                                                  &inner_error);
+       if (inner_error) {
+               g_propagate_error (error, inner_error);
+               return FALSE;
+       }
+
+       if (!tracker_load_domain_config (tracker_sparql_connection_get_domain (),
+                                        &store_name,
+                                        &inner_error)) {
+               g_propagate_error (error, inner_error);
+               return FALSE;
+       }
+
+       priv->watch_name_id =
+               g_bus_watch_name_on_connection (priv->d_connection,
+                                               store_name,
+                                               G_BUS_NAME_WATCHER_FLAGS_NONE,
+                                               on_tracker_store_appeared,
+                                               on_tracker_store_disappeared,
+                                               proxy,
+                                               NULL);
+       g_free (store_name);
+
+       g_signal_connect (priv->miner, "started",
+                         G_CALLBACK (miner_started_cb), proxy);
+       g_signal_connect (priv->miner, "stopped",
+                         G_CALLBACK (miner_stopped_cb), proxy);
+       g_signal_connect (priv->miner, "paused",
+                         G_CALLBACK (miner_paused_cb), proxy);
+       g_signal_connect (priv->miner, "resumed",
+                         G_CALLBACK (miner_resumed_cb), proxy);
+       g_signal_connect (priv->miner, "progress",
+                         G_CALLBACK (miner_progress_cb), proxy);
+       return TRUE;
+}
+
+static void
+tracker_miner_proxy_initable_iface_init (GInitableIface *iface)
+{
+       iface->init = tracker_miner_proxy_initable_init;
+}
+
+TrackerMinerProxy *
+tracker_miner_proxy_new (TrackerMiner     *miner,
+                         GDBusConnection  *connection,
+                         const gchar      *dbus_path,
+                         GCancellable     *cancellable,
+                         GError          **error)
+{
+       return g_initable_new (TRACKER_TYPE_MINER_PROXY,
+                              cancellable, error,
+                              "miner", miner,
+                              "dbus-connection", connection,
+                              "dbus-path", dbus_path,
+                              NULL);
+}
diff --git a/src/libtracker-miner/tracker-miner-proxy.h b/src/libtracker-miner/tracker-miner-proxy.h
new file mode 100644
index 000000000..a41c39139
--- /dev/null
+++ b/src/libtracker-miner/tracker-miner-proxy.h
@@ -0,0 +1,61 @@
+/*
+ * Copyright (C) 2017, Red Hat, Inc.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ * Boston, MA  02110-1301, USA.
+ *
+ * Authors: Carlos Garnacho <carlosg gnome org>
+ */
+
+#ifndef __TRACKER_MINER_PROXY_H__
+#define __TRACKER_MINER_PROXY_H__
+
+#if !defined (__LIBTRACKER_MINER_H_INSIDE__) && !defined (TRACKER_COMPILATION)
+#error "Only <libtracker-miner/tracker-miner.h> can be included directly."
+#endif
+
+#include <glib-object.h>
+#include <gio/gio.h>
+#include "tracker-miner-object.h"
+
+#define TRACKER_TYPE_MINER_PROXY         (tracker_miner_proxy_get_type())
+#define TRACKER_MINER_PROXY(o)           (G_TYPE_CHECK_INSTANCE_CAST ((o), TRACKER_TYPE_MINER_PROXY, 
TrackerMinerProxy))
+#define TRACKER_MINER_PROXY_CLASS(c)     (G_TYPE_CHECK_CLASS_CAST ((c),    TRACKER_TYPE_MINER_PROXY, 
TrackerMinerProxyClass))
+#define TRACKER_IS_MINER_PROXY(o)        (G_TYPE_CHECK_INSTANCE_TYPE ((o), TRACKER_TYPE_MINER_PROXY))
+#define TRACKER_IS_MINER_PROXY_CLASS(c)  (G_TYPE_CHECK_CLASS_TYPE ((c),    TRACKER_TYPE_MINER_PROXY))
+#define TRACKER_MINER_PROXY_GET_CLASS(o) (G_TYPE_INSTANCE_GET_CLASS ((o),  TRACKER_TYPE_MINER_PROXY, 
TrackerMinerProxyClass))
+
+typedef struct _TrackerMinerProxy TrackerMinerProxy;
+typedef struct _TrackerMinerProxyClass TrackerMinerProxyClass;
+
+struct _TrackerMinerProxy {
+       GObject parent_instance;
+};
+
+struct _TrackerMinerProxyClass {
+       GObjectClass parent_class;
+       /*<private>*/
+       gpointer padding[10];
+};
+
+GType               tracker_miner_proxy_get_type (void) G_GNUC_CONST;
+
+TrackerMinerProxy * tracker_miner_proxy_new      (TrackerMiner     *miner,
+                                                  GDBusConnection  *connection,
+                                                  const gchar      *dbus_path,
+                                                  GCancellable     *cancellable,
+                                                  GError          **error);
+
+#endif /* __TRACKER_MINER_PROXY_H__ */
diff --git a/src/libtracker-miner/tracker-miner.deps b/src/libtracker-miner/tracker-miner.deps
new file mode 100644
index 000000000..cd10dfde4
--- /dev/null
+++ b/src/libtracker-miner/tracker-miner.deps
@@ -0,0 +1 @@
+gio-2.0
diff --git a/src/libtracker-miner/tracker-miner.h b/src/libtracker-miner/tracker-miner.h
new file mode 100644
index 000000000..9d6d20ee1
--- /dev/null
+++ b/src/libtracker-miner/tracker-miner.h
@@ -0,0 +1,38 @@
+/*
+ * Copyright (C) 2009, Nokia <ivan frade nokia com>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ * Boston, MA  02110-1301, USA.
+ */
+
+#ifndef __LIBTRACKER_MINER_H__
+#define __LIBTRACKER_MINER_H__
+
+#define __LIBTRACKER_MINER_H_INSIDE__
+
+#include <libtracker-miner/tracker-data-provider.h>
+#include <libtracker-miner/tracker-decorator.h>
+#include <libtracker-miner/tracker-decorator-fs.h>
+#include <libtracker-miner/tracker-miner-object.h>
+#include <libtracker-miner/tracker-miner-online.h>
+#include <libtracker-miner/tracker-miner-fs.h>
+#include <libtracker-miner/tracker-miner-enums.h>
+#include <libtracker-miner/tracker-miner-enum-types.h>
+#include <libtracker-miner/tracker-indexing-tree.h>
+#include <libtracker-miner/tracker-miner-proxy.h>
+
+#undef __LIBTRACKER_MINER_H_INSIDE__
+
+#endif /* __LIBTRACKER_MINER_H__ */
diff --git a/src/libtracker-miner/tracker-miner.pc.in b/src/libtracker-miner/tracker-miner.pc.in
new file mode 100644
index 000000000..36a8a3248
--- /dev/null
+++ b/src/libtracker-miner/tracker-miner.pc.in
@@ -0,0 +1,11 @@
+prefix=@prefix@
+exec_prefix=@exec_prefix@
+libdir=@libdir@
+includedir=@includedir@
+
+Name: tracker-miner
+Description: A library to develop tracker data miners
+Version: @VERSION@
+Requires: glib-2.0 gio-2.0 tracker-sparql-@TRACKER_API_VERSION@
+Libs: -L${libdir} -ltracker-miner-@TRACKER_API_VERSION@
+Cflags: -I${includedir}/tracker-@TRACKER_API_VERSION@
diff --git a/src/libtracker-miner/tracker-miner.vapi b/src/libtracker-miner/tracker-miner.vapi
new file mode 100644
index 000000000..617735243
--- /dev/null
+++ b/src/libtracker-miner/tracker-miner.vapi
@@ -0,0 +1,208 @@
+/* tracker-miner.vapi generated by vapigen, do not modify. */
+
+[CCode (cprefix = "Tracker", gir_namespace = "TrackerMiner", gir_version = "1.0", lower_case_cprefix = 
"tracker_")]
+namespace Tracker {
+       [CCode (cheader_filename = "libtracker-miner/tracker-miner.h", type_id = "tracker_decorator_get_type 
()")]
+       public abstract class Decorator : Tracker.Miner, GLib.Initable {
+               [CCode (has_construct_function = false)]
+               protected Decorator ();
+               public void delete_id (int id);
+               public static GLib.Quark error_quark ();
+               [CCode (array_length = false, array_null_terminated = true)]
+               public unowned string[] get_class_names ();
+               public unowned string get_data_source ();
+               public uint get_n_items ();
+               public async Tracker.DecoratorInfo next (GLib.Cancellable? cancellable = null) throws 
GLib.Error;
+               public void prepend_id (int id, int class_name_id);
+               public void set_priority_rdf_types (string rdf_types);
+               [CCode (array_length = false, array_null_terminated = true)]
+               [NoAccessorMethod]
+               public string[] class_names { owned get; set; }
+               [NoAccessorMethod]
+               public int commit_batch_size { get; set; }
+               public string data_source { get; construct; }
+               [CCode (array_length = false, array_null_terminated = true)]
+               public string[] priority_rdf_types { set; }
+               public virtual signal void finished ();
+               public virtual signal void items_available ();
+       }
+       [CCode (cheader_filename = "libtracker-miner/tracker-miner.h", type_id = 
"tracker_decorator_fs_get_type ()")]
+       public abstract class DecoratorFS : Tracker.Decorator, GLib.Initable {
+               [CCode (has_construct_function = false)]
+               protected DecoratorFS ();
+               public int prepend_file (GLib.File file);
+       }
+       [CCode (cheader_filename = "libtracker-miner/tracker-miner.h", ref_function = 
"tracker_decorator_info_ref", type_id = "tracker_decorator_info_get_type ()", unref_function = 
"tracker_decorator_info_unref")]
+       [Compact]
+       public class DecoratorInfo {
+               public unowned string get_mimetype ();
+               public Tracker.Sparql.Builder get_sparql ();
+               public unowned GLib.Task get_task ();
+               public unowned string get_url ();
+               public unowned string get_urn ();
+               public Tracker.DecoratorInfo @ref ();
+               public void unref ();
+       }
+       [CCode (cheader_filename = "libtracker-miner/tracker-miner.h", type_id = 
"tracker_indexing_tree_get_type ()")]
+       public class IndexingTree : GLib.Object {
+               [CCode (has_construct_function = false)]
+               public IndexingTree ();
+               public void add (GLib.File directory, Tracker.DirectoryFlags flags);
+               public void add_filter (Tracker.FilterType filter, string glob_string);
+               public void clear_filters (Tracker.FilterType type);
+               public bool file_is_indexable (GLib.File file, GLib.FileType file_type);
+               public bool file_is_root (GLib.File file);
+               public bool file_matches_filter (Tracker.FilterType type, GLib.File file);
+               public Tracker.FilterPolicy get_default_policy (Tracker.FilterType filter);
+               public bool get_filter_hidden ();
+               public unowned GLib.File get_master_root ();
+               public unowned GLib.File get_root (GLib.File file, out Tracker.DirectoryFlags 
directory_flags);
+               public GLib.List<weak GLib.File> list_roots ();
+               public bool parent_is_indexable (GLib.File parent, GLib.List<GLib.File> children);
+               public void remove (GLib.File directory);
+               public void set_default_policy (Tracker.FilterType filter, Tracker.FilterPolicy policy);
+               public void set_filter_hidden (bool filter_hidden);
+               [CCode (has_construct_function = false)]
+               public IndexingTree.with_root (GLib.File root);
+               public bool filter_hidden { get; set; }
+               [NoAccessorMethod]
+               public GLib.File root { owned get; construct; }
+               public virtual signal void directory_added (GLib.File directory);
+               public virtual signal void directory_removed (GLib.File directory);
+               public virtual signal void directory_updated (GLib.File directory);
+       }
+       [CCode (cheader_filename = "libtracker-miner/tracker-miner.h", type_id = "tracker_miner_get_type ()")]
+       public abstract class Miner : GLib.Object, GLib.Initable {
+               [CCode (has_construct_function = false)]
+               protected Miner ();
+               public static GLib.Quark error_quark ();
+               public Tracker.Sparql.Connection get_connection ();
+               public bool is_paused ();
+               public bool is_started ();
+               public void pause ();
+               public bool resume ();
+               public void start ();
+               public void stop ();
+               [NoAccessorMethod]
+               public string name { owned get; construct; }
+               [NoAccessorMethod]
+               public virtual double progress { get; set construct; }
+               [NoAccessorMethod]
+               public int remaining_time { get; set construct; }
+               [NoAccessorMethod]
+               public string status { owned get; set construct; }
+               [HasEmitter]
+               public virtual signal void paused ();
+               public virtual signal void resumed ();
+               public virtual signal void started ();
+               public virtual signal void stopped ();
+       }
+       [CCode (cheader_filename = "libtracker-miner/tracker-miner.h", type_id = "tracker_miner_fs_get_type 
()")]
+       public abstract class MinerFS : Tracker.Miner, GLib.Initable {
+               [CCode (has_construct_function = false)]
+               protected MinerFS ();
+               public void check_file (GLib.File file, int priority, bool check_parents);
+               public static GLib.Quark error_quark ();
+               public void notify_finish (GLib.File file, GLib.Task task, string sparql, GLib.Error error);
+               public unowned Tracker.DataProvider get_data_provider ();
+               public unowned Tracker.IndexingTree get_indexing_tree ();
+               public double get_throttle ();
+               public unowned string? get_urn (GLib.File file);
+               public bool has_items_to_process ();
+               public string query_urn (GLib.File file);
+               public void set_throttle (double throttle);
+               public void writeback_notify (GLib.File file, GLib.Error error);
+               public Tracker.DataProvider data_provider { get; construct; }
+               [NoAccessorMethod]
+               public uint processing_pool_ready_limit { get; set construct; }
+               [NoAccessorMethod]
+               public uint processing_pool_wait_limit { get; set construct; }
+               [NoAccessorMethod]
+               public GLib.File root { owned get; construct; }
+               public double throttle { get; set; }
+               public virtual signal void finished (double elapsed, uint directories_found, uint 
directories_ignored, uint files_found, uint files_ignored);
+               public virtual signal void finished_root (GLib.File root);
+               public virtual signal bool process_file (GLib.File file, Tracker.Sparql.Builder builder, 
GLib.Cancellable? cancellable = null);
+               public virtual signal bool process_file_attributes (GLib.File file, Tracker.Sparql.Builder 
builder, GLib.Cancellable? cancellable = null);
+               public signal bool writeback_file (GLib.File file, [CCode (array_length = false, 
array_null_terminated = true)] string[] rdf_types, GLib.GenericArray<string[]> results, GLib.Cancellable? 
cancellable = null);
+       }
+       [CCode (cheader_filename = "libtracker-miner/tracker-miner.h", type_id = 
"tracker_miner_online_get_type ()")]
+       public abstract class MinerOnline : Tracker.Miner, GLib.Initable {
+               [CCode (has_construct_function = false)]
+               protected MinerOnline ();
+               public Tracker.NetworkType get_network_type ();
+               public Tracker.NetworkType network_type { get; }
+               public virtual signal bool connected (Tracker.NetworkType network);
+               public virtual signal void disconnected ();
+       }
+       [CCode (cheader_filename = "libtracker-miner/tracker-miner.h", type_id = 
"tracker_data_provider_get_type ()")]
+       public interface DataProvider : GLib.Object {
+               public abstract Tracker.Enumerator begin (GLib.File url, string attributes, 
Tracker.DirectoryFlags flags, GLib.Cancellable? cancellable = null) throws GLib.Error;
+               public abstract async Tracker.Enumerator begin_async (GLib.File url, string attributes, 
Tracker.DirectoryFlags flags, int io_priority, GLib.Cancellable? cancellable = null) throws GLib.Error;
+               public abstract bool end (Tracker.Enumerator enumerator, GLib.Cancellable? cancellable = 
null) throws GLib.Error;
+               public abstract async bool end_async (Tracker.Enumerator enumerator, int io_priority, 
GLib.Cancellable? cancellable = null) throws GLib.Error;
+       }
+       [CCode (cheader_filename = "libtracker-miner/tracker-miner.h", type_id = "tracker_enumerator_get_type 
()")]
+       public interface Enumerator : GLib.Object {
+               public abstract void* next (GLib.Cancellable? cancellable = null) throws GLib.Error;
+               public abstract async void* next_async (int io_priority, GLib.Cancellable? cancellable = 
null) throws GLib.Error;
+       }
+       [CCode (cheader_filename = "libtracker-miner/tracker-miner.h", cprefix = "TRACKER_DIRECTORY_FLAG_", 
type_id = "tracker_directory_flags_get_type ()")]
+       [Flags]
+       public enum DirectoryFlags {
+               NONE,
+               RECURSE,
+               CHECK_MTIME,
+               MONITOR,
+               IGNORE,
+               PRESERVE,
+               PRIORITY,
+               NO_STAT
+       }
+       [CCode (cheader_filename = "libtracker-miner/tracker-miner.h", cprefix = "TRACKER_FILTER_POLICY_", 
type_id = "tracker_filter_policy_get_type ()")]
+       public enum FilterPolicy {
+               DENY,
+               ACCEPT
+       }
+       [CCode (cheader_filename = "libtracker-miner/tracker-miner.h", cprefix = "TRACKER_FILTER_", type_id = 
"tracker_filter_type_get_type ()")]
+       public enum FilterType {
+               FILE,
+               DIRECTORY,
+               PARENT_DIRECTORY
+       }
+       [CCode (cheader_filename = "libtracker-miner/tracker-miner.h", cprefix = "TRACKER_NETWORK_TYPE_", 
type_id = "tracker_network_type_get_type ()")]
+       public enum NetworkType {
+               NONE,
+               UNKNOWN,
+               GPRS,
+               EDGE,
+               @3G,
+               LAN
+       }
+       [CCode (cheader_filename = "libtracker-miner/tracker-miner.h", cprefix = "TRACKER_DECORATOR_ERROR_")]
+       public errordomain DecoratorError {
+               EMPTY,
+               PAUSED
+       }
+       [CCode (cheader_filename = "libtracker-miner/tracker-miner.h", cprefix = "TRACKER_MINER_ERROR_")]
+       public errordomain MinerError {
+               NAME_MISSING,
+               NAME_UNAVAILABLE,
+               PAUSED,
+               PAUSED_ALREADY,
+               INVALID_COOKIE
+       }
+       [CCode (cheader_filename = "libtracker-miner/tracker-miner.h", cprefix = "TRACKER_MINER_FS_ERROR_")]
+       public errordomain MinerFSError {
+               [CCode (cname = "TRACKER_MINER_FS_ERROR_INIT")]
+               MINER_FS_ERROR_INIT
+       }
+       [CCode (cheader_filename = "libtracker-miner/tracker-miner.h", cname = 
"TRACKER_MINER_DBUS_INTERFACE")]
+       public const string MINER_DBUS_INTERFACE;
+       [CCode (cheader_filename = "libtracker-miner/tracker-miner.h", cname = 
"TRACKER_MINER_DBUS_NAME_PREFIX")]
+       public const string MINER_DBUS_NAME_PREFIX;
+       [CCode (cheader_filename = "libtracker-miner/tracker-miner.h", cname = 
"TRACKER_MINER_DBUS_PATH_PREFIX")]
+       public const string MINER_DBUS_PATH_PREFIX;
+       [CCode (cheader_filename = "libtracker-miner/tracker-miner.h", cname = "TRACKER_MINER_ERROR_DOMAIN")]
+       public const string MINER_ERROR_DOMAIN;
+}
diff --git a/src/libtracker-miner/tracker-miner.xml b/src/libtracker-miner/tracker-miner.xml
new file mode 100644
index 000000000..f12c8a91b
--- /dev/null
+++ b/src/libtracker-miner/tracker-miner.xml
@@ -0,0 +1,56 @@
+<?xml version="1.0" encoding="UTF-8"?>
+
+<node name="/">
+  <interface name="org.freedesktop.Tracker1.Miner">
+    <annotation name="org.freedesktop.DBus.GLib.CSymbol" value="_tracker_miner_dbus"/>
+    <method name="Start">
+      <annotation name="org.freedesktop.DBus.GLib.Async"  value="true"/>
+    </method>
+    <method name="GetStatus">
+      <annotation name="org.freedesktop.DBus.GLib.Async" value="true"/>
+      <arg type="s" name="status" direction="out" />
+    </method>
+    <method name="GetProgress">
+      <annotation name="org.freedesktop.DBus.GLib.Async" value="true"/>
+      <arg type="d" name="progress" direction="out" />
+    </method>
+    <method name="GetRemainingTime">
+      <annotation name="org.freedesktop.DBus.GLib.Async" value="true"/>
+      <arg type="i" name="remaining_time" direction="out" />
+    </method>
+    <method name="GetPauseDetails">
+      <annotation name="org.freedesktop.DBus.GLib.Async" value="true"/>
+      <arg type="as" name="pause_applications" direction="out" />
+      <arg type="as" name="pause_reasons" direction="out" />
+    </method>
+    <method name="Pause">
+      <annotation name="org.freedesktop.DBus.GLib.Async"  value="true"/>
+      <arg type="s" name="application" direction="in" />
+      <arg type="s" name="reason" direction="in" />
+      <arg type="i" name="cookie" direction="out" />
+    </method>
+    <method name="PauseForProcess">
+      <annotation name="org.freedesktop.DBus.GLib.Async"  value="true"/>
+      <arg type="s" name="application" direction="in" />
+      <arg type="s" name="reason" direction="in" />
+      <arg type="i" name="cookie" direction="out" />
+    </method>
+    <method name="Resume">
+      <annotation name="org.freedesktop.DBus.GLib.Async" value="true"/>
+      <arg type="i" name="cookie" direction="in" />
+    </method>
+
+    <!-- Signals -->
+    <signal name="Started" />
+    <signal name="Stopped">
+      <arg type="b" name="interrupted" />
+    </signal>
+    <signal name="Paused" />
+    <signal name="Resumed" />
+    <signal name="Progress">
+      <arg type="s" name="status" />
+      <arg type="d" name="progress" />
+      <arg type="i" name="remaining_time" />
+    </signal>
+  </interface>
+</node>
diff --git a/src/libtracker-miner/tracker-monitor.c b/src/libtracker-miner/tracker-monitor.c
new file mode 100644
index 000000000..4daf16c15
--- /dev/null
+++ b/src/libtracker-miner/tracker-monitor.c
@@ -0,0 +1,1748 @@
+/*
+ * Copyright (C) 2009, Nokia <ivan frade nokia com>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ * Boston, MA  02110-1301, USA.
+ */
+
+#include "config-miners.h"
+
+#include <stdlib.h>
+#include <string.h>
+#include <gio/gio.h>
+
+#if defined (__OpenBSD__) || defined (__FreeBSD__) || defined (__NetBSD__) || defined (__APPLE__)
+#include <sys/types.h>
+#include <sys/time.h>
+#include <sys/resource.h>
+#define TRACKER_MONITOR_KQUEUE
+#endif
+
+#include "tracker-monitor.h"
+
+#define TRACKER_MONITOR_GET_PRIVATE(obj) (G_TYPE_INSTANCE_GET_PRIVATE ((obj), TRACKER_TYPE_MONITOR, 
TrackerMonitorPrivate))
+
+/* If this is enabled, we are assuming that GIO is fixed so that after a CREATED
+ * event emitted after a move operation from a non-monitored path to a monitored
+ * one, a CHANGES_DONE_HINT is also emitted. This allows us to NOT send the
+ * CREATED event to upper layers until the file has been fully closed.
+ *
+ * See https://bugzilla.gnome.org/show_bug.cgi?id=640077 and
+ *     https://projects.maemo.org/bugzilla/show_bug.cgi?id=219982
+ * When the upstream bugfix is integrated in GLib/GIO, we will be able to
+ * depend on an specific glib version. Until then, disable this and only
+ * enable in distros which have that patched glib.
+ **/
+#ifdef GIO_ALWAYS_SENDS_CHANGES_DONE_HINT_AFTER_CREATED
+#warning Assuming GLib/GIO always sends CHANGES_DONE_HINT after CREATED...
+#endif /* GIO_ALWAYS_SENDS_CHANGES_DONE_HINT_AFTER_CREATED */
+
+/* The life time of an item in the cache */
+#define CACHE_LIFETIME_SECONDS 1
+
+/* When we receive IO monitor events, we pause sending information to
+ * the indexer for a few seconds before continuing. We have to receive
+ * NO events for at least a few seconds before unpausing.
+ */
+#define PAUSE_ON_IO_SECONDS    5
+
+/* If this is defined, we pause the indexer when we get events. If it
+ * is not, we don't do any pausing.
+ */
+#undef  PAUSE_ON_IO
+
+struct TrackerMonitorPrivate {
+       GHashTable    *monitors;
+
+       gboolean       enabled;
+
+       GType          monitor_backend;
+
+       guint          monitor_limit;
+       gboolean       monitor_limit_warned;
+       guint          monitors_ignored;
+
+       /* For FAM, the _CHANGES_DONE event is not signalled, so we
+        * have to just use the _CHANGED event instead.
+        */
+       gboolean       use_changed_event;
+
+#ifdef PAUSE_ON_IO
+       /* Timeout id for pausing when we get IO */
+       guint          unpause_timeout_id;
+#endif /* PAUSE_ON_IO */
+
+       GHashTable    *pre_update;
+       GHashTable    *pre_delete;
+       guint          event_pairs_timeout_id;
+
+       TrackerIndexingTree *tree;
+};
+
+typedef struct {
+       GFile    *file;
+       gchar    *file_uri;
+       GFile    *other_file;
+       gchar    *other_file_uri;
+       gboolean  is_directory;
+       GTimeVal  start_time;
+       guint32   event_type;
+       gboolean  expirable;
+} EventData;
+
+enum {
+       ITEM_CREATED,
+       ITEM_UPDATED,
+       ITEM_ATTRIBUTE_UPDATED,
+       ITEM_DELETED,
+       ITEM_MOVED,
+       LAST_SIGNAL
+};
+
+enum {
+       PROP_0,
+       PROP_ENABLED
+};
+
+static void           tracker_monitor_finalize     (GObject        *object);
+static void           tracker_monitor_set_property (GObject        *object,
+                                                    guint           prop_id,
+                                                    const GValue   *value,
+                                                    GParamSpec     *pspec);
+static void           tracker_monitor_get_property (GObject        *object,
+                                                    guint           prop_id,
+                                                    GValue         *value,
+                                                    GParamSpec     *pspec);
+static guint          get_kqueue_limit             (void);
+static guint          get_inotify_limit            (void);
+static GFileMonitor * directory_monitor_new        (TrackerMonitor *monitor,
+                                                    GFile          *file);
+static void           directory_monitor_cancel     (GFileMonitor     *dir_monitor);
+
+
+static void           event_data_free              (gpointer        data);
+static void           emit_signal_for_event        (TrackerMonitor *monitor,
+                                                    EventData      *event_data);
+static gboolean       monitor_cancel_recursively   (TrackerMonitor *monitor,
+                                                    GFile          *file);
+
+static guint signals[LAST_SIGNAL] = { 0, };
+
+G_DEFINE_TYPE(TrackerMonitor, tracker_monitor, G_TYPE_OBJECT)
+
+static void
+tracker_monitor_class_init (TrackerMonitorClass *klass)
+{
+       GObjectClass *object_class;
+
+       object_class = G_OBJECT_CLASS (klass);
+
+       object_class->finalize = tracker_monitor_finalize;
+       object_class->set_property = tracker_monitor_set_property;
+       object_class->get_property = tracker_monitor_get_property;
+
+       signals[ITEM_CREATED] =
+               g_signal_new ("item-created",
+                             G_TYPE_FROM_CLASS (klass),
+                             G_SIGNAL_RUN_LAST,
+                             0,
+                             NULL, NULL,
+                             NULL,
+                             G_TYPE_NONE,
+                             2,
+                             G_TYPE_OBJECT,
+                             G_TYPE_BOOLEAN);
+       signals[ITEM_UPDATED] =
+               g_signal_new ("item-updated",
+                             G_TYPE_FROM_CLASS (klass),
+                             G_SIGNAL_RUN_LAST,
+                             0,
+                             NULL, NULL,
+                             NULL,
+                             G_TYPE_NONE,
+                             2,
+                             G_TYPE_OBJECT,
+                             G_TYPE_BOOLEAN);
+       signals[ITEM_ATTRIBUTE_UPDATED] =
+               g_signal_new ("item-attribute-updated",
+                             G_TYPE_FROM_CLASS (klass),
+                             G_SIGNAL_RUN_LAST,
+                             0,
+                             NULL, NULL,
+                             NULL,
+                             G_TYPE_NONE,
+                             2,
+                             G_TYPE_OBJECT,
+                             G_TYPE_BOOLEAN);
+       signals[ITEM_DELETED] =
+               g_signal_new ("item-deleted",
+                             G_TYPE_FROM_CLASS (klass),
+                             G_SIGNAL_RUN_LAST,
+                             0,
+                             NULL, NULL,
+                             NULL,
+                             G_TYPE_NONE,
+                             2,
+                             G_TYPE_OBJECT,
+                             G_TYPE_BOOLEAN);
+       signals[ITEM_MOVED] =
+               g_signal_new ("item-moved",
+                             G_TYPE_FROM_CLASS (klass),
+                             G_SIGNAL_RUN_LAST,
+                             0,
+                             NULL, NULL,
+                             NULL,
+                             G_TYPE_NONE,
+                             4,
+                             G_TYPE_OBJECT,
+                             G_TYPE_OBJECT,
+                             G_TYPE_BOOLEAN,
+                             G_TYPE_BOOLEAN);
+
+       g_object_class_install_property (object_class,
+                                        PROP_ENABLED,
+                                        g_param_spec_boolean ("enabled",
+                                                              "Enabled",
+                                                              "Enabled",
+                                                              TRUE,
+                                                              G_PARAM_READWRITE | G_PARAM_CONSTRUCT));
+
+       g_type_class_add_private (object_class, sizeof (TrackerMonitorPrivate));
+}
+
+static void
+tracker_monitor_init (TrackerMonitor *object)
+{
+       TrackerMonitorPrivate *priv;
+       GFile                 *file;
+       GFileMonitor          *monitor;
+       const gchar           *name;
+       GError                *error = NULL;
+
+       object->priv = TRACKER_MONITOR_GET_PRIVATE (object);
+
+       priv = object->priv;
+
+       /* By default we enable monitoring */
+       priv->enabled = TRUE;
+
+       /* Create monitors table for this module */
+       priv->monitors =
+               g_hash_table_new_full (g_file_hash,
+                                      (GEqualFunc) g_file_equal,
+                                      (GDestroyNotify) g_object_unref,
+                                      (GDestroyNotify) directory_monitor_cancel);
+
+       priv->pre_update =
+               g_hash_table_new_full (g_file_hash,
+                                      (GEqualFunc) g_file_equal,
+                                      (GDestroyNotify) g_object_unref,
+                                      event_data_free);
+       priv->pre_delete =
+               g_hash_table_new_full (g_file_hash,
+                                      (GEqualFunc) g_file_equal,
+                                      (GDestroyNotify) g_object_unref,
+                                      event_data_free);
+
+       /* For the first monitor we get the type and find out if we
+        * are using inotify, FAM, polling, etc.
+        */
+       file = g_file_new_for_path (g_get_home_dir ());
+       monitor = g_file_monitor_directory (file,
+                                           G_FILE_MONITOR_WATCH_MOUNTS,
+                                           NULL,
+                                           &error);
+
+       if (error) {
+               g_critical ("Could not create sample directory monitor: %s", error->message);
+               g_error_free (error);
+
+               /* Guessing limit... */
+               priv->monitor_limit = 100;
+       } else {
+               priv->monitor_backend = G_OBJECT_TYPE (monitor);
+
+               /* We use the name because the type itself is actually
+                * private and not available publically. Note this is
+                * subject to change, but unlikely of course.
+                */
+               name = g_type_name (priv->monitor_backend);
+
+               /* Set limits based on backend... */
+               if (strcmp (name, "GInotifyDirectoryMonitor") == 0 ||
+                   strcmp (name, "GInotifyFileMonitor") == 0) {
+                       /* Using inotify */
+                       g_message ("Monitor backend is Inotify");
+
+                       /* Setting limit based on kernel
+                        * settings in /proc...
+                        */
+                       priv->monitor_limit = get_inotify_limit ();
+
+                       /* We don't use 100% of the monitors, we allow other
+                        * applications to have at least 500 or so to use
+                        * between them selves. This only
+                        * applies to inotify because it is a
+                        * user shared resource.
+                        */
+                       priv->monitor_limit -= 500;
+
+                       /* Make sure we don't end up with a
+                        * negative maximum.
+                        */
+                       priv->monitor_limit = MAX (priv->monitor_limit, 0);
+               }
+               else if (strcmp (name, "GKqueueDirectoryMonitor") == 0 ||
+                        strcmp (name, "GKqueueFileMonitor") == 0) {
+                       /* Using kqueue(2) */
+                       g_message ("Monitor backend is kqueue");
+
+                       priv->monitor_limit = get_kqueue_limit ();
+               }
+               else if (strcmp (name, "GFamDirectoryMonitor") == 0) {
+                       /* Using Fam */
+                       g_message ("Monitor backend is Fam");
+
+                       /* Setting limit to an arbitary limit
+                        * based on testing
+                        */
+                       priv->monitor_limit = 400;
+                       priv->use_changed_event = TRUE;
+               }
+               else if (strcmp (name, "GFenDirectoryMonitor") == 0) {
+                       /* Using Fen, what is this? */
+                       g_message ("Monitor backend is Fen");
+
+                       /* Guessing limit... */
+                       priv->monitor_limit = 8192;
+               }
+               else if (strcmp (name, "GWin32DirectoryMonitor") == 0) {
+                       /* Using Windows */
+                       g_message ("Monitor backend is Windows");
+
+                       /* Guessing limit... */
+                       priv->monitor_limit = 8192;
+               }
+               else {
+                       /* Unknown */
+                       g_warning ("Monitor backend:'%s' is unknown, we have no limits "
+                                  "in place because we don't know what we are dealing with!",
+                                  name);
+
+                       /* Guessing limit... */
+                       priv->monitor_limit = 100;
+               }
+
+               g_file_monitor_cancel (monitor);
+               g_object_unref (monitor);
+       }
+
+       g_object_unref (file);
+       g_message ("Monitor limit is %d", priv->monitor_limit);
+}
+
+static void
+tracker_monitor_finalize (GObject *object)
+{
+       TrackerMonitorPrivate *priv;
+
+       priv = TRACKER_MONITOR_GET_PRIVATE (object);
+
+#ifdef PAUSE_ON_IO
+       if (priv->unpause_timeout_id) {
+               g_source_remove (priv->unpause_timeout_id);
+       }
+#endif /* PAUSE_ON_IO */
+
+       if (priv->event_pairs_timeout_id) {
+               g_source_remove (priv->event_pairs_timeout_id);
+       }
+
+       g_hash_table_unref (priv->pre_update);
+       g_hash_table_unref (priv->pre_delete);
+       g_hash_table_unref (priv->monitors);
+
+       G_OBJECT_CLASS (tracker_monitor_parent_class)->finalize (object);
+}
+
+static void
+tracker_monitor_set_property (GObject      *object,
+                              guint         prop_id,
+                              const GValue *value,
+                              GParamSpec   *pspec)
+{
+       switch (prop_id) {
+       case PROP_ENABLED:
+               tracker_monitor_set_enabled (TRACKER_MONITOR (object),
+                                            g_value_get_boolean (value));
+               break;
+
+       default:
+               G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+       }
+}
+
+static void
+tracker_monitor_get_property (GObject      *object,
+                              guint         prop_id,
+                              GValue       *value,
+                              GParamSpec   *pspec)
+{
+       TrackerMonitorPrivate *priv;
+
+       priv = TRACKER_MONITOR_GET_PRIVATE (object);
+
+       switch (prop_id) {
+       case PROP_ENABLED:
+               g_value_set_boolean (value, priv->enabled);
+               break;
+
+       default:
+               G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+       }
+}
+
+static guint
+get_kqueue_limit (void)
+{
+       guint limit = 400;
+
+#ifdef TRACKER_MONITOR_KQUEUE
+       struct rlimit rl;
+       if (getrlimit (RLIMIT_NOFILE, &rl) == 0) {
+               rl.rlim_cur = rl.rlim_max;
+       } else {
+               return limit;
+       }
+
+       if (setrlimit(RLIMIT_NOFILE, &rl) == 0)
+               limit = (rl.rlim_cur * 90) / 100;
+#endif /* TRACKER_MONITOR_KQUEUE */
+
+       return limit;
+}
+
+static guint
+get_inotify_limit (void)
+{
+       GError      *error = NULL;
+       const gchar *filename;
+       gchar       *contents = NULL;
+       guint        limit;
+
+       filename = "/proc/sys/fs/inotify/max_user_watches";
+
+       if (!g_file_get_contents (filename,
+                                 &contents,
+                                 NULL,
+                                 &error)) {
+               g_warning ("Couldn't get INotify monitor limit from:'%s', %s",
+                          filename,
+                          error ? error->message : "no error given");
+               g_clear_error (&error);
+
+               /* Setting limit to an arbitary limit */
+               limit = 8192;
+       } else {
+               limit = atoi (contents);
+               g_free (contents);
+       }
+
+       return limit;
+}
+
+#ifdef PAUSE_ON_IO
+
+static gboolean
+unpause_cb (gpointer data)
+{
+       TrackerMonitor *monitor;
+
+       monitor = data;
+
+       g_message ("Resuming indexing now we have stopped "
+                  "receiving monitor events for %d seconds",
+                  PAUSE_ON_IO_SECONDS);
+
+       monitor->priv->unpause_timeout_id = 0;
+       tracker_status_set_is_paused_for_io (FALSE);
+
+       return FALSE;
+}
+
+#endif /* PAUSE_ON_IO */
+
+static gboolean
+check_is_directory (TrackerMonitor *monitor,
+                    GFile          *file)
+{
+       GFileType file_type;
+
+       file_type = g_file_query_file_type (file, G_FILE_QUERY_INFO_NONE, NULL);
+
+       if (file_type == G_FILE_TYPE_DIRECTORY)
+               return TRUE;
+
+       if (file_type == G_FILE_TYPE_UNKNOWN) {
+               /* Whatever it was, it's gone. Check the monitors
+                * hashtable to know whether it was a directory
+                * we knew about
+                */
+               if (g_hash_table_lookup (monitor->priv->monitors, file) != NULL)
+                       return TRUE;
+       }
+
+       return FALSE;
+}
+
+static EventData *
+event_data_new (GFile    *file,
+                GFile    *other_file,
+                gboolean  is_directory,
+                guint32   event_type)
+{
+       EventData *event;
+       GTimeVal now;
+
+       event = g_slice_new0 (EventData);
+       g_get_current_time (&now);
+
+       event->file = g_object_ref (file);
+       event->file_uri = g_file_get_uri (file);
+       if (other_file) {
+               event->other_file = g_object_ref (other_file);
+               event->other_file_uri = g_file_get_uri (other_file);
+       } else {
+               event->other_file = NULL;
+               event->other_file_uri = NULL;
+       }
+       event->is_directory = is_directory;
+       event->start_time = now;
+       event->event_type = event_type;
+       /* Always expirable when created */
+       event->expirable = TRUE;
+
+       return event;
+}
+
+static void
+event_data_free (gpointer data)
+{
+       EventData *event;
+
+       event = data;
+
+       g_object_unref (event->file);
+       g_free (event->file_uri);
+       if (event->other_file) {
+               g_object_unref (event->other_file);
+               g_free (event->other_file_uri);
+       }
+       g_slice_free (EventData, data);
+}
+
+gboolean
+tracker_monitor_move (TrackerMonitor *monitor,
+                      GFile          *old_file,
+                      GFile          *new_file)
+{
+       GHashTableIter iter;
+       GHashTable *new_monitors;
+       gchar *old_prefix;
+       gpointer iter_file, iter_file_monitor;
+       guint items_moved = 0;
+
+       /* So this is tricky. What we have to do is:
+        *
+        * 1) Add all monitors for the new_file directory hierarchy
+        * 2) Then remove the monitors for old_file
+        *
+        * This order is necessary because inotify can reuse watch
+        * descriptors, and libinotify will remove handles
+        * asynchronously on IN_IGNORE, so the opposite sequence
+        * may possibly remove valid, just added, monitors.
+        */
+       new_monitors = g_hash_table_new_full (g_file_hash,
+                                             (GEqualFunc) g_file_equal,
+                                             (GDestroyNotify) g_object_unref,
+                                             NULL);
+       old_prefix = g_file_get_path (old_file);
+
+       /* Find out which subdirectories should have a file monitor added */
+       g_hash_table_iter_init (&iter, monitor->priv->monitors);
+       while (g_hash_table_iter_next (&iter, &iter_file, &iter_file_monitor)) {
+               GFile *f;
+               gchar *old_path, *new_path;
+               gchar *new_prefix;
+               gchar *p;
+
+               if (!g_file_has_prefix (iter_file, old_file) &&
+                   !g_file_equal (iter_file, old_file)) {
+                       continue;
+               }
+
+               old_path = g_file_get_path (iter_file);
+               p = strstr (old_path, old_prefix);
+
+               if (!p || strcmp (p, old_prefix) == 0) {
+                       g_free (old_path);
+                       continue;
+               }
+
+               /* Move to end of prefix */
+               p += strlen (old_prefix) + 1;
+
+               /* Check this is not the end of the string */
+               if (*p == '\0') {
+                       g_free (old_path);
+                       continue;
+               }
+
+               new_prefix = g_file_get_path (new_file);
+               new_path = g_build_path (G_DIR_SEPARATOR_S, new_prefix, p, NULL);
+               g_free (new_prefix);
+
+               f = g_file_new_for_path (new_path);
+               g_free (new_path);
+
+               if (!g_hash_table_lookup (new_monitors, f)) {
+                       g_hash_table_insert (new_monitors, f, GINT_TO_POINTER (1));
+               } else {
+                       g_object_unref (f);
+               }
+
+               g_free (old_path);
+               items_moved++;
+       }
+
+       /* Add a new monitor for the top level directory */
+       tracker_monitor_add (monitor, new_file);
+
+       /* Add a new monitor for all subdirectories */
+       g_hash_table_iter_init (&iter, new_monitors);
+       while (g_hash_table_iter_next (&iter, &iter_file, NULL)) {
+               tracker_monitor_add (monitor, iter_file);
+               g_hash_table_iter_remove (&iter);
+       }
+
+       /* Remove the monitor for the old top level directory hierarchy */
+       tracker_monitor_remove_recursively (monitor, old_file);
+
+       g_hash_table_unref (new_monitors);
+       g_free (old_prefix);
+
+       return items_moved > 0;
+}
+
+static const gchar *
+monitor_event_to_string (GFileMonitorEvent event_type)
+{
+       switch (event_type) {
+       case G_FILE_MONITOR_EVENT_CHANGED:
+               return "G_FILE_MONITOR_EVENT_CHANGED";
+       case G_FILE_MONITOR_EVENT_CHANGES_DONE_HINT:
+               return "G_FILE_MONITOR_EVENT_CHANGES_DONE_HINT";
+       case G_FILE_MONITOR_EVENT_DELETED:
+               return "G_FILE_MONITOR_EVENT_DELETED";
+       case G_FILE_MONITOR_EVENT_CREATED:
+               return "G_FILE_MONITOR_EVENT_CREATED";
+       case G_FILE_MONITOR_EVENT_ATTRIBUTE_CHANGED:
+               return "G_FILE_MONITOR_EVENT_ATTRIBUTE_CHANGED";
+       case G_FILE_MONITOR_EVENT_PRE_UNMOUNT:
+               return "G_FILE_MONITOR_EVENT_PRE_UNMOUNT";
+       case G_FILE_MONITOR_EVENT_UNMOUNTED:
+               return "G_FILE_MONITOR_EVENT_UNMOUNTED";
+       case G_FILE_MONITOR_EVENT_MOVED:
+               return "G_FILE_MONITOR_EVENT_MOVED";
+       case G_FILE_MONITOR_EVENT_RENAMED:
+       case G_FILE_MONITOR_EVENT_MOVED_IN:
+       case G_FILE_MONITOR_EVENT_MOVED_OUT:
+               break;
+       }
+
+       return "unknown";
+}
+
+static void
+emit_signal_for_event (TrackerMonitor *monitor,
+                       EventData      *event_data)
+{
+       switch (event_data->event_type) {
+       case G_FILE_MONITOR_EVENT_CREATED:
+               g_debug ("Emitting ITEM_CREATED for (%s) '%s'",
+                        event_data->is_directory ? "DIRECTORY" : "FILE",
+                        event_data->file_uri);
+               g_signal_emit (monitor,
+                              signals[ITEM_CREATED], 0,
+                              event_data->file,
+                              event_data->is_directory);
+               /* Note that if the created item is a Directory, we must NOT
+                * add automatically a new monitor. */
+               break;
+
+       case G_FILE_MONITOR_EVENT_CHANGED:
+       case G_FILE_MONITOR_EVENT_CHANGES_DONE_HINT:
+               g_debug ("Emitting ITEM_UPDATED for (%s) '%s'",
+                        event_data->is_directory ? "DIRECTORY" : "FILE",
+                        event_data->file_uri);
+               g_signal_emit (monitor,
+                              signals[ITEM_UPDATED], 0,
+                              event_data->file,
+                              event_data->is_directory);
+               break;
+
+       case G_FILE_MONITOR_EVENT_ATTRIBUTE_CHANGED:
+               g_debug ("Emitting ITEM_ATTRIBUTE_UPDATED for (%s) '%s'",
+                        event_data->is_directory ? "DIRECTORY" : "FILE",
+                        event_data->file_uri);
+               g_signal_emit (monitor,
+                              signals[ITEM_ATTRIBUTE_UPDATED], 0,
+                              event_data->file,
+                              event_data->is_directory);
+               break;
+
+       case G_FILE_MONITOR_EVENT_DELETED:
+               g_debug ("Emitting ITEM_DELETED for (%s) '%s'",
+                        event_data->is_directory ? "DIRECTORY" : "FILE",
+                        event_data->file_uri);
+               g_signal_emit (monitor,
+                              signals[ITEM_DELETED], 0,
+                              event_data->file,
+                              event_data->is_directory);
+               break;
+
+       case G_FILE_MONITOR_EVENT_MOVED:
+               /* Note that in any case we should be moving the monitors
+                * here to the new place, as the new place may be ignored.
+                * We should leave this to the upper layers. But one thing
+                * we must do is actually CANCEL all these monitors. */
+               if (event_data->is_directory) {
+                       monitor_cancel_recursively (monitor,
+                                                   event_data->file);
+               }
+
+               /* Try to avoid firing up events for ignored files */
+               if (monitor->priv->tree &&
+                   !tracker_indexing_tree_file_is_indexable (monitor->priv->tree,
+                                                             event_data->file,
+                                                             (event_data->is_directory ?
+                                                              G_FILE_TYPE_DIRECTORY :
+                                                              G_FILE_TYPE_REGULAR))) {
+                       g_debug ("Emitting ITEM_UPDATED for %s (%s) from "
+                                "a move event, source is not indexable",
+                                event_data->other_file_uri,
+                                event_data->is_directory ? "DIRECTORY" : "FILE");
+                       g_signal_emit (monitor,
+                                      signals[ITEM_UPDATED], 0,
+                                      event_data->other_file,
+                                      event_data->is_directory);
+               } else if (monitor->priv->tree &&
+                          !tracker_indexing_tree_file_is_indexable (monitor->priv->tree,
+                                                                    event_data->other_file,
+                                                                    (event_data->is_directory ?
+                                                                     G_FILE_TYPE_DIRECTORY :
+                                                                     G_FILE_TYPE_REGULAR))) {
+                       g_debug ("Emitting ITEM_DELETED for %s (%s) from "
+                                "a move event, destination is not indexable",
+                                event_data->file_uri,
+                                event_data->is_directory ? "DIRECTORY" : "FILE");
+                       g_signal_emit (monitor,
+                                      signals[ITEM_DELETED], 0,
+                                      event_data->file,
+                                      event_data->is_directory);
+               } else  {
+                       g_debug ("Emitting ITEM_MOVED for (%s) '%s'->'%s'",
+                                event_data->is_directory ? "DIRECTORY" : "FILE",
+                                event_data->file_uri,
+                                event_data->other_file_uri);
+                       g_signal_emit (monitor,
+                                      signals[ITEM_MOVED], 0,
+                                      event_data->file,
+                                      event_data->other_file,
+                                      event_data->is_directory,
+                                      TRUE);
+               }
+
+               break;
+
+       case G_FILE_MONITOR_EVENT_PRE_UNMOUNT:
+       case G_FILE_MONITOR_EVENT_UNMOUNTED:
+               /* Do nothing */
+               break;
+       }
+}
+
+static void
+event_pairs_process_in_ht (TrackerMonitor *monitor,
+                           GHashTable     *ht,
+                           GTimeVal       *now)
+{
+       GHashTableIter iter;
+       gpointer key, value;
+       GList *expired_events = NULL;
+       GList *l;
+
+       /* Start iterating the HT of events, and see if any of them was expired.
+        * If so, STEAL the item from the HT, add it in an auxiliary list, and
+        * once the whole HT has been iterated, emit the signals for the stolen
+        * items. If the signal is emitted WHILE iterating the HT, we may end up
+        * with some upper layer action modifying the HT we are iterating, and
+        * that is not good. */
+       g_hash_table_iter_init (&iter, ht);
+       while (g_hash_table_iter_next (&iter, &key, &value)) {
+               EventData *event_data = value;
+               glong seconds;
+
+               /* If event is not yet expirable, keep it */
+               if (!event_data->expirable)
+                       continue;
+
+               /* If event is expirable, but didn't expire yet, keep it */
+               seconds = now->tv_sec - event_data->start_time.tv_sec;
+               if (seconds < 2)
+                       continue;
+
+               g_debug ("Event '%s' for URI '%s' has timed out (%ld seconds have elapsed)",
+                        monitor_event_to_string (event_data->event_type),
+                        event_data->file_uri,
+                        seconds);
+               /* STEAL the item from the HT, so that disposal methods
+                * for key and value are not called. */
+               g_hash_table_iter_steal (&iter);
+               /* Unref the key, as no longer needed */
+               g_object_unref (key);
+               /* Add the expired event to our temp list */
+               expired_events = g_list_append (expired_events, event_data);
+       }
+
+       for (l = expired_events; l; l = g_list_next (l)) {
+               /* Emit signal for the expired event */
+               emit_signal_for_event (monitor, l->data);
+               /* And dispose the event data */
+               event_data_free (l->data);
+       }
+       g_list_free (expired_events);
+}
+
+static gboolean
+event_pairs_timeout_cb (gpointer user_data)
+{
+       TrackerMonitor *monitor;
+       GTimeVal now;
+
+       monitor = user_data;
+       g_get_current_time (&now);
+
+       /* Process PRE-UPDATE hash table */
+       event_pairs_process_in_ht (monitor, monitor->priv->pre_update, &now);
+
+       /* Process PRE-DELETE hash table */
+       event_pairs_process_in_ht (monitor, monitor->priv->pre_delete, &now);
+
+       if (g_hash_table_size (monitor->priv->pre_update) > 0 ||
+           g_hash_table_size (monitor->priv->pre_delete) > 0) {
+               return TRUE;
+       }
+
+       g_debug ("No more events to pair");
+       monitor->priv->event_pairs_timeout_id = 0;
+       return FALSE;
+}
+
+static void
+monitor_event_file_created (TrackerMonitor *monitor,
+                            GFile          *file)
+{
+       EventData *new_event;
+
+       /*  - When a G_FILE_MONITOR_EVENT_CREATED(A) is received,
+        *    -- Add it to the cache, replacing any previous element
+        *       (shouldn't be any)
+        */
+       new_event = event_data_new (file,
+                                   NULL,
+                                   FALSE,
+                                   G_FILE_MONITOR_EVENT_CREATED);
+
+       /* If we know that there always must be a CHANGES_DONE_HINT
+        * emitted after a CREATED on a file event, set the event
+        * as non-expirable. Will be set expirable when CHANGES_DONE_HINT
+        * is actually received */
+#ifdef GIO_ALWAYS_SENDS_CHANGES_DONE_HINT_AFTER_CREATED
+       new_event->expirable = FALSE;
+#endif /* GIO_ALWAYS_SENDS_CHANGES_DONE_HINT_AFTER_CREATED */
+
+       g_hash_table_replace (monitor->priv->pre_update,
+                             g_object_ref (file),
+                             new_event);
+}
+
+static void
+monitor_event_file_changed (TrackerMonitor *monitor,
+                            GFile          *file)
+{
+       EventData *previous_update_event_data;
+
+       /* Get previous event data, if any */
+       previous_update_event_data = g_hash_table_lookup (monitor->priv->pre_update, file);
+
+       /* If use_changed_event, treat as an ATTRIBUTE_CHANGED. Otherwise,
+        * assume there will be a CHANGES_DONE_HINT afterwards... */
+       if (!monitor->priv->use_changed_event) {
+               /* Process the CHANGED event knowing that there will be a CHANGES_DONE_HINT */
+               if (previous_update_event_data) {
+                       if (previous_update_event_data->event_type == G_FILE_MONITOR_EVENT_ATTRIBUTE_CHANGED) 
{
+                               /* If there is a previous ATTRIBUTE_CHANGED still not notified,
+                                * remove it, as we know there will be a CHANGES_DONE_HINT afterwards
+                                */
+                               g_hash_table_remove (monitor->priv->pre_update, file);
+                       } else if (previous_update_event_data->event_type == G_FILE_MONITOR_EVENT_CREATED) {
+#ifdef GIO_ALWAYS_SENDS_CHANGES_DONE_HINT_AFTER_CREATED
+                               /* If we got a CHANGED event before the CREATED was expired,
+                                * set the CREATED as not expirable, as we expect a CHANGES_DONE_HINT
+                                * afterwards. */
+                               previous_update_event_data->expirable = FALSE;
+#endif /* GIO_ALWAYS_SENDS_CHANGES_DONE_HINT_AFTER_CREATED */
+                       }
+               }
+               return;
+       }
+
+       /* For FAM-based monitors, there won't be a CHANGES_DONE_HINT, so use the CHANGED
+        * events. */
+
+       if (!previous_update_event_data) {
+               /* If no previous one, insert it */
+               g_hash_table_insert (monitor->priv->pre_update,
+                                    g_object_ref (file),
+                                    event_data_new (file,
+                                                    NULL,
+                                                    FALSE,
+                                                    G_FILE_MONITOR_EVENT_CHANGED));
+               return;
+       }
+
+       if (previous_update_event_data->event_type == G_FILE_MONITOR_EVENT_ATTRIBUTE_CHANGED) {
+               /* Replace the previous ATTRIBUTE_CHANGED event with a CHANGED one. */
+               g_hash_table_replace (monitor->priv->pre_update,
+                                     g_object_ref (file),
+                                     event_data_new (file,
+                                                     NULL,
+                                                     FALSE,
+                                                     G_FILE_MONITOR_EVENT_CHANGED));
+       } else {
+               /* Update the start_time of the previous one */
+               g_get_current_time (&(previous_update_event_data->start_time));
+       }
+}
+
+static void
+monitor_event_file_attribute_changed (TrackerMonitor *monitor,
+                                      GFile          *file)
+{
+       EventData *previous_update_event_data;
+
+       /* Get previous event data, if any */
+       previous_update_event_data = g_hash_table_lookup (monitor->priv->pre_update, file);
+       if (!previous_update_event_data) {
+               /* If no previous one, insert it */
+               g_hash_table_insert (monitor->priv->pre_update,
+                                    g_object_ref (file),
+                                    event_data_new (file,
+                                                    NULL,
+                                                    FALSE,
+                                                    G_FILE_MONITOR_EVENT_ATTRIBUTE_CHANGED));
+               return;
+       }
+
+       if (previous_update_event_data->event_type == G_FILE_MONITOR_EVENT_ATTRIBUTE_CHANGED) {
+               /* Update the start_time of the previous one, if it is an ATTRIBUTE_CHANGED
+                * event. */
+               g_get_current_time (&(previous_update_event_data->start_time));
+
+               /* No need to update event time in CREATED, as these events
+                * only expire when there is a CHANGES_DONE_HINT.
+                */
+       }
+}
+
+static void
+monitor_event_file_changes_done (TrackerMonitor *monitor,
+                                 GFile          *file)
+{
+       EventData *previous_update_event_data;
+
+       /* Get previous event data, if any */
+       previous_update_event_data = g_hash_table_lookup (monitor->priv->pre_update, file);
+       if (!previous_update_event_data) {
+               /* Insert new update item in cache */
+               g_hash_table_insert (monitor->priv->pre_update,
+                                    g_object_ref (file),
+                                    event_data_new (file,
+                                                    NULL,
+                                                    FALSE,
+                                                    G_FILE_MONITOR_EVENT_CHANGES_DONE_HINT));
+               return;
+       }
+
+       /* Refresh event timer, and make sure the event is now set as expirable */
+       g_get_current_time (&(previous_update_event_data->start_time));
+       previous_update_event_data->expirable = TRUE;
+}
+
+static void
+monitor_event_file_deleted (TrackerMonitor *monitor,
+                            GFile          *file)
+{
+       EventData *new_event;
+       EventData *previous_update_event_data;
+
+       /* Get previous event data, if any */
+       previous_update_event_data = g_hash_table_lookup (monitor->priv->pre_update, file);
+
+       /* Remove any previous pending event (if blacklisting enabled, there may be some) */
+       if (previous_update_event_data) {
+               GFileMonitorEvent previous_update_event_type;
+
+               previous_update_event_type = previous_update_event_data->event_type;
+               g_hash_table_remove (monitor->priv->pre_update, file);
+
+               if (previous_update_event_type == G_FILE_MONITOR_EVENT_CREATED) {
+                       /* Oh, oh, oh, we got a previous CREATED event waiting in the event
+                        * cache... so we cancel it with the DELETED and don't notify anything */
+                       return;
+               }
+               /* else, keep on notifying the event */
+       }
+
+       new_event = event_data_new (file,
+                                   NULL,
+                                   FALSE,
+                                   G_FILE_MONITOR_EVENT_DELETED);
+       emit_signal_for_event (monitor, new_event);
+       event_data_free (new_event);
+}
+
+static void
+monitor_event_file_moved (TrackerMonitor *monitor,
+                          GFile          *src_file,
+                          GFile          *dst_file)
+{
+       EventData *new_event;
+       EventData *previous_update_event_data;
+
+       /* Get previous event data, if any */
+       previous_update_event_data = g_hash_table_lookup (monitor->priv->pre_update, src_file);
+
+       /* Some event-merging that can also be enabled if doing blacklisting, as we are
+        * queueing the CHANGES_DONE_HINT in the cache :
+        *   (a) CREATED(A)      + MOVED(A->B)  = CREATED (B)
+        *   (b) UPDATED(A)      + MOVED(A->B)  = MOVED(A->B) + UPDATED(B)
+        *   (c) ATTR_UPDATED(A) + MOVED(A->B)  = MOVED(A->B) + UPDATED(B)
+        * Notes:
+        *  - In case (a), the CREATED event is queued in the cache.
+        *  - In case (a), note that B may already exist before, so instead of a CREATED
+        *    we should be issuing an UPDATED instead... we don't do it as at the end we
+        *    don't really mind, and we save a call to g_file_query_exists().
+        *  - In case (b), the MOVED event is emitted right away, while the UPDATED
+        *    one is queued in the cache.
+        *  - In case (c), we issue an UPDATED instead of ATTR_UPDATED, because there may
+        *    already be another UPDATED or CREATED event for B, so we make sure in this
+        *    way that not only attributes get checked at the end.
+        * */
+       if (previous_update_event_data) {
+               if (previous_update_event_data->event_type == G_FILE_MONITOR_EVENT_CREATED) {
+                       /* (a) CREATED(A) + MOVED(A->B)  = UPDATED (B)
+                        *
+                        * Oh, oh, oh, we got a previous created event
+                        * waiting in the event cache... so we remove it, and we
+                        * convert the MOVE event into a UPDATED(other_file),
+                        *
+                        * It is UPDATED instead of CREATED because the destination
+                        * file could probably exist, and mistakenly reporting
+                        * a CREATED event there can trick the monitor event
+                        * compression (for example, if we get a DELETED right after,
+                        * both CREATED/DELETED events turn into NOOP, so a no
+                        * longer existing file didn't trigger a DELETED.
+                        *
+                        * Instead, it is safer to just issue UPDATED, this event
+                        * wouldn't get compressed, and CREATED==UPDATED to the
+                        * miners' eyes.
+                        */
+                       g_hash_table_remove (monitor->priv->pre_update, src_file);
+                       g_hash_table_replace (monitor->priv->pre_update,
+                                             g_object_ref (dst_file),
+                                             event_data_new (dst_file,
+                                                             NULL,
+                                                             FALSE,
+                                                             G_FILE_MONITOR_EVENT_CHANGED));
+
+                       /* Do not notify the moved event now */
+                       return;
+               }
+
+               /*   (b) UPDATED(A)      + MOVED(A->B)  = MOVED(A->B) + UPDATED(B)
+                *   (c) ATTR_UPDATED(A) + MOVED(A->B)  = MOVED(A->B) + UPDATED(B)
+                *
+                * We setup here the UPDATED(B) event, added to the cache */
+               g_hash_table_replace (monitor->priv->pre_update,
+                                     g_object_ref (dst_file),
+                                     event_data_new (dst_file,
+                                                     NULL,
+                                                     FALSE,
+                                                     G_FILE_MONITOR_EVENT_CHANGES_DONE_HINT));
+               /* Remove previous event */
+               g_hash_table_remove (monitor->priv->pre_update, src_file);
+
+               /* And keep on notifying the MOVED event */
+       }
+
+       new_event = event_data_new (src_file,
+                                   dst_file,
+                                   FALSE,
+                                   G_FILE_MONITOR_EVENT_MOVED);
+       emit_signal_for_event (monitor, new_event);
+       event_data_free (new_event);
+}
+
+static void
+monitor_event_directory_created_or_changed (TrackerMonitor    *monitor,
+                                            GFile             *dir,
+                                            GFileMonitorEvent  event_type)
+{
+       EventData *previous_delete_event_data;
+
+       /* If any previous event on this item, notify it
+        *  before creating it */
+       previous_delete_event_data = g_hash_table_lookup (monitor->priv->pre_delete, dir);
+       if (previous_delete_event_data) {
+               emit_signal_for_event (monitor, previous_delete_event_data);
+               g_hash_table_remove (monitor->priv->pre_delete, dir);
+       }
+
+       if (!g_hash_table_lookup (monitor->priv->pre_update, dir)) {
+               g_hash_table_insert (monitor->priv->pre_update,
+                                    g_object_ref (dir),
+                                    event_data_new (dir,
+                                                    NULL,
+                                                    TRUE,
+                                                    event_type));
+       }
+}
+
+static void
+monitor_event_directory_deleted (TrackerMonitor *monitor,
+                                 GFile          *dir)
+{
+       EventData *previous_update_event_data;
+       EventData *previous_delete_event_data;
+
+       /* If any previous update event on this item, notify it */
+       previous_update_event_data = g_hash_table_lookup (monitor->priv->pre_update, dir);
+       if (previous_update_event_data) {
+               emit_signal_for_event (monitor, previous_update_event_data);
+               g_hash_table_remove (monitor->priv->pre_update, dir);
+       }
+
+       /* Check if there is a previous delete event */
+       previous_delete_event_data = g_hash_table_lookup (monitor->priv->pre_delete, dir);
+       if (previous_delete_event_data &&
+           previous_delete_event_data->event_type == G_FILE_MONITOR_EVENT_MOVED) {
+               g_debug ("Processing MOVE(A->B) + DELETE(A) as MOVE(A->B) for directory '%s->%s'",
+                        previous_delete_event_data->file_uri,
+                        previous_delete_event_data->other_file_uri);
+
+               emit_signal_for_event (monitor, previous_delete_event_data);
+               g_hash_table_remove (monitor->priv->pre_delete, dir);
+               return;
+       }
+
+       /* If no previous, add to HT */
+       g_hash_table_replace (monitor->priv->pre_delete,
+                             g_object_ref (dir),
+                             event_data_new (dir,
+                                             NULL,
+                                             TRUE,
+                                             G_FILE_MONITOR_EVENT_DELETED));
+}
+
+static void
+monitor_event_directory_moved (TrackerMonitor *monitor,
+                               GFile          *src_dir,
+                               GFile          *dst_dir)
+{
+       EventData *previous_update_event_data;
+       EventData *previous_delete_event_data;
+
+       /* If any previous update event on this item, notify it */
+       previous_update_event_data = g_hash_table_lookup (monitor->priv->pre_update, src_dir);
+       if (previous_update_event_data) {
+               emit_signal_for_event (monitor, previous_update_event_data);
+               g_hash_table_remove (monitor->priv->pre_update, src_dir);
+       }
+
+       /* Check if there is a previous delete event */
+       previous_delete_event_data = g_hash_table_lookup (monitor->priv->pre_delete, src_dir);
+       if (previous_delete_event_data &&
+           previous_delete_event_data->event_type == G_FILE_MONITOR_EVENT_DELETED) {
+               EventData *new_event;
+
+               new_event = event_data_new (src_dir,
+                                           dst_dir,
+                                           TRUE,
+                                           G_FILE_MONITOR_EVENT_MOVED);
+               g_debug ("Processing DELETE(A) + MOVE(A->B) as MOVE(A->B) for directory '%s->%s'",
+                        new_event->file_uri,
+                        new_event->other_file_uri);
+
+               emit_signal_for_event (monitor, new_event);
+               event_data_free (new_event);
+
+               /* And remove the previous DELETE */
+               g_hash_table_remove (monitor->priv->pre_delete, src_dir);
+               return;
+       }
+
+       /* If no previous, add to HT */
+       g_hash_table_replace (monitor->priv->pre_delete,
+                             g_object_ref (src_dir),
+                             event_data_new (src_dir,
+                                             dst_dir,
+                                             TRUE,
+                                             G_FILE_MONITOR_EVENT_MOVED));
+}
+
+static void
+monitor_event_cb (GFileMonitor      *file_monitor,
+                  GFile             *file,
+                  GFile             *other_file,
+                  GFileMonitorEvent  event_type,
+                  gpointer           user_data)
+{
+       TrackerMonitor *monitor;
+       gchar *file_uri;
+       gchar *other_file_uri;
+       gboolean is_directory;
+
+       monitor = user_data;
+
+       if (G_UNLIKELY (!monitor->priv->enabled)) {
+               g_debug ("Silently dropping monitor event, monitor disabled for now");
+               return;
+       }
+
+       /* Get URIs as paths may not be in UTF-8 */
+       file_uri = g_file_get_uri (file);
+
+       if (!other_file) {
+               is_directory = check_is_directory (monitor, file);
+
+               /* Avoid non-indexable-files */
+               if (monitor->priv->tree &&
+                   !tracker_indexing_tree_file_is_indexable (monitor->priv->tree,
+                                                             file,
+                                                             (is_directory ?
+                                                              G_FILE_TYPE_DIRECTORY :
+                                                              G_FILE_TYPE_REGULAR))) {
+                       g_free (file_uri);
+                       return;
+               }
+
+               other_file_uri = NULL;
+               g_debug ("Received monitor event:%d (%s) for %s:'%s'",
+                        event_type,
+                        monitor_event_to_string (event_type),
+                        is_directory ? "directory" : "file",
+                        file_uri);
+       } else {
+               /* If we have other_file, it means an item was moved from file to other_file;
+                * so, it makes sense to check if the other_file is directory instead of
+                * the origin file, as this one will not exist any more */
+               is_directory = check_is_directory (monitor, other_file);
+
+               /* Avoid doing anything of both
+                * file/other_file are non-indexable
+                */
+               if (monitor->priv->tree &&
+                   !tracker_indexing_tree_file_is_indexable (monitor->priv->tree,
+                                                             file,
+                                                             (is_directory ?
+                                                              G_FILE_TYPE_DIRECTORY :
+                                                              G_FILE_TYPE_REGULAR)) &&
+                   !tracker_indexing_tree_file_is_indexable (monitor->priv->tree,
+                                                             other_file,
+                                                             (is_directory ?
+                                                              G_FILE_TYPE_DIRECTORY :
+                                                              G_FILE_TYPE_REGULAR))) {
+                       g_free (file_uri);
+                       return;
+               }
+
+               other_file_uri = g_file_get_uri (other_file);
+               g_debug ("Received monitor event:%d (%s) for files '%s'->'%s'",
+                        event_type,
+                        monitor_event_to_string (event_type),
+                        file_uri,
+                        other_file_uri);
+       }
+
+#ifdef PAUSE_ON_IO
+       if (monitor->priv->unpause_timeout_id != 0) {
+               g_source_remove (monitor->priv->unpause_timeout_id);
+       } else {
+               g_message ("Pausing indexing because we are "
+                          "receiving monitor events");
+
+               tracker_status_set_is_paused_for_io (TRUE);
+       }
+
+       monitor->priv->unpause_timeout_id =
+               g_timeout_add_seconds (PAUSE_ON_IO_SECONDS,
+                                      unpause_cb,
+                                      monitor);
+#endif /* PAUSE_ON_IO */
+
+       if (!is_directory) {
+               /* FILE Events */
+               switch (event_type) {
+               case G_FILE_MONITOR_EVENT_CREATED:
+                       monitor_event_file_created (monitor, file);
+                       break;
+               case G_FILE_MONITOR_EVENT_CHANGED:
+                       monitor_event_file_changed (monitor, file);
+                       break;
+               case G_FILE_MONITOR_EVENT_ATTRIBUTE_CHANGED:
+                       monitor_event_file_attribute_changed (monitor, file);
+                       break;
+               case G_FILE_MONITOR_EVENT_CHANGES_DONE_HINT:
+                       monitor_event_file_changes_done (monitor, file);
+                       break;
+               case G_FILE_MONITOR_EVENT_DELETED:
+                       monitor_event_file_deleted (monitor, file);
+                       break;
+               case G_FILE_MONITOR_EVENT_MOVED:
+                       monitor_event_file_moved (monitor, file, other_file);
+                       break;
+               case G_FILE_MONITOR_EVENT_PRE_UNMOUNT:
+               case G_FILE_MONITOR_EVENT_UNMOUNTED:
+                       /* Do nothing */
+               case G_FILE_MONITOR_EVENT_RENAMED:
+               case G_FILE_MONITOR_EVENT_MOVED_IN:
+               case G_FILE_MONITOR_EVENT_MOVED_OUT:
+                       /* We don't use G_FILE_MONITOR_WATCH_MOVES */
+                       break;
+               }
+       } else {
+               /* DIRECTORY Events */
+
+               switch (event_type) {
+               case G_FILE_MONITOR_EVENT_CREATED:
+               case G_FILE_MONITOR_EVENT_CHANGES_DONE_HINT:
+               case G_FILE_MONITOR_EVENT_ATTRIBUTE_CHANGED:
+               case G_FILE_MONITOR_EVENT_CHANGED:
+                       monitor_event_directory_created_or_changed (monitor, file, event_type);
+                       break;
+               case G_FILE_MONITOR_EVENT_DELETED:
+                       monitor_event_directory_deleted (monitor, file);
+                       break;
+               case G_FILE_MONITOR_EVENT_MOVED:
+                       monitor_event_directory_moved (monitor, file, other_file);
+                       break;
+               case G_FILE_MONITOR_EVENT_PRE_UNMOUNT:
+               case G_FILE_MONITOR_EVENT_UNMOUNTED:
+                       /* Do nothing */
+               case G_FILE_MONITOR_EVENT_RENAMED:
+               case G_FILE_MONITOR_EVENT_MOVED_IN:
+               case G_FILE_MONITOR_EVENT_MOVED_OUT:
+                       /* We don't use G_FILE_MONITOR_WATCH_MOVES */
+                       break;
+               }
+       }
+
+       if (g_hash_table_size (monitor->priv->pre_update) > 0 ||
+           g_hash_table_size (monitor->priv->pre_delete) > 0) {
+               if (monitor->priv->event_pairs_timeout_id == 0) {
+                       g_debug ("Waiting for event pairs");
+                       monitor->priv->event_pairs_timeout_id =
+                               g_timeout_add_seconds (CACHE_LIFETIME_SECONDS,
+                                                      event_pairs_timeout_cb,
+                                                      monitor);
+               }
+       } else {
+               if (monitor->priv->event_pairs_timeout_id != 0) {
+                       g_source_remove (monitor->priv->event_pairs_timeout_id);
+               }
+
+               monitor->priv->event_pairs_timeout_id = 0;
+       }
+
+       g_free (file_uri);
+       g_free (other_file_uri);
+}
+
+static GFileMonitor *
+directory_monitor_new (TrackerMonitor *monitor,
+                       GFile          *file)
+{
+       GFileMonitor *file_monitor;
+       GError *error = NULL;
+
+       file_monitor = g_file_monitor_directory (file,
+                                                G_FILE_MONITOR_SEND_MOVED | G_FILE_MONITOR_WATCH_MOUNTS,
+                                                NULL,
+                                                &error);
+
+       if (error) {
+               gchar *uri;
+
+               uri = g_file_get_uri (file);
+               g_warning ("Could not add monitor for path:'%s', %s",
+                          uri, error->message);
+
+               g_error_free (error);
+               g_free (uri);
+
+               return NULL;
+       }
+
+       g_signal_connect (file_monitor, "changed",
+                         G_CALLBACK (monitor_event_cb),
+                         monitor);
+
+       return file_monitor;
+}
+
+static void
+directory_monitor_cancel (GFileMonitor *monitor)
+{
+       if (monitor) {
+               g_file_monitor_cancel (G_FILE_MONITOR (monitor));
+               g_object_unref (monitor);
+       }
+}
+
+TrackerMonitor *
+tracker_monitor_new (void)
+{
+       return g_object_new (TRACKER_TYPE_MONITOR, NULL);
+}
+
+gboolean
+tracker_monitor_get_enabled (TrackerMonitor *monitor)
+{
+       g_return_val_if_fail (TRACKER_IS_MONITOR (monitor), FALSE);
+
+       return monitor->priv->enabled;
+}
+
+TrackerIndexingTree *
+tracker_monitor_get_indexing_tree (TrackerMonitor *monitor)
+{
+       g_return_val_if_fail (TRACKER_IS_MONITOR (monitor), NULL);
+
+       return monitor->priv->tree;
+}
+
+void
+tracker_monitor_set_indexing_tree (TrackerMonitor      *monitor,
+                                   TrackerIndexingTree *tree)
+{
+       g_return_if_fail (TRACKER_IS_MONITOR (monitor));
+       g_return_if_fail (!tree || TRACKER_IS_INDEXING_TREE (tree));
+
+       if (monitor->priv->tree) {
+               g_object_unref (monitor->priv->tree);
+               monitor->priv->tree = NULL;
+       }
+
+       if (tree) {
+               monitor->priv->tree = g_object_ref (tree);
+       }
+}
+
+void
+tracker_monitor_set_enabled (TrackerMonitor *monitor,
+                             gboolean        enabled)
+{
+       GList *keys, *k;
+
+       g_return_if_fail (TRACKER_IS_MONITOR (monitor));
+
+       /* Don't replace all monitors if we are already
+        * enabled/disabled.
+        */
+       if (monitor->priv->enabled == enabled) {
+               return;
+       }
+
+       monitor->priv->enabled = enabled;
+       g_object_notify (G_OBJECT (monitor), "enabled");
+
+       keys = g_hash_table_get_keys (monitor->priv->monitors);
+
+       /* Update state on all monitored dirs */
+       for (k = keys; k != NULL; k = k->next) {
+               GFile *file;
+
+               file = k->data;
+
+               if (enabled) {
+                       GFileMonitor *dir_monitor;
+
+                       dir_monitor = directory_monitor_new (monitor, file);
+                       g_hash_table_replace (monitor->priv->monitors,
+                                             g_object_ref (file), dir_monitor);
+               } else {
+                       /* Remove monitor */
+                       g_hash_table_replace (monitor->priv->monitors,
+                                             g_object_ref (file), NULL);
+               }
+       }
+
+       g_list_free (keys);
+}
+
+gboolean
+tracker_monitor_add (TrackerMonitor *monitor,
+                     GFile          *file)
+{
+       GFileMonitor *dir_monitor = NULL;
+       gchar *uri;
+
+       g_return_val_if_fail (TRACKER_IS_MONITOR (monitor), FALSE);
+       g_return_val_if_fail (G_IS_FILE (file), FALSE);
+
+       if (g_hash_table_lookup (monitor->priv->monitors, file)) {
+               return TRUE;
+       }
+
+       /* Cap the number of monitors */
+       if (g_hash_table_size (monitor->priv->monitors) >= monitor->priv->monitor_limit) {
+               monitor->priv->monitors_ignored++;
+
+               if (!monitor->priv->monitor_limit_warned) {
+                       g_warning ("The maximum number of monitors to set (%d) "
+                                  "has been reached, not adding any new ones",
+                                  monitor->priv->monitor_limit);
+                       monitor->priv->monitor_limit_warned = TRUE;
+               }
+
+               return FALSE;
+       }
+
+       uri = g_file_get_uri (file);
+
+       if (monitor->priv->enabled) {
+               /* We don't check if a file exists or not since we might want
+                * to monitor locations which don't exist yet.
+                *
+                * Also, we assume ALL paths passed are directories.
+                */
+               dir_monitor = directory_monitor_new (monitor, file);
+
+               if (!dir_monitor) {
+                       g_warning ("Could not add monitor for path:'%s'",
+                                  uri);
+                       g_free (uri);
+                       return FALSE;
+               }
+       }
+
+       /* NOTE: it is ok to add a NULL file_monitor, when our
+        * enabled/disabled state changes, we iterate all keys and
+        * add or remove monitors.
+        */
+       g_hash_table_insert (monitor->priv->monitors,
+                            g_object_ref (file),
+                            dir_monitor);
+
+       g_debug ("Added monitor for path:'%s', total monitors:%d",
+                uri,
+                g_hash_table_size (monitor->priv->monitors));
+
+       g_free (uri);
+
+       return TRUE;
+}
+
+gboolean
+tracker_monitor_remove (TrackerMonitor *monitor,
+                        GFile          *file)
+{
+       gboolean removed;
+
+       g_return_val_if_fail (TRACKER_IS_MONITOR (monitor), FALSE);
+       g_return_val_if_fail (G_IS_FILE (file), FALSE);
+
+       removed = g_hash_table_remove (monitor->priv->monitors, file);
+
+       if (removed) {
+               gchar *uri;
+
+               uri = g_file_get_uri (file);
+               g_debug ("Removed monitor for path:'%s', total monitors:%d",
+                        uri,
+                        g_hash_table_size (monitor->priv->monitors));
+
+               g_free (uri);
+       }
+
+       return removed;
+}
+
+/* If @is_strict is %TRUE, return %TRUE iff @file is a child of @prefix.
+ * If @is_strict is %FALSE, additionally return %TRUE if @file equals @prefix.
+ */
+static gboolean
+file_has_maybe_strict_prefix (GFile    *file,
+                              GFile    *prefix,
+                              gboolean  is_strict)
+{
+       return (g_file_has_prefix (file, prefix) ||
+               (!is_strict && g_file_equal (file, prefix)));
+}
+
+static gboolean
+remove_recursively (TrackerMonitor *monitor,
+                    GFile          *file,
+                    gboolean        remove_top_level)
+{
+       GHashTableIter iter;
+       gpointer iter_file, iter_file_monitor;
+       guint items_removed = 0;
+       gchar *uri;
+
+       g_return_val_if_fail (TRACKER_IS_MONITOR (monitor), FALSE);
+       g_return_val_if_fail (G_IS_FILE (file), FALSE);
+
+       g_hash_table_iter_init (&iter, monitor->priv->monitors);
+       while (g_hash_table_iter_next (&iter, &iter_file, &iter_file_monitor)) {
+               if (!file_has_maybe_strict_prefix (iter_file, file,
+                                                  !remove_top_level)) {
+                       continue;
+               }
+
+               g_hash_table_iter_remove (&iter);
+               items_removed++;
+       }
+
+       uri = g_file_get_uri (file);
+       g_debug ("Removed all monitors %srecursively for path:'%s', "
+                "total monitors:%d",
+                !remove_top_level ? "(except top level) " : "",
+                uri, g_hash_table_size (monitor->priv->monitors));
+       g_free (uri);
+
+       if (items_removed > 0) {
+               /* We reset this because now it is possible we have limit - 1 */
+               monitor->priv->monitor_limit_warned = FALSE;
+               return TRUE;
+       }
+
+       return FALSE;
+}
+
+gboolean
+tracker_monitor_remove_recursively (TrackerMonitor *monitor,
+                                    GFile          *file)
+{
+       return remove_recursively (monitor, file, TRUE);
+}
+
+gboolean
+tracker_monitor_remove_children_recursively (TrackerMonitor *monitor,
+                                             GFile          *file)
+{
+       return remove_recursively (monitor, file, FALSE);
+}
+
+static gboolean
+monitor_cancel_recursively (TrackerMonitor *monitor,
+                            GFile          *file)
+{
+       GHashTableIter iter;
+       gpointer iter_file, iter_file_monitor;
+       guint items_cancelled = 0;
+
+       g_hash_table_iter_init (&iter, monitor->priv->monitors);
+       while (g_hash_table_iter_next (&iter, &iter_file, &iter_file_monitor)) {
+               gchar *uri;
+
+               if (!g_file_has_prefix (iter_file, file) &&
+                   !g_file_equal (iter_file, file)) {
+                       continue;
+               }
+
+               uri = g_file_get_uri (iter_file);
+               g_file_monitor_cancel (G_FILE_MONITOR (iter_file_monitor));
+               g_debug ("Cancelled monitor for path:'%s'", uri);
+               g_free (uri);
+
+               items_cancelled++;
+       }
+
+       return items_cancelled > 0;
+}
+
+gboolean
+tracker_monitor_is_watched (TrackerMonitor *monitor,
+                            GFile          *file)
+{
+       g_return_val_if_fail (TRACKER_IS_MONITOR (monitor), FALSE);
+       g_return_val_if_fail (G_IS_FILE (file), FALSE);
+
+       return g_hash_table_lookup (monitor->priv->monitors, file) != NULL;
+}
+
+gboolean
+tracker_monitor_is_watched_by_string (TrackerMonitor *monitor,
+                                      const gchar    *path)
+{
+       GFile      *file;
+       gboolean    watched;
+
+       g_return_val_if_fail (TRACKER_IS_MONITOR (monitor), FALSE);
+       g_return_val_if_fail (path != NULL, FALSE);
+
+       file = g_file_new_for_path (path);
+       watched = g_hash_table_lookup (monitor->priv->monitors, file) != NULL;
+       g_object_unref (file);
+
+       return watched;
+}
+
+guint
+tracker_monitor_get_count (TrackerMonitor *monitor)
+{
+       g_return_val_if_fail (TRACKER_IS_MONITOR (monitor), 0);
+
+       return g_hash_table_size (monitor->priv->monitors);
+}
+
+guint
+tracker_monitor_get_ignored (TrackerMonitor *monitor)
+{
+       g_return_val_if_fail (TRACKER_IS_MONITOR (monitor), 0);
+
+       return monitor->priv->monitors_ignored;
+}
+
+guint
+tracker_monitor_get_limit (TrackerMonitor *monitor)
+{
+       g_return_val_if_fail (TRACKER_IS_MONITOR (monitor), 0);
+
+       return monitor->priv->monitor_limit;
+}
diff --git a/src/libtracker-miner/tracker-monitor.h b/src/libtracker-miner/tracker-monitor.h
new file mode 100644
index 000000000..a4a610682
--- /dev/null
+++ b/src/libtracker-miner/tracker-monitor.h
@@ -0,0 +1,84 @@
+/*
+ * Copyright (C) 2009, Nokia <ivan frade nokia com>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ * Boston, MA  02110-1301, USA.
+ */
+
+#ifndef __LIBTRACKER_MINER_MONITOR_H__
+#define __LIBTRACKER_MINER_MONITOR_H__
+
+#if !defined (__LIBTRACKER_MINER_H_INSIDE__) && !defined (TRACKER_COMPILATION)
+#error "Only <libtracker-miner/tracker-miner.h> can be included directly."
+#endif
+
+#include <glib-object.h>
+#include <gio/gio.h>
+#include "tracker-indexing-tree.h"
+
+G_BEGIN_DECLS
+
+#define TRACKER_TYPE_MONITOR            (tracker_monitor_get_type ())
+#define TRACKER_MONITOR(object)                 (G_TYPE_CHECK_INSTANCE_CAST ((object), TRACKER_TYPE_MONITOR, 
TrackerMonitor))
+#define TRACKER_MONITOR_CLASS(klass)    (G_TYPE_CHECK_CLASS_CAST ((klass), TRACKER_TYPE_MONITOR, 
TrackerMonitorClass))
+#define TRACKER_IS_MONITOR(object)      (G_TYPE_CHECK_INSTANCE_TYPE ((object), TRACKER_TYPE_MONITOR))
+#define TRACKER_IS_MONITOR_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), TRACKER_TYPE_MONITOR))
+#define TRACKER_MONITOR_GET_CLASS(obj)  (G_TYPE_INSTANCE_GET_CLASS ((obj), TRACKER_TYPE_MONITOR, 
TrackerMonitorClass))
+
+typedef struct TrackerMonitor         TrackerMonitor;
+typedef struct TrackerMonitorClass    TrackerMonitorClass;
+typedef struct TrackerMonitorPrivate  TrackerMonitorPrivate;
+
+struct TrackerMonitor {
+       GObject         parent;
+       TrackerMonitorPrivate *priv;
+};
+
+struct TrackerMonitorClass {
+       GObjectClass parent;
+};
+
+GType           tracker_monitor_get_type             (void);
+TrackerMonitor *tracker_monitor_new                  (void);
+
+TrackerIndexingTree * tracker_monitor_get_indexing_tree (TrackerMonitor *monitor);
+void                  tracker_monitor_set_indexing_tree (TrackerMonitor      *monitor,
+                                                         TrackerIndexingTree *tree);
+
+gboolean        tracker_monitor_get_enabled          (TrackerMonitor *monitor);
+void            tracker_monitor_set_enabled          (TrackerMonitor *monitor,
+                                                      gboolean        enabled);
+gboolean        tracker_monitor_add                  (TrackerMonitor *monitor,
+                                                      GFile          *file);
+gboolean        tracker_monitor_remove               (TrackerMonitor *monitor,
+                                                      GFile          *file);
+gboolean        tracker_monitor_remove_recursively   (TrackerMonitor *monitor,
+                                                      GFile          *file);
+gboolean        tracker_monitor_remove_children_recursively (TrackerMonitor *monitor,
+                                                             GFile          *file);
+gboolean        tracker_monitor_move                 (TrackerMonitor *monitor,
+                                                      GFile          *old_file,
+                                                      GFile          *new_file);
+gboolean        tracker_monitor_is_watched           (TrackerMonitor *monitor,
+                                                      GFile          *file);
+gboolean        tracker_monitor_is_watched_by_string (TrackerMonitor *monitor,
+                                                      const gchar    *path);
+guint           tracker_monitor_get_count            (TrackerMonitor *monitor);
+guint           tracker_monitor_get_ignored          (TrackerMonitor *monitor);
+guint           tracker_monitor_get_limit            (TrackerMonitor *monitor);
+
+G_END_DECLS
+
+#endif /* __LIBTRACKER_MINER_MONITOR_H__ */
diff --git a/src/libtracker-miner/tracker-priority-queue.c b/src/libtracker-miner/tracker-priority-queue.c
new file mode 100644
index 000000000..49e474936
--- /dev/null
+++ b/src/libtracker-miner/tracker-priority-queue.c
@@ -0,0 +1,455 @@
+/*
+ * Copyright (C) 2011, Nokia <ivan frade nokia com>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ * Boston, MA  02110-1301, USA.
+ *
+ * Author: Carlos Garnacho <carlos lanedo com>
+ */
+
+#include "config-miners.h"
+
+#include "tracker-priority-queue.h"
+
+typedef struct PrioritySegment PrioritySegment;
+
+struct PrioritySegment
+{
+       gint priority;
+       GList *first_elem;
+       GList *last_elem;
+};
+
+struct _TrackerPriorityQueue
+{
+       GQueue queue;
+       GArray *segments;
+
+       gint ref_count;
+};
+
+TrackerPriorityQueue *
+tracker_priority_queue_new (void)
+{
+       TrackerPriorityQueue *queue;
+
+       queue = g_slice_new (TrackerPriorityQueue);
+       g_queue_init (&queue->queue);
+       queue->segments = g_array_new (FALSE, FALSE,
+                                      sizeof (PrioritySegment));
+
+       queue->ref_count = 1;
+
+       return queue;
+}
+
+TrackerPriorityQueue *
+tracker_priority_queue_ref (TrackerPriorityQueue *queue)
+{
+       g_atomic_int_inc (&queue->ref_count);
+       return queue;
+}
+
+void
+tracker_priority_queue_unref (TrackerPriorityQueue *queue)
+{
+       if (g_atomic_int_dec_and_test (&queue->ref_count)) {
+               g_queue_clear (&queue->queue);
+               g_array_free (queue->segments, TRUE);
+               g_slice_free (TrackerPriorityQueue, queue);
+       }
+}
+
+static void
+queue_insert_before_link (GQueue *queue,
+                          GList  *sibling,
+                          GList  *link_)
+{
+       if (sibling == queue->head)
+               g_queue_push_head_link (queue, link_);
+       else {
+               link_->next = sibling;
+               link_->prev = sibling->prev;
+               sibling->prev->next = link_;
+               sibling->prev = link_;
+               queue->length++;
+       }
+}
+
+static void
+queue_insert_after_link (GQueue *queue,
+                         GList  *sibling,
+                         GList  *link_)
+{
+       if (sibling == queue->tail)
+               g_queue_push_tail_link (queue, link_);
+       else
+               queue_insert_before_link (queue, sibling->next, link_);
+}
+
+static void
+insert_node (TrackerPriorityQueue *queue,
+             gint                  priority,
+             GList                *node)
+{
+       PrioritySegment *segment = NULL;
+       gboolean found = FALSE;
+       gint l, r, c;
+
+       /* Perform binary search to find out the segment for
+        * the given priority, create one if it isn't found.
+        */
+       l = 0;
+       r = queue->segments->len - 1;
+
+       while (queue->segments->len > 0 && !found) {
+               c = (r + l) / 2;
+               segment = &g_array_index (queue->segments, PrioritySegment, c);
+
+               if (segment->priority == priority) {
+                       found = TRUE;
+                       break;
+               } else if (segment->priority > priority) {
+                       r = c - 1;
+               } else if (segment->priority < priority) {
+                       l = c + 1;
+               }
+
+               if (l > r) {
+                       break;
+               }
+       }
+
+       if (found) {
+               /* Element found, append at the end of segment */
+               g_assert (segment != NULL);
+               g_assert (segment->priority == priority);
+
+               queue_insert_after_link (&queue->queue, segment->last_elem, node);
+               segment->last_elem = node;
+       } else {
+               PrioritySegment new_segment = { 0 };
+
+               new_segment.priority = priority;
+
+               if (segment) {
+                       g_assert (segment->priority != priority);
+
+                       /* Binary search got to one of the closest results,
+                        * but we may have come from either of both sides,
+                        * so check whether we have to insert after the
+                        * segment we got.
+                        */
+                       if (segment->priority > priority) {
+                               /* We have to insert to the left of this element */
+                               queue_insert_before_link (&queue->queue, segment->first_elem, node);
+                       } else {
+                               /* We have to insert to the right of this element */
+                               queue_insert_after_link (&queue->queue, segment->last_elem, node);
+                               c++;
+                       }
+
+                       new_segment.first_elem = new_segment.last_elem = node;
+                       g_array_insert_val (queue->segments, c, new_segment);
+               } else {
+                       /* Segments list has 0 elements */
+                       g_assert (queue->segments->len == 0);
+                       g_assert (g_queue_get_length (&queue->queue) == 0);
+
+                       g_queue_push_head_link (&queue->queue, node);
+                       new_segment.first_elem = new_segment.last_elem = node;
+
+                       g_array_append_val (queue->segments, new_segment);
+               }
+       }
+}
+
+void
+tracker_priority_queue_foreach (TrackerPriorityQueue *queue,
+                                GFunc                 func,
+                                gpointer              user_data)
+{
+       g_return_if_fail (queue != NULL);
+       g_return_if_fail (func != NULL);
+
+       g_queue_foreach (&queue->queue, func, user_data);
+}
+
+gboolean
+tracker_priority_queue_foreach_remove (TrackerPriorityQueue *queue,
+                                       GEqualFunc            compare_func,
+                                       gpointer              compare_user_data,
+                                       GDestroyNotify        destroy_notify)
+{
+       PrioritySegment *segment;
+       gint n_segment = 0;
+       gboolean updated = FALSE;
+       GList *list;
+
+       g_return_val_if_fail (queue != NULL, FALSE);
+       g_return_val_if_fail (compare_func != NULL, FALSE);
+
+       list = queue->queue.head;
+
+       if (!list) {
+               return FALSE;
+       }
+
+       segment = &g_array_index (queue->segments, PrioritySegment, n_segment);
+
+       while (list) {
+               gboolean fetch_segment = FALSE;
+               GList *elem;
+
+               elem = list;
+               list = list->next;
+
+               if ((compare_func) (elem->data, compare_user_data)) {
+                       /* Update segment limits */
+                       if (elem == segment->first_elem &&
+                           elem == segment->last_elem) {
+                               /* Last element of segment, remove it */
+                               g_array_remove_index (queue->segments,
+                                                     n_segment);
+                               fetch_segment = TRUE;
+                       } else if (elem == segment->first_elem) {
+                               /* First elemen in segment */
+                               segment->first_elem = elem->next;
+                       } else if (elem == segment->last_elem) {
+                               segment->last_elem = elem->prev;
+                               n_segment++;
+                               fetch_segment = TRUE;
+                       }
+
+                       if (destroy_notify) {
+                               (destroy_notify) (elem->data);
+                       }
+
+                       g_queue_delete_link (&queue->queue, elem);
+                       updated = TRUE;
+               } else {
+                       if (list != NULL &&
+                           elem == segment->last_elem) {
+                               /* Move on to the next segment */
+                               n_segment++;
+                               fetch_segment = TRUE;
+                       }
+               }
+
+               if (list && fetch_segment) {
+                       /* Fetch the next segment */
+                       g_assert (n_segment < queue->segments->len);
+                       segment = &g_array_index (queue->segments,
+                                                 PrioritySegment,
+                                                 n_segment);
+               }
+       }
+
+       return updated;
+}
+
+gboolean
+tracker_priority_queue_is_empty (TrackerPriorityQueue *queue)
+{
+       g_return_val_if_fail (queue != NULL, FALSE);
+
+       return g_queue_is_empty (&queue->queue);
+}
+
+guint
+tracker_priority_queue_get_length (TrackerPriorityQueue *queue)
+{
+       g_return_val_if_fail (queue != NULL, 0);
+
+       return g_queue_get_length (&queue->queue);
+}
+
+GList *
+tracker_priority_queue_add (TrackerPriorityQueue *queue,
+                            gpointer              data,
+                            gint                  priority)
+{
+       GList *node;
+
+       g_return_val_if_fail (queue != NULL, NULL);
+       g_return_val_if_fail (data != NULL, NULL);
+
+       node = g_list_alloc ();
+       node->data = data;
+       insert_node (queue, priority, node);
+
+       return node;
+}
+
+void
+tracker_priority_queue_add_node (TrackerPriorityQueue *queue,
+                                 GList                *node,
+                                 gint                  priority)
+{
+       g_return_if_fail (queue != NULL);
+       g_return_if_fail (node != NULL);
+
+       insert_node (queue, priority, node);
+}
+
+void
+tracker_priority_queue_remove_node (TrackerPriorityQueue *queue,
+                                    GList                *node)
+{
+       guint i;
+
+       g_return_if_fail (queue != NULL);
+
+       /* Check if it is the first or last of a segment */
+       for (i = 0; i < queue->segments->len; i++) {
+               PrioritySegment *segment;
+
+               segment = &g_array_index (queue->segments, PrioritySegment, i);
+
+               if (segment->first_elem == node) {
+                       if (segment->last_elem == node)
+                               g_array_remove_index (queue->segments, i);
+                       else
+                               segment->first_elem = node->next;
+                       break;
+               }
+
+               if (segment->last_elem == node) {
+                       segment->last_elem = node->prev;
+                       break;
+               }
+       }
+
+       g_queue_delete_link (&queue->queue, node);
+}
+
+gpointer
+tracker_priority_queue_find (TrackerPriorityQueue *queue,
+                             gint                 *priority_out,
+                             GEqualFunc            compare_func,
+                             gpointer              user_data)
+{
+       PrioritySegment *segment;
+       gint n_segment = 0;
+       GList *list;
+
+       g_return_val_if_fail (queue != NULL, NULL);
+       g_return_val_if_fail (compare_func != NULL, NULL);
+
+       list = queue->queue.head;
+       segment = &g_array_index (queue->segments, PrioritySegment, n_segment);
+
+       while (list) {
+               if ((compare_func) (list->data, user_data)) {
+                       if (priority_out) {
+                               *priority_out = segment->priority;
+                       }
+
+                       return list->data;
+               }
+
+               if (list->next != NULL &&
+                   list == segment->last_elem) {
+                       /* Move on to the next segment */
+                       n_segment++;
+                       g_assert (n_segment < queue->segments->len);
+                       segment = &g_array_index (queue->segments,
+                                                 PrioritySegment,
+                                                 n_segment);
+               }
+
+               list = list->next;
+       }
+
+       return NULL;
+}
+
+gpointer
+tracker_priority_queue_peek (TrackerPriorityQueue *queue,
+                             gint                 *priority_out)
+{
+       g_return_val_if_fail (queue != NULL, NULL);
+
+       if (priority_out && queue->segments->len > 0) {
+               PrioritySegment *segment;
+
+               segment = &g_array_index (queue->segments, PrioritySegment, 0);
+               *priority_out = segment->priority;
+       }
+
+       return g_queue_peek_head (&queue->queue);
+}
+
+gpointer
+tracker_priority_queue_pop (TrackerPriorityQueue *queue,
+                            gint                 *priority_out)
+{
+       GList *node;
+       gpointer data;
+
+       node = tracker_priority_queue_pop_node (queue, priority_out);
+       if (node == NULL)
+               return NULL;
+
+       data = node->data;
+       g_list_free_1 (node);
+
+       return data;
+}
+
+GList *
+tracker_priority_queue_pop_node (TrackerPriorityQueue *queue,
+                                 gint                 *priority_out)
+{
+       PrioritySegment *segment;
+       GList *node;
+
+       g_return_val_if_fail (queue != NULL, NULL);
+
+       node = g_queue_peek_head_link (&queue->queue);
+
+       if (!node) {
+               /* No elements in queue */
+               return NULL;
+       }
+
+       segment = &g_array_index (queue->segments, PrioritySegment, 0);
+       g_assert (segment->first_elem == node);
+
+       if (priority_out) {
+               *priority_out = segment->priority;
+       }
+
+       if (segment->last_elem == node) {
+               /* It is the last element of the first
+                * segment, remove segment as well.
+                */
+               g_array_remove_index (queue->segments, 0);
+       } else {
+
+               /* Move segments first element forward */
+               segment->first_elem = segment->first_elem->next;
+       }
+
+       return g_queue_pop_head_link (&queue->queue);
+}
+
+GList *
+tracker_priority_queue_get_head (TrackerPriorityQueue *queue)
+{
+       g_return_val_if_fail (queue != NULL, NULL);
+
+       return queue->queue.head;
+}
diff --git a/src/libtracker-miner/tracker-priority-queue.h b/src/libtracker-miner/tracker-priority-queue.h
new file mode 100644
index 000000000..0dbc9b83d
--- /dev/null
+++ b/src/libtracker-miner/tracker-priority-queue.h
@@ -0,0 +1,74 @@
+/*
+ * Copyright (C) 2011, Nokia <ivan frade nokia com>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ * Boston, MA  02110-1301, USA.
+ *
+ * Author: Carlos Garnacho <carlos lanedo com>
+ */
+
+#ifndef __LIBTRACKER_MINER_PRIORITY_QUEUE_H__
+#define __LIBTRACKER_MINER_PRIORITY_QUEUE_H__
+
+#include <glib.h>
+
+G_BEGIN_DECLS
+
+typedef struct _TrackerPriorityQueue TrackerPriorityQueue;
+
+TrackerPriorityQueue *tracker_priority_queue_new   (void);
+
+TrackerPriorityQueue *tracker_priority_queue_ref   (TrackerPriorityQueue *queue);
+void                  tracker_priority_queue_unref (TrackerPriorityQueue *queue);
+
+gboolean tracker_priority_queue_is_empty           (TrackerPriorityQueue *queue);
+
+guint    tracker_priority_queue_get_length         (TrackerPriorityQueue *queue);
+
+GList *  tracker_priority_queue_add     (TrackerPriorityQueue *queue,
+                                         gpointer              data,
+                                         gint                  priority);
+void     tracker_priority_queue_foreach (TrackerPriorityQueue *queue,
+                                         GFunc                 func,
+                                         gpointer              user_data);
+
+gboolean tracker_priority_queue_foreach_remove (TrackerPriorityQueue *queue,
+                                                GEqualFunc            compare_func,
+                                                gpointer              compare_user_data,
+                                                GDestroyNotify        destroy_notify);
+
+gpointer tracker_priority_queue_find           (TrackerPriorityQueue *queue,
+                                                gint                 *priority_out,
+                                                GEqualFunc            compare_func,
+                                                gpointer              data);
+
+gpointer tracker_priority_queue_peek    (TrackerPriorityQueue *queue,
+                                         gint                 *priority_out);
+gpointer tracker_priority_queue_pop     (TrackerPriorityQueue *queue,
+                                         gint                 *priority_out);
+
+GList *  tracker_priority_queue_get_head    (TrackerPriorityQueue *queue);
+void     tracker_priority_queue_add_node    (TrackerPriorityQueue *queue,
+                                             GList                *node,
+                                             gint                  priority);
+void     tracker_priority_queue_remove_node (TrackerPriorityQueue  *queue,
+                                             GList                 *node);
+GList *  tracker_priority_queue_pop_node    (TrackerPriorityQueue *queue,
+                                             gint                 *priority_out);
+
+
+G_END_DECLS
+
+#endif /* __LIBTRACKER_TRACKER_PRIORITY_QUEUE_H__ */
diff --git a/src/libtracker-miner/tracker-sparql-buffer.c b/src/libtracker-miner/tracker-sparql-buffer.c
new file mode 100644
index 000000000..e84f7c799
--- /dev/null
+++ b/src/libtracker-miner/tracker-sparql-buffer.c
@@ -0,0 +1,795 @@
+/*
+ * Copyright (C) 2011, Nokia <ivan frade nokia com>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ * Boston, MA  02110-1301, USA.
+ *
+ * Author: Carlos Garnacho <carlos lanedo com>
+ */
+
+#include "config-miners.h"
+
+#include <libtracker-sparql/tracker-sparql.h>
+
+#include "tracker-sparql-buffer.h"
+
+/* Maximum time (seconds) before forcing a sparql buffer flush */
+#define MAX_SPARQL_BUFFER_TIME  15
+
+typedef struct _TrackerSparqlBufferPrivate TrackerSparqlBufferPrivate;
+typedef struct _SparqlTaskData SparqlTaskData;
+typedef struct _UpdateArrayData UpdateArrayData;
+typedef struct _UpdateData UpdateData;
+typedef struct _BulkOperationMerge BulkOperationMerge;
+
+enum {
+       PROP_0,
+       PROP_CONNECTION
+};
+
+enum {
+       TASK_TYPE_SPARQL_STR,
+       TASK_TYPE_SPARQL,
+       TASK_TYPE_BULK
+};
+
+struct _TrackerSparqlBufferPrivate
+{
+       TrackerSparqlConnection *connection;
+       guint flush_timeout_id;
+       GPtrArray *tasks;
+       gint n_updates;
+};
+
+struct _SparqlTaskData
+{
+       guint type;
+
+       union {
+               gchar *str;
+               TrackerSparqlBuilder *builder;
+
+               struct {
+                       gchar *str;
+                       guint flags;
+               } bulk;
+       } data;
+
+       GTask *async_task;
+};
+
+struct _UpdateData {
+       TrackerSparqlBuffer *buffer;
+       TrackerTask *task;
+};
+
+struct _UpdateArrayData {
+       TrackerSparqlBuffer *buffer;
+       GPtrArray *tasks;
+       GArray *sparql_array;
+};
+
+struct _BulkOperationMerge {
+       const gchar *bulk_operation;
+       GList *tasks;
+};
+
+
+
+G_DEFINE_TYPE (TrackerSparqlBuffer, tracker_sparql_buffer, TRACKER_TYPE_TASK_POOL)
+
+static void
+tracker_sparql_buffer_finalize (GObject *object)
+{
+       TrackerSparqlBufferPrivate *priv;
+
+       priv = TRACKER_SPARQL_BUFFER (object)->priv;
+
+       if (priv->flush_timeout_id != 0) {
+               g_source_remove (priv->flush_timeout_id);
+       }
+
+       G_OBJECT_CLASS (tracker_sparql_buffer_parent_class)->finalize (object);
+}
+
+static void
+tracker_sparql_buffer_set_property (GObject      *object,
+                                    guint         param_id,
+                                    const GValue *value,
+                                    GParamSpec   *pspec)
+{
+       TrackerSparqlBufferPrivate *priv;
+
+       priv = TRACKER_SPARQL_BUFFER (object)->priv;
+
+       switch (param_id) {
+       case PROP_CONNECTION:
+               priv->connection = g_value_dup_object (value);
+               break;
+       default:
+               G_OBJECT_WARN_INVALID_PROPERTY_ID (object, param_id, pspec);
+               break;
+       }
+}
+
+static void
+tracker_sparql_buffer_get_property (GObject    *object,
+                                    guint       param_id,
+                                    GValue     *value,
+                                    GParamSpec *pspec)
+{
+       TrackerSparqlBufferPrivate *priv;
+
+       priv = TRACKER_SPARQL_BUFFER (object)->priv;
+
+       switch (param_id) {
+       case PROP_CONNECTION:
+               g_value_set_object (value,
+                                   priv->connection);
+               break;
+       default:
+               G_OBJECT_WARN_INVALID_PROPERTY_ID (object, param_id, pspec);
+               break;
+       }
+}
+
+static void
+tracker_sparql_buffer_class_init (TrackerSparqlBufferClass *klass)
+{
+       GObjectClass *object_class = G_OBJECT_CLASS (klass);
+
+       object_class->finalize = tracker_sparql_buffer_finalize;
+       object_class->set_property = tracker_sparql_buffer_set_property;
+       object_class->get_property = tracker_sparql_buffer_get_property;
+
+       g_object_class_install_property (object_class,
+                                        PROP_CONNECTION,
+                                        g_param_spec_object ("connection",
+                                                             "sparql connection",
+                                                             "Sparql Connection",
+                                                             TRACKER_SPARQL_TYPE_CONNECTION,
+                                                             G_PARAM_READWRITE |
+                                                             G_PARAM_CONSTRUCT_ONLY));
+
+       g_type_class_add_private (object_class,
+                                 sizeof (TrackerSparqlBufferPrivate));
+}
+
+static gboolean
+flush_timeout_cb (gpointer user_data)
+{
+       TrackerSparqlBuffer *buffer = user_data;
+       TrackerSparqlBufferPrivate *priv = buffer->priv;
+
+       tracker_sparql_buffer_flush (buffer, "Buffer time reached");
+       priv->flush_timeout_id = 0;
+
+       return FALSE;
+}
+
+static void
+reset_flush_timeout (TrackerSparqlBuffer *buffer)
+{
+       TrackerSparqlBufferPrivate *priv;
+
+       priv = buffer->priv;
+
+       if (priv->flush_timeout_id != 0) {
+               g_source_remove (priv->flush_timeout_id);
+       }
+
+       priv->flush_timeout_id = g_timeout_add_seconds (MAX_SPARQL_BUFFER_TIME,
+                                                       flush_timeout_cb,
+                                                       buffer);
+}
+
+static void
+tracker_sparql_buffer_init (TrackerSparqlBuffer *buffer)
+{
+       buffer->priv = G_TYPE_INSTANCE_GET_PRIVATE (buffer,
+                                                   TRACKER_TYPE_SPARQL_BUFFER,
+                                                   TrackerSparqlBufferPrivate);
+}
+
+TrackerSparqlBuffer *
+tracker_sparql_buffer_new (TrackerSparqlConnection *connection,
+                           guint                    limit)
+{
+       return g_object_new (TRACKER_TYPE_SPARQL_BUFFER,
+                            "connection", connection,
+                            "limit", limit,
+                            NULL);
+}
+
+static void
+remove_task_foreach (TrackerTask     *task,
+                     TrackerTaskPool *pool)
+{
+       tracker_task_pool_remove (pool, task);
+}
+
+static void
+update_array_data_free (UpdateArrayData *update_data)
+{
+       if (!update_data)
+               return;
+
+       if (update_data->sparql_array) {
+               /* The array contains pointers to strings in the tasks, so no need to
+                * deallocate its pointed contents, just the array itself. */
+               g_array_free (update_data->sparql_array, TRUE);
+       }
+
+       g_ptr_array_foreach (update_data->tasks,
+                            (GFunc) remove_task_foreach,
+                            update_data->buffer);
+       g_ptr_array_free (update_data->tasks, TRUE);
+
+       g_slice_free (UpdateArrayData, update_data);
+}
+
+static void
+tracker_sparql_buffer_update_array_cb (GObject      *object,
+                                       GAsyncResult *result,
+                                       gpointer      user_data)
+{
+       TrackerSparqlBufferPrivate *priv;
+       TrackerSparqlBuffer *buffer;
+       GError *global_error = NULL;
+       GPtrArray *sparql_array_errors;
+       UpdateArrayData *update_data;
+       gint i;
+
+       /* Get arrays of errors and queries */
+       update_data = user_data;
+       buffer = TRACKER_SPARQL_BUFFER (update_data->buffer);
+       priv = buffer->priv;
+       priv->n_updates--;
+
+       g_debug ("(Sparql buffer) Finished array-update with %u tasks",
+                update_data->tasks->len);
+
+       sparql_array_errors = tracker_sparql_connection_update_array_finish (priv->connection,
+                                                                            result,
+                                                                            &global_error);
+       if (global_error) {
+               g_critical ("  (Sparql buffer) Error in array-update: %s",
+                           global_error->message);
+       }
+
+       /* Report status on each task of the batch update */
+       for (i = 0; i < update_data->tasks->len; i++) {
+               TrackerTask *task;
+               SparqlTaskData *task_data;
+               GError *error = NULL;
+
+               task = g_ptr_array_index (update_data->tasks, i);
+               task_data = tracker_task_get_data (task);
+
+               if (global_error) {
+                       error = global_error;
+               } else {
+                       error = g_ptr_array_index (sparql_array_errors, i);
+                       if (error) {
+                               const gchar *sparql = NULL;
+                               GFile *file;
+                               gchar *uri;
+
+                               file = tracker_task_get_file (task);
+                               uri = g_file_get_uri (file);
+                               g_critical ("  (Sparql buffer) Error in task %u (%s) of the array-update: %s",
+                                           i, uri, error->message);
+                               g_free (uri);
+
+                               uri = g_file_get_uri (tracker_task_get_file (task));
+                               g_debug ("    Affected file: %s", uri);
+                               g_free (uri);
+
+                               switch (task_data->type) {
+                               case TASK_TYPE_SPARQL_STR:
+                                       sparql = task_data->data.str;
+                                       break;
+                               case TASK_TYPE_SPARQL:
+                                       sparql = tracker_sparql_builder_get_result (task_data->data.builder);
+                                       break;
+                               case TASK_TYPE_BULK:
+                                       sparql = task_data->data.bulk.str;
+                                       break;
+                               default:
+                                       break;
+                               }
+
+                               if (sparql) {
+                                       g_debug ("    Sparql: %s", sparql);
+                               }
+                       }
+               }
+
+               /* Call finished handler with the error, if any */
+               if (error) {
+                       g_task_return_error (task_data->async_task,
+                                            g_error_copy (error));
+               } else {
+                       g_task_return_pointer (task_data->async_task, task, NULL);
+               }
+
+               /* No need to deallocate the task here, it will be done when
+                * unref-ing the UpdateArrayData below */
+       }
+
+       /* Unref the arrays of errors and queries */
+       if (sparql_array_errors) {
+               g_ptr_array_unref (sparql_array_errors);
+       }
+
+       /* Note that tasks are actually deallocated here */
+       update_array_data_free (update_data);
+
+       if (global_error) {
+               g_error_free (global_error);
+       }
+
+       if (tracker_task_pool_limit_reached (TRACKER_TASK_POOL (buffer))) {
+               tracker_sparql_buffer_flush (buffer, "SPARQL buffer limit reached (after flush)");
+       }
+}
+
+static gchar *
+bulk_operation_merge_finish (BulkOperationMerge *merge)
+{
+       if (merge->bulk_operation && merge->tasks) {
+               GString *equals_string = NULL, *children_string = NULL, *sparql;
+               gint n_equals = 0;
+               gboolean include_logical_resources = FALSE;
+               GList *l;
+
+               for (l = merge->tasks; l; l = l->next) {
+                       SparqlTaskData *task_data;
+                       TrackerTask *task = l->data;
+                       gchar *uri;
+
+                       task_data = tracker_task_get_data (task);
+                       uri = g_file_get_uri (tracker_task_get_file (task));
+
+                       if (task_data->data.bulk.flags & TRACKER_BULK_MATCH_EQUALS) {
+                               if (!equals_string) {
+                                       equals_string = g_string_new ("");
+                               } else {
+                                       g_string_append_c (equals_string, ',');
+                               }
+
+                               g_string_append_printf (equals_string, "\"%s\"", uri);
+                               n_equals++;
+                       }
+
+                       if (task_data->data.bulk.flags & TRACKER_BULK_MATCH_CHILDREN) {
+                               gchar *dir_uri;
+
+                               if (!children_string) {
+                                       children_string = g_string_new (NULL);
+                               } else {
+                                       g_string_append (children_string, "||");
+                               }
+
+                               if (uri[strlen (uri) - 1] == '/')
+                                       dir_uri = g_strdup (uri);
+                               else
+                                       dir_uri = g_strdup_printf ("%s/", uri);
+
+                               g_string_append_printf (children_string,
+                                                       "STRSTARTS (?u, \"%s\")",
+                                                       dir_uri);
+                               g_free (dir_uri);
+                       }
+
+                       if (task_data->data.bulk.flags & TRACKER_BULK_MATCH_LOGICAL_RESOURCES) {
+                               include_logical_resources = TRUE;
+                       }
+
+                       g_free (uri);
+               }
+
+               sparql = g_string_new ("");
+
+               if (equals_string) {
+                       g_string_append (sparql, merge->bulk_operation);
+                       g_string_append_printf (sparql, " WHERE { ");
+
+                       if (n_equals == 1) {
+                               g_string_append_printf (sparql,
+                                                       "  ?f nie:url %s .",
+                                                       equals_string->str);
+                       } else {
+                               g_string_append_printf (sparql,
+                                                       "  ?f nie:url ?u ."
+                                                       "  FILTER (?u IN (%s))",
+                                                       equals_string->str);
+                       }
+                       g_string_free (equals_string, TRUE);
+
+                       if (include_logical_resources) {
+                               g_string_append (sparql, "  ?ie nie:isStoredAs ?f .");
+                       }
+                       g_string_append_printf (sparql, " } ");
+               }
+
+               if (children_string) {
+                       g_string_append (sparql, merge->bulk_operation);
+                       g_string_append_printf (sparql,
+                                               " WHERE { "
+                                               "  ?f nie:url ?u ."
+                                               "  FILTER (%s)",
+                                               children_string->str);
+                       g_string_free (children_string, TRUE);
+
+                       if (include_logical_resources) {
+                               g_string_append (sparql, "  ?ie nie:isStoredAs ?f .");
+                       }
+                       g_string_append_printf (sparql, "} ");
+               }
+
+               return g_string_free (sparql, FALSE);
+       }
+
+       return NULL;
+}
+
+static BulkOperationMerge *
+bulk_operation_merge_new (const gchar *bulk_operation)
+{
+       BulkOperationMerge *operation;
+
+       operation = g_slice_new0 (BulkOperationMerge);
+       operation->bulk_operation = bulk_operation;
+
+       return operation;
+}
+
+static void
+bulk_operation_merge_free (BulkOperationMerge *operation)
+{
+       g_list_foreach (operation->tasks,
+                       (GFunc) tracker_task_unref,
+                       NULL);
+       g_list_free (operation->tasks);
+       g_slice_free (BulkOperationMerge, operation);
+}
+
+gboolean
+tracker_sparql_buffer_flush (TrackerSparqlBuffer *buffer,
+                             const gchar         *reason)
+{
+       TrackerSparqlBufferPrivate *priv;
+       GArray *sparql_array;
+       GPtrArray *bulk_sparql;
+       UpdateArrayData *update_data;
+       gint i;
+
+       priv = buffer->priv;
+
+       if (priv->n_updates > 0) {
+               return FALSE;
+       }
+
+       if (!priv->tasks ||
+           priv->tasks->len == 0) {
+               return FALSE;
+       }
+
+       g_debug ("Flushing SPARQL buffer, reason: %s", reason);
+
+       if (priv->flush_timeout_id != 0) {
+               g_source_remove (priv->flush_timeout_id);
+               priv->flush_timeout_id = 0;
+       }
+
+       /* Loop buffer and construct array of strings */
+       sparql_array = g_array_new (FALSE, TRUE, sizeof (gchar *));
+       bulk_sparql = g_ptr_array_new_with_free_func ((GDestroyNotify) g_free);
+
+       for (i = 0; i < priv->tasks->len; i++) {
+               SparqlTaskData *task_data;
+               TrackerTask *task;
+
+               task = g_ptr_array_index (priv->tasks, i);
+               task_data = tracker_task_get_data (task);
+
+               if (task_data->type == TASK_TYPE_SPARQL_STR) {
+                       g_array_append_val (sparql_array, task_data->data.str);
+               } else if (task_data->type == TASK_TYPE_SPARQL) {
+                       const gchar *str;
+
+                       str = tracker_sparql_builder_get_result (task_data->data.builder);
+                       g_array_append_val (sparql_array, str);
+               } else if (task_data->type == TASK_TYPE_BULK) {
+                       BulkOperationMerge *bulk = NULL;
+                       gchar *str;
+
+                       bulk = bulk_operation_merge_new (task_data->data.bulk.str);
+                       bulk->tasks = g_list_prepend (bulk->tasks,
+                                                     tracker_task_ref (task));
+
+                       str = bulk_operation_merge_finish (bulk);
+                       g_ptr_array_add (bulk_sparql, str);
+                       g_array_append_val (sparql_array, str);
+
+                       bulk_operation_merge_free (bulk);
+               }
+       }
+
+       update_data = g_slice_new0 (UpdateArrayData);
+       update_data->buffer = buffer;
+       update_data->tasks = g_ptr_array_ref (priv->tasks);
+       update_data->sparql_array = sparql_array;
+
+       /* Empty pool, update_data will keep
+        * references to the tasks to keep
+        * these alive.
+        */
+       g_ptr_array_unref (priv->tasks);
+       priv->tasks = NULL;
+       priv->n_updates++;
+
+       /* Start the update */
+       tracker_sparql_connection_update_array_async (priv->connection,
+                                                     (gchar **) update_data->sparql_array->data,
+                                                     update_data->sparql_array->len,
+                                                     G_PRIORITY_DEFAULT,
+                                                     NULL,
+                                                     tracker_sparql_buffer_update_array_cb,
+                                                     update_data);
+
+       /* These strings we generated here can be freed now */
+       g_ptr_array_free (bulk_sparql, TRUE);
+
+       return TRUE;
+}
+
+static void
+tracker_sparql_buffer_update_cb (GObject      *object,
+                                 GAsyncResult *result,
+                                 gpointer      user_data)
+{
+       UpdateData *update_data = user_data;
+       SparqlTaskData *task_data;
+       GError *error = NULL;
+
+       tracker_sparql_connection_update_finish (TRACKER_SPARQL_CONNECTION (object),
+                                                result, &error);
+
+       task_data = tracker_task_get_data (update_data->task);
+
+       /* Call finished handler with the error, if any */
+       if (error) {
+               g_task_return_error (task_data->async_task, error);
+       } else {
+               g_task_return_pointer (task_data->async_task,
+                                      update_data->task, NULL);
+       }
+
+       tracker_task_pool_remove (TRACKER_TASK_POOL (update_data->buffer),
+                                 update_data->task);
+       g_slice_free (UpdateData, update_data);
+}
+
+static void
+sparql_buffer_push_high_priority (TrackerSparqlBuffer *buffer,
+                                  TrackerTask         *task,
+                                  SparqlTaskData      *data)
+{
+       TrackerSparqlBufferPrivate *priv;
+       UpdateData *update_data;
+       const gchar *sparql = NULL;
+
+       priv = buffer->priv;
+
+       /* Task pool addition adds a reference (below) */
+       update_data = g_slice_new0 (UpdateData);
+       update_data->buffer = buffer;
+       update_data->task = task;
+
+       if (data->type == TASK_TYPE_SPARQL_STR) {
+               sparql = data->data.str;
+       } else if (data->type == TASK_TYPE_SPARQL) {
+               sparql = tracker_sparql_builder_get_result (data->data.builder);
+       }
+
+       tracker_task_pool_add (TRACKER_TASK_POOL (buffer), task);
+       tracker_sparql_connection_update_async (priv->connection,
+                                               sparql,
+                                               G_PRIORITY_HIGH,
+                                               NULL,
+                                               tracker_sparql_buffer_update_cb,
+                                               update_data);
+}
+
+static void
+sparql_buffer_push_to_pool (TrackerSparqlBuffer *buffer,
+                            TrackerTask         *task)
+{
+       TrackerSparqlBufferPrivate *priv;
+
+       priv = buffer->priv;
+
+       if (tracker_task_pool_get_size (TRACKER_TASK_POOL (buffer)) == 0) {
+               reset_flush_timeout (buffer);
+       }
+
+       /* Task pool addition increments reference */
+       tracker_task_pool_add (TRACKER_TASK_POOL (buffer), task);
+
+       if (!priv->tasks) {
+               priv->tasks = g_ptr_array_new_with_free_func ((GDestroyNotify) tracker_task_unref);
+       }
+
+       /* We add a reference here because we unref when removed from
+        * the GPtrArray. */
+       g_ptr_array_add (priv->tasks, tracker_task_ref (task));
+
+       if (tracker_task_pool_limit_reached (TRACKER_TASK_POOL (buffer))) {
+               tracker_sparql_buffer_flush (buffer, "SPARQL buffer limit reached");
+       } else if (priv->tasks->len > tracker_task_pool_get_limit (TRACKER_TASK_POOL (buffer)) / 2) {
+               /* We've filled half of the buffer, flush it as we receive more tasks */
+               tracker_sparql_buffer_flush (buffer, "SPARQL buffer half-full");
+       }
+}
+
+void
+tracker_sparql_buffer_push (TrackerSparqlBuffer *buffer,
+                            TrackerTask         *task,
+                            gint                 priority,
+                            GAsyncReadyCallback  cb,
+                            gpointer             user_data)
+{
+       SparqlTaskData *data;
+
+       g_return_if_fail (TRACKER_IS_SPARQL_BUFFER (buffer));
+       g_return_if_fail (task != NULL);
+
+       /* NOTE: We don't own the task and if we want it we have to
+        * reference it, each function below references task in
+        * different ways.
+        */
+       data = tracker_task_get_data (task);
+
+       if (!data->async_task) {
+               data->async_task = g_task_new (buffer, NULL, cb, user_data);
+               g_task_set_task_data (data->async_task,
+                                     tracker_task_ref (task),
+                                     (GDestroyNotify) tracker_task_unref);
+       }
+
+       if (priority <= G_PRIORITY_HIGH &&
+           data->type != TASK_TYPE_BULK) {
+               sparql_buffer_push_high_priority (buffer, task, data);
+       } else {
+               sparql_buffer_push_to_pool (buffer, task);
+       }
+}
+
+static SparqlTaskData *
+sparql_task_data_new (guint    type,
+                      gpointer data,
+                      guint    flags)
+{
+       SparqlTaskData *task_data;
+
+       task_data = g_slice_new0 (SparqlTaskData);
+       task_data->type = type;
+
+       switch (type) {
+       case TASK_TYPE_SPARQL_STR:
+               task_data->data.str = data;
+               break;
+       case TASK_TYPE_SPARQL:
+               task_data->data.builder = g_object_ref (data);
+               break;
+       case TASK_TYPE_BULK:
+               task_data->data.bulk.str = data;
+               task_data->data.bulk.flags = flags;
+               break;
+       }
+
+       return task_data;
+}
+
+static void
+sparql_task_data_free (SparqlTaskData *data)
+{
+       switch (data->type) {
+       case TASK_TYPE_SPARQL_STR:
+               g_free (data->data.str);
+               break;
+       case TASK_TYPE_SPARQL:
+               g_object_unref (data->data.builder);
+               break;
+       case TASK_TYPE_BULK:
+               /* nothing to free, the string is interned */
+               break;
+       }
+
+       if (data->async_task) {
+               g_object_unref (data->async_task);
+       }
+
+       g_slice_free (SparqlTaskData, data);
+}
+
+TrackerTask *
+tracker_sparql_task_new_take_sparql_str (GFile *file,
+                                         gchar *sparql_str)
+{
+       SparqlTaskData *data;
+
+       data = sparql_task_data_new (TASK_TYPE_SPARQL_STR, sparql_str, 0);
+       return tracker_task_new (file, data,
+                                (GDestroyNotify) sparql_task_data_free);
+}
+
+TrackerTask *
+tracker_sparql_task_new_with_sparql_str (GFile       *file,
+                                         const gchar *sparql_str)
+{
+       SparqlTaskData *data;
+
+       data = sparql_task_data_new (TASK_TYPE_SPARQL_STR,
+                                    g_strdup (sparql_str), 0);
+       return tracker_task_new (file, data,
+                                (GDestroyNotify) sparql_task_data_free);
+}
+
+TrackerTask *
+tracker_sparql_task_new_with_sparql (GFile                *file,
+                                     TrackerSparqlBuilder *builder)
+{
+       SparqlTaskData *data;
+
+       data = sparql_task_data_new (TASK_TYPE_SPARQL, builder, 0);
+       return tracker_task_new (file, data,
+                                (GDestroyNotify) sparql_task_data_free);
+}
+
+TrackerTask *
+tracker_sparql_task_new_bulk (GFile                *file,
+                              const gchar          *sparql_str,
+                              TrackerBulkTaskFlags  flags)
+{
+       SparqlTaskData *data;
+
+       data = sparql_task_data_new (TASK_TYPE_BULK,
+                                    (gchar *) g_intern_string (sparql_str),
+                                    flags);
+       return tracker_task_new (file, data,
+                                (GDestroyNotify) sparql_task_data_free);
+}
+
+TrackerTask *
+tracker_sparql_buffer_push_finish (TrackerSparqlBuffer  *buffer,
+                                   GAsyncResult         *res,
+                                   GError              **error)
+{
+       TrackerTask *task;
+
+       g_return_val_if_fail (TRACKER_IS_SPARQL_BUFFER (buffer), NULL);
+       g_return_val_if_fail (G_IS_ASYNC_RESULT (res), NULL);
+       g_return_val_if_fail (!error || !*error, NULL);
+
+       task = g_task_propagate_pointer (G_TASK (res), error);
+
+       if (!task)
+               task = g_task_get_task_data (G_TASK (res));
+
+       return task;
+}
diff --git a/src/libtracker-miner/tracker-sparql-buffer.h b/src/libtracker-miner/tracker-sparql-buffer.h
new file mode 100644
index 000000000..f7f34bdbe
--- /dev/null
+++ b/src/libtracker-miner/tracker-sparql-buffer.h
@@ -0,0 +1,92 @@
+/*
+ * Copyright (C) 2011, Nokia <ivan frade nokia com>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ * Boston, MA  02110-1301, USA.
+ *
+ * Author: Carlos Garnacho <carlos lanedo com>
+ */
+
+#ifndef __LIBTRACKER_MINER_SPARQL_BUFFER_H__
+#define __LIBTRACKER_MINER_SPARQL_BUFFER_H__
+
+#include <glib-object.h>
+#include <gio/gio.h>
+
+#include <libtracker-sparql/tracker-sparql.h>
+
+#include "tracker-task-pool.h"
+
+G_BEGIN_DECLS
+
+/* Task pool */
+#define TRACKER_TYPE_SPARQL_BUFFER         (tracker_sparql_buffer_get_type())
+#define TRACKER_SPARQL_BUFFER(o)           (G_TYPE_CHECK_INSTANCE_CAST ((o), TRACKER_TYPE_SPARQL_BUFFER, 
TrackerSparqlBuffer))
+#define TRACKER_SPARQL_BUFFER_CLASS(c)     (G_TYPE_CHECK_CLASS_CAST ((c),    TRACKER_TYPE_SPARQL_BUFFER, 
TrackerSparqlBufferClass))
+#define TRACKER_IS_SPARQL_BUFFER(o)        (G_TYPE_CHECK_INSTANCE_TYPE ((o), TRACKER_TYPE_SPARQL_BUFFER))
+#define TRACKER_IS_SPARQL_BUFFER_CLASS(c)  (G_TYPE_CHECK_CLASS_TYPE ((c),    TRACKER_TYPE_SPARQL_BUFFER))
+#define TRACKER_SPARQL_BUFFER_GET_CLASS(o) (G_TYPE_INSTANCE_GET_CLASS ((o),  TRACKER_TYPE_SPARQL_BUFFER, 
TrackerSparqlBufferClass))
+
+typedef struct _TrackerSparqlBuffer TrackerSparqlBuffer;
+typedef struct _TrackerSparqlBufferClass TrackerSparqlBufferClass;
+
+typedef enum {
+       TRACKER_BULK_MATCH_EQUALS   = 1 << 0,
+       TRACKER_BULK_MATCH_CHILDREN = 1 << 1,
+       TRACKER_BULK_MATCH_LOGICAL_RESOURCES = 1 << 2
+} TrackerBulkTaskFlags;
+
+struct _TrackerSparqlBuffer
+{
+       TrackerTaskPool parent_instance;
+       gpointer priv;
+};
+
+struct _TrackerSparqlBufferClass
+{
+       TrackerTaskPoolClass parent_class;
+};
+
+GType                tracker_sparql_buffer_get_type (void) G_GNUC_CONST;
+
+TrackerSparqlBuffer *tracker_sparql_buffer_new   (TrackerSparqlConnection *connection,
+                                                  guint                    limit);
+
+gboolean             tracker_sparql_buffer_flush (TrackerSparqlBuffer *buffer,
+                                                  const gchar         *reason);
+
+void                 tracker_sparql_buffer_push  (TrackerSparqlBuffer *buffer,
+                                                  TrackerTask         *task,
+                                                  gint                 priority,
+                                                  GAsyncReadyCallback  cb,
+                                                  gpointer             user_data);
+
+TrackerTask *        tracker_sparql_buffer_push_finish (TrackerSparqlBuffer  *buffer,
+                                                        GAsyncResult         *res,
+                                                        GError              **error);
+
+TrackerTask *        tracker_sparql_task_new_take_sparql_str (GFile                *file,
+                                                              gchar                *sparql_str);
+TrackerTask *        tracker_sparql_task_new_with_sparql_str (GFile                *file,
+                                                              const gchar          *sparql_str);
+TrackerTask *        tracker_sparql_task_new_with_sparql     (GFile                *file,
+                                                              TrackerSparqlBuilder *builder);
+TrackerTask *        tracker_sparql_task_new_bulk            (GFile                *file,
+                                                              const gchar          *sparql_str,
+                                                              TrackerBulkTaskFlags  flags);
+
+G_END_DECLS
+
+#endif /* __LIBTRACKER_MINER_SPARQL_BUFFER_H__ */
diff --git a/src/libtracker-miner/tracker-task-pool.c b/src/libtracker-miner/tracker-task-pool.c
new file mode 100644
index 000000000..b03dff026
--- /dev/null
+++ b/src/libtracker-miner/tracker-task-pool.c
@@ -0,0 +1,348 @@
+/*
+ * Copyright (C) 2011, Nokia <ivan frade nokia com>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ * Boston, MA  02110-1301, USA.
+ *
+ * Author: Carlos Garnacho <carlos lanedo com>
+ */
+
+#include "config-miners.h"
+
+#include "tracker-task-pool.h"
+
+enum {
+       PROP_0,
+       PROP_LIMIT,
+       PROP_LIMIT_REACHED
+};
+
+G_DEFINE_TYPE (TrackerTaskPool, tracker_task_pool, G_TYPE_OBJECT)
+
+typedef struct _TrackerTaskPoolPrivate TrackerTaskPoolPrivate;
+
+struct _TrackerTaskPoolPrivate
+{
+       GHashTable *tasks;
+       guint limit;
+};
+
+struct _TrackerTask
+{
+       GFile *file;
+       gpointer data;
+       GDestroyNotify destroy_notify;
+       gint ref_count;
+};
+
+static void
+tracker_task_pool_finalize (GObject *object)
+{
+       TrackerTaskPoolPrivate *priv;
+
+       priv = TRACKER_TASK_POOL (object)->priv;
+       g_hash_table_unref (priv->tasks);
+
+       G_OBJECT_CLASS (tracker_task_pool_parent_class)->finalize (object);
+}
+
+static void
+tracker_task_pool_set_property (GObject      *object,
+                                guint         param_id,
+                                const GValue *value,
+                                GParamSpec   *pspec)
+{
+       TrackerTaskPool *pool = TRACKER_TASK_POOL (object);
+
+
+       switch (param_id) {
+       case PROP_LIMIT:
+               tracker_task_pool_set_limit (pool,
+                                            g_value_get_uint (value));
+               break;
+       default:
+               G_OBJECT_WARN_INVALID_PROPERTY_ID (object, param_id, pspec);
+       }
+}
+
+static void
+tracker_task_pool_get_property (GObject    *object,
+                                guint       param_id,
+                                GValue     *value,
+                                GParamSpec *pspec)
+{
+       TrackerTaskPool *pool = TRACKER_TASK_POOL (object);
+
+       switch (param_id) {
+       case PROP_LIMIT:
+               g_value_set_uint (value,
+                                 tracker_task_pool_get_limit (pool));
+               break;
+       case PROP_LIMIT_REACHED:
+               g_value_set_boolean (value,
+                                    tracker_task_pool_limit_reached (pool));
+               break;
+       default:
+               G_OBJECT_WARN_INVALID_PROPERTY_ID (object, param_id, pspec);
+       }
+}
+
+static void
+tracker_task_pool_class_init (TrackerTaskPoolClass *klass)
+{
+       GObjectClass *object_class = G_OBJECT_CLASS (klass);
+
+       object_class->finalize = tracker_task_pool_finalize;
+       object_class->set_property = tracker_task_pool_set_property;
+       object_class->get_property = tracker_task_pool_get_property;
+
+       g_object_class_install_property (object_class,
+                                        PROP_LIMIT,
+                                        g_param_spec_uint ("limit",
+                                                           "Limit",
+                                                           "Task limit",
+                                                           1, G_MAXUINT, 1,
+                                                           G_PARAM_READWRITE));
+       g_object_class_install_property (object_class,
+                                        PROP_LIMIT_REACHED,
+                                        g_param_spec_boolean ("limit-reached",
+                                                              "Limit reached",
+                                                              "Task limit reached",
+                                                              FALSE,
+                                                              G_PARAM_READABLE));
+
+       g_type_class_add_private (klass, sizeof (TrackerTaskPoolPrivate));
+}
+
+static gboolean
+file_equal (GFile *file1,
+            GFile *file2)
+{
+       if (file1 == file2) {
+               return TRUE;
+       } else {
+               return g_file_equal (file1, file2);
+       }
+}
+
+static void
+tracker_task_pool_init (TrackerTaskPool *pool)
+{
+       TrackerTaskPoolPrivate *priv;
+
+       priv = pool->priv = G_TYPE_INSTANCE_GET_PRIVATE (pool,
+                                                        TRACKER_TYPE_TASK_POOL,
+                                                        TrackerTaskPoolPrivate);
+       priv->tasks = g_hash_table_new_full (g_file_hash,
+                                            (GEqualFunc) file_equal,
+                                            NULL,
+                                            (GDestroyNotify) tracker_task_unref);
+       priv->limit = 0;
+}
+
+TrackerTaskPool *
+tracker_task_pool_new (guint limit)
+{
+       return g_object_new (TRACKER_TYPE_TASK_POOL,
+                            "limit", limit,
+                            NULL);
+}
+
+void
+tracker_task_pool_set_limit (TrackerTaskPool *pool,
+                             guint            limit)
+{
+       TrackerTaskPoolPrivate *priv;
+       gboolean old_limit_reached;
+
+       g_return_if_fail (TRACKER_IS_TASK_POOL (pool));
+
+       old_limit_reached = tracker_task_pool_limit_reached (pool);
+
+       priv = pool->priv;
+       priv->limit = limit;
+
+       if (old_limit_reached !=
+           tracker_task_pool_limit_reached (pool)) {
+               g_object_notify (G_OBJECT (pool), "limit-reached");
+       }
+}
+
+guint
+tracker_task_pool_get_limit (TrackerTaskPool *pool)
+{
+       TrackerTaskPoolPrivate *priv;
+
+       g_return_val_if_fail (TRACKER_IS_TASK_POOL (pool), 0);
+
+       priv = pool->priv;
+       return priv->limit;
+}
+
+guint
+tracker_task_pool_get_size (TrackerTaskPool *pool)
+{
+       TrackerTaskPoolPrivate *priv;
+
+       g_return_val_if_fail (TRACKER_IS_TASK_POOL (pool), 0);
+
+       priv = pool->priv;
+       return g_hash_table_size (priv->tasks);
+}
+
+gboolean
+tracker_task_pool_limit_reached (TrackerTaskPool *pool)
+{
+       TrackerTaskPoolPrivate *priv;
+
+       g_return_val_if_fail (TRACKER_IS_TASK_POOL (pool), FALSE);
+
+       priv = pool->priv;
+       return (g_hash_table_size (priv->tasks) >= priv->limit);
+}
+
+void
+tracker_task_pool_add (TrackerTaskPool *pool,
+                       TrackerTask     *task)
+{
+       TrackerTaskPoolPrivate *priv;
+
+       g_return_if_fail (TRACKER_IS_TASK_POOL (pool));
+
+       priv = pool->priv;
+
+       g_hash_table_insert (priv->tasks,
+                            tracker_task_get_file (task),
+                            tracker_task_ref (task));
+
+       if (g_hash_table_size (priv->tasks) == priv->limit) {
+               g_object_notify (G_OBJECT (pool), "limit-reached");
+       }
+}
+
+gboolean
+tracker_task_pool_remove (TrackerTaskPool *pool,
+                          TrackerTask     *task)
+{
+       TrackerTaskPoolPrivate *priv;
+
+       g_return_val_if_fail (TRACKER_IS_TASK_POOL (pool), FALSE);
+
+       priv = pool->priv;
+
+       if (g_hash_table_remove (priv->tasks,
+                                tracker_task_get_file (task))) {
+               if (g_hash_table_size (priv->tasks) == priv->limit - 1) {
+                       /* We've gone below the threshold again */
+                       g_object_notify (G_OBJECT (pool), "limit-reached");
+               }
+
+               return TRUE;
+       }
+
+       return FALSE;
+}
+
+void
+tracker_task_pool_foreach (TrackerTaskPool *pool,
+                           GFunc            func,
+                           gpointer         user_data)
+{
+       TrackerTaskPoolPrivate *priv;
+       GHashTableIter iter;
+       TrackerTask *task;
+
+       g_return_if_fail (TRACKER_IS_TASK_POOL (pool));
+       g_return_if_fail (func != NULL);
+
+       priv = pool->priv;
+       g_hash_table_iter_init (&iter, priv->tasks);
+
+       while (g_hash_table_iter_next (&iter, NULL, (gpointer *) &task)) {
+               (func) (task, user_data);
+       }
+}
+
+TrackerTask *
+tracker_task_pool_find (TrackerTaskPool *pool,
+                        GFile           *file)
+{
+       TrackerTaskPoolPrivate *priv;
+
+       g_return_val_if_fail (TRACKER_IS_TASK_POOL (pool), NULL);
+       g_return_val_if_fail (G_IS_FILE (file), NULL);
+
+       priv = pool->priv;
+       return g_hash_table_lookup (priv->tasks, file);
+}
+
+/* Task */
+TrackerTask *
+tracker_task_new (GFile          *file,
+                  gpointer        data,
+                  GDestroyNotify  destroy_notify)
+{
+       TrackerTask *task;
+
+       task = g_slice_new0 (TrackerTask);
+       task->file = g_object_ref (file);
+       task->destroy_notify = destroy_notify;
+       task->data = data;
+       task->ref_count = 1;
+
+       return task;
+}
+
+TrackerTask *
+tracker_task_ref (TrackerTask *task)
+{
+       g_return_val_if_fail (task != NULL, NULL);
+
+       g_atomic_int_inc (&task->ref_count);
+
+       return task;
+}
+void
+tracker_task_unref (TrackerTask *task)
+{
+       g_return_if_fail (task != NULL);
+
+       if (g_atomic_int_dec_and_test (&task->ref_count)) {
+               g_object_unref (task->file);
+
+               if (task->data &&
+                   task->destroy_notify) {
+                       (task->destroy_notify) (task->data);
+               }
+
+               g_slice_free (TrackerTask, task);
+       }
+}
+
+GFile *
+tracker_task_get_file (TrackerTask *task)
+{
+       g_return_val_if_fail (task != NULL, NULL);
+
+       return task->file;
+}
+
+gpointer
+tracker_task_get_data (TrackerTask *task)
+{
+       g_return_val_if_fail (task != NULL, NULL);
+
+       return task->data;
+}
diff --git a/src/libtracker-miner/tracker-task-pool.h b/src/libtracker-miner/tracker-task-pool.h
new file mode 100644
index 000000000..7a2b86d07
--- /dev/null
+++ b/src/libtracker-miner/tracker-task-pool.h
@@ -0,0 +1,92 @@
+/*
+ * Copyright (C) 2011, Nokia <ivan frade nokia com>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ * Boston, MA  02110-1301, USA.
+ *
+ * Author: Carlos Garnacho <carlos lanedo com>
+ */
+
+#ifndef __LIBTRACKER_MINER_TASK_POOL_H__
+#define __LIBTRACKER_MINER_TASK_POOL_H__
+
+#include <glib-object.h>
+#include <gio/gio.h>
+
+G_BEGIN_DECLS
+
+/* Task pool */
+#define TRACKER_TYPE_TASK_POOL         (tracker_task_pool_get_type())
+#define TRACKER_TASK_POOL(o)           (G_TYPE_CHECK_INSTANCE_CAST ((o), TRACKER_TYPE_TASK_POOL, 
TrackerTaskPool))
+#define TRACKER_TASK_POOL_CLASS(c)     (G_TYPE_CHECK_CLASS_CAST ((c),    TRACKER_TYPE_TASK_POOL, 
TrackerTaskPoolClass))
+#define TRACKER_IS_TASK_POOL(o)        (G_TYPE_CHECK_INSTANCE_TYPE ((o), TRACKER_TYPE_TASK_POOL))
+#define TRACKER_IS_TASK_POOL_CLASS(c)  (G_TYPE_CHECK_CLASS_TYPE ((c),    TRACKER_TYPE_TASK_POOL))
+#define TRACKER_TASK_POOL_GET_CLASS(o) (G_TYPE_INSTANCE_GET_CLASS ((o),  TRACKER_TYPE_TASK_POOL, 
TrackerTaskPoolClass))
+
+typedef struct _TrackerTaskPool TrackerTaskPool;
+typedef struct _TrackerTaskPoolClass TrackerTaskPoolClass;
+typedef struct _TrackerTask TrackerTask;
+
+struct _TrackerTaskPool
+{
+       GObject parent_instance;
+       gpointer priv;
+};
+
+struct _TrackerTaskPoolClass
+{
+       GObjectClass parent_class;
+};
+
+GType    tracker_task_pool_get_type      (void) G_GNUC_CONST;
+
+TrackerTaskPool *tracker_task_pool_new   (guint limit);
+
+void     tracker_task_pool_set_limit     (TrackerTaskPool *pool,
+                                          guint            limit);
+guint    tracker_task_pool_get_limit     (TrackerTaskPool *pool);
+guint    tracker_task_pool_get_size      (TrackerTaskPool *pool);
+
+gboolean tracker_task_pool_limit_reached (TrackerTaskPool *pool);
+
+void     tracker_task_pool_add           (TrackerTaskPool *pool,
+                                          TrackerTask     *task);
+
+gboolean tracker_task_pool_remove        (TrackerTaskPool *pool,
+                                          TrackerTask     *task);
+
+void     tracker_task_pool_foreach       (TrackerTaskPool *pool,
+                                          GFunc            func,
+                                          gpointer         user_data);
+
+TrackerTask * tracker_task_pool_find     (TrackerTaskPool *pool,
+                                          GFile           *file);
+
+/* Task */
+TrackerTask * tracker_task_new         (GFile          *file,
+                                        gpointer        data,
+                                        GDestroyNotify  destroy_notify);
+
+GFile *       tracker_task_get_file    (TrackerTask    *task);
+
+TrackerTask * tracker_task_ref         (TrackerTask    *task);
+void          tracker_task_unref       (TrackerTask    *task);
+
+gpointer      tracker_task_get_data    (TrackerTask    *task);
+
+
+G_END_DECLS
+
+#endif /* __LIBTRACKER_MINER_TASK_POOL_H__ */
diff --git a/src/libtracker-miner/tracker-utils.c b/src/libtracker-miner/tracker-utils.c
new file mode 100644
index 000000000..1837bc7dd
--- /dev/null
+++ b/src/libtracker-miner/tracker-utils.c
@@ -0,0 +1,36 @@
+/*
+ * Copyright (C) 2009, Nokia <ivan frade nokia com>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ * Boston, MA  02110-1301, USA.
+ */
+
+#include "config-miners.h"
+
+#include "tracker-utils.h"
+
+gboolean
+tracker_accumulator_check_file (GSignalInvocationHint *hint,
+                                GValue                *return_accumulator,
+                                const GValue          *handler_return,
+                                gpointer               accumulator_data)
+{
+       gboolean use;
+
+       use = g_value_get_boolean (handler_return);
+       g_value_set_boolean (return_accumulator, use);
+
+       return (use == TRUE);
+}
diff --git a/src/libtracker-miner/tracker-utils.h b/src/libtracker-miner/tracker-utils.h
new file mode 100644
index 000000000..88b363496
--- /dev/null
+++ b/src/libtracker-miner/tracker-utils.h
@@ -0,0 +1,38 @@
+/*
+ * Copyright (C) 2009, Nokia <ivan frade nokia com>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ * Boston, MA  02110-1301, USA.
+ */
+
+#ifndef __LIBTRACKER_MINER_UTILS_H__
+#define __LIBTRACKER_MINER_UTILS_H__
+
+#if !defined (__LIBTRACKER_MINER_H_INSIDE__) && !defined (TRACKER_COMPILATION)
+#error "Only <libtracker-miner/tracker-miner.h> can be included directly."
+#endif
+
+#include <glib-object.h>
+
+G_BEGIN_DECLS
+
+gboolean tracker_accumulator_check_file (GSignalInvocationHint *hint,
+                                         GValue                *return_accumulator,
+                                         const GValue          *handler_return,
+                                         gpointer               accumulator_data);
+
+G_END_DECLS
+
+#endif /* __LIBTRACKER_MINER_UTILS_H__ */
diff --git a/src/meson.build b/src/meson.build
index 61a83023f..1717ac1e9 100644
--- a/src/meson.build
+++ b/src/meson.build
@@ -1,5 +1,6 @@
 # Shared common code
 subdir('libtracker-miners-common')
+subdir('libtracker-miner')
 
 # Internal data extraction helpers
 subdir('libtracker-extract')
diff --git a/tests/libtracker-miner/.gitignore b/tests/libtracker-miner/.gitignore
new file mode 100644
index 000000000..ea67dbaa5
--- /dev/null
+++ b/tests/libtracker-miner/.gitignore
@@ -0,0 +1,15 @@
+tracker-crawler
+tracker-crawler-test
+tracker-miner-manager
+tracker-miner-manager-test
+tracker-miner-mock.[ch]
+tracker-monitor-test
+tracker-thumbnailer-test
+tracker-password-provider-test
+tracker-priority-queue-test
+tracker-task-pool-test
+tracker-indexing-tree-test
+tracker-connection-mock.c
+tracker-file-enumerator-test
+tracker-file-notifier-test
+tracker-file-system-test
diff --git a/tests/libtracker-miner/data/dir/empty-dir/.hidden 
b/tests/libtracker-miner/data/dir/empty-dir/.hidden
new file mode 100644
index 000000000..e69de29bb
diff --git a/tests/libtracker-miner/data/dir/file1 b/tests/libtracker-miner/data/dir/file1
new file mode 100644
index 000000000..e69de29bb
diff --git a/tests/libtracker-miner/data/dir/file2 b/tests/libtracker-miner/data/dir/file2
new file mode 100644
index 000000000..e69de29bb
diff --git a/tests/libtracker-miner/data/empty-dir/.hidden b/tests/libtracker-miner/data/empty-dir/.hidden
new file mode 100644
index 000000000..e69de29bb
diff --git a/tests/libtracker-miner/data/file1 b/tests/libtracker-miner/data/file1
new file mode 100644
index 000000000..e69de29bb
diff --git a/tests/libtracker-miner/empty-gobject.c b/tests/libtracker-miner/empty-gobject.c
new file mode 100644
index 000000000..4c0bbad75
--- /dev/null
+++ b/tests/libtracker-miner/empty-gobject.c
@@ -0,0 +1,140 @@
+/* empty-gobject.c generated by valac, the Vala compiler
+ * generated from empty-gobject.vala, do not modify */
+
+
+#include <glib.h>
+#include <glib-object.h>
+
+
+#define TYPE_EMPTY_OBJECT (empty_object_get_type ())
+#define EMPTY_OBJECT(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), TYPE_EMPTY_OBJECT, EmptyObject))
+#define EMPTY_OBJECT_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), TYPE_EMPTY_OBJECT, EmptyObjectClass))
+#define IS_EMPTY_OBJECT(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), TYPE_EMPTY_OBJECT))
+#define IS_EMPTY_OBJECT_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), TYPE_EMPTY_OBJECT))
+#define EMPTY_OBJECT_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), TYPE_EMPTY_OBJECT, EmptyObjectClass))
+
+typedef struct _EmptyObject EmptyObject;
+typedef struct _EmptyObjectClass EmptyObjectClass;
+typedef struct _EmptyObjectPrivate EmptyObjectPrivate;
+
+struct _EmptyObject {
+       GObject parent_instance;
+       EmptyObjectPrivate * priv;
+};
+
+struct _EmptyObjectClass {
+       GObjectClass parent_class;
+};
+
+struct _EmptyObjectPrivate {
+       gint _id;
+};
+
+
+static gpointer empty_object_parent_class = NULL;
+
+GType empty_object_get_type (void);
+#define EMPTY_OBJECT_GET_PRIVATE(o) (G_TYPE_INSTANCE_GET_PRIVATE ((o), TYPE_EMPTY_OBJECT, 
EmptyObjectPrivate))
+enum  {
+       EMPTY_OBJECT_DUMMY_PROPERTY,
+       EMPTY_OBJECT_ID
+};
+EmptyObject* empty_object_new (void);
+EmptyObject* empty_object_construct (GType object_type);
+gint empty_object_get_id (EmptyObject* self);
+void empty_object_set_id (EmptyObject* self, gint value);
+static void empty_object_finalize (GObject* obj);
+static void empty_object_get_property (GObject * object, guint property_id, GValue * value, GParamSpec * 
pspec);
+static void empty_object_set_property (GObject * object, guint property_id, const GValue * value, GParamSpec 
* pspec);
+
+
+
+EmptyObject* empty_object_construct (GType object_type) {
+       EmptyObject * self;
+       self = (EmptyObject*) g_object_new (object_type, NULL);
+       return self;
+}
+
+
+EmptyObject* empty_object_new (void) {
+       return empty_object_construct (TYPE_EMPTY_OBJECT);
+}
+
+
+gint empty_object_get_id (EmptyObject* self) {
+       gint result;
+       g_return_val_if_fail (self != NULL, 0);
+       result = self->priv->_id;
+       return result;
+}
+
+
+void empty_object_set_id (EmptyObject* self, gint value) {
+       g_return_if_fail (self != NULL);
+       self->priv->_id = value;
+       g_object_notify ((GObject *) self, "id");
+}
+
+
+static void empty_object_class_init (EmptyObjectClass * klass) {
+       empty_object_parent_class = g_type_class_peek_parent (klass);
+       g_type_class_add_private (klass, sizeof (EmptyObjectPrivate));
+       G_OBJECT_CLASS (klass)->get_property = empty_object_get_property;
+       G_OBJECT_CLASS (klass)->set_property = empty_object_set_property;
+       G_OBJECT_CLASS (klass)->finalize = empty_object_finalize;
+       g_object_class_install_property (G_OBJECT_CLASS (klass), EMPTY_OBJECT_ID, g_param_spec_int ("id", 
"id", "id", G_MININT, G_MAXINT, 0, G_PARAM_STATIC_NAME | G_PARAM_STATIC_NICK | G_PARAM_STATIC_BLURB | 
G_PARAM_READABLE | G_PARAM_WRITABLE));
+}
+
+
+static void empty_object_instance_init (EmptyObject * self) {
+       self->priv = EMPTY_OBJECT_GET_PRIVATE (self);
+}
+
+
+static void empty_object_finalize (GObject* obj) {
+       EmptyObject * self;
+       self = EMPTY_OBJECT (obj);
+       G_OBJECT_CLASS (empty_object_parent_class)->finalize (obj);
+}
+
+
+GType empty_object_get_type (void) {
+       static GType empty_object_type_id = 0;
+       if (empty_object_type_id == 0) {
+               static const GTypeInfo g_define_type_info = { sizeof (EmptyObjectClass), (GBaseInitFunc) 
NULL, (GBaseFinalizeFunc) NULL, (GClassInitFunc) empty_object_class_init, (GClassFinalizeFunc) NULL, NULL, 
sizeof (EmptyObject), 0, (GInstanceInitFunc) empty_object_instance_init, NULL };
+               empty_object_type_id = g_type_register_static (G_TYPE_OBJECT, "EmptyObject", 
&g_define_type_info, 0);
+       }
+       return empty_object_type_id;
+}
+
+
+static void empty_object_get_property (GObject * object, guint property_id, GValue * value, GParamSpec * 
pspec) {
+       EmptyObject * self;
+       self = EMPTY_OBJECT (object);
+       switch (property_id) {
+               case EMPTY_OBJECT_ID:
+               g_value_set_int (value, empty_object_get_id (self));
+               break;
+               default:
+               G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+               break;
+       }
+}
+
+
+static void empty_object_set_property (GObject * object, guint property_id, const GValue * value, GParamSpec 
* pspec) {
+       EmptyObject * self;
+       self = EMPTY_OBJECT (object);
+       switch (property_id) {
+               case EMPTY_OBJECT_ID:
+               empty_object_set_id (self, g_value_get_int (value));
+               break;
+               default:
+               G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+               break;
+       }
+}
+
+
+
+
diff --git a/tests/libtracker-miner/empty-gobject.h b/tests/libtracker-miner/empty-gobject.h
new file mode 100644
index 000000000..c760ac751
--- /dev/null
+++ b/tests/libtracker-miner/empty-gobject.h
@@ -0,0 +1,43 @@
+/* empty-gobject.h generated by valac, the Vala compiler, do not modify */
+
+
+#ifndef __EMPTY_GOBJECT_H__
+#define __EMPTY_GOBJECT_H__
+
+#include <glib.h>
+#include <glib-object.h>
+
+G_BEGIN_DECLS
+
+
+#define TYPE_EMPTY_OBJECT (empty_object_get_type ())
+#define EMPTY_OBJECT(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), TYPE_EMPTY_OBJECT, EmptyObject))
+#define EMPTY_OBJECT_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), TYPE_EMPTY_OBJECT, EmptyObjectClass))
+#define IS_EMPTY_OBJECT(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), TYPE_EMPTY_OBJECT))
+#define IS_EMPTY_OBJECT_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), TYPE_EMPTY_OBJECT))
+#define EMPTY_OBJECT_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), TYPE_EMPTY_OBJECT, EmptyObjectClass))
+
+typedef struct _EmptyObject EmptyObject;
+typedef struct _EmptyObjectClass EmptyObjectClass;
+typedef struct _EmptyObjectPrivate EmptyObjectPrivate;
+
+struct _EmptyObject {
+       GObject parent_instance;
+       EmptyObjectPrivate * priv;
+};
+
+struct _EmptyObjectClass {
+       GObjectClass parent_class;
+};
+
+
+GType empty_object_get_type (void);
+EmptyObject* empty_object_new (void);
+EmptyObject* empty_object_construct (GType object_type);
+gint empty_object_get_id (EmptyObject* self);
+void empty_object_set_id (EmptyObject* self, gint value);
+
+
+G_END_DECLS
+
+#endif
diff --git a/tests/libtracker-miner/meson.build b/tests/libtracker-miner/meson.build
new file mode 100644
index 000000000..4c70c65e0
--- /dev/null
+++ b/tests/libtracker-miner/meson.build
@@ -0,0 +1,69 @@
+miner_test_c_args = test_c_args + [
+  '-DLIBEXEC_PATH="@0@/@1@"'.format(get_option('prefix'), get_option('libexecdir')),
+  '-DTEST',
+  '-DTEST_DATA_DIR="@0@/data"'.format(meson.current_source_dir()),
+  '-DTEST_MINERS_DIR="@0@/mock-miners"'.format(meson.current_source_dir()),
+  '-DTEST_ONTOLOGIES_DIR="@0@"'.format(tracker_uninstalled_nepomuk_ontologies_dir),
+]
+
+crawler_test = executable('tracker-crawler-test',
+  'tracker-crawler-test.c',
+  shared_libtracker_miner_crawler_sources,
+  dependencies: [tracker_miners_common_dep, tracker_miner, tracker_sparql],
+  c_args: miner_test_c_args
+)
+test('miner-crawler', crawler_test)
+
+file_notifier_test = executable('tracker-file-notifier-test',
+  'tracker-file-notifier-test.c',
+  dependencies: [tracker_miners_common_dep, tracker_miner, tracker_sparql],
+  c_args: miner_test_c_args
+)
+test('miner-file-notifier', file_notifier_test)
+
+file_system_test = executable('tracker-file-system-test',
+  'tracker-file-system-test.c',
+  dependencies: [tracker_miners_common_dep, tracker_miner, tracker_sparql],
+  c_args: miner_test_c_args
+)
+test('miner-file-system', file_system_test)
+
+indexing_tree_test = executable('tracker-indexing-tree-test',
+  'tracker-indexing-tree-test.c',
+  dependencies: [tracker_miners_common_dep, tracker_miner, tracker_sparql],
+  c_args: miner_test_c_args
+)
+test('miner-indexing-tree', indexing_tree_test)
+
+monitor_test = executable('tracker-monitor-test',
+  'tracker-monitor-test.c',
+  shared_libtracker_miner_monitor_sources,
+  dependencies: [tracker_miners_common_dep, tracker_miner, tracker_sparql],
+  c_args: miner_test_c_args
+)
+test('miner-monitor', monitor_test,
+  # FIXME: why is this test so slow?
+  timeout: 180)
+
+priority_queue_test = executable('tracker-priority-queue-test',
+  'tracker-priority-queue-test.c',
+  dependencies: [tracker_miners_common_dep, tracker_miner, tracker_sparql],
+  c_args: miner_test_c_args
+)
+test('miner-priority-queue', priority_queue_test)
+
+task_pool_test = executable('tracker-task-pool-test',
+  'tracker-task-pool-test.c',
+  dependencies: [tracker_miners_common_dep, tracker_miner, tracker_sparql],
+  c_args: miner_test_c_args
+)
+test('miner-task-pool', task_pool_test)
+
+thumbnailer_test = executable('tracker-thumbnailer-test',
+  'empty-gobject.c',
+  'thumbnailer-mock.c',
+  'tracker-thumbnailer-test.c',
+  dependencies: [tracker_miners_common_dep, tracker_miner, tracker_sparql],
+  c_args: miner_test_c_args
+)
+test('miner-thumbnailer', thumbnailer_test)
diff --git a/tests/libtracker-miner/miners-mock.c b/tests/libtracker-miner/miners-mock.c
new file mode 100644
index 000000000..fe46b398a
--- /dev/null
+++ b/tests/libtracker-miner/miners-mock.c
@@ -0,0 +1,276 @@
+/*
+ * Copyright (C) 2010, Nokia <ivan frade nokia com>
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+ * 02110-1301, USA.
+ */
+
+#include <string.h>
+
+#include <glib-object.h>
+#include <gobject/gvaluecollector.h>
+
+#include "empty-gobject.h"
+#include "miners-mock.h"
+#include "tracker-miner-mock.h"
+
+GHashTable *miners = NULL;
+
+void
+miners_mock_init ()
+{
+       TrackerMinerMock *miner;
+
+       miners = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, g_object_unref);
+
+       miner = tracker_miner_mock_new (MOCK_MINER_1);
+       tracker_miner_mock_set_paused (miner, FALSE);
+       g_hash_table_insert (miners, g_strdup (MOCK_MINER_1), miner);
+
+       miner = tracker_miner_mock_new (MOCK_MINER_2);
+       tracker_miner_mock_set_paused (miner, TRUE);
+       g_hash_table_insert (miners, g_strdup (MOCK_MINER_2), miner);
+}
+
+/*
+ * DBus overrides
+ */
+#if 0
+/* Todo: port to gdbus */
+
+DBusGConnection *
+dbus_g_bus_get (DBusBusType type, GError **error)
+{
+       return (DBusGConnection *) empty_object_new ();
+}
+
+DBusGProxy *
+dbus_g_proxy_new_for_name (DBusGConnection *connection,
+                           const gchar *service,
+                           const gchar *path,
+                           const gchar *interface )
+{
+       TrackerMinerMock *miner;
+
+       miner = (TrackerMinerMock *)g_hash_table_lookup (miners, service);
+       if (!miner) {
+               return (DBusGProxy *) empty_object_new ();
+       }
+       return (DBusGProxy *) miner;
+}
+
+void
+dbus_g_proxy_add_signal (DBusGProxy *proxy, const char *signal_name, GType first_type,...)
+{
+}
+
+void
+dbus_g_proxy_connect_signal (DBusGProxy *proxy,
+                             const char *signal_name,
+                             GCallback handler,
+                             void *data,
+                             GClosureNotify free_data_func)
+{
+       TrackerMinerMock *miner = (TrackerMinerMock *)proxy;
+
+       if (g_strcmp0 (signal_name, "NameOwnerChanged") == 0) {
+               return;
+       }
+
+       g_signal_connect (miner, g_utf8_strdown (signal_name, -1), handler, data);
+
+}
+
+/*
+ * Two mock miners available but only 1 running
+ */
+gboolean
+dbus_g_proxy_call (DBusGProxy *proxy,
+                   const gchar *function_name,
+                   GError  **error,
+                   GType first_arg_type, ...)
+{
+       va_list args;
+       GType   arg_type;
+       const gchar *running_services[] = { "org.gnome.Tomboy",
+                                           "org.gnome.GConf",
+                                           MOCK_MINER_1,
+                                           "org.gnome.SessionManager",
+                                           NULL};
+
+       va_start (args, first_arg_type);
+
+       if (g_strcmp0 (function_name, "ListNames") == 0) {
+               /*
+                *  G_TYPE_INVALID,
+                *  G_TYPE_STRV, &result,
+                *  G_TYPE_INVALID
+                */
+               GValue value = { 0, };
+               gchar *local_error = NULL;
+
+               arg_type = va_arg (args, GType);
+
+               g_assert (arg_type == G_TYPE_STRV);
+               g_value_init (&value, arg_type);
+               g_value_set_boxed (&value, running_services);
+               G_VALUE_LCOPY (&value,
+                              args, 0,
+                              &local_error);
+               g_free (local_error);
+               g_value_unset (&value);
+
+       } else if (g_strcmp0 (function_name, "NameHasOwner") == 0) {
+               /*
+                * G_TYPE_STRING, miner,
+                * G_TYPE_INVALID,
+                * G_TYPE_BOOLEAN, &active,
+                *  G_TYPE_INVALID)) {
+                */
+               GValue value = { 0, };
+               gchar *local_error = NULL;
+               const gchar *miner_name;
+               TrackerMinerMock *miner;
+               gboolean     active;
+
+               g_value_init (&value, G_TYPE_STRING);
+               G_VALUE_COLLECT (&value, args, 0, &local_error);
+               g_free (local_error);
+               miner_name = g_value_get_string (&value);
+
+               miner = (TrackerMinerMock *)g_hash_table_lookup (miners, miner_name);
+               active = !tracker_miner_mock_get_paused (miner);
+               g_value_unset (&value);
+
+               arg_type = va_arg (args, GType);
+               g_assert (arg_type == G_TYPE_INVALID);
+
+               arg_type = va_arg (args, GType);
+               g_assert (arg_type == G_TYPE_BOOLEAN);
+               g_value_init (&value, arg_type);
+               g_value_set_boolean (&value, active);
+               G_VALUE_LCOPY (&value,
+                              args, 0,
+                              &local_error);
+               g_free (local_error);
+               g_value_unset (&value);
+
+       } else if (g_strcmp0 (function_name, "GetPauseDetails") == 0) {
+               /*
+                *  G_TYPE_INVALID,
+                *  G_TYPE_STRV, &apps,
+                *  G_TYPE_STRV, &reasons,
+                *  G_TYPE_INVALID
+                */
+               GValue value = { 0, };
+               gchar *local_error = NULL;
+               gint   amount;
+               gchar **apps, **reasons;
+               TrackerMinerMock *miner = (TrackerMinerMock *)proxy;
+
+               arg_type = va_arg (args, GType);
+               g_assert (arg_type == G_TYPE_STRV);
+               g_value_init (&value, arg_type);
+               apps = tracker_miner_mock_get_apps (miner, &amount);
+               if (apps == NULL || amount == 0) {
+                       apps = g_new0 (gchar *, 1);
+               }
+               g_value_set_boxed (&value, apps);
+               G_VALUE_LCOPY (&value,
+                              args, 0,
+                              &local_error);
+               g_free (local_error);
+               g_value_unset (&value);
+
+               arg_type = va_arg (args, GType);
+               g_assert (arg_type == G_TYPE_STRV);
+               g_value_init (&value, arg_type);
+               reasons = tracker_miner_mock_get_reasons (miner, &amount);
+               if (reasons == NULL || amount == 0) {
+                       reasons = g_new0 (gchar *, 1);
+               }
+               g_value_set_boxed (&value, reasons);
+               G_VALUE_LCOPY (&value,
+                              args, 0,
+                              &local_error);
+               g_free (local_error);
+               g_value_unset (&value);
+
+       } else if (g_strcmp0 (function_name, "Pause") == 0) {
+               /*
+                *  G_TYPE_STRING, &app,
+                *  G_TYPE_STRING, &reason,
+                *  G_TYPE_INVALID,
+                *  G_TYPE_INT, &cookie,
+                *  G_TYPE_INVALID
+                */
+               GValue value_app = { 0, };
+               gchar *local_error = NULL;
+               GValue value_reason = {0, };
+               const gchar *app;
+               const gchar *reason;
+               TrackerMinerMock *miner = (TrackerMinerMock *)proxy;
+
+               g_value_init (&value_app, G_TYPE_STRING);
+               G_VALUE_COLLECT (&value_app, args, 0, &local_error);
+               g_free (local_error);
+               app = g_value_get_string (&value_app);
+
+               arg_type = va_arg (args, GType);
+               g_value_init (&value_reason, G_TYPE_STRING);
+               G_VALUE_COLLECT (&value_reason, args, 0, &local_error);
+               g_free (local_error);
+               reason = g_value_get_string (&value_reason);
+
+               tracker_miner_mock_pause (miner, app, reason);
+
+       } else if (g_strcmp0 (function_name, "Resume") == 0) {
+               /*
+                * G_TYPE_INT, &cookie
+                * G_TYPE_INVALID
+                */
+               TrackerMinerMock *miner = (TrackerMinerMock *)proxy;
+               tracker_miner_mock_resume (miner);
+
+       } else if (g_strcmp0 (function_name, "GetProgress") == 0) {
+               /* Whatever */
+       } else if (g_strcmp0 (function_name, "GetStatus") == 0) {
+               /* Whatever */
+       } else {
+               g_critical ("dbus_g_proxy_call '%s' unsupported", function_name);
+       }
+
+       va_end (args);
+       return TRUE;
+}
+
+
+void
+dbus_g_proxy_call_no_reply (DBusGProxy        *proxy,
+                            const char        *method,
+                            GType              first_arg_type,
+                            ...)
+{
+}
+
+
+void
+dbus_g_connection_unref (DBusGConnection *conn)
+{
+       /* It is an EmptyGObject */
+       g_object_unref (conn);
+}
+
+#endif
diff --git a/tests/libtracker-miner/miners-mock.h b/tests/libtracker-miner/miners-mock.h
new file mode 100644
index 000000000..36ffdb295
--- /dev/null
+++ b/tests/libtracker-miner/miners-mock.h
@@ -0,0 +1,42 @@
+/*
+ * Copyright (C) 2010, Nokia <ivan frade nokia com>
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+ * 02110-1301, USA.
+ */
+
+#ifndef __MINERS_MOCK_H__
+#define __MINERS_MOCK_H__
+
+#include <glib.h>
+
+G_BEGIN_DECLS
+
+#define MOCK_MINER_1 "org.freedesktop.Tracker1.Miner.Mock1"
+#define MOCK_MINER_2 "org.freedesktop.Tracker1.Miner.Mock2"
+
+/*
+ * Assumptions:
+ *
+ *  There are this two miners, 
+ *  Initial state: Mock1 is running, Mock2 is paused
+ *
+ */
+void    miners_mock_init (void);
+
+G_END_DECLS
+
+
+#endif
diff --git a/tests/libtracker-miner/mock-miners/mock-miner-1.desktop 
b/tests/libtracker-miner/mock-miners/mock-miner-1.desktop
new file mode 100644
index 000000000..1286bb89f
--- /dev/null
+++ b/tests/libtracker-miner/mock-miners/mock-miner-1.desktop
@@ -0,0 +1,5 @@
+[Desktop Entry]
+Name=Mock miner for testing
+Comment=Comment in the mock miner
+DBusName=org.freedesktop.Tracker1.Miner.Mock1
+DBusPath=/org/freedesktop/Tracker1/Miner/Mock1
diff --git a/tests/libtracker-miner/mock-miners/mock-miner-2.desktop 
b/tests/libtracker-miner/mock-miners/mock-miner-2.desktop
new file mode 100644
index 000000000..d753b5ae0
--- /dev/null
+++ b/tests/libtracker-miner/mock-miners/mock-miner-2.desktop
@@ -0,0 +1,5 @@
+[Desktop Entry]
+Name=Yet another mock miner
+Comment=Stupid and tedious test for the comment
+DBusName=org.freedesktop.Tracker1.Miner.Mock2
+DBusPath=/org/freedesktop/Tracker1/Miner/Mock2
diff --git a/tests/libtracker-miner/thumbnailer-mock.c b/tests/libtracker-miner/thumbnailer-mock.c
new file mode 100644
index 000000000..880f56fd9
--- /dev/null
+++ b/tests/libtracker-miner/thumbnailer-mock.c
@@ -0,0 +1,133 @@
+/*
+ * Copyright (C) 2010, Nokia <ivan frade nokia com>
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+ * 02110-1301, USA.
+ */
+
+#include <glib-object.h>
+#include <gobject/gvaluecollector.h>
+
+#include "empty-gobject.h"
+#include "thumbnailer-mock.h"
+
+static GList *calls = NULL;
+
+void
+dbus_mock_call_log_reset ()
+{
+       if (calls) {
+               g_list_foreach (calls, (GFunc)g_free, NULL);
+               g_list_free (calls);
+               calls = NULL;
+       }
+}
+
+GList *
+dbus_mock_call_log_get ()
+{
+       return calls;
+}
+
+#if 0
+
+static void
+dbus_mock_call_log_append (const gchar *function_name)
+{
+       calls = g_list_append (calls, g_strdup (function_name));
+}
+
+
+/* Port to gdbus */
+/*
+ * DBus overrides
+ */
+
+DBusGConnection *
+dbus_g_bus_get (DBusBusType type, GError **error)
+{
+       return (DBusGConnection *) empty_object_new ();
+}
+
+DBusGProxy *
+dbus_g_proxy_new_for_name (DBusGConnection *connection,
+                           const gchar *service,
+                           const gchar *path,
+                           const gchar *interface )
+{
+       return (DBusGProxy *) empty_object_new ();
+}
+
+gboolean
+dbus_g_proxy_call (DBusGProxy *proxy,
+                   const gchar *function_name,
+                   GError  **error,
+                   GType first_arg_type, ...)
+{
+       va_list args;
+       GType arg_type;
+       const gchar *supported_mimes[] = { "mock/one", "mock/two", NULL};
+       int     counter;
+
+       g_assert (g_strcmp0 (function_name, "GetSupported") == 0);
+
+       /*
+         G_TYPE_INVALID,
+         G_TYPE_STRV, &uri_schemes,
+         G_TYPE_STRV, &mime_types,
+         G_TYPE_INVALID);
+
+         Set the mock values in the second parameter :)
+       */
+
+       va_start (args, first_arg_type);
+       arg_type = va_arg (args, GType);
+
+       counter = 1;
+       while (arg_type != G_TYPE_INVALID) {
+
+               if (arg_type == G_TYPE_STRV && counter == 2) {
+                       gchar *local_error = NULL;
+                       GValue value = { 0, };
+                       g_value_init (&value, arg_type);
+                       g_value_set_boxed (&value, supported_mimes);
+                       G_VALUE_LCOPY (&value,
+                                      args, 0,
+                                      &local_error);
+                       g_free (local_error);
+                       g_value_unset (&value);
+               } else {
+                       gpointer *out_param;
+                       out_param = va_arg (args, gpointer *);
+               }
+               arg_type = va_arg (args, GType);
+               counter += 1;
+       }
+
+       va_end (args);
+       return TRUE;
+}
+
+
+void
+dbus_g_proxy_call_no_reply (DBusGProxy        *proxy,
+                            const char        *method,
+                            GType              first_arg_type,
+                            ...)
+{
+       dbus_mock_call_log_append (method);
+}
+#endif
+
diff --git a/tests/libtracker-miner/thumbnailer-mock.h b/tests/libtracker-miner/thumbnailer-mock.h
new file mode 100644
index 000000000..8dd8b7279
--- /dev/null
+++ b/tests/libtracker-miner/thumbnailer-mock.h
@@ -0,0 +1,33 @@
+/*
+ * Copyright (C) 2010, Nokia <ivan frade nokia com>
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+ * 02110-1301, USA.
+ */
+
+#ifndef __THUMBNAILER_MOCK_H__
+#define __THUMBNAILER_MOCK_H__
+
+#include <glib.h>
+
+G_BEGIN_DECLS
+
+void    dbus_mock_call_log_reset (void);
+GList * dbus_mock_call_log_get   (void);
+
+G_END_DECLS
+
+
+#endif
diff --git a/tests/libtracker-miner/tracker-connection-mock.vala 
b/tests/libtracker-miner/tracker-connection-mock.vala
new file mode 100644
index 000000000..f8ed123fc
--- /dev/null
+++ b/tests/libtracker-miner/tracker-connection-mock.vala
@@ -0,0 +1,97 @@
+using GLib;
+using Tracker;
+
+
+public class TrackerMockResults : Tracker.Sparql.Cursor {
+       int rows;
+       int current_row = -1;
+       string[,] results;
+       string[] var_names;
+       Sparql.ValueType[] types;
+       int cols;
+
+       public TrackerMockResults (owned string[,] results, int rows, int cols, string[] var_names, 
Sparql.ValueType[] types) {
+               this.rows = rows;
+               this.cols = cols;
+               this.results = (owned) results;
+               this.types = types;
+               this.var_names = var_names;
+       }
+
+       public override int n_columns { get { return cols; } }
+
+       public override Sparql.ValueType get_value_type (int column)
+       requires (current_row >= 0) {
+               return this.types[column];
+       }
+
+       public override unowned string? get_variable_name (int column)
+       requires (current_row >= 0) {
+               return this.var_names[column];
+       }
+
+       public override unowned string? get_string (int column, out long length = null)
+       requires (current_row >= 0) {
+               unowned string str;
+
+               str = results[current_row, column];
+
+               length = str.length;
+
+               return str;
+       }
+
+       public override bool next (Cancellable? cancellable = null) throws GLib.Error {
+               if (current_row >= rows - 1) {
+                       return false;
+               }
+               current_row++;
+               return true;
+       }
+
+       public override async bool next_async (Cancellable? cancellable = null) throws GLib.Error {
+               /* This cursor isn't blocking, it's fine to just call next here */
+               return next (cancellable);
+       }
+
+       public override void rewind () {
+               current_row = 0;
+       }
+}
+
+
+
+
+public class TrackerMockConnection : Sparql.Connection {
+
+    TrackerMockResults results = null;
+    TrackerMockResults hardcoded = new TrackerMockResults ({{"11", "12"}, {"21", "22"}}, 2, 2,
+                                                           {"artist", "album"},
+                                                           {Sparql.ValueType.STRING, 
Sparql.ValueType.STRING});
+
+       public override Sparql.Cursor query (string sparql,
+                                  Cancellable? cancellable = null)
+    throws Sparql.Error, IOError, DBusError {
+        if (this.results != null) {
+            return results;
+        } else {
+            return hardcoded;
+        }
+    }
+
+
+       public async override Sparql.Cursor query_async (string sparql, Cancellable? cancellable = null)
+    throws Sparql.Error, IOError, DBusError {
+        if (this.results != null) {
+            return results;
+        } else {
+            return hardcoded;
+        }
+    }
+
+
+    public void set_results (TrackerMockResults results) {
+        this.results = results;
+    }
+
+}
diff --git a/tests/libtracker-miner/tracker-crawler-test.c b/tests/libtracker-miner/tracker-crawler-test.c
new file mode 100644
index 000000000..b3b18d886
--- /dev/null
+++ b/tests/libtracker-miner/tracker-crawler-test.c
@@ -0,0 +1,350 @@
+/*
+ * Copyright (C) 2010, Nokia <ivan frade nokia com>
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+ * 02110-1301, USA.
+ */
+
+#include "config-miners.h"
+
+#include <locale.h>
+
+#include <libtracker-miner/tracker-crawler.h>
+
+typedef struct CrawlerTest CrawlerTest;
+
+struct CrawlerTest {
+       GMainLoop *main_loop;
+       guint directories_found;
+       guint directories_ignored;
+       guint files_found;
+       guint files_ignored;
+       gboolean interrupted;
+
+       /* signals statistics */
+       guint n_check_directory;
+       guint n_check_directory_contents;
+       guint n_check_file;
+};
+
+static void
+crawler_finished_cb (TrackerCrawler *crawler,
+                     gboolean        interrupted,
+                     gpointer        user_data)
+{
+       CrawlerTest *test = user_data;
+
+       test->interrupted = interrupted;
+
+       if (test->main_loop) {
+               g_main_loop_quit (test->main_loop);
+       }
+}
+
+static void
+crawler_directory_crawled_cb (TrackerCrawler *crawler,
+                              GFile          *directory,
+                              GNode          *tree,
+                              guint           directories_found,
+                              guint           directories_ignored,
+                              guint           files_found,
+                              guint           files_ignored,
+                              gpointer        user_data)
+{
+       CrawlerTest *test = user_data;
+
+       test->directories_found = directories_found;
+       test->directories_ignored = directories_ignored;
+       test->files_found = files_found;
+       test->files_ignored = files_ignored;
+
+       g_assert_cmpint (g_node_n_nodes (tree, G_TRAVERSE_ALL), ==, directories_found + files_found);
+}
+
+static gboolean
+crawler_check_directory_cb (TrackerCrawler *crawler,
+                           GFile          *file,
+                           gpointer        user_data)
+{
+       CrawlerTest *test = user_data;
+
+       test->n_check_directory++;
+
+       return TRUE;
+}
+
+static gboolean
+crawler_check_file_cb (TrackerCrawler *crawler,
+                      GFile          *file,
+                      gpointer        user_data)
+{
+       CrawlerTest *test = user_data;
+
+       test->n_check_file++;
+
+       return TRUE;
+}
+
+static gboolean
+crawler_check_directory_contents_cb (TrackerCrawler *crawler,
+                                    GFile          *file,
+                                    GList          *contents,
+                                    gpointer        user_data)
+{
+       CrawlerTest *test = user_data;
+
+       test->n_check_directory_contents++;
+
+       return TRUE;
+}
+
+static void
+test_crawler_crawl (void)
+{
+       TrackerCrawler *crawler;
+       CrawlerTest test = { 0 };
+       gboolean started;
+       GFile *file;
+
+       test.main_loop = g_main_loop_new (NULL, FALSE);
+
+       crawler = tracker_crawler_new (NULL);
+       g_signal_connect (crawler, "finished",
+                         G_CALLBACK (crawler_finished_cb), &test);
+
+       file = g_file_new_for_path (TEST_DATA_DIR);
+
+       started = tracker_crawler_start (crawler, file, TRACKER_DIRECTORY_FLAG_NONE, -1);
+
+       g_assert_cmpint (started, ==, 1);
+
+       g_main_loop_run (test.main_loop);
+
+       g_assert_cmpint (test.interrupted, ==, 0);
+
+       g_main_loop_unref (test.main_loop);
+       g_object_unref (crawler);
+       g_object_unref (file);
+}
+
+static void
+test_crawler_crawl_interrupted (void)
+{
+       TrackerCrawler *crawler;
+       CrawlerTest test = { 0 };
+       gboolean started;
+       GFile *file;
+
+       crawler = tracker_crawler_new (NULL);
+       g_signal_connect (crawler, "finished",
+                         G_CALLBACK (crawler_finished_cb), &test);
+
+       file = g_file_new_for_path (TEST_DATA_DIR);
+
+       started = tracker_crawler_start (crawler, file, TRACKER_DIRECTORY_FLAG_NONE, -1);
+
+       g_assert_cmpint (started, ==, 1);
+
+       tracker_crawler_stop (crawler);
+
+       g_assert_cmpint (test.interrupted, ==, 1);
+
+       g_object_unref (crawler);
+       g_object_unref (file);
+}
+
+static void
+test_crawler_crawl_nonexisting (void)
+{
+       TrackerCrawler *crawler;
+       GFile *file;
+       gboolean started;
+
+       crawler = tracker_crawler_new (NULL);
+       file = g_file_new_for_path (TEST_DATA_DIR "-idontexist");
+
+       started = tracker_crawler_start (crawler, file, TRACKER_DIRECTORY_FLAG_NONE, -1);
+
+       g_assert_cmpint (started, ==, 0);
+
+       g_object_unref (crawler);
+       g_object_unref (file);
+}
+
+static void
+test_crawler_crawl_recursive (void)
+{
+       TrackerCrawler *crawler;
+       CrawlerTest test = { 0 };
+       GFile *file;
+
+       test.main_loop = g_main_loop_new (NULL, FALSE);
+
+       crawler = tracker_crawler_new (NULL);
+       g_signal_connect (crawler, "finished",
+                         G_CALLBACK (crawler_finished_cb), &test);
+       g_signal_connect (crawler, "directory-crawled",
+                         G_CALLBACK (crawler_directory_crawled_cb), &test);
+
+       file = g_file_new_for_path (TEST_DATA_DIR);
+
+       tracker_crawler_start (crawler, file, TRACKER_DIRECTORY_FLAG_NONE, -1);
+
+       g_main_loop_run (test.main_loop);
+
+       /* There are 4 directories and 5 (2 hidden) files */
+       g_assert_cmpint (test.directories_found, ==, 4);
+       g_assert_cmpint (test.directories_ignored, ==, 0);
+       g_assert_cmpint (test.files_found, ==, 5);
+       g_assert_cmpint (test.files_ignored, ==, 0);
+
+       g_main_loop_unref (test.main_loop);
+       g_object_unref (crawler);
+       g_object_unref (file);
+}
+
+static void
+test_crawler_crawl_non_recursive (void)
+{
+       TrackerCrawler *crawler;
+       CrawlerTest test = { 0 };
+       GFile *file;
+
+       test.main_loop = g_main_loop_new (NULL, FALSE);
+
+       crawler = tracker_crawler_new (NULL);
+       g_signal_connect (crawler, "finished",
+                         G_CALLBACK (crawler_finished_cb), &test);
+       g_signal_connect (crawler, "directory-crawled",
+                         G_CALLBACK (crawler_directory_crawled_cb), &test);
+
+       file = g_file_new_for_path (TEST_DATA_DIR);
+
+       tracker_crawler_start (crawler, file, TRACKER_DIRECTORY_FLAG_NONE, 1);
+
+       g_main_loop_run (test.main_loop);
+
+       /* There are 3 directories (including parent) and 1 file in toplevel dir */
+       g_assert_cmpint (test.directories_found, ==, 3);
+       g_assert_cmpint (test.directories_ignored, ==, 0);
+       g_assert_cmpint (test.files_found, ==, 1);
+       g_assert_cmpint (test.files_ignored, ==, 0);
+
+       g_main_loop_unref (test.main_loop);
+       g_object_unref (crawler);
+       g_object_unref (file);
+}
+
+static void
+test_crawler_crawl_n_signals (void)
+{
+       TrackerCrawler *crawler;
+       CrawlerTest test = { 0 };
+       GFile *file;
+
+       test.main_loop = g_main_loop_new (NULL, FALSE);
+
+       crawler = tracker_crawler_new (NULL);
+       g_signal_connect (crawler, "finished",
+                         G_CALLBACK (crawler_finished_cb), &test);
+       g_signal_connect (crawler, "directory-crawled",
+                         G_CALLBACK (crawler_directory_crawled_cb), &test);
+       g_signal_connect (crawler, "check-directory",
+                         G_CALLBACK (crawler_check_directory_cb), &test);
+       g_signal_connect (crawler, "check-directory-contents",
+                         G_CALLBACK (crawler_check_directory_contents_cb), &test);
+       g_signal_connect (crawler, "check-file",
+                         G_CALLBACK (crawler_check_file_cb), &test);
+
+       file = g_file_new_for_path (TEST_DATA_DIR);
+
+       tracker_crawler_start (crawler, file, TRACKER_DIRECTORY_FLAG_NONE, -1);
+
+       g_main_loop_run (test.main_loop);
+
+       g_assert_cmpint (test.directories_found, ==, test.n_check_directory);
+       g_assert_cmpint (test.directories_found, ==, test.n_check_directory_contents);
+       g_assert_cmpint (test.files_found, ==, test.n_check_file);
+
+       g_main_loop_unref (test.main_loop);
+       g_object_unref (crawler);
+       g_object_unref (file);
+}
+
+static void
+test_crawler_crawl_n_signals_non_recursive (void)
+{
+       TrackerCrawler *crawler;
+       CrawlerTest test = { 0 };
+       GFile *file;
+
+       setlocale (LC_ALL, "");
+
+       test.main_loop = g_main_loop_new (NULL, FALSE);
+
+       crawler = tracker_crawler_new (NULL);
+       g_signal_connect (crawler, "finished",
+                         G_CALLBACK (crawler_finished_cb), &test);
+       g_signal_connect (crawler, "directory-crawled",
+                         G_CALLBACK (crawler_directory_crawled_cb), &test);
+       g_signal_connect (crawler, "check-directory",
+                         G_CALLBACK (crawler_check_directory_cb), &test);
+       g_signal_connect (crawler, "check-directory-contents",
+                         G_CALLBACK (crawler_check_directory_contents_cb), &test);
+       g_signal_connect (crawler, "check-file",
+                         G_CALLBACK (crawler_check_file_cb), &test);
+
+       file = g_file_new_for_path (TEST_DATA_DIR);
+
+       tracker_crawler_start (crawler, file, TRACKER_DIRECTORY_FLAG_NONE, 1);
+
+       g_main_loop_run (test.main_loop);
+
+       g_assert_cmpint (test.directories_found, ==, test.n_check_directory);
+       g_assert_cmpint (1, ==, test.n_check_directory_contents);
+       g_assert_cmpint (test.files_found, ==, test.n_check_file);
+
+       g_main_loop_unref (test.main_loop);
+       g_object_unref (crawler);
+       g_object_unref (file);
+}
+
+int
+main (int    argc,
+      char **argv)
+{
+       g_test_init (&argc, &argv, NULL);
+
+       g_test_message ("Testing filesystem crawler");
+
+       g_test_add_func ("/libtracker-miner/tracker-crawler/crawl",
+                        test_crawler_crawl);
+       g_test_add_func ("/libtracker-miner/tracker-crawler/crawl-interrupted",
+                        test_crawler_crawl_interrupted);
+       g_test_add_func ("/libtracker-miner/tracker-crawler/crawl-nonexisting",
+                        test_crawler_crawl_nonexisting);
+
+       g_test_add_func ("/libtracker-miner/tracker-crawler/crawl-recursive",
+                        test_crawler_crawl_recursive);
+       g_test_add_func ("/libtracker-miner/tracker-crawler/crawl-non-recursive",
+                        test_crawler_crawl_non_recursive);
+
+       g_test_add_func ("/libtracker-miner/tracker-crawler/crawl-n-signals",
+                        test_crawler_crawl_n_signals);
+       g_test_add_func ("/libtracker-miner/tracker-crawler/crawl-n-signals-non-recursive",
+                        test_crawler_crawl_n_signals_non_recursive);
+
+       return g_test_run ();
+}
diff --git a/tests/libtracker-miner/tracker-file-enumerator-test.c 
b/tests/libtracker-miner/tracker-file-enumerator-test.c
new file mode 100644
index 000000000..139992b60
--- /dev/null
+++ b/tests/libtracker-miner/tracker-file-enumerator-test.c
@@ -0,0 +1,94 @@
+/*
+ * Copyright (C) 2014, Softathome <contact softathome com>
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+ * 02110-1301, USA.
+ */
+
+#include "config-miners.h"
+
+#include <locale.h>
+
+#include <libtracker-miner/tracker-miner.h>
+/* Normally private */
+#include <libtracker-miner/tracker-file-data-provider.h>
+
+static void
+test_enumerator_and_provider (void)
+{
+       GFileEnumerator *fe;
+       TrackerDataProvider *data_provider;
+       GFileEnumerator *enumerator;
+       GFileInfo *info;
+       GFile *url;
+       GSList *files, *l;
+       GError *error = NULL;
+       gint count = 0;
+       const gchar *path;
+
+       data_provider = tracker_file_data_provider_new ();
+       g_assert_nonnull (data_provider);
+
+       /* FIXME: Use better tmp data structure */
+       url = g_file_new_for_path (g_get_tmp_dir ());
+       g_assert_nonnull (url);
+
+       /* fe = g_file_enumerate_children ( */
+       /*                                 0, */
+       /*                                 NULL, */
+       /*                                 &error); */
+
+       /* g_assert_no_error (error); */
+       /* g_assert_nonnull (fe); */
+
+       /* enumerator = tracker_file_enumerator_new (fe); */
+       /* g_assert_nonnull (enumerator); */
+
+       enumerator = tracker_data_provider_begin (data_provider,
+                                                 url,
+                                                 G_FILE_ATTRIBUTE_STANDARD_NAME "," \
+                                                 G_FILE_ATTRIBUTE_STANDARD_TYPE,
+                                                 G_FILE_QUERY_INFO_NOFOLLOW_SYMLINKS,
+                                                 NULL,
+                                                 &error);
+       g_assert_no_error (error);
+       g_assert_nonnull (enumerator);
+
+       while ((info = g_file_enumerator_next_file (enumerator, NULL, &error)) != NULL) {
+               g_assert_no_error (error);
+               count++;
+       }
+
+       g_assert_no_error (error);
+       g_assert (count > 0);
+
+       g_object_unref (enumerator);
+       g_object_unref (data_provider);
+}
+
+int
+main (int argc, char **argv)
+{
+       setlocale (LC_ALL, "");
+
+       g_test_init (&argc, &argv, NULL);
+
+       g_test_message ("Testing file enumerator");
+
+       g_test_add_func ("/libtracker-miner/tracker-enumerator-and-provider",
+                        test_enumerator_and_provider);
+
+       return g_test_run ();
+}
diff --git a/tests/libtracker-miner/tracker-file-notifier-test.c 
b/tests/libtracker-miner/tracker-file-notifier-test.c
new file mode 100644
index 000000000..8355c930d
--- /dev/null
+++ b/tests/libtracker-miner/tracker-file-notifier-test.c
@@ -0,0 +1,794 @@
+/*
+ * Copyright (C) 2011, Nokia <ivan frade nokia com>
+ *
+ * Author: Carlos Garnacho  <carlos lanedo com>
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+ * 02110-1301, USA.
+ */
+
+#include "config-miners.h"
+
+#include <string.h>
+#include <unistd.h>
+#include <stdlib.h>
+#include <locale.h>
+
+#include <glib.h>
+#include <glib/gstdio.h>
+
+#include <libtracker-miner/tracker-miner-enums.h>
+#include <libtracker-miner/tracker-file-notifier.h>
+
+typedef struct {
+       gint op;
+       gchar *path;
+       gchar *other_path;
+} FilesystemOperation;
+
+/* Fixture struct */
+typedef struct {
+       GFile *test_file;
+       gchar *test_path;
+
+       TrackerSparqlConnection *connection;
+       TrackerIndexingTree *indexing_tree;
+       GMainLoop *main_loop;
+
+       /* The file notifier to test */
+       TrackerFileNotifier *notifier;
+
+       guint expire_timeout_id;
+       gboolean expect_finished;
+
+       FilesystemOperation *expect_results;
+       guint expect_n_results;
+
+       GList *ops;
+} TestCommonContext;
+
+typedef enum {
+       OPERATION_CREATE,
+       OPERATION_UPDATE,
+       OPERATION_DELETE,
+       OPERATION_MOVE
+} OperationType;
+
+#if GLIB_MINOR_VERSION < 30
+gchar *
+g_mkdtemp (gchar *tmpl)
+{
+       return mkdtemp (tmpl);
+}
+#endif
+
+#define test_add(path,fun)       \
+       g_test_add (path, \
+                   TestCommonContext, \
+                   NULL, \
+                   test_common_context_setup, \
+                   fun, \
+                   test_common_context_teardown)
+
+static void
+filesystem_operation_free (FilesystemOperation *op)
+{
+       g_free (op->path);
+       g_free (op->other_path);
+       g_free (op);
+}
+
+static void
+perform_file_operation (TestCommonContext *fixture,
+                        gchar             *command,
+                        gchar             *filename,
+                        gchar             *other_filename)
+{
+       gchar *path, *other_path, *call;
+
+       path = g_build_filename (fixture->test_path, filename, NULL);
+
+       if (other_filename) {
+               other_path = g_build_filename (fixture->test_path, filename, NULL);
+               call = g_strdup_printf ("%s %s %s", command, path, other_path);
+               g_free (other_path);
+       } else {
+               call = g_strdup_printf ("%s %s", command, path);
+       }
+
+       system (call);
+
+       g_free (call);
+       g_free (path);
+}
+
+#define CREATE_FOLDER(fixture,p) perform_file_operation((fixture),"mkdir",(p),NULL)
+#define CREATE_UPDATE_FILE(fixture,p) perform_file_operation((fixture),"touch",(p),NULL)
+#define DELETE_FILE(fixture,p) perform_file_operation((fixture),"rm",(p),NULL)
+#define DELETE_FOLDER(fixture,p) perform_file_operation((fixture),"rm -rf",(p),NULL)
+
+static void
+file_notifier_file_created_cb (TrackerFileNotifier *notifier,
+                               GFile               *file,
+                               gpointer             user_data)
+{
+       TestCommonContext *fixture = user_data;
+       FilesystemOperation *op;
+
+       op = g_new0 (FilesystemOperation, 1);
+       op->op = OPERATION_CREATE;
+       op->path = g_file_get_relative_path (fixture->test_file , file);
+
+       fixture->ops = g_list_prepend (fixture->ops, op);
+
+       if (!fixture->expect_finished &&
+           fixture->expect_n_results == g_list_length (fixture->ops)) {
+               g_main_loop_quit (fixture->main_loop);
+       }
+}
+
+static void
+file_notifier_file_updated_cb (TrackerFileNotifier *notifier,
+                               GFile               *file,
+                               gboolean             attributes_only,
+                               gpointer             user_data)
+{
+       TestCommonContext *fixture = user_data;
+       FilesystemOperation *op;
+
+       op = g_new0 (FilesystemOperation, 1);
+       op->op = OPERATION_UPDATE;
+       op->path = g_file_get_relative_path (fixture->test_file , file);
+
+       fixture->ops = g_list_prepend (fixture->ops, op);
+
+       if (!fixture->expect_finished &&
+           fixture->expect_n_results == g_list_length (fixture->ops)) {
+               g_main_loop_quit (fixture->main_loop);
+       }
+}
+
+static void
+file_notifier_file_deleted_cb (TrackerFileNotifier *notifier,
+                               GFile               *file,
+                               gpointer             user_data)
+{
+       TestCommonContext *fixture = user_data;
+       FilesystemOperation *op;
+       guint i;
+
+       op = g_new0 (FilesystemOperation, 1);
+       op->op = OPERATION_DELETE;
+       op->path = g_file_get_relative_path (fixture->test_file , file);
+
+       for (i = 0; i < fixture->expect_n_results; i++) {
+               if (fixture->expect_results[i].op == op->op &&
+                   g_strcmp0 (fixture->expect_results[i].path, op->path) != 0 &&
+                   g_str_has_prefix (op->path, fixture->expect_results[i].path)) {
+                       /* Deleted file is the child of a directory
+                        * that's expected to be deleted.
+                        */
+                       filesystem_operation_free (op);
+                       return;
+               }
+       }
+
+       fixture->ops = g_list_prepend (fixture->ops, op);
+
+       if (!fixture->expect_finished &&
+           fixture->expect_n_results == g_list_length (fixture->ops)) {
+               g_main_loop_quit (fixture->main_loop);
+       }
+}
+
+static void
+file_notifier_file_moved_cb (TrackerFileNotifier *notifier,
+                             GFile               *file,
+                             GFile               *other_file,
+                             gpointer             user_data)
+{
+       TestCommonContext *fixture = user_data;
+       FilesystemOperation *op;
+
+       op = g_new0 (FilesystemOperation, 1);
+       op->op = OPERATION_MOVE;
+       op->path = g_file_get_relative_path (fixture->test_file , file);
+       op->other_path = g_file_get_relative_path (fixture->test_file ,
+                                                  other_file);
+
+       fixture->ops = g_list_prepend (fixture->ops, op);
+
+       if (!fixture->expect_finished &&
+           fixture->expect_n_results == g_list_length (fixture->ops)) {
+               g_main_loop_quit (fixture->main_loop);
+       }
+}
+
+static void
+file_notifier_finished_cb (TrackerFileNotifier *notifier,
+                          gpointer             user_data)
+{
+       TestCommonContext *fixture = user_data;
+
+       if (fixture->expect_finished) {
+               g_main_loop_quit (fixture->main_loop);
+       };
+}
+
+static void
+test_common_context_index_dir (TestCommonContext     *fixture,
+                               const gchar           *filename,
+                               TrackerDirectoryFlags  flags)
+{
+       GFile *file;
+       gchar *path;
+
+       path = g_build_filename (fixture->test_path, filename, NULL);
+       file = g_file_new_for_path (path);
+       g_free (path);
+
+       tracker_indexing_tree_add (fixture->indexing_tree, file, flags);
+       g_object_unref (file);
+}
+
+static void
+test_common_context_remove_dir (TestCommonContext     *fixture,
+                               const gchar           *filename)
+{
+       GFile *file;
+       gchar *path;
+
+       path = g_build_filename (fixture->test_path, filename, NULL);
+       file = g_file_new_for_path (path);
+       g_free (path);
+
+       tracker_indexing_tree_remove (fixture->indexing_tree, file);
+       g_object_unref (file);
+}
+
+static void
+test_common_context_setup (TestCommonContext *fixture,
+                           gconstpointer      data)
+{
+       GFile *data_loc, *ontology;
+       GError *error = NULL;
+
+       fixture->test_path = g_build_filename (g_get_tmp_dir (),
+                                              "tracker-test-XXXXXX",
+                                              NULL);
+       fixture->test_path = g_mkdtemp (fixture->test_path);
+       fixture->test_file = g_file_new_for_path (fixture->test_path);
+
+       data_loc = g_file_get_child (fixture->test_file, ".data");
+       ontology = g_file_new_for_path (TEST_ONTOLOGIES_DIR);
+       fixture->connection = tracker_sparql_connection_local_new (0, data_loc, data_loc, ontology, NULL, 
&error);
+       g_assert_no_error (error);
+
+       fixture->ops = NULL;
+
+       /* Create basic folders within the test location */
+       CREATE_FOLDER (fixture, "recursive");
+       CREATE_FOLDER (fixture, "non-recursive");
+       CREATE_FOLDER (fixture, "non-indexed");
+
+       fixture->indexing_tree = tracker_indexing_tree_new ();
+       tracker_indexing_tree_set_filter_hidden (fixture->indexing_tree, TRUE);
+
+       fixture->main_loop = g_main_loop_new (NULL, FALSE);
+       fixture->notifier = tracker_file_notifier_new (fixture->indexing_tree, FALSE,
+                                                      fixture->connection);
+
+       g_signal_connect (fixture->notifier, "file-created",
+                         G_CALLBACK (file_notifier_file_created_cb), fixture);
+       g_signal_connect (fixture->notifier, "file-updated",
+                         G_CALLBACK (file_notifier_file_updated_cb), fixture);
+       g_signal_connect (fixture->notifier, "file-deleted",
+                         G_CALLBACK (file_notifier_file_deleted_cb), fixture);
+       g_signal_connect (fixture->notifier, "file-moved",
+                         G_CALLBACK (file_notifier_file_moved_cb), fixture);
+       g_signal_connect (fixture->notifier, "finished",
+                         G_CALLBACK (file_notifier_finished_cb), fixture);
+}
+
+static void
+test_common_context_teardown (TestCommonContext *fixture,
+                              gconstpointer      data)
+{
+       DELETE_FOLDER (fixture, NULL);
+
+       g_list_foreach (fixture->ops, (GFunc) filesystem_operation_free, NULL);
+       g_list_free (fixture->ops);
+
+       if (fixture->notifier) {
+               g_object_unref (fixture->notifier);
+       }
+
+       if (fixture->indexing_tree) {
+               g_object_unref (fixture->indexing_tree);
+       }
+
+       if (fixture->test_file) {
+               g_object_unref (fixture->test_file);
+       }
+
+       if (fixture->test_path) {
+               g_free (fixture->test_path);
+       }
+
+       g_clear_object (&fixture->connection);
+}
+
+static gboolean
+timeout_expired_cb (gpointer user_data)
+{
+       TestCommonContext *fixture = user_data;
+
+       fixture->expire_timeout_id = 0;
+       g_main_loop_quit (fixture->main_loop);
+
+       return FALSE;
+}
+
+static void
+test_common_context_expect_results (TestCommonContext   *fixture,
+                                    FilesystemOperation *results,
+                                    guint                n_results,
+                                   guint                max_timeout,
+                                   gboolean             expect_finished)
+{
+       GList *ops;
+       guint i, id;
+
+       fixture->expect_finished = expect_finished;
+       fixture->expect_n_results = n_results;
+       fixture->expect_results = results;
+
+       if (fixture->expect_n_results != g_list_length (fixture->ops)) {
+               if (max_timeout != 0) {
+                       id = g_timeout_add_seconds (max_timeout,
+                                                   (GSourceFunc) timeout_expired_cb,
+                                                   fixture);
+                       fixture->expire_timeout_id = id;
+               }
+
+               g_main_loop_run (fixture->main_loop);
+
+               if (max_timeout != 0 && fixture->expire_timeout_id != 0) {
+                       g_source_remove (fixture->expire_timeout_id);
+               }
+       }
+
+       for (i = 0; i < n_results; i++) {
+               gboolean matched = FALSE;
+
+               ops = fixture->ops;
+
+               while (ops) {
+                       FilesystemOperation *op = ops->data;
+
+                       if (op->op == results[i].op &&
+                           g_strcmp0 (op->path, results[i].path) == 0 &&
+                           g_strcmp0 (op->other_path, results[i].other_path) == 0) {
+                               filesystem_operation_free (op);
+                               fixture->ops = g_list_delete_link (fixture->ops, ops);
+                               matched = TRUE;
+                               break;
+                       }
+
+                       ops = ops->next;
+               }
+
+               if (!matched) {
+                       if (results[i].op == OPERATION_MOVE) {
+                               g_critical ("Expected operation %d on %s (-> %s) didn't happen",
+                                           results[i].op, results[i].path,
+                                           results[i].other_path);
+                       } else {
+                               g_critical ("Expected operation %d on %s didn't happen",
+                                           results[i].op, results[i].path);
+                       }
+               }
+       }
+
+       ops = fixture->ops;
+
+       while (ops) {
+               FilesystemOperation *op = ops->data;
+
+               if (op->op == OPERATION_MOVE) {
+                       g_critical ("Unexpected operation %d on %s (-> %s) happened",
+                                   op->op, op->path,
+                                   op->other_path);
+               } else {
+                       g_critical ("Unexpected operation %d on %s happened",
+                                   op->op, op->path);
+               }
+       }
+
+       g_assert_cmpint (g_list_length (fixture->ops), ==, 0);
+}
+
+static void
+test_file_notifier_crawling_non_recursive (TestCommonContext *fixture,
+                                           gconstpointer      data)
+{
+       FilesystemOperation expected_results[] = {
+               { OPERATION_CREATE, "non-recursive", NULL },
+               { OPERATION_CREATE, "non-recursive/folder", NULL },
+               { OPERATION_CREATE, "non-recursive/bbb", NULL },
+       };
+
+       CREATE_FOLDER (fixture, "non-recursive/folder");
+       CREATE_UPDATE_FILE (fixture, "non-recursive/folder/aaa");
+       CREATE_UPDATE_FILE (fixture, "non-recursive/bbb");
+
+       test_common_context_index_dir (fixture, "non-recursive",
+                                      TRACKER_DIRECTORY_FLAG_CHECK_MTIME);
+
+       tracker_file_notifier_start (fixture->notifier);
+
+       test_common_context_expect_results (fixture, expected_results,
+                                           G_N_ELEMENTS (expected_results),
+                                           2, TRUE);
+
+       tracker_file_notifier_stop (fixture->notifier);
+}
+
+static void
+test_file_notifier_crawling_recursive (TestCommonContext *fixture,
+                                      gconstpointer      data)
+{
+       FilesystemOperation expected_results[] = {
+               { OPERATION_CREATE, "recursive", NULL },
+               { OPERATION_CREATE, "recursive/folder", NULL },
+               { OPERATION_CREATE, "recursive/folder/aaa", NULL },
+               { OPERATION_CREATE, "recursive/bbb", NULL },
+       };
+
+       CREATE_FOLDER (fixture, "recursive/folder");
+       CREATE_UPDATE_FILE (fixture, "recursive/folder/aaa");
+       CREATE_UPDATE_FILE (fixture, "recursive/bbb");
+
+       test_common_context_index_dir (fixture, "recursive",
+                                      TRACKER_DIRECTORY_FLAG_RECURSE |
+                                      TRACKER_DIRECTORY_FLAG_CHECK_MTIME);
+
+       tracker_file_notifier_start (fixture->notifier);
+
+       test_common_context_expect_results (fixture, expected_results,
+                                           G_N_ELEMENTS (expected_results),
+                                           2, TRUE);
+
+       tracker_file_notifier_stop (fixture->notifier);
+}
+
+static void
+test_file_notifier_crawling_non_recursive_within_recursive (TestCommonContext *fixture,
+                                                           gconstpointer      data)
+{
+       FilesystemOperation expected_results[] = {
+               { OPERATION_CREATE, "recursive", NULL },
+               { OPERATION_CREATE, "recursive/folder", NULL },
+               { OPERATION_CREATE, "recursive/folder/aaa", NULL },
+               { OPERATION_CREATE, "recursive/bbb", NULL },
+               { OPERATION_CREATE, "recursive/folder/non-recursive", NULL },
+               { OPERATION_CREATE, "recursive/folder/non-recursive/ccc", NULL },
+               { OPERATION_CREATE, "recursive/folder/non-recursive/folder", NULL },
+       };
+
+       CREATE_FOLDER (fixture, "recursive/folder");
+       CREATE_UPDATE_FILE (fixture, "recursive/folder/aaa");
+       CREATE_UPDATE_FILE (fixture, "recursive/bbb");
+       CREATE_FOLDER (fixture, "recursive/folder/non-recursive");
+       CREATE_UPDATE_FILE (fixture, "recursive/folder/non-recursive/ccc");
+       CREATE_FOLDER (fixture, "recursive/folder/non-recursive/folder");
+       CREATE_UPDATE_FILE (fixture, "recursive/folder/non-recursive/folder/ddd");
+
+       test_common_context_index_dir (fixture, "recursive",
+                                      TRACKER_DIRECTORY_FLAG_RECURSE |
+                                      TRACKER_DIRECTORY_FLAG_CHECK_MTIME);
+       test_common_context_index_dir (fixture, "recursive/folder/non-recursive",
+                                      TRACKER_DIRECTORY_FLAG_NONE |
+                                      TRACKER_DIRECTORY_FLAG_CHECK_MTIME);
+
+       tracker_file_notifier_start (fixture->notifier);
+
+       test_common_context_expect_results (fixture, expected_results,
+                                           G_N_ELEMENTS (expected_results),
+                                           2, TRUE);
+
+       tracker_file_notifier_stop (fixture->notifier);
+}
+
+static void
+test_file_notifier_crawling_recursive_within_non_recursive (TestCommonContext *fixture,
+                                                           gconstpointer      data)
+{
+       FilesystemOperation expected_results[] = {
+               { OPERATION_CREATE, "non-recursive", NULL },
+               { OPERATION_CREATE, "non-recursive/folder", NULL },
+               { OPERATION_CREATE, "non-recursive/bbb", NULL },
+               { OPERATION_CREATE, "non-recursive/folder/recursive", NULL },
+               { OPERATION_CREATE, "non-recursive/folder/recursive/ccc", NULL },
+               { OPERATION_CREATE, "non-recursive/folder/recursive/folder", NULL },
+               { OPERATION_CREATE, "non-recursive/folder/recursive/folder/ddd", NULL },
+       };
+
+       CREATE_FOLDER (fixture, "non-recursive/folder");
+       CREATE_UPDATE_FILE (fixture, "non-recursive/folder/aaa");
+       CREATE_UPDATE_FILE (fixture, "non-recursive/bbb");
+       CREATE_FOLDER (fixture, "non-recursive/folder/recursive");
+       CREATE_UPDATE_FILE (fixture, "non-recursive/folder/recursive/ccc");
+       CREATE_FOLDER (fixture, "non-recursive/folder/recursive/folder");
+       CREATE_UPDATE_FILE (fixture, "non-recursive/folder/recursive/folder/ddd");
+
+       test_common_context_index_dir (fixture, "non-recursive/folder/recursive",
+                                      TRACKER_DIRECTORY_FLAG_RECURSE |
+                                      TRACKER_DIRECTORY_FLAG_CHECK_MTIME);
+       test_common_context_index_dir (fixture, "non-recursive",
+                                      TRACKER_DIRECTORY_FLAG_NONE |
+                                      TRACKER_DIRECTORY_FLAG_CHECK_MTIME);
+
+       tracker_file_notifier_start (fixture->notifier);
+
+       test_common_context_expect_results (fixture, expected_results,
+                                           G_N_ELEMENTS (expected_results),
+                                           2, TRUE);
+
+       tracker_file_notifier_stop (fixture->notifier);
+}
+
+static void
+test_file_notifier_crawling_ignore_within_recursive (TestCommonContext *fixture,
+                                                    gconstpointer      data)
+{
+       FilesystemOperation expected_results[] = {
+               { OPERATION_CREATE, "recursive", NULL },
+               { OPERATION_CREATE, "recursive/folder", NULL },
+               { OPERATION_CREATE, "recursive/folder/aaa", NULL },
+               { OPERATION_CREATE, "recursive/bbb", NULL },
+               { OPERATION_DELETE, "recursive/folder/ignore", NULL }
+       };
+
+       CREATE_FOLDER (fixture, "recursive/folder");
+       CREATE_UPDATE_FILE (fixture, "recursive/folder/aaa");
+       CREATE_UPDATE_FILE (fixture, "recursive/bbb");
+       CREATE_FOLDER (fixture, "recursive/folder/ignore");
+       CREATE_UPDATE_FILE (fixture, "recursive/folder/ignore/ccc");
+       CREATE_FOLDER (fixture, "recursive/folder/ignore/folder");
+       CREATE_UPDATE_FILE (fixture, "recursive/folder/ignore/folder/ddd");
+
+       test_common_context_index_dir (fixture, "recursive",
+                                      TRACKER_DIRECTORY_FLAG_RECURSE |
+                                      TRACKER_DIRECTORY_FLAG_CHECK_MTIME);
+       test_common_context_index_dir (fixture, "recursive/folder/ignore",
+                                      TRACKER_DIRECTORY_FLAG_IGNORE |
+                                      TRACKER_DIRECTORY_FLAG_CHECK_MTIME);
+
+       tracker_file_notifier_start (fixture->notifier);
+
+       test_common_context_expect_results (fixture, expected_results,
+                                           G_N_ELEMENTS (expected_results),
+                                           2, TRUE);
+
+       tracker_file_notifier_stop (fixture->notifier);
+}
+
+static void
+test_file_notifier_changes_remove_non_recursive (TestCommonContext *fixture,
+                                                gconstpointer      data)
+{
+       FilesystemOperation expected_results[] = {
+               { OPERATION_DELETE, "non-recursive", NULL }
+       };
+
+       test_file_notifier_crawling_non_recursive (fixture, data);
+
+       test_common_context_remove_dir (fixture, "non-recursive");
+       tracker_file_notifier_start (fixture->notifier);
+       test_common_context_expect_results (fixture, expected_results,
+                                           G_N_ELEMENTS (expected_results),
+                                           1, FALSE);
+       tracker_file_notifier_stop (fixture->notifier);
+}
+
+static void
+test_file_notifier_changes_remove_recursive (TestCommonContext *fixture,
+                                             gconstpointer      data)
+{
+       FilesystemOperation expected_results[] = {
+               { OPERATION_DELETE, "recursive", NULL }
+       };
+
+       test_file_notifier_crawling_recursive (fixture, data);
+
+       test_common_context_remove_dir (fixture, "recursive");
+       tracker_file_notifier_start (fixture->notifier);
+       test_common_context_expect_results (fixture, expected_results,
+                                           G_N_ELEMENTS (expected_results),
+                                           1, FALSE);
+       tracker_file_notifier_stop (fixture->notifier);
+}
+
+static void
+test_file_notifier_changes_remove_ignore (TestCommonContext *fixture,
+                                          gconstpointer      data)
+{
+       FilesystemOperation expected_results[] = {
+               { OPERATION_CREATE, "recursive/folder/ignore", NULL },
+               { OPERATION_CREATE, "recursive/folder/ignore/ccc", NULL },
+               { OPERATION_CREATE, "recursive/folder/ignore/folder", NULL },
+               { OPERATION_CREATE, "recursive/folder/ignore/folder/ddd", NULL }
+       };
+       FilesystemOperation expected_results2[] = {
+               { OPERATION_DELETE, "recursive/folder/ignore", NULL }
+       };
+
+       /* Start off from ignore test case */
+       test_file_notifier_crawling_ignore_within_recursive (fixture, data);
+
+       /* Remove ignored folder */
+       test_common_context_remove_dir (fixture, "recursive/folder/ignore");
+       tracker_file_notifier_start (fixture->notifier);
+       test_common_context_expect_results (fixture, expected_results,
+                                           G_N_ELEMENTS (expected_results),
+                                           1, FALSE);
+       tracker_file_notifier_stop (fixture->notifier);
+
+       /* And add it back */
+       fixture->expect_n_results = G_N_ELEMENTS (expected_results2);
+       test_common_context_index_dir (fixture, "recursive/folder/ignore",
+                                      TRACKER_DIRECTORY_FLAG_IGNORE);
+       tracker_file_notifier_start (fixture->notifier);
+       test_common_context_expect_results (fixture, expected_results2,
+                                           G_N_ELEMENTS (expected_results2),
+                                           1, FALSE);
+       tracker_file_notifier_stop (fixture->notifier);
+}
+
+static void
+test_file_notifier_monitor_updates_non_recursive (TestCommonContext *fixture,
+                                                  gconstpointer      data)
+{
+       FilesystemOperation expected_results[] = {
+               { OPERATION_CREATE, "non-recursive", NULL },
+               { OPERATION_CREATE, "non-recursive/folder", NULL },
+               { OPERATION_CREATE, "non-recursive/bbb", NULL }
+       };
+       FilesystemOperation expected_results2[] = {
+               { OPERATION_UPDATE, "non-recursive/bbb", NULL },
+               { OPERATION_CREATE, "non-recursive/ccc", NULL }
+       };
+       FilesystemOperation expected_results3[] = {
+               { OPERATION_DELETE, "non-recursive/folder", NULL },
+               { OPERATION_DELETE, "non-recursive/ccc", NULL }
+       };
+
+       CREATE_FOLDER (fixture, "non-recursive/folder");
+       CREATE_UPDATE_FILE (fixture, "non-recursive/bbb");
+
+       test_common_context_index_dir (fixture, "non-recursive",
+                                      TRACKER_DIRECTORY_FLAG_MONITOR |
+                                      TRACKER_DIRECTORY_FLAG_CHECK_MTIME);
+
+       tracker_file_notifier_start (fixture->notifier);
+       test_common_context_expect_results (fixture, expected_results,
+                                           G_N_ELEMENTS (expected_results),
+                                           2, TRUE);
+       tracker_file_notifier_stop (fixture->notifier);
+
+       /* Perform file updates */
+       tracker_file_notifier_start (fixture->notifier);
+       CREATE_UPDATE_FILE (fixture, "non-recursive/folder/aaa");
+       CREATE_UPDATE_FILE (fixture, "non-recursive/bbb");
+       CREATE_UPDATE_FILE (fixture, "non-recursive/ccc");
+       test_common_context_expect_results (fixture, expected_results2,
+                                           G_N_ELEMENTS (expected_results2),
+                                           3, FALSE);
+
+       DELETE_FILE (fixture, "non-recursive/ccc");
+       DELETE_FOLDER (fixture, "non-recursive/folder");
+       test_common_context_expect_results (fixture, expected_results3,
+                                           G_N_ELEMENTS (expected_results3),
+                                           3, FALSE);
+       tracker_file_notifier_stop (fixture->notifier);
+}
+
+static void
+test_file_notifier_monitor_updates_recursive (TestCommonContext *fixture,
+                                              gconstpointer      data)
+{
+       FilesystemOperation expected_results[] = {
+               { OPERATION_CREATE, "recursive", NULL },
+               { OPERATION_CREATE, "recursive/bbb", NULL }
+       };
+       FilesystemOperation expected_results2[] = {
+               { OPERATION_CREATE, "recursive/folder", NULL },
+               { OPERATION_CREATE, "recursive/folder/aaa", NULL },
+               { OPERATION_UPDATE, "recursive/bbb", NULL },
+       };
+       FilesystemOperation expected_results3[] = {
+               { OPERATION_DELETE, "recursive/folder", NULL },
+               { OPERATION_DELETE, "recursive/bbb", NULL }
+       };
+
+       CREATE_UPDATE_FILE (fixture, "recursive/bbb");
+
+       test_common_context_index_dir (fixture, "recursive",
+                                      TRACKER_DIRECTORY_FLAG_RECURSE |
+                                      TRACKER_DIRECTORY_FLAG_MONITOR |
+                                      TRACKER_DIRECTORY_FLAG_CHECK_MTIME);
+
+       tracker_file_notifier_start (fixture->notifier);
+       test_common_context_expect_results (fixture, expected_results,
+                                           G_N_ELEMENTS (expected_results),
+                                           2, TRUE);
+       tracker_file_notifier_stop (fixture->notifier);
+
+       /* Perform file updates */
+       tracker_file_notifier_start (fixture->notifier);
+       CREATE_FOLDER (fixture, "recursive/folder");
+       CREATE_UPDATE_FILE (fixture, "recursive/folder/aaa");
+       CREATE_UPDATE_FILE (fixture, "recursive/bbb");
+       test_common_context_expect_results (fixture, expected_results2,
+                                           G_N_ELEMENTS (expected_results2),
+                                           5, FALSE);
+
+       DELETE_FILE (fixture, "recursive/bbb");
+       DELETE_FOLDER (fixture, "recursive/folder");
+       test_common_context_expect_results (fixture, expected_results3,
+                                           G_N_ELEMENTS (expected_results3),
+                                           5, FALSE);
+       tracker_file_notifier_stop (fixture->notifier);
+}
+
+gint
+main (gint    argc,
+      gchar **argv)
+{
+       setlocale (LC_ALL, "");
+
+       g_test_init (&argc, &argv, NULL);
+
+       g_test_message ("Testing file notifier");
+
+       /* Crawling */
+       test_add ("/libtracker-miner/file-notifier/crawling-non-recursive",
+                 test_file_notifier_crawling_non_recursive);
+       test_add ("/libtracker-miner/file-notifier/crawling-recursive",
+                 test_file_notifier_crawling_recursive);
+       test_add ("/libtracker-miner/file-notifier/crawling-non-recursive-within-recursive",
+                 test_file_notifier_crawling_non_recursive_within_recursive);
+       test_add ("/libtracker-miner/file-notifier/crawling-recursive-within-non-recursive",
+                 test_file_notifier_crawling_recursive_within_non_recursive);
+       test_add ("/libtracker-miner/file-notifier/crawling-ignore-within-recursive",
+                 test_file_notifier_crawling_ignore_within_recursive);
+
+       /* Config changes */
+       test_add ("/libtracker-miner/file-notifier/changes-remove-non-recursive",
+                 test_file_notifier_changes_remove_non_recursive);
+       test_add ("/libtracker-miner/file-notifier/changes-remove-recursive",
+                 test_file_notifier_changes_remove_recursive);
+       test_add ("/libtracker-miner/file-notifier/changes-remove-ignore",
+                 test_file_notifier_changes_remove_ignore);
+
+       /* Monitoring */
+       test_add ("/libtracker-miner/file-notifier/monitor-updates-non-recursive",
+                 test_file_notifier_monitor_updates_non_recursive);
+       test_add ("/libtracker-miner/file-notifier/monitor-updates-recursive",
+                 test_file_notifier_monitor_updates_recursive);
+
+       return g_test_run ();
+}
diff --git a/tests/libtracker-miner/tracker-file-system-test.c 
b/tests/libtracker-miner/tracker-file-system-test.c
new file mode 100644
index 000000000..c7919a7d1
--- /dev/null
+++ b/tests/libtracker-miner/tracker-file-system-test.c
@@ -0,0 +1,254 @@
+/*
+ * Copyright (C) 2011, Nokia <ivan frade nokia com>
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+ * 02110-1301, USA.
+ */
+#include <string.h>
+#include <unistd.h>
+
+#include <glib.h>
+#include <glib/gstdio.h>
+
+#include <libtracker-miner/tracker-file-system.h>
+
+/* Fixture struct */
+typedef struct {
+       /* The filesystem to test */
+       TrackerFileSystem *file_system;
+} TestCommonContext;
+
+#define test_add(path,fun)       \
+       g_test_add (path, \
+                   TestCommonContext, \
+                   NULL, \
+                   test_common_context_setup, \
+                   fun, \
+                   test_common_context_teardown)
+
+static void
+test_common_context_setup (TestCommonContext *fixture,
+                           gconstpointer      data)
+{
+       fixture->file_system = tracker_file_system_new (NULL);
+}
+
+static void
+test_common_context_teardown (TestCommonContext *fixture,
+                              gconstpointer      data)
+{
+       if (fixture->file_system)
+               g_object_unref (fixture->file_system);
+}
+
+static void
+test_file_system_insertions (TestCommonContext *fixture,
+                             gconstpointer      data)
+{
+       GFile *file, *canonical, *other;
+
+       file = g_file_new_for_uri ("file:///aaa/");
+       canonical = tracker_file_system_peek_file (fixture->file_system, file);
+       g_assert (canonical == NULL);
+
+       canonical = tracker_file_system_get_file (fixture->file_system, file,
+                                                 G_FILE_TYPE_DIRECTORY, NULL);
+       g_object_unref (file);
+
+       g_assert (canonical != NULL);
+
+       file = g_file_new_for_uri ("file:///aaa/");
+       other = tracker_file_system_get_file (fixture->file_system, file,
+                                             G_FILE_TYPE_DIRECTORY, NULL);
+       g_assert (canonical == other);
+
+       other = tracker_file_system_peek_file (fixture->file_system, file);
+       g_object_unref (file);
+       g_assert (other != NULL);
+}
+
+static void
+test_file_system_children (TestCommonContext *fixture,
+                          gconstpointer      data)
+{
+       GFile *file, *parent, *child, *other;
+
+       file = g_file_new_for_uri ("file:///aaa/");
+       parent = tracker_file_system_get_file (fixture->file_system, file,
+                                                 G_FILE_TYPE_DIRECTORY, NULL);
+       g_object_unref (file);
+
+       file = g_file_new_for_uri ("file:///aaa/bbb");
+       child = tracker_file_system_get_file (fixture->file_system, file,
+                                             G_FILE_TYPE_REGULAR, parent);
+       g_assert (child != NULL);
+       g_object_unref (file);
+
+       file = g_file_new_for_uri ("file:///aaa/bbb");
+       other = tracker_file_system_get_file (fixture->file_system, file,
+                                             G_FILE_TYPE_REGULAR, NULL);
+       g_assert (other != NULL);
+       g_assert (child == other);
+
+       g_object_unref (file);
+}
+
+static void
+test_file_system_indirect_children (TestCommonContext *fixture,
+                                   gconstpointer      data)
+{
+       GFile *file, *parent, *child, *other;
+
+       file = g_file_new_for_uri ("file:///aaa/");
+       parent = tracker_file_system_get_file (fixture->file_system, file,
+                                              G_FILE_TYPE_DIRECTORY, NULL);
+       g_object_unref (file);
+
+       file = g_file_new_for_uri ("file:///aaa/bbb/ccc");
+       child = tracker_file_system_get_file (fixture->file_system, file,
+                                             G_FILE_TYPE_REGULAR, parent);
+       g_assert (child != NULL);
+       g_object_unref (file);
+
+       file = g_file_new_for_uri ("file:///aaa/bbb/ccc");
+       other = tracker_file_system_get_file (fixture->file_system, file,
+                                             G_FILE_TYPE_REGULAR, NULL);
+       g_assert (other != NULL);
+       g_assert (child == other);
+
+       /* FIXME: check missing parent in between */
+
+       g_object_unref (file);
+}
+
+static void
+test_file_system_reparenting (TestCommonContext *fixture,
+                             gconstpointer      data)
+{
+       GFile *file, *parent, *child, *grandchild, *other;
+
+       file = g_file_new_for_uri ("file:///aaa/");
+       parent = tracker_file_system_get_file (fixture->file_system, file,
+                                              G_FILE_TYPE_DIRECTORY, NULL);
+       g_object_unref (file);
+
+       file = g_file_new_for_uri ("file:///aaa/bbb/ccc");
+       grandchild = tracker_file_system_get_file (fixture->file_system, file,
+                                                  G_FILE_TYPE_REGULAR, parent);
+       g_assert (grandchild != NULL);
+       g_object_unref (file);
+
+       file = g_file_new_for_uri ("file:///aaa/bbb");
+       child = tracker_file_system_get_file (fixture->file_system, file,
+                                             G_FILE_TYPE_REGULAR, parent);
+       g_assert (child != NULL);
+       g_object_unref (file);
+
+       file = g_file_new_for_uri ("file:///aaa/bbb/ccc");
+       other = tracker_file_system_peek_file (fixture->file_system, file);
+       g_assert (other != NULL);
+       g_assert (grandchild == other);
+       g_object_unref (file);
+
+       /* Delete child in between */
+       g_object_unref (child);
+
+       /* Check that child doesn't exist anymore */
+       file = g_file_new_for_uri ("file:///aaa/bbb");
+       child = tracker_file_system_peek_file (fixture->file_system, file);
+       g_assert (child == NULL);
+       g_object_unref (file);
+
+       /* Check that grand child still exists */
+       file = g_file_new_for_uri ("file:///aaa/bbb/ccc");
+       other = tracker_file_system_peek_file (fixture->file_system, file);
+       g_assert (other != NULL);
+       g_assert (grandchild == other);
+       g_object_unref (file);
+}
+
+static void
+test_file_system_properties (TestCommonContext *fixture,
+                            gconstpointer      data)
+{
+       GQuark property1_quark, property2_quark;
+       gchar *value = "value";
+       gchar *ret_value;
+       GFile *file, *f;
+
+       property1_quark = g_quark_from_string ("file-system-test-property1");
+       tracker_file_system_register_property (property1_quark,
+                                              NULL);
+       property2_quark = g_quark_from_string ("file-system-test-property2");
+       tracker_file_system_register_property (property2_quark,
+                                              NULL);
+
+       f = g_file_new_for_uri ("file:///aaa/");
+       file = tracker_file_system_get_file (fixture->file_system, f,
+                                            G_FILE_TYPE_REGULAR, NULL);
+       g_object_unref (f);
+
+       /* Set both properties */
+       tracker_file_system_set_property (fixture->file_system, file,
+                                         property1_quark, value);
+       tracker_file_system_set_property (fixture->file_system, file,
+                                         property2_quark, value);
+
+       /* Check second property and remove it */
+       ret_value = tracker_file_system_get_property (fixture->file_system,
+                                                     file, property2_quark);
+       g_assert (ret_value == value);
+
+       tracker_file_system_unset_property (fixture->file_system,
+                                           file, property2_quark);
+
+       ret_value = tracker_file_system_get_property (fixture->file_system,
+                                                     file, property2_quark);
+       g_assert (ret_value == NULL);
+
+       /* Check first property and remove it */
+       ret_value = tracker_file_system_get_property (fixture->file_system,
+                                                     file, property1_quark);
+       g_assert (ret_value == value);
+
+       tracker_file_system_unset_property (fixture->file_system,
+                                           file, property1_quark);
+
+       ret_value = tracker_file_system_get_property (fixture->file_system,
+                                                     file, property1_quark);
+       g_assert (ret_value == NULL);
+}
+
+gint
+main (gint    argc,
+      gchar **argv)
+{
+       g_test_init (&argc, &argv, NULL);
+
+       g_test_message ("Testing file system abstraction");
+
+       test_add ("/libtracker-miner/file-system/insertions",
+                 test_file_system_insertions);
+       test_add ("/libtracker-miner/file-system/children",
+                 test_file_system_children);
+       test_add ("/libtracker-miner/file-system/indirect-children",
+                 test_file_system_indirect_children);
+       test_add ("/libtracker-miner/file-system/reparenting",
+                 test_file_system_reparenting);
+       test_add ("/libtracker-miner/file-system/file-properties",
+                 test_file_system_properties);
+
+       return g_test_run ();
+}
diff --git a/tests/libtracker-miner/tracker-indexing-tree-test.c 
b/tests/libtracker-miner/tracker-indexing-tree-test.c
new file mode 100644
index 000000000..78b874c1d
--- /dev/null
+++ b/tests/libtracker-miner/tracker-indexing-tree-test.c
@@ -0,0 +1,986 @@
+/*
+ * Copyright (C) 2011, Nokia <ivan frade nokia com>
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+ * 02110-1301, USA.
+ */
+
+#include <string.h>
+#include <unistd.h>
+
+#include <glib.h>
+#include <glib/gstdio.h>
+
+#include <libtracker-miner/tracker-indexing-tree.h>
+
+/*
+ * Test directory structure:
+ *  - Directory A
+ *     -- Directory AA
+ *         --- Directory AAA
+ *              ---- Directory AAAA
+ *              ---- Directory AAAB
+ *         --- Directory AAB
+ *     -- Directory AB
+ *         --- Directory ABA
+ *         --- Directory ABB
+ */
+typedef enum {
+       TEST_DIRECTORY_A = 0,
+       TEST_DIRECTORY_AA,
+       TEST_DIRECTORY_AAA,
+       TEST_DIRECTORY_AAAA,
+       TEST_DIRECTORY_AAAB,
+       TEST_DIRECTORY_AAB,
+       TEST_DIRECTORY_AB,
+       TEST_DIRECTORY_ABA,
+       TEST_DIRECTORY_ABB,
+       TEST_DIRECTORY_LAST
+} TestDirectory;
+
+/* Fixture struct */
+typedef struct {
+       /* Array with all existing test directories */
+       GFile *test_dir[TEST_DIRECTORY_LAST];
+       /* The tree to test */
+       TrackerIndexingTree *tree;
+} TestCommonContext;
+
+#define ASSERT_INDEXABLE(fixture, id)    \
+       g_assert (tracker_indexing_tree_file_is_indexable (fixture->tree, \
+                                                          fixture->test_dir[id], \
+                                                          G_FILE_TYPE_DIRECTORY) == TRUE)
+#define ASSERT_NOT_INDEXABLE(fixture, id)        \
+       g_assert (tracker_indexing_tree_file_is_indexable (fixture->tree, \
+                                                          fixture->test_dir[id], \
+                                                          G_FILE_TYPE_DIRECTORY) == FALSE)
+
+#define test_add(path,fun)       \
+       g_test_add (path, \
+                   TestCommonContext, \
+                   NULL, \
+                   test_common_context_setup, \
+                   fun, \
+                   test_common_context_teardown)
+
+static void
+test_common_context_setup (TestCommonContext *fixture,
+                           gconstpointer      data)
+{
+       guint i;
+       static const gchar *test_directories_subpaths [TEST_DIRECTORY_LAST] = {
+               "/A",
+               "/A/A",
+               "/A/A/A",
+               "/A/A/A/A",
+               "/A/A/A/B",
+               "/A/A/B",
+               "/A/B/",
+               "/A/B/A",
+               "/A/B/B"
+       };
+
+       /* Initialize aux directories */
+       for (i = 0; i < TEST_DIRECTORY_LAST; i++)
+               fixture->test_dir[i] = g_file_new_for_path (test_directories_subpaths[i]);
+
+       fixture->tree = tracker_indexing_tree_new ();
+}
+
+static void
+test_common_context_teardown (TestCommonContext *fixture,
+                              gconstpointer      data)
+{
+       gint i;
+
+       /* Deinit aux directories, from last to first */
+       for (i = TEST_DIRECTORY_LAST-1; i >= 0; i--) {
+               if (fixture->test_dir[i])
+                       g_object_unref (fixture->test_dir[i]);
+       }
+
+       if (fixture->tree)
+               g_object_unref (fixture->tree);
+}
+
+/* If A is ignored,
+ *  -A, AA, AB, AAA, AAB, ABA and ABB are not indexable
+ */
+static void
+test_indexing_tree_001 (TestCommonContext *fixture,
+                        gconstpointer      data)
+{
+       tracker_indexing_tree_add (fixture->tree,
+                                  fixture->test_dir[TEST_DIRECTORY_A],
+                                  TRACKER_DIRECTORY_FLAG_IGNORE);
+
+       ASSERT_NOT_INDEXABLE (fixture, TEST_DIRECTORY_A);
+       ASSERT_NOT_INDEXABLE (fixture, TEST_DIRECTORY_AA);
+       ASSERT_NOT_INDEXABLE (fixture, TEST_DIRECTORY_AAA);
+       ASSERT_NOT_INDEXABLE (fixture, TEST_DIRECTORY_AAAA);
+       ASSERT_NOT_INDEXABLE (fixture, TEST_DIRECTORY_AAAB);
+       ASSERT_NOT_INDEXABLE (fixture, TEST_DIRECTORY_AAB);
+       ASSERT_NOT_INDEXABLE (fixture, TEST_DIRECTORY_AB);
+       ASSERT_NOT_INDEXABLE (fixture, TEST_DIRECTORY_ABA);
+       ASSERT_NOT_INDEXABLE (fixture, TEST_DIRECTORY_ABA);
+}
+
+/* If A is monitored (NOT recursively):
+ *  -A, AA, AB are indexable
+ *  -AAA, AAB, ABA and ABB are not indexable
+ */
+static void
+test_indexing_tree_002 (TestCommonContext *fixture,
+                        gconstpointer      data)
+{
+       tracker_indexing_tree_add (fixture->tree,
+                                  fixture->test_dir[TEST_DIRECTORY_A],
+                                  TRACKER_DIRECTORY_FLAG_MONITOR);
+
+       ASSERT_INDEXABLE (fixture, TEST_DIRECTORY_A);
+       ASSERT_INDEXABLE (fixture, TEST_DIRECTORY_AA);
+       ASSERT_NOT_INDEXABLE (fixture, TEST_DIRECTORY_AAA);
+       ASSERT_NOT_INDEXABLE (fixture, TEST_DIRECTORY_AAAA);
+       ASSERT_NOT_INDEXABLE (fixture, TEST_DIRECTORY_AAAB);
+       ASSERT_NOT_INDEXABLE (fixture, TEST_DIRECTORY_AAB);
+       ASSERT_INDEXABLE (fixture, TEST_DIRECTORY_AB);
+       ASSERT_NOT_INDEXABLE (fixture, TEST_DIRECTORY_ABA);
+       ASSERT_NOT_INDEXABLE (fixture, TEST_DIRECTORY_ABA);
+}
+
+/* If A is monitored (recursively):
+ *  -A, AA, AB, AAA, AAB, ABA and ABB are indexable
+ */
+static void
+test_indexing_tree_003 (TestCommonContext *fixture,
+                        gconstpointer      data)
+{
+       tracker_indexing_tree_add (fixture->tree,
+                                  fixture->test_dir[TEST_DIRECTORY_A],
+                                  TRACKER_DIRECTORY_FLAG_MONITOR | TRACKER_DIRECTORY_FLAG_RECURSE);
+
+       ASSERT_INDEXABLE (fixture, TEST_DIRECTORY_A);
+       ASSERT_INDEXABLE (fixture, TEST_DIRECTORY_AA);
+       ASSERT_INDEXABLE (fixture, TEST_DIRECTORY_AAA);
+       ASSERT_INDEXABLE (fixture, TEST_DIRECTORY_AAAA);
+       ASSERT_INDEXABLE (fixture, TEST_DIRECTORY_AAAB);
+       ASSERT_INDEXABLE (fixture, TEST_DIRECTORY_AAB);
+       ASSERT_INDEXABLE (fixture, TEST_DIRECTORY_AB);
+       ASSERT_INDEXABLE (fixture, TEST_DIRECTORY_ABA);
+       ASSERT_INDEXABLE (fixture, TEST_DIRECTORY_ABA);
+}
+
+/* If A is ignored and AA is ignored:
+ *  -A, AA, AB, AAA, AAB, ABA and ABB are not indexable
+ */
+static void
+test_indexing_tree_004 (TestCommonContext *fixture,
+                        gconstpointer      data)
+{
+       tracker_indexing_tree_add (fixture->tree,
+                                  fixture->test_dir[TEST_DIRECTORY_A],
+                                  TRACKER_DIRECTORY_FLAG_IGNORE);
+       tracker_indexing_tree_add (fixture->tree,
+                                  fixture->test_dir[TEST_DIRECTORY_AA],
+                                  TRACKER_DIRECTORY_FLAG_IGNORE);
+
+       ASSERT_NOT_INDEXABLE (fixture, TEST_DIRECTORY_A);
+       ASSERT_NOT_INDEXABLE (fixture, TEST_DIRECTORY_AA);
+       ASSERT_NOT_INDEXABLE (fixture, TEST_DIRECTORY_AAA);
+       ASSERT_NOT_INDEXABLE (fixture, TEST_DIRECTORY_AAAA);
+       ASSERT_NOT_INDEXABLE (fixture, TEST_DIRECTORY_AAAB);
+       ASSERT_NOT_INDEXABLE (fixture, TEST_DIRECTORY_AAB);
+       ASSERT_NOT_INDEXABLE (fixture, TEST_DIRECTORY_AB);
+       ASSERT_NOT_INDEXABLE (fixture, TEST_DIRECTORY_ABA);
+       ASSERT_NOT_INDEXABLE (fixture, TEST_DIRECTORY_ABA);
+}
+
+/* If A is ignored and AA is monitored (not recursively):
+ *  -AA, AAA, AAB are indexable
+ *  -A, AAAA, AAAB, AB, ABA, ABB are not indexable
+ */
+static void
+test_indexing_tree_005 (TestCommonContext *fixture,
+                        gconstpointer      data)
+{
+       tracker_indexing_tree_add (fixture->tree,
+                                  fixture->test_dir[TEST_DIRECTORY_A],
+                                  TRACKER_DIRECTORY_FLAG_IGNORE);
+       tracker_indexing_tree_add (fixture->tree,
+                                  fixture->test_dir[TEST_DIRECTORY_AA],
+                                  TRACKER_DIRECTORY_FLAG_MONITOR);
+
+       ASSERT_NOT_INDEXABLE (fixture, TEST_DIRECTORY_A);
+       ASSERT_INDEXABLE (fixture, TEST_DIRECTORY_AA);
+       ASSERT_INDEXABLE (fixture, TEST_DIRECTORY_AAA);
+       ASSERT_NOT_INDEXABLE (fixture, TEST_DIRECTORY_AAAA);
+       ASSERT_NOT_INDEXABLE (fixture, TEST_DIRECTORY_AAAB);
+       ASSERT_INDEXABLE (fixture, TEST_DIRECTORY_AAB);
+       ASSERT_NOT_INDEXABLE (fixture, TEST_DIRECTORY_AB);
+       ASSERT_NOT_INDEXABLE (fixture, TEST_DIRECTORY_ABA);
+       ASSERT_NOT_INDEXABLE (fixture, TEST_DIRECTORY_ABA);
+}
+
+/* If A is ignored and AA is monitored (recursively):
+ *  -AA, AAA, AAAA, AAAB, AAB are indexable
+ *  -A, AB, ABA, ABB are not indexable
+ */
+static void
+test_indexing_tree_006 (TestCommonContext *fixture,
+                        gconstpointer      data)
+{
+       tracker_indexing_tree_add (fixture->tree,
+                                  fixture->test_dir[TEST_DIRECTORY_A],
+                                  TRACKER_DIRECTORY_FLAG_IGNORE);
+       tracker_indexing_tree_add (fixture->tree,
+                                  fixture->test_dir[TEST_DIRECTORY_AA],
+                                  TRACKER_DIRECTORY_FLAG_MONITOR | TRACKER_DIRECTORY_FLAG_RECURSE);
+
+       ASSERT_NOT_INDEXABLE (fixture, TEST_DIRECTORY_A);
+       ASSERT_INDEXABLE (fixture, TEST_DIRECTORY_AA);
+       ASSERT_INDEXABLE (fixture, TEST_DIRECTORY_AAA);
+       ASSERT_INDEXABLE (fixture, TEST_DIRECTORY_AAAA);
+       ASSERT_INDEXABLE (fixture, TEST_DIRECTORY_AAAB);
+       ASSERT_INDEXABLE (fixture, TEST_DIRECTORY_AAB);
+       ASSERT_NOT_INDEXABLE (fixture, TEST_DIRECTORY_AB);
+       ASSERT_NOT_INDEXABLE (fixture, TEST_DIRECTORY_ABA);
+       ASSERT_NOT_INDEXABLE (fixture, TEST_DIRECTORY_ABA);
+}
+
+/* If A is monitored (not recursively) and AA is ignored:
+ *  -A and AB are indexable
+ *  -AA, AAA, AAAA, AAAB, AAB, ABA, ABB are not indexable
+ */
+static void
+test_indexing_tree_007 (TestCommonContext *fixture,
+                        gconstpointer      data)
+{
+       tracker_indexing_tree_add (fixture->tree,
+                                  fixture->test_dir[TEST_DIRECTORY_A],
+                                  TRACKER_DIRECTORY_FLAG_MONITOR);
+       tracker_indexing_tree_add (fixture->tree,
+                                  fixture->test_dir[TEST_DIRECTORY_AA],
+                                  TRACKER_DIRECTORY_FLAG_IGNORE);
+
+       ASSERT_INDEXABLE (fixture, TEST_DIRECTORY_A);
+       ASSERT_NOT_INDEXABLE (fixture, TEST_DIRECTORY_AA);
+       ASSERT_NOT_INDEXABLE (fixture, TEST_DIRECTORY_AAA);
+       ASSERT_NOT_INDEXABLE (fixture, TEST_DIRECTORY_AAAA);
+       ASSERT_NOT_INDEXABLE (fixture, TEST_DIRECTORY_AAAB);
+       ASSERT_NOT_INDEXABLE (fixture, TEST_DIRECTORY_AAB);
+       ASSERT_INDEXABLE (fixture, TEST_DIRECTORY_AB);
+       ASSERT_NOT_INDEXABLE (fixture, TEST_DIRECTORY_ABA);
+       ASSERT_NOT_INDEXABLE (fixture, TEST_DIRECTORY_ABA);
+}
+
+/* If A is monitored (not recursively) and AA is monitored (not recursively):
+ *  -A, AA, AAA, AAB, AB are indexable
+ *  -AAAA, AAAB, ABA, ABB are not indexable
+ */
+static void
+test_indexing_tree_008 (TestCommonContext *fixture,
+                        gconstpointer      data)
+{
+       tracker_indexing_tree_add (fixture->tree,
+                                  fixture->test_dir[TEST_DIRECTORY_A],
+                                  TRACKER_DIRECTORY_FLAG_MONITOR);
+       tracker_indexing_tree_add (fixture->tree,
+                                  fixture->test_dir[TEST_DIRECTORY_AA],
+                                  TRACKER_DIRECTORY_FLAG_MONITOR);
+
+       ASSERT_INDEXABLE (fixture, TEST_DIRECTORY_A);
+       ASSERT_INDEXABLE (fixture, TEST_DIRECTORY_AA);
+       ASSERT_INDEXABLE (fixture, TEST_DIRECTORY_AAA);
+       ASSERT_NOT_INDEXABLE (fixture, TEST_DIRECTORY_AAAA);
+       ASSERT_NOT_INDEXABLE (fixture, TEST_DIRECTORY_AAAB);
+       ASSERT_INDEXABLE (fixture, TEST_DIRECTORY_AAB);
+       ASSERT_INDEXABLE (fixture, TEST_DIRECTORY_AB);
+       ASSERT_NOT_INDEXABLE (fixture, TEST_DIRECTORY_ABA);
+       ASSERT_NOT_INDEXABLE (fixture, TEST_DIRECTORY_ABA);
+}
+
+/* If A is monitored (not recursively) and AA is monitored (recursively):
+ *  -A, AA, AAA, AAAA, AAAB, AAB, AB are indexable
+ *  -ABA, ABB are not indexable
+ */
+static void
+test_indexing_tree_009 (TestCommonContext *fixture,
+                        gconstpointer      data)
+{
+       tracker_indexing_tree_add (fixture->tree,
+                                  fixture->test_dir[TEST_DIRECTORY_A],
+                                  TRACKER_DIRECTORY_FLAG_MONITOR);
+       tracker_indexing_tree_add (fixture->tree,
+                                  fixture->test_dir[TEST_DIRECTORY_AA],
+                                  TRACKER_DIRECTORY_FLAG_MONITOR | TRACKER_DIRECTORY_FLAG_RECURSE);
+
+       ASSERT_INDEXABLE (fixture, TEST_DIRECTORY_A);
+       ASSERT_INDEXABLE (fixture, TEST_DIRECTORY_AA);
+       ASSERT_INDEXABLE (fixture, TEST_DIRECTORY_AAA);
+       ASSERT_INDEXABLE (fixture, TEST_DIRECTORY_AAAA);
+       ASSERT_INDEXABLE (fixture, TEST_DIRECTORY_AAAB);
+       ASSERT_INDEXABLE (fixture, TEST_DIRECTORY_AAB);
+       ASSERT_INDEXABLE (fixture, TEST_DIRECTORY_AB);
+       ASSERT_NOT_INDEXABLE (fixture, TEST_DIRECTORY_ABA);
+       ASSERT_NOT_INDEXABLE (fixture, TEST_DIRECTORY_ABA);
+}
+
+/* If A is monitored (recursively) and AA is ignored:
+ *  -A, AB, ABA, ABB are indexable
+ *  -AA, AAA, AAAA, AAAB, AAB are not indexable
+ */
+static void
+test_indexing_tree_010 (TestCommonContext *fixture,
+                        gconstpointer      data)
+{
+       tracker_indexing_tree_add (fixture->tree,
+                                  fixture->test_dir[TEST_DIRECTORY_A],
+                                  TRACKER_DIRECTORY_FLAG_MONITOR | TRACKER_DIRECTORY_FLAG_RECURSE);
+       tracker_indexing_tree_add (fixture->tree,
+                                  fixture->test_dir[TEST_DIRECTORY_AA],
+                                  TRACKER_DIRECTORY_FLAG_IGNORE);
+
+       ASSERT_INDEXABLE (fixture, TEST_DIRECTORY_A);
+       ASSERT_NOT_INDEXABLE (fixture, TEST_DIRECTORY_AA);
+       ASSERT_NOT_INDEXABLE (fixture, TEST_DIRECTORY_AAA);
+       ASSERT_NOT_INDEXABLE (fixture, TEST_DIRECTORY_AAAA);
+       ASSERT_NOT_INDEXABLE (fixture, TEST_DIRECTORY_AAAB);
+       ASSERT_NOT_INDEXABLE (fixture, TEST_DIRECTORY_AAB);
+       ASSERT_INDEXABLE (fixture, TEST_DIRECTORY_AB);
+       ASSERT_INDEXABLE (fixture, TEST_DIRECTORY_ABA);
+       ASSERT_INDEXABLE (fixture, TEST_DIRECTORY_ABA);
+}
+
+/* If A is monitored (recursively) and AA is monitored (not recursively):
+ *  -A, AA, AAA, AAB, AB, ABA, ABB are indexable
+ *  -AAAA, AAAB are not indexable
+ */
+static void
+test_indexing_tree_011 (TestCommonContext *fixture,
+                        gconstpointer      data)
+{
+       tracker_indexing_tree_add (fixture->tree,
+                                  fixture->test_dir[TEST_DIRECTORY_A],
+                                  TRACKER_DIRECTORY_FLAG_MONITOR | TRACKER_DIRECTORY_FLAG_RECURSE);
+       tracker_indexing_tree_add (fixture->tree,
+                                  fixture->test_dir[TEST_DIRECTORY_AA],
+                                  TRACKER_DIRECTORY_FLAG_MONITOR);
+
+       ASSERT_INDEXABLE (fixture, TEST_DIRECTORY_A);
+       ASSERT_INDEXABLE (fixture, TEST_DIRECTORY_AA);
+       ASSERT_INDEXABLE (fixture, TEST_DIRECTORY_AAA);
+       ASSERT_NOT_INDEXABLE (fixture, TEST_DIRECTORY_AAAA);
+       ASSERT_NOT_INDEXABLE (fixture, TEST_DIRECTORY_AAAB);
+       ASSERT_INDEXABLE (fixture, TEST_DIRECTORY_AAB);
+       ASSERT_INDEXABLE (fixture, TEST_DIRECTORY_AB);
+       ASSERT_INDEXABLE (fixture, TEST_DIRECTORY_ABA);
+       ASSERT_INDEXABLE (fixture, TEST_DIRECTORY_ABA);
+}
+
+/* If A is monitored (recursively) and AA is monitored (recursively):
+ *  -A, AA, AAA, AAAA, AAAB, AAB, AB, ABA, ABB are indexable
+ */
+static void
+test_indexing_tree_012 (TestCommonContext *fixture,
+                        gconstpointer      data)
+{
+       tracker_indexing_tree_add (fixture->tree,
+                                  fixture->test_dir[TEST_DIRECTORY_A],
+                                  TRACKER_DIRECTORY_FLAG_MONITOR | TRACKER_DIRECTORY_FLAG_RECURSE);
+       tracker_indexing_tree_add (fixture->tree,
+                                  fixture->test_dir[TEST_DIRECTORY_AA],
+                                  TRACKER_DIRECTORY_FLAG_MONITOR | TRACKER_DIRECTORY_FLAG_RECURSE);
+
+       ASSERT_INDEXABLE (fixture, TEST_DIRECTORY_A);
+       ASSERT_INDEXABLE (fixture, TEST_DIRECTORY_AA);
+       ASSERT_INDEXABLE (fixture, TEST_DIRECTORY_AAA);
+       ASSERT_INDEXABLE (fixture, TEST_DIRECTORY_AAAA);
+       ASSERT_INDEXABLE (fixture, TEST_DIRECTORY_AAAB);
+       ASSERT_INDEXABLE (fixture, TEST_DIRECTORY_AAB);
+       ASSERT_INDEXABLE (fixture, TEST_DIRECTORY_AB);
+       ASSERT_INDEXABLE (fixture, TEST_DIRECTORY_ABA);
+       ASSERT_INDEXABLE (fixture, TEST_DIRECTORY_ABA);
+}
+
+/* If A is ignored and AA is ignored, then A is removed from tree
+ *  -A, AA, AAA, AAAA, AAAB, AAB, AB, ABA and ABB are not indexable
+ */
+static void
+test_indexing_tree_013 (TestCommonContext *fixture,
+                        gconstpointer      data)
+{
+       tracker_indexing_tree_add (fixture->tree,
+                                  fixture->test_dir[TEST_DIRECTORY_A],
+                                  TRACKER_DIRECTORY_FLAG_IGNORE);
+       tracker_indexing_tree_add (fixture->tree,
+                                  fixture->test_dir[TEST_DIRECTORY_AA],
+                                  TRACKER_DIRECTORY_FLAG_IGNORE);
+
+       tracker_indexing_tree_remove (fixture->tree,
+                                     fixture->test_dir[TEST_DIRECTORY_A]);
+
+       ASSERT_NOT_INDEXABLE (fixture, TEST_DIRECTORY_A);
+       ASSERT_NOT_INDEXABLE (fixture, TEST_DIRECTORY_AA);
+       ASSERT_NOT_INDEXABLE (fixture, TEST_DIRECTORY_AAA);
+       ASSERT_NOT_INDEXABLE (fixture, TEST_DIRECTORY_AAAA);
+       ASSERT_NOT_INDEXABLE (fixture, TEST_DIRECTORY_AAAB);
+       ASSERT_NOT_INDEXABLE (fixture, TEST_DIRECTORY_AAB);
+       ASSERT_NOT_INDEXABLE (fixture, TEST_DIRECTORY_AB);
+       ASSERT_NOT_INDEXABLE (fixture, TEST_DIRECTORY_ABA);
+       ASSERT_NOT_INDEXABLE (fixture, TEST_DIRECTORY_ABA);
+}
+
+/* If A is ignored and AA is ignored, then AA is removed from tree
+ *  -A, AA, AAA, AAAA, AAAB, AAB, AB, ABA and ABB are not indexable
+ */
+static void
+test_indexing_tree_014 (TestCommonContext *fixture,
+                        gconstpointer      data)
+{
+       tracker_indexing_tree_add (fixture->tree,
+                                  fixture->test_dir[TEST_DIRECTORY_A],
+                                  TRACKER_DIRECTORY_FLAG_IGNORE);
+       tracker_indexing_tree_add (fixture->tree,
+                                  fixture->test_dir[TEST_DIRECTORY_AA],
+                                  TRACKER_DIRECTORY_FLAG_IGNORE);
+
+       tracker_indexing_tree_remove (fixture->tree,
+                                     fixture->test_dir[TEST_DIRECTORY_AA]);
+
+       ASSERT_NOT_INDEXABLE (fixture, TEST_DIRECTORY_A);
+       ASSERT_NOT_INDEXABLE (fixture, TEST_DIRECTORY_AA);
+       ASSERT_NOT_INDEXABLE (fixture, TEST_DIRECTORY_AAA);
+       ASSERT_NOT_INDEXABLE (fixture, TEST_DIRECTORY_AAAA);
+       ASSERT_NOT_INDEXABLE (fixture, TEST_DIRECTORY_AAAB);
+       ASSERT_NOT_INDEXABLE (fixture, TEST_DIRECTORY_AAB);
+       ASSERT_NOT_INDEXABLE (fixture, TEST_DIRECTORY_AB);
+       ASSERT_NOT_INDEXABLE (fixture, TEST_DIRECTORY_ABA);
+       ASSERT_NOT_INDEXABLE (fixture, TEST_DIRECTORY_ABA);
+}
+
+/* If A is ignored and AA is monitored (not recursively), then A is removed
+ * from tree.
+ *  -AA, AAA, AAB are indexable
+ *  -A, AAAA, AAAB, AB, ABA and ABB are not indexable
+ */
+static void
+test_indexing_tree_015 (TestCommonContext *fixture,
+                        gconstpointer      data)
+{
+       tracker_indexing_tree_add (fixture->tree,
+                                  fixture->test_dir[TEST_DIRECTORY_A],
+                                  TRACKER_DIRECTORY_FLAG_IGNORE);
+       tracker_indexing_tree_add (fixture->tree,
+                                  fixture->test_dir[TEST_DIRECTORY_AA],
+                                  TRACKER_DIRECTORY_FLAG_MONITOR);
+
+       tracker_indexing_tree_remove (fixture->tree,
+                                     fixture->test_dir[TEST_DIRECTORY_A]);
+
+       ASSERT_NOT_INDEXABLE (fixture, TEST_DIRECTORY_A);
+       ASSERT_INDEXABLE (fixture, TEST_DIRECTORY_AA);
+       ASSERT_INDEXABLE (fixture, TEST_DIRECTORY_AAA);
+       ASSERT_NOT_INDEXABLE (fixture, TEST_DIRECTORY_AAAA);
+       ASSERT_NOT_INDEXABLE (fixture, TEST_DIRECTORY_AAAB);
+       ASSERT_INDEXABLE (fixture, TEST_DIRECTORY_AAB);
+       ASSERT_NOT_INDEXABLE (fixture, TEST_DIRECTORY_AB);
+       ASSERT_NOT_INDEXABLE (fixture, TEST_DIRECTORY_ABA);
+       ASSERT_NOT_INDEXABLE (fixture, TEST_DIRECTORY_ABA);
+}
+
+/* If A is ignored and AA is monitored (not recursively), then AA is removed
+ * from tree.
+ *  -A, AA, AAA, AAAA, AAAB, AAB, AB, ABA and ABB are not indexable
+ */
+static void
+test_indexing_tree_016 (TestCommonContext *fixture,
+                        gconstpointer      data)
+{
+       tracker_indexing_tree_add (fixture->tree,
+                                  fixture->test_dir[TEST_DIRECTORY_A],
+                                  TRACKER_DIRECTORY_FLAG_IGNORE);
+       tracker_indexing_tree_add (fixture->tree,
+                                  fixture->test_dir[TEST_DIRECTORY_AA],
+                                  TRACKER_DIRECTORY_FLAG_MONITOR);
+
+       tracker_indexing_tree_remove (fixture->tree,
+                                     fixture->test_dir[TEST_DIRECTORY_AA]);
+
+       ASSERT_NOT_INDEXABLE (fixture, TEST_DIRECTORY_A);
+       ASSERT_NOT_INDEXABLE (fixture, TEST_DIRECTORY_AA);
+       ASSERT_NOT_INDEXABLE (fixture, TEST_DIRECTORY_AAA);
+       ASSERT_NOT_INDEXABLE (fixture, TEST_DIRECTORY_AAAA);
+       ASSERT_NOT_INDEXABLE (fixture, TEST_DIRECTORY_AAAB);
+       ASSERT_NOT_INDEXABLE (fixture, TEST_DIRECTORY_AAB);
+       ASSERT_NOT_INDEXABLE (fixture, TEST_DIRECTORY_AB);
+       ASSERT_NOT_INDEXABLE (fixture, TEST_DIRECTORY_ABA);
+       ASSERT_NOT_INDEXABLE (fixture, TEST_DIRECTORY_ABA);
+}
+
+/* If A is ignored and AA is monitored (recursively), then A is removed from
+ * tree.
+ *  -AA, AAA, AAAA, AAAB, AAB are indexable
+ *  -A, AB, ABA and ABB are not indexable
+ */
+static void
+test_indexing_tree_017 (TestCommonContext *fixture,
+                        gconstpointer      data)
+{
+       tracker_indexing_tree_add (fixture->tree,
+                                  fixture->test_dir[TEST_DIRECTORY_A],
+                                  TRACKER_DIRECTORY_FLAG_IGNORE);
+       tracker_indexing_tree_add (fixture->tree,
+                                  fixture->test_dir[TEST_DIRECTORY_AA],
+                                  TRACKER_DIRECTORY_FLAG_MONITOR | TRACKER_DIRECTORY_FLAG_RECURSE);
+
+       tracker_indexing_tree_remove (fixture->tree,
+                                     fixture->test_dir[TEST_DIRECTORY_A]);
+
+       ASSERT_NOT_INDEXABLE (fixture, TEST_DIRECTORY_A);
+       ASSERT_INDEXABLE (fixture, TEST_DIRECTORY_AA);
+       ASSERT_INDEXABLE (fixture, TEST_DIRECTORY_AAA);
+       ASSERT_INDEXABLE (fixture, TEST_DIRECTORY_AAAA);
+       ASSERT_INDEXABLE (fixture, TEST_DIRECTORY_AAAB);
+       ASSERT_INDEXABLE (fixture, TEST_DIRECTORY_AAB);
+       ASSERT_NOT_INDEXABLE (fixture, TEST_DIRECTORY_AB);
+       ASSERT_NOT_INDEXABLE (fixture, TEST_DIRECTORY_ABA);
+       ASSERT_NOT_INDEXABLE (fixture, TEST_DIRECTORY_ABA);
+}
+
+/* If A is ignored and AA is monitored (recursively), then AA is removed
+ * from tree.
+ *  -A, AA, AAA, AAAA, AAAB, AAB, AB, ABA and ABB are not indexable
+ */
+static void
+test_indexing_tree_018 (TestCommonContext *fixture,
+                        gconstpointer      data)
+{
+       tracker_indexing_tree_add (fixture->tree,
+                                  fixture->test_dir[TEST_DIRECTORY_A],
+                                  TRACKER_DIRECTORY_FLAG_IGNORE);
+       tracker_indexing_tree_add (fixture->tree,
+                                  fixture->test_dir[TEST_DIRECTORY_AA],
+                                  TRACKER_DIRECTORY_FLAG_MONITOR | TRACKER_DIRECTORY_FLAG_RECURSE);
+
+       tracker_indexing_tree_remove (fixture->tree,
+                                     fixture->test_dir[TEST_DIRECTORY_AA]);
+
+       ASSERT_NOT_INDEXABLE (fixture, TEST_DIRECTORY_A);
+       ASSERT_NOT_INDEXABLE (fixture, TEST_DIRECTORY_AA);
+       ASSERT_NOT_INDEXABLE (fixture, TEST_DIRECTORY_AAA);
+       ASSERT_NOT_INDEXABLE (fixture, TEST_DIRECTORY_AAAA);
+       ASSERT_NOT_INDEXABLE (fixture, TEST_DIRECTORY_AAAB);
+       ASSERT_NOT_INDEXABLE (fixture, TEST_DIRECTORY_AAB);
+       ASSERT_NOT_INDEXABLE (fixture, TEST_DIRECTORY_AB);
+       ASSERT_NOT_INDEXABLE (fixture, TEST_DIRECTORY_ABA);
+       ASSERT_NOT_INDEXABLE (fixture, TEST_DIRECTORY_ABA);
+}
+
+/* If A is monitored (not recursively) and AA is ignored, then A is removed
+ * from tree.
+ *  -A, AA, AAA, AAAA, AAAB, AAB, AB, ABA and ABB are not indexable
+ */
+static void
+test_indexing_tree_019 (TestCommonContext *fixture,
+                        gconstpointer      data)
+{
+       tracker_indexing_tree_add (fixture->tree,
+                                  fixture->test_dir[TEST_DIRECTORY_A],
+                                  TRACKER_DIRECTORY_FLAG_MONITOR);
+       tracker_indexing_tree_add (fixture->tree,
+                                  fixture->test_dir[TEST_DIRECTORY_AA],
+                                  TRACKER_DIRECTORY_FLAG_IGNORE);
+
+       tracker_indexing_tree_remove (fixture->tree,
+                                     fixture->test_dir[TEST_DIRECTORY_A]);
+
+       ASSERT_NOT_INDEXABLE (fixture, TEST_DIRECTORY_A);
+       ASSERT_NOT_INDEXABLE (fixture, TEST_DIRECTORY_AA);
+       ASSERT_NOT_INDEXABLE (fixture, TEST_DIRECTORY_AAA);
+       ASSERT_NOT_INDEXABLE (fixture, TEST_DIRECTORY_AAAA);
+       ASSERT_NOT_INDEXABLE (fixture, TEST_DIRECTORY_AAAB);
+       ASSERT_NOT_INDEXABLE (fixture, TEST_DIRECTORY_AAB);
+       ASSERT_NOT_INDEXABLE (fixture, TEST_DIRECTORY_AB);
+       ASSERT_NOT_INDEXABLE (fixture, TEST_DIRECTORY_ABA);
+       ASSERT_NOT_INDEXABLE (fixture, TEST_DIRECTORY_ABA);
+}
+
+/* If A is monitored (not recursively) and AA is ignored, then AA is removed
+ * from tree.
+ *  -A, AA, AB are indexable.
+ *  -AAA, AAAA, AAAB, AAB, ABA and ABB are not indexable
+ */
+static void
+test_indexing_tree_020 (TestCommonContext *fixture,
+                        gconstpointer      data)
+{
+       tracker_indexing_tree_add (fixture->tree,
+                                  fixture->test_dir[TEST_DIRECTORY_A],
+                                  TRACKER_DIRECTORY_FLAG_MONITOR);
+       tracker_indexing_tree_add (fixture->tree,
+                                  fixture->test_dir[TEST_DIRECTORY_AA],
+                                  TRACKER_DIRECTORY_FLAG_IGNORE);
+
+       tracker_indexing_tree_remove (fixture->tree,
+                                     fixture->test_dir[TEST_DIRECTORY_AA]);
+
+       ASSERT_INDEXABLE (fixture, TEST_DIRECTORY_A);
+       ASSERT_INDEXABLE (fixture, TEST_DIRECTORY_AA);
+       ASSERT_NOT_INDEXABLE (fixture, TEST_DIRECTORY_AAA);
+       ASSERT_NOT_INDEXABLE (fixture, TEST_DIRECTORY_AAAA);
+       ASSERT_NOT_INDEXABLE (fixture, TEST_DIRECTORY_AAAB);
+       ASSERT_NOT_INDEXABLE (fixture, TEST_DIRECTORY_AAB);
+       ASSERT_INDEXABLE (fixture, TEST_DIRECTORY_AB);
+       ASSERT_NOT_INDEXABLE (fixture, TEST_DIRECTORY_ABA);
+       ASSERT_NOT_INDEXABLE (fixture, TEST_DIRECTORY_ABA);
+}
+
+/* If A is monitored (not recursively) and AA is monitored (not recursively),
+ *  then A is removed from tree.
+ *  -AA, AAA, AAB are indexable.
+ *  -A, AAAA, AAAB, AB, ABA and ABB are not indexable
+ */
+static void
+test_indexing_tree_021 (TestCommonContext *fixture,
+                        gconstpointer      data)
+{
+       tracker_indexing_tree_add (fixture->tree,
+                                  fixture->test_dir[TEST_DIRECTORY_A],
+                                  TRACKER_DIRECTORY_FLAG_MONITOR);
+       tracker_indexing_tree_add (fixture->tree,
+                                  fixture->test_dir[TEST_DIRECTORY_AA],
+                                  TRACKER_DIRECTORY_FLAG_MONITOR);
+
+       tracker_indexing_tree_remove (fixture->tree,
+                                     fixture->test_dir[TEST_DIRECTORY_A]);
+
+       ASSERT_NOT_INDEXABLE (fixture, TEST_DIRECTORY_A);
+       ASSERT_INDEXABLE (fixture, TEST_DIRECTORY_AA);
+       ASSERT_INDEXABLE (fixture, TEST_DIRECTORY_AAA);
+       ASSERT_NOT_INDEXABLE (fixture, TEST_DIRECTORY_AAAA);
+       ASSERT_NOT_INDEXABLE (fixture, TEST_DIRECTORY_AAAB);
+       ASSERT_INDEXABLE (fixture, TEST_DIRECTORY_AAB);
+       ASSERT_NOT_INDEXABLE (fixture, TEST_DIRECTORY_AB);
+       ASSERT_NOT_INDEXABLE (fixture, TEST_DIRECTORY_ABA);
+       ASSERT_NOT_INDEXABLE (fixture, TEST_DIRECTORY_ABA);
+}
+
+/* If A is monitored (not recursively) and AA is monitored (not recursively),
+ *  then AA is removed from tree.
+ *  -A, AA, AB are indexable.
+ *  -AAA, AAAA, AAAB, AAB, ABA and ABB are not indexable
+ */
+static void
+test_indexing_tree_022 (TestCommonContext *fixture,
+                        gconstpointer      data)
+{
+       tracker_indexing_tree_add (fixture->tree,
+                                  fixture->test_dir[TEST_DIRECTORY_A],
+                                  TRACKER_DIRECTORY_FLAG_MONITOR);
+       tracker_indexing_tree_add (fixture->tree,
+                                  fixture->test_dir[TEST_DIRECTORY_AA],
+                                  TRACKER_DIRECTORY_FLAG_MONITOR);
+
+       tracker_indexing_tree_remove (fixture->tree,
+                                     fixture->test_dir[TEST_DIRECTORY_AA]);
+
+       ASSERT_INDEXABLE (fixture, TEST_DIRECTORY_A);
+       ASSERT_INDEXABLE (fixture, TEST_DIRECTORY_AA);
+       ASSERT_NOT_INDEXABLE (fixture, TEST_DIRECTORY_AAA);
+       ASSERT_NOT_INDEXABLE (fixture, TEST_DIRECTORY_AAAA);
+       ASSERT_NOT_INDEXABLE (fixture, TEST_DIRECTORY_AAAB);
+       ASSERT_NOT_INDEXABLE (fixture, TEST_DIRECTORY_AAB);
+       ASSERT_INDEXABLE (fixture, TEST_DIRECTORY_AB);
+       ASSERT_NOT_INDEXABLE (fixture, TEST_DIRECTORY_ABA);
+       ASSERT_NOT_INDEXABLE (fixture, TEST_DIRECTORY_ABA);
+}
+
+/* If A is monitored (not recursively) and AA is monitored (recursively),
+ *  then A is removed from tree.
+ *  -AA, AAA, AAAA, AAAB, AAB are indexable.
+ *  -A, AB, ABA and ABB are not indexable
+ */
+static void
+test_indexing_tree_023 (TestCommonContext *fixture,
+                        gconstpointer      data)
+{
+       tracker_indexing_tree_add (fixture->tree,
+                                  fixture->test_dir[TEST_DIRECTORY_A],
+                                  TRACKER_DIRECTORY_FLAG_MONITOR);
+       tracker_indexing_tree_add (fixture->tree,
+                                  fixture->test_dir[TEST_DIRECTORY_AA],
+                                  TRACKER_DIRECTORY_FLAG_MONITOR | TRACKER_DIRECTORY_FLAG_RECURSE);
+
+       tracker_indexing_tree_remove (fixture->tree,
+                                     fixture->test_dir[TEST_DIRECTORY_A]);
+
+       ASSERT_NOT_INDEXABLE (fixture, TEST_DIRECTORY_A);
+       ASSERT_INDEXABLE (fixture, TEST_DIRECTORY_AA);
+       ASSERT_INDEXABLE (fixture, TEST_DIRECTORY_AAA);
+       ASSERT_INDEXABLE (fixture, TEST_DIRECTORY_AAAA);
+       ASSERT_INDEXABLE (fixture, TEST_DIRECTORY_AAAB);
+       ASSERT_INDEXABLE (fixture, TEST_DIRECTORY_AAB);
+       ASSERT_NOT_INDEXABLE (fixture, TEST_DIRECTORY_AB);
+       ASSERT_NOT_INDEXABLE (fixture, TEST_DIRECTORY_ABA);
+       ASSERT_NOT_INDEXABLE (fixture, TEST_DIRECTORY_ABA);
+}
+
+/* If A is monitored (not recursively) and AA is monitored (recursively),
+ *  then AA is removed from tree.
+ *  -A, AA, AB are indexable.
+ *  -AAA, AAAA, AAAB, AAB, ABA and ABB are not indexable
+ */
+static void
+test_indexing_tree_024 (TestCommonContext *fixture,
+                        gconstpointer      data)
+{
+       tracker_indexing_tree_add (fixture->tree,
+                                  fixture->test_dir[TEST_DIRECTORY_A],
+                                  TRACKER_DIRECTORY_FLAG_MONITOR);
+       tracker_indexing_tree_add (fixture->tree,
+                                  fixture->test_dir[TEST_DIRECTORY_AA],
+                                  TRACKER_DIRECTORY_FLAG_MONITOR | TRACKER_DIRECTORY_FLAG_RECURSE);
+
+       tracker_indexing_tree_remove (fixture->tree,
+                                     fixture->test_dir[TEST_DIRECTORY_AA]);
+
+       ASSERT_INDEXABLE (fixture, TEST_DIRECTORY_A);
+       ASSERT_INDEXABLE (fixture, TEST_DIRECTORY_AA);
+       ASSERT_NOT_INDEXABLE (fixture, TEST_DIRECTORY_AAA);
+       ASSERT_NOT_INDEXABLE (fixture, TEST_DIRECTORY_AAAA);
+       ASSERT_NOT_INDEXABLE (fixture, TEST_DIRECTORY_AAAB);
+       ASSERT_NOT_INDEXABLE (fixture, TEST_DIRECTORY_AAB);
+       ASSERT_INDEXABLE (fixture, TEST_DIRECTORY_AB);
+       ASSERT_NOT_INDEXABLE (fixture, TEST_DIRECTORY_ABA);
+       ASSERT_NOT_INDEXABLE (fixture, TEST_DIRECTORY_ABA);
+}
+
+/* If A is monitored (recursively) and AA is ignored, then A is removed
+ * from tree.
+ *  -A, AA, AAA, AAAA, AAAB, AAB, AB, ABA and ABB are not indexable
+ */
+static void
+test_indexing_tree_025 (TestCommonContext *fixture,
+                        gconstpointer      data)
+{
+       tracker_indexing_tree_add (fixture->tree,
+                                  fixture->test_dir[TEST_DIRECTORY_A],
+                                  TRACKER_DIRECTORY_FLAG_MONITOR | TRACKER_DIRECTORY_FLAG_RECURSE);
+       tracker_indexing_tree_add (fixture->tree,
+                                  fixture->test_dir[TEST_DIRECTORY_AA],
+                                  TRACKER_DIRECTORY_FLAG_IGNORE);
+
+       tracker_indexing_tree_remove (fixture->tree,
+                                     fixture->test_dir[TEST_DIRECTORY_A]);
+
+       ASSERT_NOT_INDEXABLE (fixture, TEST_DIRECTORY_A);
+       ASSERT_NOT_INDEXABLE (fixture, TEST_DIRECTORY_AA);
+       ASSERT_NOT_INDEXABLE (fixture, TEST_DIRECTORY_AAA);
+       ASSERT_NOT_INDEXABLE (fixture, TEST_DIRECTORY_AAAA);
+       ASSERT_NOT_INDEXABLE (fixture, TEST_DIRECTORY_AAAB);
+       ASSERT_NOT_INDEXABLE (fixture, TEST_DIRECTORY_AAB);
+       ASSERT_NOT_INDEXABLE (fixture, TEST_DIRECTORY_AB);
+       ASSERT_NOT_INDEXABLE (fixture, TEST_DIRECTORY_ABA);
+       ASSERT_NOT_INDEXABLE (fixture, TEST_DIRECTORY_ABA);
+}
+
+/* If A is monitored (recursively) and AA is ignored, then AA is removed
+ * from tree.
+ *  -A, AA, AAA, AAAA, AAAB, AAB, AB, ABA and ABB are indexable
+ */
+static void
+test_indexing_tree_026 (TestCommonContext *fixture,
+                        gconstpointer      data)
+{
+       tracker_indexing_tree_add (fixture->tree,
+                                  fixture->test_dir[TEST_DIRECTORY_A],
+                                  TRACKER_DIRECTORY_FLAG_MONITOR | TRACKER_DIRECTORY_FLAG_RECURSE);
+       tracker_indexing_tree_add (fixture->tree,
+                                  fixture->test_dir[TEST_DIRECTORY_AA],
+                                  TRACKER_DIRECTORY_FLAG_IGNORE);
+
+       tracker_indexing_tree_remove (fixture->tree,
+                                     fixture->test_dir[TEST_DIRECTORY_AA]);
+
+       ASSERT_INDEXABLE (fixture, TEST_DIRECTORY_A);
+       ASSERT_INDEXABLE (fixture, TEST_DIRECTORY_AA);
+       ASSERT_INDEXABLE (fixture, TEST_DIRECTORY_AAA);
+       ASSERT_INDEXABLE (fixture, TEST_DIRECTORY_AAAA);
+       ASSERT_INDEXABLE (fixture, TEST_DIRECTORY_AAAB);
+       ASSERT_INDEXABLE (fixture, TEST_DIRECTORY_AAB);
+       ASSERT_INDEXABLE (fixture, TEST_DIRECTORY_AB);
+       ASSERT_INDEXABLE (fixture, TEST_DIRECTORY_ABA);
+       ASSERT_INDEXABLE (fixture, TEST_DIRECTORY_ABA);
+}
+
+/* If A is monitored (recursively) and AA is monitored (not recursively), then
+ *  A is removed from tree.
+ *  -AA, AAA, AAB are indexable
+ *  -A, AAAA, AAAB, AB, ABA and ABB are not indexable
+ */
+static void
+test_indexing_tree_027 (TestCommonContext *fixture,
+                        gconstpointer      data)
+{
+       tracker_indexing_tree_add (fixture->tree,
+                                  fixture->test_dir[TEST_DIRECTORY_A],
+                                  TRACKER_DIRECTORY_FLAG_MONITOR | TRACKER_DIRECTORY_FLAG_RECURSE);
+       tracker_indexing_tree_add (fixture->tree,
+                                  fixture->test_dir[TEST_DIRECTORY_AA],
+                                  TRACKER_DIRECTORY_FLAG_MONITOR);
+
+       tracker_indexing_tree_remove (fixture->tree,
+                                     fixture->test_dir[TEST_DIRECTORY_A]);
+
+       ASSERT_NOT_INDEXABLE (fixture, TEST_DIRECTORY_A);
+       ASSERT_INDEXABLE (fixture, TEST_DIRECTORY_AA);
+       ASSERT_INDEXABLE (fixture, TEST_DIRECTORY_AAA);
+       ASSERT_NOT_INDEXABLE (fixture, TEST_DIRECTORY_AAAA);
+       ASSERT_NOT_INDEXABLE (fixture, TEST_DIRECTORY_AAAB);
+       ASSERT_INDEXABLE (fixture, TEST_DIRECTORY_AAB);
+       ASSERT_NOT_INDEXABLE (fixture, TEST_DIRECTORY_AB);
+       ASSERT_NOT_INDEXABLE (fixture, TEST_DIRECTORY_ABA);
+       ASSERT_NOT_INDEXABLE (fixture, TEST_DIRECTORY_ABA);
+}
+
+/* If A is monitored (recursively) and AA is monitored (not recursively), then
+ *  AA is removed from tree.
+ *  -A, AA, AAA, AAAA, AAAB, AAB, AB, ABA and ABB are indexable
+ */
+static void
+test_indexing_tree_028 (TestCommonContext *fixture,
+                        gconstpointer      data)
+{
+       tracker_indexing_tree_add (fixture->tree,
+                                  fixture->test_dir[TEST_DIRECTORY_A],
+                                  TRACKER_DIRECTORY_FLAG_MONITOR | TRACKER_DIRECTORY_FLAG_RECURSE);
+       tracker_indexing_tree_add (fixture->tree,
+                                  fixture->test_dir[TEST_DIRECTORY_AA],
+                                  TRACKER_DIRECTORY_FLAG_MONITOR);
+
+       tracker_indexing_tree_remove (fixture->tree,
+                                     fixture->test_dir[TEST_DIRECTORY_AA]);
+
+       ASSERT_INDEXABLE (fixture, TEST_DIRECTORY_A);
+       ASSERT_INDEXABLE (fixture, TEST_DIRECTORY_AA);
+       ASSERT_INDEXABLE (fixture, TEST_DIRECTORY_AAA);
+       ASSERT_INDEXABLE (fixture, TEST_DIRECTORY_AAAA);
+       ASSERT_INDEXABLE (fixture, TEST_DIRECTORY_AAAB);
+       ASSERT_INDEXABLE (fixture, TEST_DIRECTORY_AAB);
+       ASSERT_INDEXABLE (fixture, TEST_DIRECTORY_AB);
+       ASSERT_INDEXABLE (fixture, TEST_DIRECTORY_ABA);
+       ASSERT_INDEXABLE (fixture, TEST_DIRECTORY_ABA);
+}
+
+/* If A is monitored (recursively) and AA is monitored (recursively),
+ * then A is removed from tree.
+ *  -AA, AAA, AAAA, AAAB, AAB are indexable
+ *  -A, AB, ABA and ABB are not indexable
+ */
+static void
+test_indexing_tree_029 (TestCommonContext *fixture,
+                        gconstpointer      data)
+{
+       tracker_indexing_tree_add (fixture->tree,
+                                  fixture->test_dir[TEST_DIRECTORY_A],
+                                  TRACKER_DIRECTORY_FLAG_MONITOR | TRACKER_DIRECTORY_FLAG_RECURSE);
+       tracker_indexing_tree_add (fixture->tree,
+                                  fixture->test_dir[TEST_DIRECTORY_AA],
+                                  TRACKER_DIRECTORY_FLAG_MONITOR | TRACKER_DIRECTORY_FLAG_RECURSE);
+
+       tracker_indexing_tree_remove (fixture->tree,
+                                     fixture->test_dir[TEST_DIRECTORY_A]);
+
+       ASSERT_NOT_INDEXABLE (fixture, TEST_DIRECTORY_A);
+       ASSERT_INDEXABLE (fixture, TEST_DIRECTORY_AA);
+       ASSERT_INDEXABLE (fixture, TEST_DIRECTORY_AAA);
+       ASSERT_INDEXABLE (fixture, TEST_DIRECTORY_AAAA);
+       ASSERT_INDEXABLE (fixture, TEST_DIRECTORY_AAAB);
+       ASSERT_INDEXABLE (fixture, TEST_DIRECTORY_AAB);
+       ASSERT_NOT_INDEXABLE (fixture, TEST_DIRECTORY_AB);
+       ASSERT_NOT_INDEXABLE (fixture, TEST_DIRECTORY_ABA);
+       ASSERT_NOT_INDEXABLE (fixture, TEST_DIRECTORY_ABA);
+}
+
+/* If A is monitored (recursively) and AA is monitored (recursively),
+ * then AA is removed from tree.
+ *  -A, AA, AAA, AAAA, AAAB, AAB, AB, ABA and ABB are indexable
+ */
+static void
+test_indexing_tree_030 (TestCommonContext *fixture,
+                        gconstpointer      data)
+{
+       tracker_indexing_tree_add (fixture->tree,
+                                  fixture->test_dir[TEST_DIRECTORY_A],
+                                  TRACKER_DIRECTORY_FLAG_MONITOR | TRACKER_DIRECTORY_FLAG_RECURSE);
+       tracker_indexing_tree_add (fixture->tree,
+                                  fixture->test_dir[TEST_DIRECTORY_AA],
+                                  TRACKER_DIRECTORY_FLAG_MONITOR | TRACKER_DIRECTORY_FLAG_RECURSE);
+
+       tracker_indexing_tree_remove (fixture->tree,
+                                     fixture->test_dir[TEST_DIRECTORY_AA]);
+
+       ASSERT_INDEXABLE (fixture, TEST_DIRECTORY_A);
+       ASSERT_INDEXABLE (fixture, TEST_DIRECTORY_AA);
+       ASSERT_INDEXABLE (fixture, TEST_DIRECTORY_AAA);
+       ASSERT_INDEXABLE (fixture, TEST_DIRECTORY_AAAA);
+       ASSERT_INDEXABLE (fixture, TEST_DIRECTORY_AAAB);
+       ASSERT_INDEXABLE (fixture, TEST_DIRECTORY_AAB);
+       ASSERT_INDEXABLE (fixture, TEST_DIRECTORY_AB);
+       ASSERT_INDEXABLE (fixture, TEST_DIRECTORY_ABA);
+       ASSERT_INDEXABLE (fixture, TEST_DIRECTORY_ABA);
+}
+
+gint
+main (gint    argc,
+      gchar **argv)
+{
+       g_test_init (&argc, &argv, NULL);
+
+       g_test_message ("Testing indexing tree");
+
+       test_add ("/libtracker-miner/indexing-tree/001", test_indexing_tree_001);
+       test_add ("/libtracker-miner/indexing-tree/002", test_indexing_tree_002);
+       test_add ("/libtracker-miner/indexing-tree/003", test_indexing_tree_003);
+       test_add ("/libtracker-miner/indexing-tree/004", test_indexing_tree_004);
+       test_add ("/libtracker-miner/indexing-tree/005", test_indexing_tree_005);
+       test_add ("/libtracker-miner/indexing-tree/006", test_indexing_tree_006);
+       test_add ("/libtracker-miner/indexing-tree/007", test_indexing_tree_007);
+       test_add ("/libtracker-miner/indexing-tree/008", test_indexing_tree_008);
+       test_add ("/libtracker-miner/indexing-tree/009", test_indexing_tree_009);
+       test_add ("/libtracker-miner/indexing-tree/010", test_indexing_tree_010);
+       test_add ("/libtracker-miner/indexing-tree/011", test_indexing_tree_011);
+       test_add ("/libtracker-miner/indexing-tree/012", test_indexing_tree_012);
+       test_add ("/libtracker-miner/indexing-tree/013", test_indexing_tree_013);
+       test_add ("/libtracker-miner/indexing-tree/014", test_indexing_tree_014);
+       test_add ("/libtracker-miner/indexing-tree/015", test_indexing_tree_015);
+       test_add ("/libtracker-miner/indexing-tree/016", test_indexing_tree_016);
+       test_add ("/libtracker-miner/indexing-tree/017", test_indexing_tree_017);
+       test_add ("/libtracker-miner/indexing-tree/018", test_indexing_tree_018);
+       test_add ("/libtracker-miner/indexing-tree/019", test_indexing_tree_019);
+       test_add ("/libtracker-miner/indexing-tree/020", test_indexing_tree_020);
+       test_add ("/libtracker-miner/indexing-tree/021", test_indexing_tree_021);
+       test_add ("/libtracker-miner/indexing-tree/022", test_indexing_tree_022);
+       test_add ("/libtracker-miner/indexing-tree/023", test_indexing_tree_023);
+       test_add ("/libtracker-miner/indexing-tree/024", test_indexing_tree_024);
+       test_add ("/libtracker-miner/indexing-tree/025", test_indexing_tree_025);
+       test_add ("/libtracker-miner/indexing-tree/026", test_indexing_tree_026);
+       test_add ("/libtracker-miner/indexing-tree/027", test_indexing_tree_027);
+       test_add ("/libtracker-miner/indexing-tree/028", test_indexing_tree_028);
+       test_add ("/libtracker-miner/indexing-tree/029", test_indexing_tree_029);
+       test_add ("/libtracker-miner/indexing-tree/030", test_indexing_tree_030);
+
+       return g_test_run ();
+}
diff --git a/tests/libtracker-miner/tracker-miner-mock.vala b/tests/libtracker-miner/tracker-miner-mock.vala
new file mode 100644
index 000000000..302a3387b
--- /dev/null
+++ b/tests/libtracker-miner/tracker-miner-mock.vala
@@ -0,0 +1,70 @@
+//
+// Copyright (C) 2010, Nokia
+//
+// This program is free software; you can redistribute it and/or
+// modify it under the terms of the GNU General Public License
+// as published by the Free Software Foundation; either version 2
+// of the License, or (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with this program; if not, write to the Free Software
+// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+// 02110-1301, USA.
+//
+
+using GLib;
+
+public class TrackerMinerMock : GLib.Object {
+
+    public bool is_paused ;
+    public string pause_reason { get; set; default = ""; }
+    public string name { get; set; default = ""; } 
+    public string[] apps { get { return _apps; } }
+    public string[] reasons { get { return _apps; } }
+
+    public signal void progress (string miner, string status, double progress);
+    public signal void paused ();
+    public signal void resumed ();
+
+    string[] _apps;
+    string[] _reasons;
+
+    public TrackerMinerMock (string name) {
+        this.name = name;
+        this._apps = {};
+        this._reasons = {};
+    }
+
+    public void set_paused (bool paused) { this.is_paused = paused; }
+    public bool get_paused () { return this.is_paused ; }
+
+    public void pause (string app, string reason) {
+
+        if (this._apps.length == 0) {
+            this._apps = { app };
+        } else {
+            this._apps += app;
+        }
+
+        if (this._reasons.length == 0) {
+            this._reasons = { reason };
+        } else {
+            this._reasons += reason;
+        }
+        this.is_paused = true;
+        this.paused ();
+    }
+
+    public void resume () {
+        this._apps = null;
+        this._reasons = null;
+        this.is_paused = false;
+        this.resumed ();
+    }
+
+}
diff --git a/tests/libtracker-miner/tracker-monitor-test.c b/tests/libtracker-miner/tracker-monitor-test.c
new file mode 100644
index 000000000..3ba9412d7
--- /dev/null
+++ b/tests/libtracker-miner/tracker-monitor-test.c
@@ -0,0 +1,2020 @@
+/*
+ * Copyright (C) 2010, Nokia <ivan frade nokia com>
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+ * 02110-1301, USA.
+ */
+
+#include <string.h>
+#include <unistd.h>
+
+#include <glib.h>
+#include <glib/gstdio.h>
+
+/* Special case, the monitor header is not normally exported */
+#include <libtracker-miner/tracker-monitor.h>
+
+/* -------------- COMMON FOR ALL FILE EVENT TESTS ----------------- */
+
+#define TEST_TIMEOUT 5 /* seconds */
+
+typedef enum {
+       MONITOR_SIGNAL_NONE                   = 0,
+       MONITOR_SIGNAL_ITEM_CREATED           = 1 << 0,
+       MONITOR_SIGNAL_ITEM_UPDATED           = 1 << 1,
+       MONITOR_SIGNAL_ITEM_ATTRIBUTE_UPDATED = 1 << 2,
+       MONITOR_SIGNAL_ITEM_DELETED           = 1 << 3,
+       MONITOR_SIGNAL_ITEM_MOVED_FROM        = 1 << 4,
+       MONITOR_SIGNAL_ITEM_MOVED_TO          = 1 << 5
+} MonitorSignal;
+
+/* Fixture object type */
+typedef struct {
+       TrackerMonitor *monitor;
+       GFile *monitored_directory_file;
+       gchar *monitored_directory;
+       gchar *not_monitored_directory;
+       GHashTable *events;
+       GMainLoop *main_loop;
+} TrackerMonitorTestFixture;
+
+static void
+add_event (GHashTable    *events,
+           GFile         *file,
+           MonitorSignal  new_event)
+{
+       gpointer previous_file;
+       gpointer previous_mask;
+
+       /* Lookup file in HT */
+       if (g_hash_table_lookup_extended (events,
+                                         file,
+                                         &previous_file,
+                                         &previous_mask)) {
+               guint mask;
+
+               mask = GPOINTER_TO_UINT (previous_mask);
+               mask |= new_event;
+               g_hash_table_replace (events,
+                                     g_object_ref (previous_file),
+                                     GUINT_TO_POINTER (mask));
+       }
+}
+
+static void
+test_monitor_events_created_cb (TrackerMonitor *monitor,
+                                GFile          *file,
+                                gboolean        is_directory,
+                                gpointer        user_data)
+{
+       gchar *path;
+
+       g_assert (file != NULL);
+       path = g_file_get_path (file);
+       g_assert (path != NULL);
+
+       g_debug ("***** '%s' (%s) (CREATED)",
+                path,
+                is_directory ? "DIR" : "FILE");
+
+       g_free (path);
+
+       add_event ((GHashTable *) user_data,
+                  file,
+                  MONITOR_SIGNAL_ITEM_CREATED);
+}
+
+static void
+test_monitor_events_updated_cb (TrackerMonitor *monitor,
+                                GFile          *file,
+                                gboolean        is_directory,
+                                gpointer        user_data)
+{
+       gchar *path;
+
+       g_assert (file != NULL);
+       path = g_file_get_path (file);
+       g_assert (path != NULL);
+
+       g_debug ("***** '%s' (%s) (UPDATED)",
+                path,
+                is_directory ? "DIR" : "FILE");
+
+       g_free (path);
+
+       add_event ((GHashTable *) user_data,
+                  file,
+                  MONITOR_SIGNAL_ITEM_UPDATED);
+}
+
+static void
+test_monitor_events_attribute_updated_cb (TrackerMonitor *monitor,
+                                          GFile          *file,
+                                          gboolean        is_directory,
+                                          gpointer        user_data)
+{
+       gchar *path;
+
+       g_assert (file != NULL);
+       path = g_file_get_path (file);
+       g_assert (path != NULL);
+
+       g_debug ("***** '%s' (%s) (ATRIBUTE UPDATED)",
+                path,
+                is_directory ? "DIR" : "FILE");
+
+       g_free (path);
+
+       add_event ((GHashTable *) user_data,
+                  file,
+                  MONITOR_SIGNAL_ITEM_ATTRIBUTE_UPDATED);
+}
+
+static void
+test_monitor_events_deleted_cb (TrackerMonitor *monitor,
+                                GFile          *file,
+                                gboolean        is_directory,
+                                gpointer        user_data)
+{
+       gchar *path;
+
+       g_assert (file != NULL);
+       path = g_file_get_path (file);
+       g_assert (path != NULL);
+
+       g_debug ("***** '%s' (%s) (DELETED)",
+                path,
+                is_directory ? "DIR" : "FILE");
+
+       g_free (path);
+
+       add_event ((GHashTable *) user_data,
+                  file,
+                  MONITOR_SIGNAL_ITEM_DELETED);
+}
+
+static void
+test_monitor_events_moved_cb (TrackerMonitor *monitor,
+                              GFile          *file,
+                              GFile          *other_file,
+                              gboolean        is_directory,
+                              gboolean        is_source_monitored,
+                              gpointer        user_data)
+{
+       gchar *path;
+       gchar *other_path;
+
+       g_assert (file != NULL);
+       path = g_file_get_path (other_file);
+       other_path = g_file_get_path (other_file);
+
+       g_debug ("***** '%s'->'%s' (%s) (MOVED) (source %smonitored)",
+                path,
+                other_path,
+                is_directory ? "DIR" : "FILE",
+                is_source_monitored ? "" : "not ");
+
+       g_free (other_path);
+       g_free (path);
+
+       /* Add event to the files */
+       add_event ((GHashTable *) user_data,
+                  file,
+                  MONITOR_SIGNAL_ITEM_MOVED_FROM);
+       add_event ((GHashTable *) user_data,
+                  other_file,
+                  MONITOR_SIGNAL_ITEM_MOVED_TO);
+}
+
+static void
+test_monitor_common_setup (TrackerMonitorTestFixture *fixture,
+                           gconstpointer              data)
+{
+       gchar *basename;
+
+       /* Create HT to store received events */
+       fixture->events = g_hash_table_new_full (g_file_hash,
+                                                (GEqualFunc) g_file_equal,
+                                                NULL,
+                                                NULL);
+
+       /* Create and setup the tracker monitor */
+       fixture->monitor = tracker_monitor_new ();
+       g_assert (fixture->monitor != NULL);
+
+       g_signal_connect (fixture->monitor, "item-created",
+                         G_CALLBACK (test_monitor_events_created_cb),
+                         fixture->events);
+       g_signal_connect (fixture->monitor, "item-updated",
+                         G_CALLBACK (test_monitor_events_updated_cb),
+                         fixture->events);
+       g_signal_connect (fixture->monitor, "item-attribute-updated",
+                         G_CALLBACK (test_monitor_events_attribute_updated_cb),
+                         fixture->events);
+       g_signal_connect (fixture->monitor, "item-deleted",
+                         G_CALLBACK (test_monitor_events_deleted_cb),
+                         fixture->events);
+       g_signal_connect (fixture->monitor, "item-moved",
+                         G_CALLBACK (test_monitor_events_moved_cb),
+                         fixture->events);
+
+       /* Initially, set it disabled */
+       tracker_monitor_set_enabled (fixture->monitor, FALSE);
+
+       /* Create a temp directory to monitor in the test */
+       basename = g_strdup_printf ("monitor-test-%d", getpid ());
+       fixture->monitored_directory = g_build_path (G_DIR_SEPARATOR_S, g_get_tmp_dir (), basename, NULL);
+       fixture->monitored_directory_file = g_file_new_for_path (fixture->monitored_directory);
+       g_assert (fixture->monitored_directory_file != NULL);
+       g_assert_cmpint (g_file_make_directory_with_parents (fixture->monitored_directory_file, NULL, NULL), 
==, TRUE);
+       g_free (basename);
+       g_assert_cmpint (tracker_monitor_add (fixture->monitor, fixture->monitored_directory_file), ==, TRUE);
+       g_assert_cmpint (tracker_monitor_get_count (fixture->monitor), ==, 1);
+
+       /* Setup also not-monitored directory */
+       fixture->not_monitored_directory = g_strdup (g_get_tmp_dir ());
+
+       /* Create new main loop */
+       fixture->main_loop = g_main_loop_new (NULL, FALSE);
+       g_assert (fixture->main_loop != NULL);
+}
+
+static void
+test_monitor_common_teardown (TrackerMonitorTestFixture *fixture,
+                              gconstpointer              data)
+{
+       /* Remove the main loop */
+       g_main_loop_unref (fixture->main_loop);
+
+       /* Cleanup monitor */
+       g_assert_cmpint (tracker_monitor_remove (fixture->monitor, fixture->monitored_directory_file), ==, 
TRUE);
+       g_assert_cmpint (tracker_monitor_get_count (fixture->monitor), ==, 0);
+
+       /* Destroy monitor */
+       g_assert (fixture->monitor != NULL);
+       g_object_unref (fixture->monitor);
+
+       /* Remove the HT of events */
+       g_hash_table_destroy (fixture->events);
+
+       /* Remove base test directories */
+       g_assert (fixture->monitored_directory_file != NULL);
+       g_assert (fixture->monitored_directory != NULL);
+       g_assert_cmpint (g_file_delete (fixture->monitored_directory_file, NULL, NULL), ==, TRUE);
+       g_object_unref (fixture->monitored_directory_file);
+       g_free (fixture->monitored_directory);
+
+       g_assert (fixture->not_monitored_directory != NULL);
+       g_free (fixture->not_monitored_directory);
+}
+
+static void
+create_directory (const gchar  *parent,
+                  const gchar  *directory_name,
+                  GFile       **outfile)
+{
+       GFile *dirfile;
+       gchar *path;
+
+       path = g_build_path (G_DIR_SEPARATOR_S, parent, directory_name, NULL);
+       dirfile = g_file_new_for_path (path);
+       g_assert (dirfile != NULL);
+       g_assert_cmpint (g_file_make_directory_with_parents (dirfile, NULL, NULL), ==, TRUE);
+       if (outfile) {
+               *outfile = dirfile;
+       } else {
+               g_object_unref (dirfile);
+       }
+       g_free (path);
+}
+
+static void
+set_file_contents (const gchar  *directory,
+                   const gchar  *filename,
+                   const gchar  *contents,
+                   GFile       **outfile)
+{
+       FILE *file;
+       size_t length;
+       gchar *file_path;
+
+       g_assert (directory != NULL);
+       g_assert (filename != NULL);
+       g_assert (contents != NULL);
+
+       file_path = g_build_filename (directory, filename, NULL);
+
+       file = g_fopen (file_path, "wb");
+       g_assert (file != NULL);
+       length = strlen (contents);
+       g_assert_cmpint (fwrite (contents, 1, length, file), >=, length);
+       g_assert_cmpint (fflush (file), ==, 0);
+       g_assert_cmpint (fclose (file), !=, EOF);
+
+       if (outfile) {
+               *outfile = g_file_new_for_path (file_path);
+       }
+       g_free (file_path);
+}
+
+static void
+set_file_permissions (const gchar  *directory,
+                      const gchar  *filename,
+                      gint          permissions)
+{
+       gchar *file_path;
+
+       g_assert (directory != NULL);
+       g_assert (filename != NULL);
+
+       file_path = g_build_filename (directory, filename, NULL);
+       g_assert_cmpint (g_chmod (file_path, permissions), ==, 0);
+       g_free (file_path);
+}
+
+static void
+print_file_events_cb (gpointer key,
+                      gpointer value,
+                      gpointer user_data)
+{
+       GFile *file;
+       guint events;
+       gchar *uri;
+
+       file = key;
+       events = GPOINTER_TO_UINT (value);
+       uri = g_file_get_uri (file);
+
+       g_print ("Signals received for '%s': \n"
+                "   CREATED:           %s\n"
+                "   UPDATED:           %s\n"
+                "   ATTRIBUTE UPDATED: %s\n"
+                "   DELETED:           %s\n"
+                "   MOVED_FROM:        %s\n"
+                "   MOVED_TO:          %s\n",
+                uri,
+                events & MONITOR_SIGNAL_ITEM_CREATED ? "yes" : "no",
+                events & MONITOR_SIGNAL_ITEM_UPDATED ? "yes" : "no",
+                events & MONITOR_SIGNAL_ITEM_ATTRIBUTE_UPDATED ? "yes" : "no",
+                events & MONITOR_SIGNAL_ITEM_DELETED ? "yes" : "no",
+                events & MONITOR_SIGNAL_ITEM_MOVED_FROM ? "yes" : "no",
+                events & MONITOR_SIGNAL_ITEM_MOVED_TO ? "yes" : "no");
+
+       g_free (uri);
+}
+
+static gboolean
+timeout_cb (gpointer data)
+{
+       g_main_loop_quit ((GMainLoop *) data);
+       return FALSE;
+}
+
+static void
+events_wait (TrackerMonitorTestFixture *fixture)
+{
+       /* Setup timeout to stop the main loop after some seconds */
+       g_timeout_add_seconds (TEST_TIMEOUT, timeout_cb, fixture->main_loop);
+       g_debug ("Waiting %u seconds for monitor events...", TEST_TIMEOUT);
+       g_main_loop_run (fixture->main_loop);
+
+       /* Print signals received for each file */
+       g_hash_table_foreach (fixture->events, print_file_events_cb, NULL);
+}
+
+/* ----------------------------- FILE EVENT TESTS --------------------------------- */
+
+static void
+test_monitor_file_event_created (TrackerMonitorTestFixture *fixture,
+                                 gconstpointer              data)
+{
+       GFile *test_file;
+       guint file_events;
+
+       /* Set up environment */
+       tracker_monitor_set_enabled (fixture->monitor, TRUE);
+
+       /* Create file to test with */
+       set_file_contents (fixture->monitored_directory, "created.txt", "foo", &test_file);
+       g_assert (test_file != NULL);
+       g_hash_table_insert (fixture->events,
+                            g_object_ref (test_file),
+                            GUINT_TO_POINTER (MONITOR_SIGNAL_NONE));
+
+       /* Wait for events */
+       events_wait (fixture);
+
+       /* Get events in the file */
+       file_events = GPOINTER_TO_UINT (g_hash_table_lookup (fixture->events, test_file));
+
+       /* Fail if we didn't get the CREATE signal */
+       g_assert_cmpuint ((file_events & MONITOR_SIGNAL_ITEM_CREATED), >, 0);
+
+       /* Fail if we got a MOVE or DELETE signal */
+       g_assert_cmpuint ((file_events & MONITOR_SIGNAL_ITEM_MOVED_FROM), ==, 0);
+       g_assert_cmpuint ((file_events & MONITOR_SIGNAL_ITEM_MOVED_TO), ==, 0);
+       g_assert_cmpuint ((file_events & MONITOR_SIGNAL_ITEM_DELETED), ==, 0);
+
+       /* A single CREATED event should now never trigger an UPDATED event */
+       g_assert_cmpuint ((file_events & MONITOR_SIGNAL_ITEM_UPDATED), ==, 0);
+       g_assert_cmpuint ((file_events & MONITOR_SIGNAL_ITEM_ATTRIBUTE_UPDATED), ==, 0);
+
+       /* Cleanup environment */
+       tracker_monitor_set_enabled (fixture->monitor, FALSE);
+
+       /* Remove the test file */
+       g_assert_cmpint (g_file_delete (test_file, NULL, NULL), ==, TRUE);
+       g_object_unref (test_file);
+}
+
+static void
+test_monitor_file_event_updated (TrackerMonitorTestFixture *fixture,
+                                 gconstpointer              data)
+{
+       GFile *test_file;
+       guint file_events;
+
+       /* Create file to test with, before setting up environment */
+       set_file_contents (fixture->monitored_directory, "created.txt", "foo", NULL);
+
+       /* Set up environment */
+       tracker_monitor_set_enabled (fixture->monitor, TRUE);
+
+       /* Now, trigger update of the already created file */
+       set_file_contents (fixture->monitored_directory, "created.txt", "barrrr", &test_file);
+       g_assert (test_file != NULL);
+       g_hash_table_insert (fixture->events,
+                            g_object_ref (test_file),
+                            GUINT_TO_POINTER (MONITOR_SIGNAL_NONE));
+
+       /* Wait for events */
+       events_wait (fixture);
+
+       /* Get events in the file */
+       file_events = GPOINTER_TO_UINT (g_hash_table_lookup (fixture->events, test_file));
+
+       /* Fail if we didn't get the UPDATE signal */
+       g_assert_cmpuint ((file_events & MONITOR_SIGNAL_ITEM_UPDATED), >, 0);
+
+       /* Fail if we got a CREATE, MOVE or DELETE signal */
+       g_assert_cmpuint ((file_events & MONITOR_SIGNAL_ITEM_CREATED), ==, 0);
+       g_assert_cmpuint ((file_events & MONITOR_SIGNAL_ITEM_MOVED_FROM), ==, 0);
+       g_assert_cmpuint ((file_events & MONITOR_SIGNAL_ITEM_MOVED_TO), ==, 0);
+       g_assert_cmpuint ((file_events & MONITOR_SIGNAL_ITEM_DELETED), ==, 0);
+
+       /* Cleanup environment */
+       tracker_monitor_set_enabled (fixture->monitor, FALSE);
+
+       /* Remove the test file */
+       g_assert_cmpint (g_file_delete (test_file, NULL, NULL), ==, TRUE);
+       g_object_unref (test_file);
+}
+
+static void
+test_monitor_file_event_attribute_updated (TrackerMonitorTestFixture *fixture,
+                                           gconstpointer              data)
+{
+       GFile *test_file;
+       guint file_events;
+
+       /* Create file to test with, before setting up environment */
+       set_file_contents (fixture->monitored_directory, "created.txt", "foo", &test_file);
+       g_assert (test_file != NULL);
+
+       /* Set up environment */
+       tracker_monitor_set_enabled (fixture->monitor, TRUE);
+
+       /* Now, trigger attribute update of the already created file */
+       set_file_permissions (fixture->monitored_directory,
+                             "created.txt",
+                             S_IRWXU);
+
+       g_hash_table_insert (fixture->events,
+                            g_object_ref (test_file),
+                            GUINT_TO_POINTER (MONITOR_SIGNAL_NONE));
+
+       /* Wait for events */
+       events_wait (fixture);
+
+       /* Get events in the file */
+       file_events = GPOINTER_TO_UINT (g_hash_table_lookup (fixture->events, test_file));
+
+       /* Fail if we didn't get the ATTRIBUTE UPDATE signal */
+       g_assert_cmpuint ((file_events & MONITOR_SIGNAL_ITEM_ATTRIBUTE_UPDATED), >, 0);
+
+       /* Fail if we got a UPDATE, CREATE, MOVE or DELETE signal */
+       g_assert_cmpuint ((file_events & MONITOR_SIGNAL_ITEM_UPDATED), ==, 0);
+       g_assert_cmpuint ((file_events & MONITOR_SIGNAL_ITEM_CREATED), ==, 0);
+       g_assert_cmpuint ((file_events & MONITOR_SIGNAL_ITEM_MOVED_FROM), ==, 0);
+       g_assert_cmpuint ((file_events & MONITOR_SIGNAL_ITEM_MOVED_TO), ==, 0);
+       g_assert_cmpuint ((file_events & MONITOR_SIGNAL_ITEM_DELETED), ==, 0);
+
+       /* Cleanup environment */
+       tracker_monitor_set_enabled (fixture->monitor, FALSE);
+
+       /* Remove the test file */
+       g_assert_cmpint (g_file_delete (test_file, NULL, NULL), ==, TRUE);
+       g_object_unref (test_file);
+}
+
+static void
+test_monitor_file_event_deleted (TrackerMonitorTestFixture *fixture,
+                                 gconstpointer              data)
+{
+       GFile *test_file;
+       guint file_events;
+
+       /* Create file to test with, before setting up environment */
+       set_file_contents (fixture->monitored_directory, "created.txt", "foo", &test_file);
+       g_assert (test_file != NULL);
+
+       /* Set up environment */
+       tracker_monitor_set_enabled (fixture->monitor, TRUE);
+
+       /* Now, remove file */
+       g_assert_cmpint (g_file_delete (test_file, NULL, NULL), ==, TRUE);
+       g_hash_table_insert (fixture->events,
+                            g_object_ref (test_file),
+                            GUINT_TO_POINTER (MONITOR_SIGNAL_NONE));
+
+       /* Wait for events */
+       events_wait (fixture);
+
+       /* Get events in the file */
+       file_events = GPOINTER_TO_UINT (g_hash_table_lookup (fixture->events, test_file));
+
+       /* Fail if we didn't get the DELETED signal */
+       g_assert_cmpuint ((file_events & MONITOR_SIGNAL_ITEM_DELETED), >, 0);
+
+       /* Fail if we got a CREATE, UDPATE or MOVE signal */
+       g_assert_cmpuint ((file_events & MONITOR_SIGNAL_ITEM_CREATED), ==, 0);
+       g_assert_cmpuint ((file_events & MONITOR_SIGNAL_ITEM_UPDATED), ==, 0);
+       g_assert_cmpuint ((file_events & MONITOR_SIGNAL_ITEM_ATTRIBUTE_UPDATED), ==, 0);
+       g_assert_cmpuint ((file_events & MONITOR_SIGNAL_ITEM_MOVED_FROM), ==, 0);
+       g_assert_cmpuint ((file_events & MONITOR_SIGNAL_ITEM_MOVED_TO), ==, 0);
+
+       /* Cleanup environment */
+       tracker_monitor_set_enabled (fixture->monitor, FALSE);
+       g_object_unref (test_file);
+}
+
+static void
+test_monitor_file_event_moved_to_monitored (TrackerMonitorTestFixture *fixture,
+                                            gconstpointer              data)
+{
+       GFile *source_file;
+       gchar *source_path;
+       GFile *dest_file;
+       gchar *dest_path;
+       guint file_events;
+
+       /* Create file to test with, before setting up environment */
+       set_file_contents (fixture->monitored_directory, "created.txt", "foo", &source_file);
+       g_assert (source_file != NULL);
+
+       /* Set up environment */
+       tracker_monitor_set_enabled (fixture->monitor, TRUE);
+
+       /* Now, rename the file */
+       source_path = g_file_get_path (source_file);
+       dest_path = g_build_filename (fixture->monitored_directory, "renamed.txt", NULL);
+       dest_file = g_file_new_for_path (dest_path);
+       g_assert (dest_file != NULL);
+
+       g_assert_cmpint (g_rename (source_path, dest_path), ==, 0);
+
+       g_hash_table_insert (fixture->events,
+                            g_object_ref (source_file),
+                            GUINT_TO_POINTER (MONITOR_SIGNAL_NONE));
+       g_hash_table_insert (fixture->events,
+                            g_object_ref (dest_file),
+                            GUINT_TO_POINTER (MONITOR_SIGNAL_NONE));
+
+       /* Wait for events */
+       events_wait (fixture);
+
+       /* Get events in the source file */
+       file_events = GPOINTER_TO_UINT (g_hash_table_lookup (fixture->events, source_file));
+       /* Fail if we didn't get the MOVED_FROM signal */
+       g_assert_cmpuint ((file_events & MONITOR_SIGNAL_ITEM_MOVED_FROM), >, 0);
+       /* Fail if we got a CREATE, UPDATE, DELETE or MOVE_TO signal */
+       g_assert_cmpuint ((file_events & MONITOR_SIGNAL_ITEM_CREATED), ==, 0);
+       g_assert_cmpuint ((file_events & MONITOR_SIGNAL_ITEM_UPDATED), ==, 0);
+       g_assert_cmpuint ((file_events & MONITOR_SIGNAL_ITEM_ATTRIBUTE_UPDATED), ==, 0);
+       g_assert_cmpuint ((file_events & MONITOR_SIGNAL_ITEM_MOVED_TO), ==, 0);
+       g_assert_cmpuint ((file_events & MONITOR_SIGNAL_ITEM_DELETED), ==, 0);
+
+       /* Get events in the dest file */
+       file_events = GPOINTER_TO_UINT (g_hash_table_lookup (fixture->events, dest_file));
+       /* Fail if we didn't get the MOVED_TO signal */
+       g_assert_cmpuint ((file_events & MONITOR_SIGNAL_ITEM_MOVED_TO), >, 0);
+       /* Fail if we got a CREATE, DELETE or MOVE_FROM signal (UPDATE may actually be possible) */
+       g_assert_cmpuint ((file_events & MONITOR_SIGNAL_ITEM_CREATED), ==, 0);
+       g_assert_cmpuint ((file_events & MONITOR_SIGNAL_ITEM_MOVED_FROM), ==, 0);
+       g_assert_cmpuint ((file_events & MONITOR_SIGNAL_ITEM_DELETED), ==, 0);
+
+       /* Cleanup environment */
+       tracker_monitor_set_enabled (fixture->monitor, FALSE);
+       g_assert_cmpint (g_file_delete (dest_file, NULL, NULL), ==, TRUE);
+       g_object_unref (source_file);
+       g_object_unref (dest_file);
+       g_free (source_path);
+       g_free (dest_path);
+}
+
+static void
+test_monitor_file_event_moved_to_not_monitored (TrackerMonitorTestFixture *fixture,
+                                                gconstpointer              data)
+{
+       GFile *source_file;
+       gchar *source_path;
+       GFile *dest_file;
+       gchar *dest_path;
+       guint file_events;
+
+       /* Create file to test with, before setting up environment */
+       set_file_contents (fixture->monitored_directory, "created.txt", "foo", &source_file);
+       g_assert (source_file != NULL);
+
+       /* Set up environment */
+       tracker_monitor_set_enabled (fixture->monitor, TRUE);
+
+       /* Now, rename the file */
+       source_path = g_file_get_path (source_file);
+       dest_path = g_build_filename (fixture->not_monitored_directory, "out.txt", NULL);
+       dest_file = g_file_new_for_path (dest_path);
+       g_assert (dest_file != NULL);
+
+       g_assert_cmpint (g_rename (source_path, dest_path), ==, 0);
+
+       g_hash_table_insert (fixture->events,
+                            g_object_ref (source_file),
+                            GUINT_TO_POINTER (MONITOR_SIGNAL_NONE));
+       g_hash_table_insert (fixture->events,
+                            g_object_ref (dest_file),
+                            GUINT_TO_POINTER (MONITOR_SIGNAL_NONE));
+
+       /* Wait for events */
+       events_wait (fixture);
+
+       /* Get events in the source file */
+       file_events = GPOINTER_TO_UINT (g_hash_table_lookup (fixture->events, source_file));
+       /* Fail if we didn't get the DELETED signal */
+       g_assert_cmpuint ((file_events & MONITOR_SIGNAL_ITEM_DELETED), >, 0);
+       /* Fail if we got a CREATE, UPDATE or MOVE signal */
+       g_assert_cmpuint ((file_events & MONITOR_SIGNAL_ITEM_CREATED), ==, 0);
+       g_assert_cmpuint ((file_events & MONITOR_SIGNAL_ITEM_UPDATED), ==, 0);
+       g_assert_cmpuint ((file_events & MONITOR_SIGNAL_ITEM_ATTRIBUTE_UPDATED), ==, 0);
+       g_assert_cmpuint ((file_events & MONITOR_SIGNAL_ITEM_MOVED_TO), ==, 0);
+       g_assert_cmpuint ((file_events & MONITOR_SIGNAL_ITEM_MOVED_FROM), ==, 0);
+
+       /* Get events in the dest file */
+       file_events = GPOINTER_TO_UINT (g_hash_table_lookup (fixture->events, dest_file));
+       /* Fail if we got a CREATE, UPDATE, DELETE or MOVE signal */
+       g_assert_cmpuint ((file_events & MONITOR_SIGNAL_ITEM_CREATED), ==, 0);
+       g_assert_cmpuint ((file_events & MONITOR_SIGNAL_ITEM_UPDATED), ==, 0);
+       g_assert_cmpuint ((file_events & MONITOR_SIGNAL_ITEM_ATTRIBUTE_UPDATED), ==, 0);
+       g_assert_cmpuint ((file_events & MONITOR_SIGNAL_ITEM_DELETED), ==, 0);
+       g_assert_cmpuint ((file_events & MONITOR_SIGNAL_ITEM_MOVED_FROM), ==, 0);
+       g_assert_cmpuint ((file_events & MONITOR_SIGNAL_ITEM_MOVED_TO), ==, 0);
+
+       /* Cleanup environment */
+       tracker_monitor_set_enabled (fixture->monitor, FALSE);
+       g_assert_cmpint (g_file_delete (dest_file, NULL, NULL), ==, TRUE);
+       g_object_unref (source_file);
+       g_object_unref (dest_file);
+       g_free (source_path);
+       g_free (dest_path);
+}
+
+static void
+test_monitor_file_event_moved_from_not_monitored (TrackerMonitorTestFixture *fixture,
+                                                  gconstpointer              data)
+{
+       GFile *source_file;
+       gchar *source_path;
+       GFile *dest_file;
+       gchar *dest_path;
+       guint file_events;
+
+       /* Create file to test with, before setting up environment */
+       set_file_contents (fixture->not_monitored_directory, "created.txt", "foo", &source_file);
+       g_assert (source_file != NULL);
+
+       /* Set up environment */
+       tracker_monitor_set_enabled (fixture->monitor, TRUE);
+
+       /* Now, rename the file */
+       source_path = g_file_get_path (source_file);
+       dest_path = g_build_filename (fixture->monitored_directory, "in.txt", NULL);
+       dest_file = g_file_new_for_path (dest_path);
+       g_assert (dest_file != NULL);
+
+       g_assert_cmpint (g_rename (source_path, dest_path), ==, 0);
+
+       g_hash_table_insert (fixture->events,
+                            g_object_ref (source_file),
+                            GUINT_TO_POINTER (MONITOR_SIGNAL_NONE));
+       g_hash_table_insert (fixture->events,
+                            g_object_ref (dest_file),
+                            GUINT_TO_POINTER (MONITOR_SIGNAL_NONE));
+
+       /* Wait for events */
+       events_wait (fixture);
+
+       /* Get events in the source file */
+       file_events = GPOINTER_TO_UINT (g_hash_table_lookup (fixture->events, source_file));
+       /* Fail if we got a CREATE, UPDATE, DELETE or MOVE signal */
+       g_assert_cmpuint ((file_events & MONITOR_SIGNAL_ITEM_CREATED), ==, 0);
+       g_assert_cmpuint ((file_events & MONITOR_SIGNAL_ITEM_UPDATED), ==, 0);
+       g_assert_cmpuint ((file_events & MONITOR_SIGNAL_ITEM_ATTRIBUTE_UPDATED), ==, 0);
+       g_assert_cmpuint ((file_events & MONITOR_SIGNAL_ITEM_DELETED), ==, 0);
+       g_assert_cmpuint ((file_events & MONITOR_SIGNAL_ITEM_MOVED_FROM), ==, 0);
+       g_assert_cmpuint ((file_events & MONITOR_SIGNAL_ITEM_MOVED_TO), ==, 0);
+
+       /* Get events in the dest file */
+       file_events = GPOINTER_TO_UINT (g_hash_table_lookup (fixture->events, dest_file));
+       /* Fail if we didn't get the CREATED signal */
+       g_assert_cmpuint ((file_events & MONITOR_SIGNAL_ITEM_CREATED), >, 0);
+       /* Fail if we got a DELETE, UPDATE or MOVE signal */
+       g_assert_cmpuint ((file_events & MONITOR_SIGNAL_ITEM_DELETED), ==, 0);
+       g_assert_cmpuint ((file_events & MONITOR_SIGNAL_ITEM_UPDATED), ==, 0);
+       g_assert_cmpuint ((file_events & MONITOR_SIGNAL_ITEM_ATTRIBUTE_UPDATED), ==, 0);
+       g_assert_cmpuint ((file_events & MONITOR_SIGNAL_ITEM_MOVED_TO), ==, 0);
+       g_assert_cmpuint ((file_events & MONITOR_SIGNAL_ITEM_MOVED_FROM), ==, 0);
+
+       /* Cleanup environment */
+       tracker_monitor_set_enabled (fixture->monitor, FALSE);
+       g_assert_cmpint (g_file_delete (dest_file, NULL, NULL), ==, TRUE);
+       g_object_unref (source_file);
+       g_object_unref (dest_file);
+       g_free (source_path);
+       g_free (dest_path);
+}
+
+/* ----------------------------- FILE EVENT BLACKLISTING TESTS -------------- */
+
+static void
+test_monitor_file_event_blacklisting_created_updated (TrackerMonitorTestFixture *fixture,
+                                                      gconstpointer              data)
+{
+       GFile *test_file;
+       guint file_events;
+       guint i;
+
+       /*
+        * Event merging:
+        *  CREATED + N*UPDATED = CREATED
+        */
+
+       /* Set up environment */
+       tracker_monitor_set_enabled (fixture->monitor, TRUE);
+
+       /* Create file to test with, before setting up environment */
+       set_file_contents (fixture->monitored_directory, "created.txt", "foo", &test_file);
+       g_assert (test_file != NULL);
+
+       /* Now, trigger 10 updates of the already created file.
+        * This will generate 10 CHANGED+CHANGES_DONE_HINT events in GIO
+        */
+       for (i=0; i<10; i++) {
+               set_file_contents (fixture->monitored_directory, "created.txt", "barrrr", NULL);
+       }
+
+       g_hash_table_insert (fixture->events,
+                            g_object_ref (test_file),
+                            GUINT_TO_POINTER (MONITOR_SIGNAL_NONE));
+
+       /* Wait for events */
+       events_wait (fixture);
+
+       /* Get events in the file */
+       file_events = GPOINTER_TO_UINT (g_hash_table_lookup (fixture->events, test_file));
+
+       /* Fail if we didn't get the CREATED signal */
+       g_assert_cmpuint ((file_events & MONITOR_SIGNAL_ITEM_CREATED), >, 0);
+
+       /* Fail if we got a UPDATE, MOVE or DELETE signal */
+       g_assert_cmpuint ((file_events & MONITOR_SIGNAL_ITEM_UPDATED), ==, 0);
+       g_assert_cmpuint ((file_events & MONITOR_SIGNAL_ITEM_ATTRIBUTE_UPDATED), ==, 0);
+       g_assert_cmpuint ((file_events & MONITOR_SIGNAL_ITEM_MOVED_FROM), ==, 0);
+       g_assert_cmpuint ((file_events & MONITOR_SIGNAL_ITEM_MOVED_TO), ==, 0);
+       g_assert_cmpuint ((file_events & MONITOR_SIGNAL_ITEM_DELETED), ==, 0);
+
+       /* Cleanup environment */
+       tracker_monitor_set_enabled (fixture->monitor, FALSE);
+
+       /* Remove the test file */
+       g_assert_cmpint (g_file_delete (test_file, NULL, NULL), ==, TRUE);
+       g_object_unref (test_file);
+}
+
+static void
+test_monitor_file_event_blacklisting_created_deleted (TrackerMonitorTestFixture *fixture,
+                                                      gconstpointer              data)
+{
+       GFile *test_file;
+       guint file_events;
+
+       /*
+        * Event merging:
+        *  CREATED + DELETED = <nothing>
+        */
+
+       /* Set up environment */
+       tracker_monitor_set_enabled (fixture->monitor, TRUE);
+
+       /* Create file to test with, before setting up environment */
+       set_file_contents (fixture->monitored_directory, "created.txt", "foo", &test_file);
+       g_assert (test_file != NULL);
+
+       /* Remove the test file */
+       g_assert_cmpint (g_file_delete (test_file, NULL, NULL), ==, TRUE);
+
+       g_hash_table_insert (fixture->events,
+                            g_object_ref (test_file),
+                            GUINT_TO_POINTER (MONITOR_SIGNAL_NONE));
+
+       /* Wait for events */
+       events_wait (fixture);
+
+       /* Get events in the file */
+       file_events = GPOINTER_TO_UINT (g_hash_table_lookup (fixture->events, test_file));
+
+       /* Fail if we got any signal */
+       g_assert_cmpuint ((file_events & MONITOR_SIGNAL_ITEM_CREATED), ==, 0);
+       g_assert_cmpuint ((file_events & MONITOR_SIGNAL_ITEM_UPDATED), ==, 0);
+       g_assert_cmpuint ((file_events & MONITOR_SIGNAL_ITEM_ATTRIBUTE_UPDATED), ==, 0);
+       g_assert_cmpuint ((file_events & MONITOR_SIGNAL_ITEM_MOVED_FROM), ==, 0);
+       g_assert_cmpuint ((file_events & MONITOR_SIGNAL_ITEM_MOVED_TO), ==, 0);
+       g_assert_cmpuint ((file_events & MONITOR_SIGNAL_ITEM_DELETED), ==, 0);
+
+       /* Cleanup environment */
+       tracker_monitor_set_enabled (fixture->monitor, FALSE);
+
+       g_object_unref (test_file);
+}
+
+static void
+test_monitor_file_event_blacklisting_created_updated_deleted (TrackerMonitorTestFixture *fixture,
+                                                              gconstpointer              data)
+{
+       GFile *test_file;
+       guint file_events;
+       guint i;
+
+       /*
+        * Event merging:
+        *  CREATED + N*UPDATED + DELETED = <nothing>
+        */
+
+       /* Set up environment */
+       tracker_monitor_set_enabled (fixture->monitor, TRUE);
+
+       /* Create file to test with, before setting up environment */
+       set_file_contents (fixture->monitored_directory, "created.txt", "foo", &test_file);
+       g_assert (test_file != NULL);
+
+       /* Now, trigger 10 updates of the already created file.
+        * This will generate 10 CHANGED+CHANGES_DONE_HINT events in GIO
+        */
+       for (i=0; i<10; i++) {
+               set_file_contents (fixture->monitored_directory, "created.txt", "barrrr", NULL);
+       }
+
+       /* Remove the test file */
+       g_assert_cmpint (g_file_delete (test_file, NULL, NULL), ==, TRUE);
+
+       g_hash_table_insert (fixture->events,
+                            g_object_ref (test_file),
+                            GUINT_TO_POINTER (MONITOR_SIGNAL_NONE));
+
+       /* Wait for events */
+       events_wait (fixture);
+
+       /* Get events in the file */
+       file_events = GPOINTER_TO_UINT (g_hash_table_lookup (fixture->events, test_file));
+
+       /* Fail if we got any signal */
+       g_assert_cmpuint ((file_events & MONITOR_SIGNAL_ITEM_CREATED), ==, 0);
+       g_assert_cmpuint ((file_events & MONITOR_SIGNAL_ITEM_UPDATED), ==, 0);
+       g_assert_cmpuint ((file_events & MONITOR_SIGNAL_ITEM_ATTRIBUTE_UPDATED), ==, 0);
+       g_assert_cmpuint ((file_events & MONITOR_SIGNAL_ITEM_MOVED_FROM), ==, 0);
+       g_assert_cmpuint ((file_events & MONITOR_SIGNAL_ITEM_MOVED_TO), ==, 0);
+       g_assert_cmpuint ((file_events & MONITOR_SIGNAL_ITEM_DELETED), ==, 0);
+
+       /* Cleanup environment */
+       tracker_monitor_set_enabled (fixture->monitor, FALSE);
+
+       g_object_unref (test_file);
+}
+
+static void
+test_monitor_file_event_blacklisting_created_moved (TrackerMonitorTestFixture *fixture,
+                                                    gconstpointer              data)
+{
+       GFile *source_file;
+       gchar *source_path;
+       GFile *dest_file;
+       gchar *dest_path;
+       guint file_events;
+
+       /*
+        * Event merging:
+        *  CREATED(A) + MOVED(A->B) = CREATED(B)
+        */
+
+       /* Set up environment */
+       tracker_monitor_set_enabled (fixture->monitor, TRUE);
+
+       /* Create file to test with, before setting up environment */
+       set_file_contents (fixture->monitored_directory, "created.txt", "foo", &source_file);
+       g_assert (source_file != NULL);
+
+       /* Now, rename the file */
+       source_path = g_file_get_path (source_file);
+       dest_path = g_build_filename (fixture->monitored_directory, "renamed.txt", NULL);
+       dest_file = g_file_new_for_path (dest_path);
+       g_assert (dest_file != NULL);
+
+       g_assert_cmpint (g_rename (source_path, dest_path), ==, 0);
+
+       g_hash_table_insert (fixture->events,
+                            g_object_ref (source_file),
+                            GUINT_TO_POINTER (MONITOR_SIGNAL_NONE));
+       g_hash_table_insert (fixture->events,
+                            g_object_ref (dest_file),
+                            GUINT_TO_POINTER (MONITOR_SIGNAL_NONE));
+
+       /* Wait for events */
+       events_wait (fixture);
+
+       /* Get events in the source file */
+       file_events = GPOINTER_TO_UINT (g_hash_table_lookup (fixture->events, source_file));
+
+       /* Fail if we got ANY event */
+       g_assert_cmpuint ((file_events & MONITOR_SIGNAL_ITEM_CREATED), ==, 0);
+       g_assert_cmpuint ((file_events & MONITOR_SIGNAL_ITEM_UPDATED), ==, 0);
+       g_assert_cmpuint ((file_events & MONITOR_SIGNAL_ITEM_ATTRIBUTE_UPDATED), ==, 0);
+       g_assert_cmpuint ((file_events & MONITOR_SIGNAL_ITEM_MOVED_FROM), ==, 0);
+       g_assert_cmpuint ((file_events & MONITOR_SIGNAL_ITEM_MOVED_TO), ==, 0);
+       g_assert_cmpuint ((file_events & MONITOR_SIGNAL_ITEM_DELETED), ==, 0);
+
+       /* Get events in the dest file */
+       file_events = GPOINTER_TO_UINT (g_hash_table_lookup (fixture->events, dest_file));
+
+       /* Fail if we didn't get the UPDATED signal */
+       g_assert_cmpuint ((file_events & MONITOR_SIGNAL_ITEM_UPDATED), >, 0);
+
+       /* Fail if we got a CREATE, UPDATE, DELETE or MOVE signal */
+       g_assert_cmpuint ((file_events & MONITOR_SIGNAL_ITEM_CREATED), ==, 0);
+       g_assert_cmpuint ((file_events & MONITOR_SIGNAL_ITEM_ATTRIBUTE_UPDATED), ==, 0);
+       g_assert_cmpuint ((file_events & MONITOR_SIGNAL_ITEM_MOVED_FROM), ==, 0);
+       g_assert_cmpuint ((file_events & MONITOR_SIGNAL_ITEM_MOVED_TO), ==, 0);
+       g_assert_cmpuint ((file_events & MONITOR_SIGNAL_ITEM_DELETED), ==, 0);
+
+       /* Cleanup environment */
+       tracker_monitor_set_enabled (fixture->monitor, FALSE);
+       g_assert_cmpint (g_file_delete (dest_file, NULL, NULL), ==, TRUE);
+       g_object_unref (source_file);
+       g_object_unref (dest_file);
+       g_free (source_path);
+       g_free (dest_path);
+}
+
+static void
+test_monitor_file_event_blacklisting_updated_deleted (TrackerMonitorTestFixture *fixture,
+                                                      gconstpointer              data)
+{
+       GFile *test_file;
+       guint file_events;
+       guint i;
+
+       /*
+        * Event merging:
+        *  N*UPDATED + DELETED = DELETED
+        */
+
+       /* Create file to test with, before setting up environment */
+       set_file_contents (fixture->monitored_directory, "created.txt", "foo", &test_file);
+       g_assert (test_file != NULL);
+
+       /* Set up environment */
+       tracker_monitor_set_enabled (fixture->monitor, TRUE);
+
+       /* Now, trigger 10 updates of the already created file.
+        * This will generate 10 CHANGED+CHANGES_DONE_HINT events in GIO
+        */
+       for (i=0; i<10; i++) {
+               set_file_contents (fixture->monitored_directory, "created.txt", "barrrr", NULL);
+       }
+
+       /* Remove the test file */
+       g_assert_cmpint (g_file_delete (test_file, NULL, NULL), ==, TRUE);
+
+       g_hash_table_insert (fixture->events,
+                            g_object_ref (test_file),
+                            GUINT_TO_POINTER (MONITOR_SIGNAL_NONE));
+
+       /* Wait for events */
+       events_wait (fixture);
+
+       /* Get events in the file */
+       file_events = GPOINTER_TO_UINT (g_hash_table_lookup (fixture->events, test_file));
+
+       /* Fail if we didn't get the DELETED signal */
+       g_assert_cmpuint ((file_events & MONITOR_SIGNAL_ITEM_DELETED), >, 0);
+
+       /* Fail if we got any signal */
+       g_assert_cmpuint ((file_events & MONITOR_SIGNAL_ITEM_CREATED), ==, 0);
+       g_assert_cmpuint ((file_events & MONITOR_SIGNAL_ITEM_UPDATED), ==, 0);
+       g_assert_cmpuint ((file_events & MONITOR_SIGNAL_ITEM_ATTRIBUTE_UPDATED), ==, 0);
+       g_assert_cmpuint ((file_events & MONITOR_SIGNAL_ITEM_MOVED_FROM), ==, 0);
+       g_assert_cmpuint ((file_events & MONITOR_SIGNAL_ITEM_MOVED_TO), ==, 0);
+
+       /* Cleanup environment */
+       tracker_monitor_set_enabled (fixture->monitor, FALSE);
+
+       g_object_unref (test_file);
+}
+
+static void
+test_monitor_file_event_blacklisting_updated_moved (TrackerMonitorTestFixture *fixture,
+                                                    gconstpointer              data)
+{
+       GFile *source_file;
+       gchar *source_path;
+       GFile *dest_file;
+       gchar *dest_path;
+       guint file_events;
+
+       /*
+        * Event merging:
+        *  UPDATED(A) + MOVED(A->B) = MOVED(A->B) + UPDATED(B)
+        */
+
+       /* Create file to test with, before setting up environment */
+       set_file_contents (fixture->monitored_directory, "created.txt", "foo", &source_file);
+       g_assert (source_file != NULL);
+
+       /* Set up environment */
+       tracker_monitor_set_enabled (fixture->monitor, TRUE);
+
+       /* Update the file */
+       set_file_contents (fixture->monitored_directory, "created.txt", "barrrr", NULL);
+
+       /* Now, rename the file */
+       source_path = g_file_get_path (source_file);
+       dest_path = g_build_filename (fixture->monitored_directory, "renamed.txt", NULL);
+       dest_file = g_file_new_for_path (dest_path);
+       g_assert (dest_file != NULL);
+
+       g_assert_cmpint (g_rename (source_path, dest_path), ==, 0);
+
+       g_hash_table_insert (fixture->events,
+                            g_object_ref (source_file),
+                            GUINT_TO_POINTER (MONITOR_SIGNAL_NONE));
+       g_hash_table_insert (fixture->events,
+                            g_object_ref (dest_file),
+                            GUINT_TO_POINTER (MONITOR_SIGNAL_NONE));
+
+       /* Wait for events */
+       events_wait (fixture);
+
+       /* Get events in the source file */
+       file_events = GPOINTER_TO_UINT (g_hash_table_lookup (fixture->events, source_file));
+
+       /* Fail if we didn't get the MOVED_FROM event */
+       g_assert_cmpuint ((file_events & MONITOR_SIGNAL_ITEM_MOVED_FROM), >, 0);
+
+       g_assert_cmpuint ((file_events & MONITOR_SIGNAL_ITEM_CREATED), ==, 0);
+       g_assert_cmpuint ((file_events & MONITOR_SIGNAL_ITEM_UPDATED), ==, 0);
+       g_assert_cmpuint ((file_events & MONITOR_SIGNAL_ITEM_ATTRIBUTE_UPDATED), ==, 0);
+       g_assert_cmpuint ((file_events & MONITOR_SIGNAL_ITEM_MOVED_TO), ==, 0);
+       g_assert_cmpuint ((file_events & MONITOR_SIGNAL_ITEM_DELETED), ==, 0);
+
+       /* Get events in the dest file */
+       file_events = GPOINTER_TO_UINT (g_hash_table_lookup (fixture->events, dest_file));
+
+       /* Fail if we didn't get the MOVED_TO and UPDATED signals */
+       g_assert_cmpuint ((file_events & MONITOR_SIGNAL_ITEM_MOVED_TO), >, 0);
+       g_assert_cmpuint ((file_events & MONITOR_SIGNAL_ITEM_UPDATED), >, 0);
+
+       /* Fail if we got a CREATE, UPDATE, DELETE or MOVE signal */
+       g_assert_cmpuint ((file_events & MONITOR_SIGNAL_ITEM_CREATED), ==, 0);
+       g_assert_cmpuint ((file_events & MONITOR_SIGNAL_ITEM_MOVED_FROM), ==, 0);
+       g_assert_cmpuint ((file_events & MONITOR_SIGNAL_ITEM_DELETED), ==, 0);
+
+       /* Cleanup environment */
+       tracker_monitor_set_enabled (fixture->monitor, FALSE);
+       g_assert_cmpint (g_file_delete (dest_file, NULL, NULL), ==, TRUE);
+       g_object_unref (source_file);
+       g_object_unref (dest_file);
+       g_free (source_path);
+       g_free (dest_path);
+}
+
+static void
+test_monitor_file_event_blacklisting_attribute_updated_moved (TrackerMonitorTestFixture *fixture,
+                                                              gconstpointer              data)
+{
+       GFile *source_file;
+       gchar *source_path;
+       GFile *dest_file;
+       gchar *dest_path;
+       guint file_events;
+
+       /*
+        * Event merging:
+        *  ATTRIBUTE_UPDATED(A) + MOVED(A->B) = MOVED(A->B) + UPDATED(B)
+        */
+
+       /* Create file to test with, before setting up environment */
+       set_file_contents (fixture->monitored_directory, "created.txt", "foo", &source_file);
+       g_assert (source_file != NULL);
+
+       /* Set up environment */
+       tracker_monitor_set_enabled (fixture->monitor, TRUE);
+
+       /* Now, trigger attribute update of the already created file */
+       set_file_permissions (fixture->monitored_directory,
+                             "created.txt",
+                             S_IRWXU);
+       set_file_contents (fixture->monitored_directory, "created.txt", "barrrr", NULL);
+
+       /* Now, rename the file */
+       source_path = g_file_get_path (source_file);
+       dest_path = g_build_filename (fixture->monitored_directory, "renamed.txt", NULL);
+       dest_file = g_file_new_for_path (dest_path);
+       g_assert (dest_file != NULL);
+
+       g_assert_cmpint (g_rename (source_path, dest_path), ==, 0);
+
+       g_hash_table_insert (fixture->events,
+                            g_object_ref (source_file),
+                            GUINT_TO_POINTER (MONITOR_SIGNAL_NONE));
+       g_hash_table_insert (fixture->events,
+                            g_object_ref (dest_file),
+                            GUINT_TO_POINTER (MONITOR_SIGNAL_NONE));
+
+       /* Wait for events */
+       events_wait (fixture);
+
+       /* Get events in the source file */
+       file_events = GPOINTER_TO_UINT (g_hash_table_lookup (fixture->events, source_file));
+
+       /* Fail if we didn't get the MOVED_FROM event */
+       g_assert_cmpuint ((file_events & MONITOR_SIGNAL_ITEM_MOVED_FROM), >, 0);
+
+       g_assert_cmpuint ((file_events & MONITOR_SIGNAL_ITEM_CREATED), ==, 0);
+       g_assert_cmpuint ((file_events & MONITOR_SIGNAL_ITEM_UPDATED), ==, 0);
+       g_assert_cmpuint ((file_events & MONITOR_SIGNAL_ITEM_ATTRIBUTE_UPDATED), ==, 0);
+       g_assert_cmpuint ((file_events & MONITOR_SIGNAL_ITEM_MOVED_TO), ==, 0);
+       g_assert_cmpuint ((file_events & MONITOR_SIGNAL_ITEM_DELETED), ==, 0);
+
+       /* Get events in the dest file */
+       file_events = GPOINTER_TO_UINT (g_hash_table_lookup (fixture->events, dest_file));
+
+       /* Fail if we didn't get the UPDATED signal */
+       g_assert_cmpuint ((file_events & MONITOR_SIGNAL_ITEM_MOVED_TO), >, 0);
+       g_assert_cmpuint ((file_events & MONITOR_SIGNAL_ITEM_UPDATED), >, 0);
+
+       /* Fail if we got a CREATE, UPDATE, DELETE or MOVE signal */
+       g_assert_cmpuint ((file_events & MONITOR_SIGNAL_ITEM_CREATED), ==, 0);
+       g_assert_cmpuint ((file_events & MONITOR_SIGNAL_ITEM_MOVED_FROM), ==, 0);
+       g_assert_cmpuint ((file_events & MONITOR_SIGNAL_ITEM_DELETED), ==, 0);
+
+       /* Cleanup environment */
+       tracker_monitor_set_enabled (fixture->monitor, FALSE);
+       g_assert_cmpint (g_file_delete (dest_file, NULL, NULL), ==, TRUE);
+       g_object_unref (source_file);
+       g_object_unref (dest_file);
+       g_free (source_path);
+       g_free (dest_path);
+}
+
+/* ----------------------------- DIRECTORY EVENT TESTS --------------------------------- */
+
+static void
+test_monitor_directory_event_created (TrackerMonitorTestFixture *fixture,
+                                      gconstpointer              data)
+{
+       GFile *test_dir;
+       guint file_events;
+
+       /* Set up environment */
+       tracker_monitor_set_enabled (fixture->monitor, TRUE);
+
+       /* Create directory to test with */
+       create_directory (fixture->monitored_directory, "foo", &test_dir);
+       g_assert (test_dir != NULL);
+       g_hash_table_insert (fixture->events,
+                            g_object_ref (test_dir),
+                            GUINT_TO_POINTER (MONITOR_SIGNAL_NONE));
+
+       /* Wait for events */
+       events_wait (fixture);
+
+       /* Get events in the file */
+       file_events = GPOINTER_TO_UINT (g_hash_table_lookup (fixture->events, test_dir));
+
+       /* Fail if we didn't get the CREATE signal */
+       g_assert_cmpuint ((file_events & MONITOR_SIGNAL_ITEM_CREATED), >, 0);
+
+       /* Fail if we got a MOVE or DELETE signal (update may actually happen) */
+       g_assert_cmpuint ((file_events & MONITOR_SIGNAL_ITEM_MOVED_FROM), ==, 0);
+       g_assert_cmpuint ((file_events & MONITOR_SIGNAL_ITEM_MOVED_TO), ==, 0);
+       g_assert_cmpuint ((file_events & MONITOR_SIGNAL_ITEM_DELETED), ==, 0);
+
+       /* Cleanup environment */
+       tracker_monitor_set_enabled (fixture->monitor, FALSE);
+
+       /* Remove the test dir */
+       g_assert_cmpint (g_file_delete (test_dir, NULL, NULL), ==, TRUE);
+       g_object_unref (test_dir);
+}
+
+static void
+test_monitor_directory_event_deleted (TrackerMonitorTestFixture *fixture,
+                                     gconstpointer              data)
+{
+       GFile *source_dir;
+       gchar *source_path;
+       guint file_events;
+
+       /* Create directory to test with in a monitored place,
+        * before setting up the environment */
+       create_directory (fixture->monitored_directory, "foo", &source_dir);
+       source_path = g_file_get_path (source_dir);
+       g_assert (source_dir != NULL);
+
+       /* Set to monitor the new dir also */
+       g_assert_cmpint (tracker_monitor_add (fixture->monitor, source_dir), ==, TRUE);
+
+       /* Set up environment */
+       tracker_monitor_set_enabled (fixture->monitor, TRUE);
+
+       /* Now, delete the directory  */
+       g_assert_cmpint (g_file_delete (source_dir, NULL, NULL), ==, TRUE);
+
+       g_hash_table_insert (fixture->events,
+                            g_object_ref (source_dir),
+                            GUINT_TO_POINTER (MONITOR_SIGNAL_NONE));
+
+       /* Wait for events */
+       events_wait (fixture);
+
+       /* Get events in the source dir */
+       file_events = GPOINTER_TO_UINT (g_hash_table_lookup (fixture->events, source_dir));
+       /* Fail if we didn't get DELETED signal */
+       g_assert_cmpuint ((file_events & MONITOR_SIGNAL_ITEM_DELETED), >, 0);
+       /* Fail if we got a CREATEd, UPDATED, MOVED_FROM or MOVED_TO signal */
+       g_assert_cmpuint ((file_events & MONITOR_SIGNAL_ITEM_CREATED), ==, 0);
+       g_assert_cmpuint ((file_events & MONITOR_SIGNAL_ITEM_UPDATED), ==, 0);
+       g_assert_cmpuint ((file_events & MONITOR_SIGNAL_ITEM_ATTRIBUTE_UPDATED), ==, 0);
+       g_assert_cmpuint ((file_events & MONITOR_SIGNAL_ITEM_MOVED_FROM), ==, 0);
+       g_assert_cmpuint ((file_events & MONITOR_SIGNAL_ITEM_MOVED_TO), ==, 0);
+
+       /* Cleanup environment */
+       tracker_monitor_set_enabled (fixture->monitor, FALSE);
+       g_assert_cmpint (tracker_monitor_remove (fixture->monitor, source_dir), ==, TRUE);
+       g_object_unref (source_dir);
+       g_free (source_path);
+}
+
+
+static void
+test_monitor_directory_event_moved_to_monitored (TrackerMonitorTestFixture *fixture,
+                                                 gconstpointer              data)
+{
+       GFile *source_dir;
+       gchar *source_path;
+       GFile *dest_dir;
+       gchar *dest_path;
+       GFile *file_in_source_dir;
+       GFile *file_in_dest_dir;
+       gchar *file_in_dest_dir_path;
+       guint file_events;
+
+       /* Create directory to test with, before setting up the environment */
+       create_directory (fixture->monitored_directory, "foo", &source_dir);
+       source_path = g_file_get_path (source_dir);
+       g_assert (source_dir != NULL);
+
+       /* Add some file to the new dir */
+       set_file_contents (source_path, "lalala.txt", "whatever", &file_in_source_dir);
+
+       /* Set up environment */
+       tracker_monitor_set_enabled (fixture->monitor, TRUE);
+
+       /* Set to monitor the new dir also */
+       g_assert_cmpint (tracker_monitor_add (fixture->monitor, source_dir), ==, TRUE);
+
+       /* Get final path of the file */
+       file_in_dest_dir_path = g_build_path (G_DIR_SEPARATOR_S,
+                                             fixture->monitored_directory,
+                                             "renamed",
+                                             "lalala.txt",
+                                             NULL);
+       file_in_dest_dir = g_file_new_for_path (file_in_dest_dir_path);
+       g_assert (file_in_dest_dir != NULL);
+
+       /* Now, rename the directory */
+       dest_dir = g_file_get_parent (file_in_dest_dir);
+       dest_path = g_file_get_path (dest_dir);
+
+       g_assert_cmpint (g_rename (source_path, dest_path), ==, 0);
+
+       g_hash_table_insert (fixture->events,
+                            g_object_ref (source_dir),
+                            GUINT_TO_POINTER (MONITOR_SIGNAL_NONE));
+       g_hash_table_insert (fixture->events,
+                            g_object_ref (dest_dir),
+                            GUINT_TO_POINTER (MONITOR_SIGNAL_NONE));
+       g_hash_table_insert (fixture->events,
+                            g_object_ref (file_in_dest_dir),
+                            GUINT_TO_POINTER (MONITOR_SIGNAL_NONE));
+       g_hash_table_insert (fixture->events,
+                            g_object_ref (file_in_source_dir),
+                            GUINT_TO_POINTER (MONITOR_SIGNAL_NONE));
+
+       /* Wait for events */
+       events_wait (fixture);
+
+       /* Get events in the source dir */
+       file_events = GPOINTER_TO_UINT (g_hash_table_lookup (fixture->events, source_dir));
+       /* Fail if we didn't get the MOVED_FROM signal */
+       g_assert_cmpuint ((file_events & MONITOR_SIGNAL_ITEM_MOVED_FROM), >, 0);
+       /* Fail if we got a CREATE, UPDATE, DELETE or MOVE_TO signal */
+       g_assert_cmpuint ((file_events & MONITOR_SIGNAL_ITEM_CREATED), ==, 0);
+       g_assert_cmpuint ((file_events & MONITOR_SIGNAL_ITEM_UPDATED), ==, 0);
+       g_assert_cmpuint ((file_events & MONITOR_SIGNAL_ITEM_ATTRIBUTE_UPDATED), ==, 0);
+       g_assert_cmpuint ((file_events & MONITOR_SIGNAL_ITEM_DELETED), ==, 0);
+       g_assert_cmpuint ((file_events & MONITOR_SIGNAL_ITEM_MOVED_TO), ==, 0);
+
+       /* Get events in the dest file */
+       file_events = GPOINTER_TO_UINT (g_hash_table_lookup (fixture->events, dest_dir));
+       /* Fail if we didn't get the MOVED_TO signal */
+       g_assert_cmpuint ((file_events & MONITOR_SIGNAL_ITEM_MOVED_TO), >, 0);
+       /* Fail if we got a CREATE, DELETE or MOVE_FROM signal (UPDATE may actually be possible) */
+       g_assert_cmpuint ((file_events & MONITOR_SIGNAL_ITEM_CREATED), ==, 0);
+       g_assert_cmpuint ((file_events & MONITOR_SIGNAL_ITEM_MOVED_FROM), ==, 0);
+       g_assert_cmpuint ((file_events & MONITOR_SIGNAL_ITEM_DELETED), ==, 0);
+
+       /* Get events in the file in source dir */
+       file_events = GPOINTER_TO_UINT (g_hash_table_lookup (fixture->events, file_in_source_dir));
+       /* Fail if we got ANY signal */
+       g_assert_cmpuint ((file_events & MONITOR_SIGNAL_ITEM_CREATED), ==, 0);
+       g_assert_cmpuint ((file_events & MONITOR_SIGNAL_ITEM_UPDATED), ==, 0);
+       g_assert_cmpuint ((file_events & MONITOR_SIGNAL_ITEM_ATTRIBUTE_UPDATED), ==, 0);
+       g_assert_cmpuint ((file_events & MONITOR_SIGNAL_ITEM_DELETED), ==, 0);
+       g_assert_cmpuint ((file_events & MONITOR_SIGNAL_ITEM_MOVED_FROM), ==, 0);
+       g_assert_cmpuint ((file_events & MONITOR_SIGNAL_ITEM_MOVED_TO), ==, 0);
+
+       /* Get events in the file in dest dir */
+       file_events = GPOINTER_TO_UINT (g_hash_table_lookup (fixture->events, file_in_dest_dir));
+       /* Fail if we got ANY signal */
+       g_assert_cmpuint ((file_events & MONITOR_SIGNAL_ITEM_CREATED), ==, 0);
+       g_assert_cmpuint ((file_events & MONITOR_SIGNAL_ITEM_UPDATED), ==, 0);
+       g_assert_cmpuint ((file_events & MONITOR_SIGNAL_ITEM_ATTRIBUTE_UPDATED), ==, 0);
+       g_assert_cmpuint ((file_events & MONITOR_SIGNAL_ITEM_DELETED), ==, 0);
+       g_assert_cmpuint ((file_events & MONITOR_SIGNAL_ITEM_MOVED_FROM), ==, 0);
+       g_assert_cmpuint ((file_events & MONITOR_SIGNAL_ITEM_MOVED_TO), ==, 0);
+
+
+       /* Cleanup environment */
+       tracker_monitor_set_enabled (fixture->monitor, FALSE);
+       /* Since tracker 0.9.33, monitors are NOT moved to the new location directly when the
+        * directory is moved, that is done by the upper layers.
+        * Note that monitor is now in dest_dir */
+       g_assert_cmpint (tracker_monitor_remove (fixture->monitor, source_dir), ==, TRUE);
+       g_assert_cmpint (tracker_monitor_remove (fixture->monitor, dest_dir), !=, TRUE);
+       g_assert_cmpint (g_file_delete (file_in_dest_dir, NULL, NULL), ==, TRUE);
+       g_assert_cmpint (g_file_delete (dest_dir, NULL, NULL), ==, TRUE);
+       g_object_unref (source_dir);
+       g_object_unref (file_in_source_dir);
+       g_object_unref (dest_dir);
+       g_object_unref (file_in_dest_dir);
+       g_free (source_path);
+       g_free (file_in_dest_dir_path);
+       g_free (dest_path);
+}
+
+/* Same test as before, BUT, creating a new file in the directory while it's being monitored.
+ * In this case, GIO dumps an extra DELETE event after the MOVE
+ */
+static void
+test_monitor_directory_event_moved_to_monitored_after_file_create (TrackerMonitorTestFixture *fixture,
+                                                                   gconstpointer              data)
+{
+       GFile *source_dir;
+       gchar *source_path;
+       GFile *dest_dir;
+       gchar *dest_path;
+       GFile *file_in_source_dir;
+       GFile *file_in_dest_dir;
+       gchar *file_in_dest_dir_path;
+       guint file_events;
+
+       /* Create directory to test with, before setting up the environment */
+       create_directory (fixture->monitored_directory, "foo", &source_dir);
+       source_path = g_file_get_path (source_dir);
+       g_assert (source_dir != NULL);
+
+       /* Set up environment */
+       tracker_monitor_set_enabled (fixture->monitor, TRUE);
+
+       /* Set to monitor the new dir also */
+       g_assert_cmpint (tracker_monitor_add (fixture->monitor, source_dir), ==, TRUE);
+
+       /* Add some file to the new dir, WHILE ALREADY MONITORING */
+       set_file_contents (source_path, "lalala.txt", "whatever", &file_in_source_dir);
+
+       /* Get final path of the file */
+       file_in_dest_dir_path = g_build_path (G_DIR_SEPARATOR_S,
+                                             fixture->monitored_directory,
+                                             "renamed",
+                                             "lalala.txt",
+                                             NULL);
+       file_in_dest_dir = g_file_new_for_path (file_in_dest_dir_path);
+       g_assert (file_in_dest_dir != NULL);
+
+       /* Now, rename the directory */
+       dest_dir = g_file_get_parent (file_in_dest_dir);
+       dest_path = g_file_get_path (dest_dir);
+
+       g_assert_cmpint (g_rename (source_path, dest_path), ==, 0);
+
+       g_hash_table_insert (fixture->events,
+                            g_object_ref (source_dir),
+                            GUINT_TO_POINTER (MONITOR_SIGNAL_NONE));
+       g_hash_table_insert (fixture->events,
+                            g_object_ref (dest_dir),
+                            GUINT_TO_POINTER (MONITOR_SIGNAL_NONE));
+       g_hash_table_insert (fixture->events,
+                            g_object_ref (file_in_dest_dir),
+                            GUINT_TO_POINTER (MONITOR_SIGNAL_NONE));
+       g_hash_table_insert (fixture->events,
+                            g_object_ref (file_in_source_dir),
+                            GUINT_TO_POINTER (MONITOR_SIGNAL_NONE));
+
+       /* Wait for events */
+       events_wait (fixture);
+
+       /* Get events in the source dir */
+       file_events = GPOINTER_TO_UINT (g_hash_table_lookup (fixture->events, source_dir));
+       /* Fail if we didn't get the MOVED_FROM signal */
+       g_assert_cmpuint ((file_events & MONITOR_SIGNAL_ITEM_MOVED_FROM), >, 0);
+       /* Fail if we got a CREATE, UPDATE, DELETE or MOVE_TO signal */
+       g_assert_cmpuint ((file_events & MONITOR_SIGNAL_ITEM_CREATED), ==, 0);
+       g_assert_cmpuint ((file_events & MONITOR_SIGNAL_ITEM_UPDATED), ==, 0);
+       g_assert_cmpuint ((file_events & MONITOR_SIGNAL_ITEM_ATTRIBUTE_UPDATED), ==, 0);
+       g_assert_cmpuint ((file_events & MONITOR_SIGNAL_ITEM_DELETED), ==, 0);
+       g_assert_cmpuint ((file_events & MONITOR_SIGNAL_ITEM_MOVED_TO), ==, 0);
+
+       /* Get events in the dest file */
+       file_events = GPOINTER_TO_UINT (g_hash_table_lookup (fixture->events, dest_dir));
+       /* Fail if we didn't get the MOVED_TO signal */
+       g_assert_cmpuint ((file_events & MONITOR_SIGNAL_ITEM_MOVED_TO), >, 0);
+       /* Fail if we got a CREATE, DELETE or MOVE_FROM signal (UPDATE may actually be possible) */
+       g_assert_cmpuint ((file_events & MONITOR_SIGNAL_ITEM_CREATED), ==, 0);
+       g_assert_cmpuint ((file_events & MONITOR_SIGNAL_ITEM_MOVED_FROM), ==, 0);
+       g_assert_cmpuint ((file_events & MONITOR_SIGNAL_ITEM_DELETED), ==, 0);
+
+       /* Get events in the file in source dir */
+       file_events = GPOINTER_TO_UINT (g_hash_table_lookup (fixture->events, file_in_source_dir));
+       /* Fail if we got ANY signal != CREATED (we created the file while monitoring) */
+       g_assert_cmpuint ((file_events & MONITOR_SIGNAL_ITEM_UPDATED), ==, 0);
+       g_assert_cmpuint ((file_events & MONITOR_SIGNAL_ITEM_ATTRIBUTE_UPDATED), ==, 0);
+       g_assert_cmpuint ((file_events & MONITOR_SIGNAL_ITEM_DELETED), ==, 0);
+       g_assert_cmpuint ((file_events & MONITOR_SIGNAL_ITEM_MOVED_FROM), ==, 0);
+       g_assert_cmpuint ((file_events & MONITOR_SIGNAL_ITEM_MOVED_TO), ==, 0);
+
+       /* Get events in the file in dest dir */
+       file_events = GPOINTER_TO_UINT (g_hash_table_lookup (fixture->events, file_in_dest_dir));
+       /* Fail if we got ANY signal */
+       g_assert_cmpuint ((file_events & MONITOR_SIGNAL_ITEM_CREATED), ==, 0);
+       g_assert_cmpuint ((file_events & MONITOR_SIGNAL_ITEM_UPDATED), ==, 0);
+       g_assert_cmpuint ((file_events & MONITOR_SIGNAL_ITEM_ATTRIBUTE_UPDATED), ==, 0);
+       g_assert_cmpuint ((file_events & MONITOR_SIGNAL_ITEM_DELETED), ==, 0);
+       g_assert_cmpuint ((file_events & MONITOR_SIGNAL_ITEM_MOVED_FROM), ==, 0);
+       g_assert_cmpuint ((file_events & MONITOR_SIGNAL_ITEM_MOVED_TO), ==, 0);
+
+
+       /* Cleanup environment */
+       tracker_monitor_set_enabled (fixture->monitor, FALSE);
+       /* Since tracker 0.9.33, monitors are NOT moved to the new location directly when the
+        * directory is moved, that is done by the upper layers.
+        * Note that monitor is now in dest_dir */
+       g_assert_cmpint (tracker_monitor_remove (fixture->monitor, source_dir), ==, TRUE);
+       g_assert_cmpint (tracker_monitor_remove (fixture->monitor, dest_dir), !=, TRUE);
+       g_assert_cmpint (g_file_delete (file_in_dest_dir, NULL, NULL), ==, TRUE);
+       g_assert_cmpint (g_file_delete (dest_dir, NULL, NULL), ==, TRUE);
+       g_object_unref (source_dir);
+       g_object_unref (file_in_source_dir);
+       g_object_unref (dest_dir);
+       g_object_unref (file_in_dest_dir);
+       g_free (source_path);
+       g_free (file_in_dest_dir_path);
+       g_free (dest_path);
+}
+
+/* Same test as before, BUT, updating an existing file in the directory while it's being monitored.
+ * In this case, GIO dumps an extra DELETE event after the MOVE
+ */
+static void
+test_monitor_directory_event_moved_to_monitored_after_file_update (TrackerMonitorTestFixture *fixture,
+                                                                   gconstpointer              data)
+{
+       GFile *source_dir;
+       gchar *source_path;
+       GFile *dest_dir;
+       gchar *dest_path;
+       GFile *file_in_source_dir;
+       GFile *file_in_dest_dir;
+       gchar *file_in_dest_dir_path;
+       guint file_events;
+
+       /* Create directory to test with, before setting up the environment */
+       create_directory (fixture->monitored_directory, "foo", &source_dir);
+       source_path = g_file_get_path (source_dir);
+       g_assert (source_dir != NULL);
+
+       /* Add some file to the new dir */
+       set_file_contents (source_path, "lalala.txt", "whatever", &file_in_source_dir);
+
+       /* Set up environment */
+       tracker_monitor_set_enabled (fixture->monitor, TRUE);
+
+       /* Set to monitor the new dir also */
+       g_assert_cmpint (tracker_monitor_add (fixture->monitor, source_dir), ==, TRUE);
+
+       /* Get final path of the file */
+       file_in_dest_dir_path = g_build_path (G_DIR_SEPARATOR_S,
+                                             fixture->monitored_directory,
+                                             "renamed",
+                                             "lalala.txt",
+                                             NULL);
+       file_in_dest_dir = g_file_new_for_path (file_in_dest_dir_path);
+       g_assert (file_in_dest_dir != NULL);
+
+       /* Update file contents */
+       set_file_contents (source_path, "lalala.txt", "hohoho", &file_in_source_dir);
+
+       /* Now, rename the directory */
+       dest_dir = g_file_get_parent (file_in_dest_dir);
+       dest_path = g_file_get_path (dest_dir);
+
+       g_assert_cmpint (g_rename (source_path, dest_path), ==, 0);
+
+       g_hash_table_insert (fixture->events,
+                            g_object_ref (source_dir),
+                            GUINT_TO_POINTER (MONITOR_SIGNAL_NONE));
+       g_hash_table_insert (fixture->events,
+                            g_object_ref (dest_dir),
+                            GUINT_TO_POINTER (MONITOR_SIGNAL_NONE));
+       g_hash_table_insert (fixture->events,
+                            g_object_ref (file_in_dest_dir),
+                            GUINT_TO_POINTER (MONITOR_SIGNAL_NONE));
+       g_hash_table_insert (fixture->events,
+                            g_object_ref (file_in_source_dir),
+                            GUINT_TO_POINTER (MONITOR_SIGNAL_NONE));
+
+       /* Wait for events */
+       events_wait (fixture);
+
+       /* Get events in the source dir */
+       file_events = GPOINTER_TO_UINT (g_hash_table_lookup (fixture->events, source_dir));
+       /* Fail if we didn't get the MOVED_FROM signal */
+       g_assert_cmpuint ((file_events & MONITOR_SIGNAL_ITEM_MOVED_FROM), >, 0);
+       /* Fail if we got a CREATE, UPDATE, DELETE or MOVE_TO signal */
+       g_assert_cmpuint ((file_events & MONITOR_SIGNAL_ITEM_CREATED), ==, 0);
+       g_assert_cmpuint ((file_events & MONITOR_SIGNAL_ITEM_UPDATED), ==, 0);
+       g_assert_cmpuint ((file_events & MONITOR_SIGNAL_ITEM_ATTRIBUTE_UPDATED), ==, 0);
+       g_assert_cmpuint ((file_events & MONITOR_SIGNAL_ITEM_DELETED), ==, 0);
+       g_assert_cmpuint ((file_events & MONITOR_SIGNAL_ITEM_MOVED_TO), ==, 0);
+
+       /* Get events in the dest file */
+       file_events = GPOINTER_TO_UINT (g_hash_table_lookup (fixture->events, dest_dir));
+       /* Fail if we didn't get the MOVED_TO signal */
+       g_assert_cmpuint ((file_events & MONITOR_SIGNAL_ITEM_MOVED_TO), >, 0);
+       /* Fail if we got a CREATE, DELETE or MOVE_FROM signal (UPDATE may actually be possible) */
+       g_assert_cmpuint ((file_events & MONITOR_SIGNAL_ITEM_CREATED), ==, 0);
+       g_assert_cmpuint ((file_events & MONITOR_SIGNAL_ITEM_MOVED_FROM), ==, 0);
+       g_assert_cmpuint ((file_events & MONITOR_SIGNAL_ITEM_DELETED), ==, 0);
+
+       /* Get events in the file in source dir */
+       file_events = GPOINTER_TO_UINT (g_hash_table_lookup (fixture->events, file_in_source_dir));
+       /* Fail if we didn't get the UPDATE signal */
+       g_assert_cmpuint ((file_events & MONITOR_SIGNAL_ITEM_UPDATED), >=, 0);
+       /* Fail if we got ANY signal */
+       g_assert_cmpuint ((file_events & MONITOR_SIGNAL_ITEM_CREATED), ==, 0);
+       g_assert_cmpuint ((file_events & MONITOR_SIGNAL_ITEM_DELETED), ==, 0);
+       g_assert_cmpuint ((file_events & MONITOR_SIGNAL_ITEM_MOVED_FROM), ==, 0);
+       g_assert_cmpuint ((file_events & MONITOR_SIGNAL_ITEM_MOVED_TO), ==, 0);
+
+       /* Get events in the file in dest dir */
+       file_events = GPOINTER_TO_UINT (g_hash_table_lookup (fixture->events, file_in_dest_dir));
+       /* Fail if we got ANY signal */
+       g_assert_cmpuint ((file_events & MONITOR_SIGNAL_ITEM_CREATED), ==, 0);
+       g_assert_cmpuint ((file_events & MONITOR_SIGNAL_ITEM_UPDATED), ==, 0);
+       g_assert_cmpuint ((file_events & MONITOR_SIGNAL_ITEM_ATTRIBUTE_UPDATED), ==, 0);
+       g_assert_cmpuint ((file_events & MONITOR_SIGNAL_ITEM_DELETED), ==, 0);
+       g_assert_cmpuint ((file_events & MONITOR_SIGNAL_ITEM_MOVED_FROM), ==, 0);
+       g_assert_cmpuint ((file_events & MONITOR_SIGNAL_ITEM_MOVED_TO), ==, 0);
+
+
+       /* Cleanup environment */
+       tracker_monitor_set_enabled (fixture->monitor, FALSE);
+       /* Since tracker 0.9.33, monitors are NOT moved to the new location directly when the
+        * directory is moved, that is done by the upper layers.
+        * Note that monitor is now in dest_dir */
+       g_assert_cmpint (tracker_monitor_remove (fixture->monitor, source_dir), ==, TRUE);
+       g_assert_cmpint (tracker_monitor_remove (fixture->monitor, dest_dir), !=, TRUE);
+       g_assert_cmpint (g_file_delete (file_in_dest_dir, NULL, NULL), ==, TRUE);
+       g_assert_cmpint (g_file_delete (dest_dir, NULL, NULL), ==, TRUE);
+       g_object_unref (source_dir);
+       g_object_unref (file_in_source_dir);
+       g_object_unref (dest_dir);
+       g_object_unref (file_in_dest_dir);
+       g_free (source_path);
+       g_free (file_in_dest_dir_path);
+       g_free (dest_path);
+}
+
+static void
+test_monitor_directory_event_moved_to_not_monitored (TrackerMonitorTestFixture *fixture,
+                                                    gconstpointer              data)
+{
+       GFile *source_dir;
+       gchar *source_path;
+       GFile *dest_dir;
+       gchar *dest_path;
+       guint file_events;
+
+       /* Create directory to test with, before setting up the environment */
+       create_directory (fixture->monitored_directory, "foo", &source_dir);
+       source_path = g_file_get_path (source_dir);
+       g_assert (source_dir != NULL);
+
+       /* Set to monitor the new dir also */
+       g_assert_cmpint (tracker_monitor_add (fixture->monitor, source_dir), ==, TRUE);
+
+       /* Set up environment */
+       tracker_monitor_set_enabled (fixture->monitor, TRUE);
+
+       /* Now, rename the directory */
+       dest_path = g_build_path (G_DIR_SEPARATOR_S, fixture->not_monitored_directory, "foo", NULL);
+       dest_dir = g_file_new_for_path (dest_path);
+
+       g_assert_cmpint (g_rename (source_path, dest_path), ==, 0);
+
+       g_hash_table_insert (fixture->events,
+                            g_object_ref (source_dir),
+                            GUINT_TO_POINTER (MONITOR_SIGNAL_NONE));
+       g_hash_table_insert (fixture->events,
+                            g_object_ref (dest_dir),
+                            GUINT_TO_POINTER (MONITOR_SIGNAL_NONE));
+
+       /* Wait for events */
+       events_wait (fixture);
+
+       /* Get events in the source dir */
+       file_events = GPOINTER_TO_UINT (g_hash_table_lookup (fixture->events, source_dir));
+       /* Fail if we didn't get the DELETED signal */
+       g_assert_cmpuint ((file_events & MONITOR_SIGNAL_ITEM_DELETED), >, 0);
+       /* Fail if we got a CREATE, UPDATE, MOVE_FROM or MOVE_TO signal */
+       g_assert_cmpuint ((file_events & MONITOR_SIGNAL_ITEM_CREATED), ==, 0);
+       g_assert_cmpuint ((file_events & MONITOR_SIGNAL_ITEM_UPDATED), ==, 0);
+       g_assert_cmpuint ((file_events & MONITOR_SIGNAL_ITEM_ATTRIBUTE_UPDATED), ==, 0);
+       g_assert_cmpuint ((file_events & MONITOR_SIGNAL_ITEM_MOVED_FROM), ==, 0);
+       g_assert_cmpuint ((file_events & MONITOR_SIGNAL_ITEM_MOVED_TO), ==, 0);
+
+       /* Get events in the dest file */
+       file_events = GPOINTER_TO_UINT (g_hash_table_lookup (fixture->events, dest_dir));
+       /* Fail if we got any signal */
+       g_assert_cmpuint ((file_events & MONITOR_SIGNAL_ITEM_CREATED), ==, 0);
+       g_assert_cmpuint ((file_events & MONITOR_SIGNAL_ITEM_UPDATED), ==, 0);
+       g_assert_cmpuint ((file_events & MONITOR_SIGNAL_ITEM_ATTRIBUTE_UPDATED), ==, 0);
+       g_assert_cmpuint ((file_events & MONITOR_SIGNAL_ITEM_MOVED_FROM), ==, 0);
+       g_assert_cmpuint ((file_events & MONITOR_SIGNAL_ITEM_MOVED_TO), ==, 0);
+       g_assert_cmpuint ((file_events & MONITOR_SIGNAL_ITEM_DELETED), ==, 0);
+
+       /* Cleanup environment */
+       tracker_monitor_set_enabled (fixture->monitor, FALSE);
+       g_assert_cmpint (tracker_monitor_remove (fixture->monitor, source_dir), ==, TRUE);
+       /* Note that monitor is NOT in dest_dir, so FAIL if we could remove it */
+       g_assert_cmpint (tracker_monitor_remove (fixture->monitor, dest_dir), !=, TRUE);
+       g_assert_cmpint (g_file_delete (dest_dir, NULL, NULL), ==, TRUE);
+       g_object_unref (source_dir);
+       g_object_unref (dest_dir);
+       g_free (source_path);
+       g_free (dest_path);
+}
+
+static void
+test_monitor_directory_event_moved_from_not_monitored (TrackerMonitorTestFixture *fixture,
+                                                      gconstpointer              data)
+{
+       GFile *source_dir;
+       gchar *source_path;
+       GFile *dest_dir;
+       gchar *dest_path;
+       guint file_events;
+
+       /* Create directory to test with in a not-monitored place,
+        * before setting up the environment */
+       create_directory (fixture->not_monitored_directory, "foo", &source_dir);
+       source_path = g_file_get_path (source_dir);
+       g_assert (source_dir != NULL);
+
+       /* Set up environment */
+       tracker_monitor_set_enabled (fixture->monitor, TRUE);
+
+       /* Now, rename the directory to somewhere monitored */
+       dest_path = g_build_path (G_DIR_SEPARATOR_S, fixture->monitored_directory, "foo", NULL);
+       dest_dir = g_file_new_for_path (dest_path);
+
+       g_assert_cmpint (g_rename (source_path, dest_path), ==, 0);
+
+       g_hash_table_insert (fixture->events,
+                            g_object_ref (source_dir),
+                            GUINT_TO_POINTER (MONITOR_SIGNAL_NONE));
+       g_hash_table_insert (fixture->events,
+                            g_object_ref (dest_dir),
+                            GUINT_TO_POINTER (MONITOR_SIGNAL_NONE));
+
+       /* Wait for events */
+       events_wait (fixture);
+
+       /* Get events in the source dir */
+       file_events = GPOINTER_TO_UINT (g_hash_table_lookup (fixture->events, source_dir));
+       /* Fail if we got any signal */
+       g_assert_cmpuint ((file_events & MONITOR_SIGNAL_ITEM_CREATED), ==, 0);
+       g_assert_cmpuint ((file_events & MONITOR_SIGNAL_ITEM_UPDATED), ==, 0);
+       g_assert_cmpuint ((file_events & MONITOR_SIGNAL_ITEM_ATTRIBUTE_UPDATED), ==, 0);
+       g_assert_cmpuint ((file_events & MONITOR_SIGNAL_ITEM_MOVED_FROM), ==, 0);
+       g_assert_cmpuint ((file_events & MONITOR_SIGNAL_ITEM_MOVED_TO), ==, 0);
+       g_assert_cmpuint ((file_events & MONITOR_SIGNAL_ITEM_DELETED), ==, 0);
+
+       /* Get events in the dest file */
+       file_events = GPOINTER_TO_UINT (g_hash_table_lookup (fixture->events, dest_dir));
+       /* Fail if we didn't get the CREATED signal */
+       g_assert_cmpuint ((file_events & MONITOR_SIGNAL_ITEM_CREATED), >, 0);
+       /* Fail if we got a CREATE, UPDATE, MOVE_FROM or MOVE_TO signal */
+       g_assert_cmpuint ((file_events & MONITOR_SIGNAL_ITEM_UPDATED), ==, 0);
+       g_assert_cmpuint ((file_events & MONITOR_SIGNAL_ITEM_ATTRIBUTE_UPDATED), ==, 0);
+       g_assert_cmpuint ((file_events & MONITOR_SIGNAL_ITEM_MOVED_FROM), ==, 0);
+       g_assert_cmpuint ((file_events & MONITOR_SIGNAL_ITEM_MOVED_TO), ==, 0);
+       g_assert_cmpuint ((file_events & MONITOR_SIGNAL_ITEM_DELETED), ==, 0);
+
+       /* Cleanup environment */
+       tracker_monitor_set_enabled (fixture->monitor, FALSE);
+       /* Note that monitor is now in dest_dir, BUT TrackerMonitor should
+        * NOT add automatically a new monitor, so FAIL if we can remove it */
+       g_assert_cmpint (tracker_monitor_remove (fixture->monitor, dest_dir), !=, TRUE);
+       g_assert_cmpint (g_file_delete (dest_dir, NULL, NULL), ==, TRUE);
+       g_object_unref (source_dir);
+       g_object_unref (dest_dir);
+       g_free (source_path);
+       g_free (dest_path);
+}
+
+/* ----------------------------- BASIC API TESTS --------------------------------- */
+
+static void
+test_monitor_basic (void)
+{
+       TrackerMonitor *monitor;
+       gchar *basename;
+       gchar *path_for_monitor;
+       GFile *file_for_monitor;
+       GFile *file_for_tmp;
+
+       /* Setup directories */
+       basename = g_strdup_printf ("monitor-test-%d", getpid ());
+       path_for_monitor = g_build_path (G_DIR_SEPARATOR_S, g_get_tmp_dir (), basename, NULL);
+       g_free (basename);
+       g_assert_cmpint (g_mkdir_with_parents (path_for_monitor, 00755), ==, 0);
+
+       file_for_monitor = g_file_new_for_path (path_for_monitor);
+       g_assert (G_IS_FILE (file_for_monitor));
+
+       file_for_tmp = g_file_new_for_path (g_get_tmp_dir ());
+       g_assert (G_IS_FILE (file_for_tmp));
+
+       /* Create a monitor */
+       monitor = tracker_monitor_new ();
+       g_assert (monitor != NULL);
+
+       /* Test general API with monitors enabled */
+       tracker_monitor_set_enabled (monitor, TRUE);
+       g_assert_cmpint (tracker_monitor_get_enabled (monitor), ==, TRUE);
+
+       g_assert_cmpint (tracker_monitor_get_count (monitor), ==, 0);
+       g_assert_cmpint (tracker_monitor_add (monitor, file_for_monitor), ==, TRUE);
+       g_assert_cmpint (tracker_monitor_add (monitor, file_for_monitor), ==, TRUE); /* Test double add on 
purpose */
+       g_assert_cmpint (tracker_monitor_get_count (monitor), ==, 1);
+       g_assert_cmpint (tracker_monitor_is_watched (monitor, file_for_monitor), ==, TRUE);
+       g_assert_cmpint (tracker_monitor_is_watched_by_string (monitor, path_for_monitor), ==, TRUE);
+       g_assert_cmpint (tracker_monitor_remove (monitor, file_for_monitor), ==, TRUE);
+       g_assert_cmpint (tracker_monitor_is_watched (monitor, file_for_monitor), ==, FALSE);
+       g_assert_cmpint (tracker_monitor_is_watched_by_string (monitor, path_for_monitor), ==, FALSE);
+       g_assert_cmpint (tracker_monitor_get_count (monitor), ==, 0);
+
+       tracker_monitor_add (monitor, file_for_monitor);
+       tracker_monitor_add (monitor, file_for_tmp);
+       g_assert_cmpint (tracker_monitor_get_count (monitor), ==, 2);
+       g_assert_cmpint (tracker_monitor_remove_recursively (monitor, file_for_tmp), ==, TRUE);
+       g_assert_cmpint (tracker_monitor_get_count (monitor), ==, 0);
+
+       /* Test general API with monitors disabled */
+       tracker_monitor_set_enabled (monitor, FALSE);
+       g_assert_cmpint (tracker_monitor_get_enabled (monitor), ==, FALSE);
+
+       g_assert_cmpint (tracker_monitor_get_count (monitor), ==, 0);
+       g_assert_cmpint (tracker_monitor_add (monitor, file_for_monitor), ==, TRUE);
+       g_assert_cmpint (tracker_monitor_get_count (monitor), ==, 1);
+       g_assert_cmpint (tracker_monitor_is_watched (monitor, file_for_monitor), ==, FALSE);
+       g_assert_cmpint (tracker_monitor_is_watched_by_string (monitor, path_for_monitor), ==, FALSE);
+       g_assert_cmpint (tracker_monitor_remove (monitor, file_for_monitor), ==, TRUE);
+       g_assert_cmpint (tracker_monitor_is_watched (monitor, file_for_monitor), ==, FALSE);
+       g_assert_cmpint (tracker_monitor_is_watched_by_string (monitor, path_for_monitor), ==, FALSE);
+       g_assert_cmpint (tracker_monitor_get_count (monitor), ==, 0);
+
+       tracker_monitor_add (monitor, file_for_monitor);
+       tracker_monitor_add (monitor, file_for_tmp);
+       g_assert_cmpint (tracker_monitor_get_count (monitor), ==, 2);
+       g_assert_cmpint (tracker_monitor_remove_recursively (monitor, file_for_tmp), ==, TRUE);
+       g_assert_cmpint (tracker_monitor_get_count (monitor), ==, 0);
+
+       /* Cleanup */
+       g_assert_cmpint (g_rmdir (path_for_monitor), ==, 0);
+       g_assert (file_for_tmp != NULL);
+       g_object_unref (file_for_tmp);
+       g_assert (file_for_monitor != NULL);
+       g_object_unref (file_for_monitor);
+       g_assert (path_for_monitor != NULL);
+       g_free (path_for_monitor);
+       g_assert (monitor != NULL);
+       g_object_unref (monitor);
+}
+
+gint
+main (gint    argc,
+      gchar **argv)
+{
+       g_test_init (&argc, &argv, NULL);
+
+       g_test_message ("Testing filesystem monitor");
+
+       /* Basic API tests */
+       g_test_add_func ("/libtracker-miner/tracker-monitor/basic",
+                        test_monitor_basic);
+
+       /* File Event tests */
+       g_test_add ("/libtracker-miner/tracker-monitor/file-event/created",
+                   TrackerMonitorTestFixture,
+                   NULL,
+                   test_monitor_common_setup,
+                   test_monitor_file_event_created,
+                   test_monitor_common_teardown);
+       g_test_add ("/libtracker-miner/tracker-monitor/file-event/updated",
+                   TrackerMonitorTestFixture,
+                   NULL,
+                   test_monitor_common_setup,
+                   test_monitor_file_event_updated,
+                   test_monitor_common_teardown);
+       g_test_add ("/libtracker-miner/tracker-monitor/file-event/attribute-updated",
+                   TrackerMonitorTestFixture,
+                   NULL,
+                   test_monitor_common_setup,
+                   test_monitor_file_event_attribute_updated,
+                   test_monitor_common_teardown);
+       g_test_add ("/libtracker-miner/tracker-monitor/file-event/deleted",
+                   TrackerMonitorTestFixture,
+                   NULL,
+                   test_monitor_common_setup,
+                   test_monitor_file_event_deleted,
+                   test_monitor_common_teardown);
+       g_test_add ("/libtracker-miner/tracker-monitor/file-event/moved/to-monitored",
+                   TrackerMonitorTestFixture,
+                   NULL,
+                   test_monitor_common_setup,
+                   test_monitor_file_event_moved_to_monitored,
+                   test_monitor_common_teardown);
+       g_test_add ("/libtracker-miner/tracker-monitor/file-event/moved/to-not-monitored",
+                   TrackerMonitorTestFixture,
+                   NULL,
+                   test_monitor_common_setup,
+                   test_monitor_file_event_moved_to_not_monitored,
+                   test_monitor_common_teardown);
+       g_test_add ("/libtracker-miner/tracker-monitor/file-event/moved/from-not-monitored",
+                   TrackerMonitorTestFixture,
+                   NULL,
+                   test_monitor_common_setup,
+                   test_monitor_file_event_moved_from_not_monitored,
+                   test_monitor_common_teardown);
+
+       /* File event blacklisting tests */
+       g_test_add ("/libtracker-miner/tracker-monitor/file-event/blacklisting/created-updated",
+                   TrackerMonitorTestFixture,
+                   NULL,
+                   test_monitor_common_setup,
+                   test_monitor_file_event_blacklisting_created_updated,
+                   test_monitor_common_teardown);
+       g_test_add ("/libtracker-miner/tracker-monitor/file-event/blacklisting/created-deleted",
+                   TrackerMonitorTestFixture,
+                   NULL,
+                   test_monitor_common_setup,
+                   test_monitor_file_event_blacklisting_created_deleted,
+                   test_monitor_common_teardown);
+       g_test_add ("/libtracker-miner/tracker-monitor/file-event/blacklisting/created-updated-deleted",
+                   TrackerMonitorTestFixture,
+                   NULL,
+                   test_monitor_common_setup,
+                   test_monitor_file_event_blacklisting_created_updated_deleted,
+                   test_monitor_common_teardown);
+       g_test_add ("/libtracker-miner/tracker-monitor/file-event/blacklisting/created-moved",
+                   TrackerMonitorTestFixture,
+                   NULL,
+                   test_monitor_common_setup,
+                   test_monitor_file_event_blacklisting_created_moved,
+                   test_monitor_common_teardown);
+       g_test_add ("/libtracker-miner/tracker-monitor/file-event/blacklisting/updated-deleted",
+                   TrackerMonitorTestFixture,
+                   NULL,
+                   test_monitor_common_setup,
+                   test_monitor_file_event_blacklisting_updated_deleted,
+                   test_monitor_common_teardown);
+       g_test_add ("/libtracker-miner/tracker-monitor/file-event/blacklisting/updated-moved",
+                   TrackerMonitorTestFixture,
+                   NULL,
+                   test_monitor_common_setup,
+                   test_monitor_file_event_blacklisting_updated_moved,
+                   test_monitor_common_teardown);
+       g_test_add ("/libtracker-miner/tracker-monitor/file-event/blacklisting/attribute-updated-moved",
+                   TrackerMonitorTestFixture,
+                   NULL,
+                   test_monitor_common_setup,
+                   test_monitor_file_event_blacklisting_attribute_updated_moved,
+                   test_monitor_common_teardown);
+
+       /* Directory Event tests */
+       g_test_add ("/libtracker-miner/tracker-monitor/directory-event/created",
+                   TrackerMonitorTestFixture,
+                   NULL,
+                   test_monitor_common_setup,
+                   test_monitor_directory_event_created,
+                   test_monitor_common_teardown);
+       g_test_add ("/libtracker-miner/tracker-monitor/directory-event/deleted",
+                   TrackerMonitorTestFixture,
+                   NULL,
+                   test_monitor_common_setup,
+                   test_monitor_directory_event_deleted,
+                   test_monitor_common_teardown);
+       g_test_add ("/libtracker-miner/tracker-monitor/directory-event/moved/to-monitored",
+                   TrackerMonitorTestFixture,
+                   NULL,
+                   test_monitor_common_setup,
+                   test_monitor_directory_event_moved_to_monitored,
+                   test_monitor_common_teardown);
+       g_test_add ("/libtracker-miner/tracker-monitor/directory-event/moved/to-monitored-after-file-create",
+                   TrackerMonitorTestFixture,
+                   NULL,
+                   test_monitor_common_setup,
+                   test_monitor_directory_event_moved_to_monitored_after_file_create,
+                   test_monitor_common_teardown);
+       g_test_add ("/libtracker-miner/tracker-monitor/directory-event/moved/to-monitored-after-file-update",
+                   TrackerMonitorTestFixture,
+                   NULL,
+                   test_monitor_common_setup,
+                   test_monitor_directory_event_moved_to_monitored_after_file_update,
+                   test_monitor_common_teardown);
+       g_test_add ("/libtracker-miner/tracker-monitor/directory-event/moved/to-not-monitored",
+                   TrackerMonitorTestFixture,
+                   NULL,
+                   test_monitor_common_setup,
+                   test_monitor_directory_event_moved_to_not_monitored,
+                   test_monitor_common_teardown);
+       g_test_add ("/libtracker-miner/tracker-monitor/directory-event/moved/from-not-monitored",
+                   TrackerMonitorTestFixture,
+                   NULL,
+                   test_monitor_common_setup,
+                   test_monitor_directory_event_moved_from_not_monitored,
+                   test_monitor_common_teardown);
+
+       return g_test_run ();
+}
diff --git a/tests/libtracker-miner/tracker-priority-queue-test.c 
b/tests/libtracker-miner/tracker-priority-queue-test.c
new file mode 100644
index 000000000..a32d0fb81
--- /dev/null
+++ b/tests/libtracker-miner/tracker-priority-queue-test.c
@@ -0,0 +1,252 @@
+/*
+ * Copyright (C) 2010, Nokia <ivan frade nokia com>
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+ * 02110-1301, USA.
+ */
+
+#include <glib-object.h>
+
+/* NOTE: We're not including tracker-miner.h here because this is private. */
+#include <libtracker-miner/tracker-priority-queue.h>
+
+static void
+test_priority_queue_ref_unref (void)
+{
+        TrackerPriorityQueue *one, *two;
+        
+        one = tracker_priority_queue_new ();
+        two = tracker_priority_queue_ref (one);
+
+        tracker_priority_queue_unref (two);
+        tracker_priority_queue_unref (one);
+}
+
+static void
+test_priority_queue_emptiness (void)
+{
+        TrackerPriorityQueue *one;
+        
+        one = tracker_priority_queue_new ();
+
+        g_assert (tracker_priority_queue_is_empty (one));
+        g_assert_cmpint (tracker_priority_queue_get_length (one), ==, 0);
+
+        tracker_priority_queue_unref (one);
+}
+
+static void
+test_priority_queue_insertion_pop (void)
+{
+        TrackerPriorityQueue *queue;
+        int                   i, priority;
+        gchar                *text, *expected;
+
+        queue = tracker_priority_queue_new ();
+
+        /* Insert in to loops to "mix" priorities in the insertion */
+        for (i = 1; i <= 10; i+=2) {
+                tracker_priority_queue_add (queue, g_strdup_printf ("test content %i", i), i);
+        }
+
+        for (i = 2; i <= 10; i+=2) {
+                tracker_priority_queue_add (queue, g_strdup_printf ("test content %i", i), i);
+        }
+
+        for (i = 1; i <= 10; i++) {
+                expected = g_strdup_printf ("test content %i", i);
+
+                text = (gchar *)tracker_priority_queue_pop (queue, &priority);
+
+                g_assert_cmpint (priority, ==, i);
+                g_assert_cmpstr (text, ==, expected);
+
+                g_free (expected);
+                g_free (text);
+        }
+
+        g_assert (tracker_priority_queue_is_empty (queue));        
+        tracker_priority_queue_unref (queue);
+}
+
+static void
+test_priority_queue_peek (void)
+{
+        TrackerPriorityQueue *queue;
+        gchar                *result;
+        gint                  priority;
+
+        queue = tracker_priority_queue_new ();
+
+        result = tracker_priority_queue_peek (queue, &priority);
+        g_assert (result == NULL);
+
+        tracker_priority_queue_add (queue, g_strdup ("Low prio"), 10);
+        tracker_priority_queue_add (queue, g_strdup ("High prio"), 1);
+
+        result = tracker_priority_queue_peek (queue, &priority);
+        g_assert_cmpint (priority, ==, 1);
+        g_assert_cmpstr (result, ==, "High prio");
+
+        result = tracker_priority_queue_pop (queue, &priority);
+        g_free (result);
+
+        result = tracker_priority_queue_peek (queue, &priority);
+        g_assert_cmpint (priority, ==, 10);
+        g_assert_cmpstr (result, ==, "Low prio");
+
+        result = tracker_priority_queue_pop (queue, &priority);
+        g_free (result);
+
+        tracker_priority_queue_unref (queue);
+}
+
+static void
+test_priority_queue_find (void)
+{
+        TrackerPriorityQueue *queue;
+        gchar *result;
+        int priority;
+
+        queue = tracker_priority_queue_new ();
+        
+        tracker_priority_queue_add (queue, g_strdup ("search me"), 10);
+        tracker_priority_queue_add (queue, g_strdup ("Not me"), 1);
+        tracker_priority_queue_add (queue, g_strdup ("Not me either"), 20);
+
+        result = (gchar *) tracker_priority_queue_find (queue, &priority, g_str_equal, "search me");
+        g_assert_cmpstr (result, !=, NULL);
+        g_assert_cmpint (priority, ==, 10);
+
+        tracker_priority_queue_unref (queue);
+}
+
+static void
+foreach_testing_cb (G_GNUC_UNUSED gpointer data,
+                                  gpointer user_data)
+{
+        gint *counter = (gint *)user_data;
+        (*counter) += 1;
+}
+
+static void
+test_priority_queue_foreach (void)
+{
+        TrackerPriorityQueue *queue;
+        gint                  counter = 0;
+
+        queue = tracker_priority_queue_new ();
+        
+        tracker_priority_queue_add (queue, g_strdup ("x"), 10);
+        tracker_priority_queue_add (queue, g_strdup ("x"), 20);        
+        tracker_priority_queue_add (queue, g_strdup ("x"), 30);
+
+        tracker_priority_queue_foreach (queue, foreach_testing_cb, &counter);
+
+        g_assert_cmpint (counter, ==, 3);
+
+        tracker_priority_queue_unref (queue);
+}
+
+static void
+test_priority_queue_foreach_remove (void)
+{
+        TrackerPriorityQueue *queue;
+        
+        queue = tracker_priority_queue_new ();
+
+        tracker_priority_queue_add (queue, g_strdup ("y"), 1);
+        tracker_priority_queue_add (queue, g_strdup ("x"), 2);
+        tracker_priority_queue_add (queue, g_strdup ("y"), 3);
+        tracker_priority_queue_add (queue, g_strdup ("x"), 4);
+        tracker_priority_queue_add (queue, g_strdup ("y"), 5);
+        g_assert_cmpint (tracker_priority_queue_get_length (queue), ==, 5);
+        
+        tracker_priority_queue_foreach_remove (queue, g_str_equal, "y", g_free);
+        g_assert_cmpint (tracker_priority_queue_get_length (queue), ==, 2);
+
+        tracker_priority_queue_foreach_remove (queue, g_str_equal, "x", g_free);
+        g_assert_cmpint (tracker_priority_queue_get_length (queue), ==, 0);
+
+        tracker_priority_queue_unref (queue);
+}
+
+static void
+test_priority_queue_branches (void)
+{
+
+        /* Few specific testing to improve the branch coverage */
+
+        TrackerPriorityQueue *queue;
+        gchar                *result;
+        gint                  priority;
+
+        queue = tracker_priority_queue_new ();
+
+        /* Removal on empty list */
+        tracker_priority_queue_foreach_remove (queue, g_str_equal, "y", g_free);
+
+
+        /* Insert multiple elements in the same priority */
+        tracker_priority_queue_add (queue, g_strdup ("x"), 5);
+        tracker_priority_queue_add (queue, g_strdup ("y"), 5);
+        tracker_priority_queue_add (queue, g_strdup ("z"), 5);
+
+        g_assert_cmpint (tracker_priority_queue_get_length (queue), ==, 3);
+
+        /* Removal with multiple elements in same priority*/
+        g_assert (tracker_priority_queue_foreach_remove (queue, g_str_equal, "z", g_free));
+        g_assert (tracker_priority_queue_foreach_remove (queue, g_str_equal, "x", g_free));
+        
+
+        /* Pop those elements */
+        result = tracker_priority_queue_pop (queue, &priority);
+        g_assert_cmpint (priority, ==, 5);
+        g_free (result);
+
+        g_assert_cmpint (tracker_priority_queue_get_length (queue), ==, 0);
+        /* Pop on empty queue */
+        result = tracker_priority_queue_pop (queue, &priority);
+        g_assert (result == NULL);
+
+        tracker_priority_queue_unref (queue);
+}
+
+int
+main (int    argc,
+      char **argv)
+{
+       g_test_init (&argc, &argv, NULL);
+
+       g_test_add_func ("/libtracker-miner/tracker-priority-queue/emptiness",
+                        test_priority_queue_emptiness);
+       g_test_add_func ("/libtracker-miner/tracker-priority-queue/ref_unref",
+                        test_priority_queue_ref_unref);
+       g_test_add_func ("/libtracker-miner/tracker-priority-queue/insertion",
+                        test_priority_queue_insertion_pop);
+       g_test_add_func ("/libtracker-miner/tracker-priority-queue/peek",
+                        test_priority_queue_peek);
+       g_test_add_func ("/libtracker-miner/tracker-priority-queue/find",
+                        test_priority_queue_find);
+       g_test_add_func ("/libtracker-miner/tracker-priority-queue/foreach",
+                        test_priority_queue_foreach);
+       g_test_add_func ("/libtracker-miner/tracker-priority-queue/foreach_remove",
+                        test_priority_queue_foreach_remove);
+
+        g_test_add_func ("/libtracker-miner/tracker-priority-queue/branches",
+                         test_priority_queue_branches);
+
+       return g_test_run ();
+}
diff --git a/tests/libtracker-miner/tracker-task-pool-test.c b/tests/libtracker-miner/tracker-task-pool-test.c
new file mode 100644
index 000000000..0ece7c39f
--- /dev/null
+++ b/tests/libtracker-miner/tracker-task-pool-test.c
@@ -0,0 +1,186 @@
+/*
+ * Copyright (C) 2011, Nokia <ivan frade nokia com>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ * Boston, MA  02110-1301, USA.
+ *
+ */
+#include <glib.h>
+
+/* NOTE: We're not including tracker-miner.h here because this is private. */
+#include <libtracker-miner/tracker-task-pool.h>
+
+static void
+test_task_pool_limit_set (void)
+{
+        TrackerTaskPool *pool;
+
+        pool = tracker_task_pool_new (5);
+        g_assert_cmpint (tracker_task_pool_get_limit(pool), ==, 5);
+
+        tracker_task_pool_set_limit (pool, 3);
+        g_assert_cmpint (tracker_task_pool_get_limit(pool), ==, 3);
+
+        g_assert (tracker_task_pool_limit_reached (pool) == FALSE);
+        g_object_unref (pool);
+}
+
+static void
+add_task (TrackerTaskPool *pool,
+          const gchar     *filename,
+          gint             expected_size,
+          gboolean         hit_limit)
+{
+        TrackerTask *task;
+
+        task = tracker_task_new (g_file_new_for_path (filename), NULL, NULL);
+        tracker_task_pool_add (pool, task);
+
+        g_assert_cmpint (tracker_task_pool_get_size (pool), ==, expected_size);
+        g_assert (tracker_task_pool_limit_reached (pool) == hit_limit);
+
+        g_object_unref (tracker_task_get_file (task));
+        tracker_task_unref (task);
+}
+
+static void
+remove_task (TrackerTaskPool *pool,
+             const gchar     *filename,
+             gint             expected_size,
+             gboolean         hit_limit)
+{
+        TrackerTask *task;
+
+        task = tracker_task_new (g_file_new_for_path (filename), NULL, NULL);
+        tracker_task_pool_remove (pool, task);
+
+        g_assert_cmpint (tracker_task_pool_get_size (pool), ==, expected_size);
+        g_assert (hit_limit == tracker_task_pool_limit_reached (pool));
+
+        g_object_unref (tracker_task_get_file (task));
+        tracker_task_unref (task);
+}
+
+static void
+test_task_pool_add_remove (void)
+{
+        TrackerTaskPool *pool;
+
+        pool = tracker_task_pool_new (3);
+
+        /* Additions ... */
+        add_task (pool, "/dev/null", 1, FALSE);
+        add_task (pool, "/dev/null2", 2, FALSE);
+        add_task (pool, "/dev/null3", 3, TRUE);
+
+        /* We can go over the limit */
+        add_task (pool, "/dev/null4", 4, TRUE);
+
+        /* Remove something that doesn't exist */
+        remove_task (pool, "/dev/null/imNotInThePool", 4, TRUE);
+
+        /* Removals ... (in different order)*/
+        remove_task (pool, "/dev/null4", 3, TRUE);
+        remove_task (pool, "/dev/null2", 2, FALSE);
+        remove_task (pool, "/dev/null3", 1, FALSE);
+        remove_task (pool, "/dev/null", 0, FALSE);
+
+        /* Remove in empty queue */
+        remove_task (pool, "/dev/null/random", 0, FALSE);
+
+        g_object_unref (pool);
+}
+
+static void
+test_task_pool_find (void)
+{
+        TrackerTaskPool *pool;
+        TrackerTask *task;
+        GFile *goal;
+
+        pool = tracker_task_pool_new (3);
+
+        add_task (pool, "/dev/null", 1, FALSE);
+        add_task (pool, "/dev/null2", 2, FALSE);
+        add_task (pool, "/dev/null3", 3, TRUE);
+
+        /* Search first, last, in the middle... */
+        goal = g_file_new_for_path ("/dev/null2");
+        task = tracker_task_pool_find (pool, goal);
+        g_assert (task);
+        g_object_unref (goal);
+
+        goal = g_file_new_for_path ("/dev/null");
+        task = tracker_task_pool_find (pool, goal);
+        g_assert (task);
+        g_object_unref (goal);
+
+        goal = g_file_new_for_path ("/dev/null3");
+        task = tracker_task_pool_find (pool, goal);
+        g_assert (task);
+        g_object_unref (goal);
+
+        goal = g_file_new_for_path ("/dev/thisDoesntExists");
+        task = tracker_task_pool_find (pool, goal);
+        g_assert (task == NULL);
+        g_object_unref (goal);
+
+        g_object_unref (pool);
+}
+
+static void
+count_elements_cb (gpointer data,
+                   gpointer user_data)
+{
+        gint *counter = (gint*)user_data;
+        (*counter) += 1;
+}
+
+static void
+test_task_pool_foreach (void)
+{
+        TrackerTaskPool *pool;
+        int counter = 0;
+
+        pool = tracker_task_pool_new (3);
+
+        add_task (pool, "/dev/null", 1, FALSE);
+        add_task (pool, "/dev/null2", 2, FALSE);
+        add_task (pool, "/dev/null3", 3, TRUE);
+
+        tracker_task_pool_foreach (pool, count_elements_cb, &counter);
+
+        g_assert_cmpint (counter, ==, 3);
+}
+
+gint
+main (gint argc, gchar **argv)
+{
+        g_test_init (&argc, &argv, NULL);
+
+        g_test_add_func ("/libtracker-miner/tracker-task-pool/limit_set",
+                         test_task_pool_limit_set);
+
+        g_test_add_func ("/libtracker-miner/tracker-task-pool/add_remove",
+                         test_task_pool_add_remove);
+
+        g_test_add_func ("/libtracker-miner/tracker-task-pool/find",
+                         test_task_pool_find);
+ 
+        g_test_add_func ("/libtracker-miner/tracker-task-pool/foreach",
+                         test_task_pool_foreach);
+
+        return g_test_run ();
+}
diff --git a/tests/libtracker-miner/tracker-thumbnailer-test.c 
b/tests/libtracker-miner/tracker-thumbnailer-test.c
new file mode 100644
index 000000000..9c05a1024
--- /dev/null
+++ b/tests/libtracker-miner/tracker-thumbnailer-test.c
@@ -0,0 +1,156 @@
+/*
+ * Copyright (C) 2010, Nokia <ivan frade nokia com>
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+ * 02110-1301, USA.
+ */
+
+#include <glib.h>
+#include <glib-object.h>
+#include <libtracker-miner/tracker-miner.h>
+#include "thumbnailer-mock.h"
+
+#if 0
+/* port thumbnailer-mock.c to gdbus first */
+
+static void
+test_thumbnailer_init ()
+{
+        g_assert (tracker_thumbnailer_init ());
+
+        tracker_thumbnailer_shutdown ();
+}
+
+static void
+test_thumbnailer_send_empty () 
+{
+        GList *dbus_calls = NULL;
+
+        dbus_mock_call_log_reset ();
+
+        tracker_thumbnailer_init ();
+        tracker_thumbnailer_send ();
+
+        dbus_calls = dbus_mock_call_log_get ();
+        g_assert (dbus_calls == NULL);
+
+        tracker_thumbnailer_shutdown ();
+}
+
+static void
+test_thumbnailer_send_moves ()
+{
+        GList *dbus_calls = NULL;
+        
+        dbus_mock_call_log_reset ();
+
+        tracker_thumbnailer_init ();
+        /* Returns TRUE, but there is no dbus call */
+        g_assert (tracker_thumbnailer_move_add ("file://a.jpeg", "mock/one", "file://b.jpeg"));
+        g_assert (dbus_mock_call_log_get () == NULL);
+
+        /* Returns FALSE, unsupported mime */
+        g_assert (!tracker_thumbnailer_move_add ("file://a.jpeg", "unsupported", "file://b.jpeg"));
+        g_assert (dbus_mock_call_log_get () == NULL);
+
+        tracker_thumbnailer_send ();
+
+        /* One call to "move" method */
+        dbus_calls = dbus_mock_call_log_get ();
+        g_assert_cmpint (g_list_length (dbus_calls), ==, 1);
+        g_assert_cmpstr (dbus_calls->data, ==, "Move");
+
+        tracker_thumbnailer_shutdown ();
+        dbus_mock_call_log_reset ();
+}
+
+static void
+test_thumbnailer_send_removes ()
+{
+        GList *dbus_calls = NULL;
+        
+        dbus_mock_call_log_reset ();
+
+
+        tracker_thumbnailer_init ();
+
+        /* Returns TRUE, but there is no dbus call */
+        g_assert (tracker_thumbnailer_remove_add ("file://a.jpeg", "mock/one"));
+        g_assert (dbus_mock_call_log_get () == NULL);
+
+        /* Returns FALSE, unsupported mime */
+        g_assert (!tracker_thumbnailer_remove_add ("file://a.jpeg", "unsupported"));
+        g_assert (dbus_mock_call_log_get () == NULL);
+
+        tracker_thumbnailer_send ();
+
+        /* One call to "Delete" method */
+        dbus_calls = dbus_mock_call_log_get ();
+        g_assert_cmpint (g_list_length (dbus_calls), ==, 1);
+        g_assert_cmpstr (dbus_calls->data, ==, "Delete");
+
+        tracker_thumbnailer_shutdown ();
+        dbus_mock_call_log_reset ();
+}
+
+static void
+test_thumbnailer_send_cleanup ()
+{
+        GList *dbus_calls = NULL;
+        
+        dbus_mock_call_log_reset ();
+
+        tracker_thumbnailer_init ();
+
+        /* Returns TRUE, and there is a dbus call */
+        g_assert (tracker_thumbnailer_cleanup ("file://tri/lu/ri"));
+
+        /* One call to "Clean" method */
+        dbus_calls = dbus_mock_call_log_get ();
+        g_assert_cmpint (g_list_length (dbus_calls), ==, 1);
+        g_assert_cmpstr (dbus_calls->data, ==, "Cleanup");
+
+        tracker_thumbnailer_shutdown ();
+        dbus_mock_call_log_reset ();
+}
+
+#endif
+
+int
+main (int    argc,
+      char **argv)
+{
+       g_test_init (&argc, &argv, NULL);
+
+       g_test_message ("Testing thumbnailer");
+
+#if 0
+/* port thumbnailer-mock.c to gdbus first */
+
+       g_test_add_func ("/libtracker-miner/tracker-thumbnailer/init",
+                        test_thumbnailer_init);
+       g_test_add_func ("/libtracker-miner/tracker-thumbnailer/send_empty",
+                        test_thumbnailer_send_empty);
+        g_test_add_func ("/libtracker-minter/tracker-thumbnailer/send_moves",
+                         test_thumbnailer_send_moves);
+        g_test_add_func ("/libtracker-minter/tracker-thumbnailer/send_removes",
+                         test_thumbnailer_send_removes);
+        g_test_add_func ("/libtracker-minter/tracker-thumbnailer/send_cleanup",
+                         test_thumbnailer_send_cleanup);
+
+#endif
+
+       return g_test_run ();
+}
diff --git a/tests/meson.build b/tests/meson.build
index 1b9dbea57..b559274ca 100644
--- a/tests/meson.build
+++ b/tests/meson.build
@@ -1,6 +1,7 @@
 subdir('common')
 
 subdir('libtracker-miners-common')
+subdir('libtracker-miner')
 
 if have_tracker_extract
   subdir('libtracker-extract')


[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]