[tracker-miners/wip/carlosg/shuffle-libtracker-miner: 1/116] Restore libtracker-miner in this repo
- From: Carlos Garnacho <carlosg src gnome org>
- To: commits-list gnome org
- Cc:
- Subject: [tracker-miners/wip/carlosg/shuffle-libtracker-miner: 1/116] Restore libtracker-miner in this repo
- Date: Thu, 12 Dec 2019 10:20:59 +0000 (UTC)
commit c97eb0144c76cf60a1fa1ef4f28b5062b972c785
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 | 8 +-
meson_options.txt | 2 +
src/libtracker-miner/.gitignore | 6 +
src/libtracker-miner/COPYING.LIB | 510 ++++
src/libtracker-miner/Makefile-shared-sources.decl | 21 +
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/Makefile.am | 121 +
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 | 68 +
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 | 793 ++++++
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 +
79 files changed, 25439 insertions(+), 2 deletions(-)
---
diff --git a/meson.build b/meson.build
index f896b40ba..f290ddd1b 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.')
@@ -107,6 +106,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 +344,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 +454,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/Makefile-shared-sources.decl
b/src/libtracker-miner/Makefile-shared-sources.decl
new file mode 100644
index 000000000..702885e4e
--- /dev/null
+++ b/src/libtracker-miner/Makefile-shared-sources.decl
@@ -0,0 +1,21 @@
+
+# Includes sources that will be shared with the
+# testers in test/libtracker-miner
+
+libtracker_miner_monitor_sources = \
+ $(top_srcdir)/src/libtracker-miner/tracker-monitor.c
+
+libtracker_miner_monitor_headers = \
+ $(top_srcdir)/src/libtracker-miner/tracker-monitor.h
+
+libtracker_miner_file_system_sources = \
+ $(top_srcdir)/src/libtracker-miner/tracker-file-system.c
+
+libtracker_miner_file_system_headers = \
+ $(top_srcdir)/src/libtracker-miner/tracker-file-system.h
+
+libtracker_miner_crawler_sources = \
+ $(top_srcdir)/src/libtracker-miner/tracker-crawler.c
+
+libtracker_miner_crawler_headers = \
+ $(top_srcdir)/src/libtracker-miner/tracker-crawler.h
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 ".". 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
+ * "org.freedesktop.Tracker1.Miner.Files".
+ *
+ * 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
+ * "/org/freedesktop/Tracker1/Miner/Files".
+ *
+ * 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/Makefile.am b/tests/libtracker-miner/Makefile.am
new file mode 100644
index 000000000..062d261d3
--- /dev/null
+++ b/tests/libtracker-miner/Makefile.am
@@ -0,0 +1,121 @@
+include $(top_srcdir)/Makefile.decl
+
+# Include list of shared sources:
+# Defines:
+# $(libtracker_miner_monitor_sources)
+# $(libtracker_miner_monitor_headers)
+#
+# Headers and sources are split for the tests to build
+# with make distcheck.
+#
+include $(top_srcdir)/src/libtracker-miner/Makefile-shared-sources.decl
+
+noinst_LTLIBRARIES += libtracker-miner-tests.la
+
+noinst_PROGRAMS += $(test_programs)
+
+test_programs = \
+ tracker-crawler-test \
+ tracker-file-enumerator-test \
+ tracker-file-notifier-test \
+ tracker-file-system-test \
+ tracker-thumbnailer-test \
+ tracker-monitor-test \
+ tracker-priority-queue-test \
+ tracker-task-pool-test \
+ tracker-indexing-tree-test
+
+AM_CPPFLAGS = \
+ $(BUILD_VALACFLAGS) \
+ -DTEST \
+ -DLIBEXEC_PATH=\""$(libexecdir)"\" \
+ -DTEST_DATA_DIR=\""$(abs_top_srcdir)/tests/libtracker-miner/data"\" \
+ -DTEST_MINERS_DIR=\""$(abs_top_srcdir)/tests/libtracker-miner/mock-miners"\" \
+ -DTEST_ONTOLOGIES_DIR=\""$(abs_top_srcdir)/src/ontologies/nepomuk"\" \
+ -I$(top_srcdir)/src \
+ -I$(top_builddir)/src \
+ -I$(top_srcdir)/tests/common \
+ $(LIBTRACKER_MINER_CFLAGS)
+
+LDADD = \
+ $(top_builddir)/src/libtracker-miner/libtracker-miner-@TRACKER_API_VERSION@.la \
+ $(top_builddir)/src/libtracker-miner/libtracker-miner-private.la \
+ $(top_builddir)/src/libtracker-sparql-backend/libtracker-sparql-@TRACKER_API_VERSION@.la \
+ $(top_builddir)/src/libtracker-common/libtracker-common.la \
+ $(BUILD_LIBS) \
+ $(LIBTRACKER_MINER_LIBS)
+
+libtracker_miner_tests_la_VALAFLAGS = \
+ --header tracker-miner-mock.h \
+ --pkg glib-2.0 \
+ --pkg gio-2.0 \
+ --pkg gio-unix-2.0 \
+ --pkg posix \
+ $(top_srcdir)/src/libtracker-sparql/tracker-sparql-$(TRACKER_API_VERSION).vapi \
+ $(BUILD_VALAFLAGS)
+
+libtracker_miner_tests_la_SOURCES = \
+ tracker-miner-mock.vala \
+ tracker-connection-mock.vala
+
+libtracker_miner_tests_la_LIBADD = \
+ $(top_builddir)/src/libtracker-data/libtracker-data.la \
+ $(top_builddir)/src/libtracker-common/libtracker-common.la \
+ $(top_builddir)/src/libtracker-sparql-backend/libtracker-sparql-@TRACKER_API_VERSION@.la \
+ $(BUILD_LIBS)
+
+tracker_crawler_test_SOURCES = \
+ $(libtracker_miner_crawler_sources) \
+ $(libtracker_miner_crawler_headers) \
+ tracker-crawler-test.c
+
+tracker_thumbnailer_test_SOURCES = \
+ tracker-thumbnailer-test.c \
+ thumbnailer-mock.c \
+ empty-gobject.c
+
+tracker_monitor_test_SOURCES = \
+ tracker-monitor-test.c
+if !ENABLE_GCOV
+# If gcov is enabled, libtracker-miner exports all symbols and this is not needed.
+tracker_monitor_test_SOURCES += \
+ $(libtracker_miner_monitor_sources)
+endif
+
+tracker_priority_queue_test_SOURCES = \
+ tracker-priority-queue-test.c
+
+tracker_task_pool_test_SOURCES = \
+ tracker-task-pool-test.c
+
+tracker_indexing_tree_test_SOURCES = \
+ tracker-indexing-tree-test.c
+
+tracker_file_system_test_SOURCES = \
+ tracker-file-system-test.c
+
+tracker_file_notifier_test_SOURCES = \
+ $(libtracker_miner_monitor_sources) \
+ tracker-file-notifier-test.c
+
+noinst_HEADERS = \
+ $(libtracker_miner_monitor_headers) \
+ tracker-miner-mock.h \
+ thumbnailer-mock.h \
+ miners-mock.h \
+ empty-gobject.h
+
+BUILT_SOURCES += \
+ $(libtracker_miner_file_system_sources) \
+ $(libtracker_miner_file_system_headers) \
+ libtracker_miner_tests_la_vala.stamp
+
+EXTRA_DIST += \
+ data/empty-dir/.hidden \
+ data/file1 \
+ data/dir/empty-dir/.hidden \
+ data/dir/file1 \
+ data/dir/file2 \
+ meson.build \
+ mock-miners/mock-miner-1.desktop \
+ mock-miners/mock-miner-2.desktop
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..5eb5a41a1
--- /dev/null
+++ b/tests/libtracker-miner/meson.build
@@ -0,0 +1,68 @@
+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()),
+]
+
+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..e17baa1e8
--- /dev/null
+++ b/tests/libtracker-miner/tracker-file-notifier-test.c
@@ -0,0 +1,793 @@
+/*
+ * 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;
+ 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");
+ fixture->connection = tracker_sparql_connection_local_new (0, data_loc, data_loc, NULL, 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]