aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorRasmus Thomsen <oss@cogitri.dev>2020-09-05 14:05:20 +0200
committerRasmus Thomsen <oss@cogitri.dev>2020-09-10 17:05:28 +0000
commit1689a9bbdb1992d7b4db35446d3d3792f3548900 (patch)
tree62fd89d8f19bdda9b5a7196cfd8380109bee1de1
parentc864c6ffd1e6f14dede7f9cc4f381797840855be (diff)
downloadaports-1689a9bbdb1992d7b4db35446d3d3792f3548900.tar.gz
aports-1689a9bbdb1992d7b4db35446d3d3792f3548900.tar.bz2
aports-1689a9bbdb1992d7b4db35446d3d3792f3548900.tar.xz
community/gnome-photos: rebuild against tracker3
-rw-r--r--community/gnome-photos/APKBUILD8
-rw-r--r--community/gnome-photos/tracker3.patch4254
2 files changed, 4259 insertions, 3 deletions
diff --git a/community/gnome-photos/APKBUILD b/community/gnome-photos/APKBUILD
index 41c23e2f8e..dc737bb535 100644
--- a/community/gnome-photos/APKBUILD
+++ b/community/gnome-photos/APKBUILD
@@ -2,7 +2,7 @@
# Maintainer: Rasmus Thomsen <oss@cogitri.dev>
pkgname=gnome-photos
pkgver=3.37.91.1
-pkgrel=0
+pkgrel=1
pkgdesc="Access, organize and share your photos on GNOME"
url="https://wiki.gnome.org/Apps/Photos"
arch="all !s390x !mips !mips64" # Limited by gegl-dev
@@ -13,7 +13,8 @@ makedepends="meson babl-dev cairo-dev gtk+3.0-dev gexiv2-dev glib-dev
libdazzle-dev libgdata-dev libjpeg-turbo-dev libpng-dev tracker-dev
dbus-dev gegl-dev geocode-glib-dev gfbgraph-dev itstool"
subpackages="$pkgname-lang $pkgname-doc"
-source="https://download.gnome.org/sources/gnome-photos/${pkgver%.*.*}/gnome-photos-$pkgver.tar.xz"
+source="https://download.gnome.org/sources/gnome-photos/${pkgver%.*.*}/gnome-photos-$pkgver.tar.xz
+ tracker3.patch"
build() {
abuild-meson \
@@ -30,4 +31,5 @@ package() {
DESTDIR="$pkgdir" meson install --no-rebuild -C output
}
-sha512sums="679562ad5eedc2fbea73fcab8047accc5516b38d88040d5cc9063ba0cadcba50cbb0287952c59394748620f5ec18740c16f3cfe0ea6545e369c6fd911fa91991 gnome-photos-3.37.91.1.tar.xz"
+sha512sums="679562ad5eedc2fbea73fcab8047accc5516b38d88040d5cc9063ba0cadcba50cbb0287952c59394748620f5ec18740c16f3cfe0ea6545e369c6fd911fa91991 gnome-photos-3.37.91.1.tar.xz
+c83dcef4603694bbbf01372187b8e41ee40749097c12a8f13e2e3a512b1038a43377f23fbe71676159277bf0a845094a06d0ddf00b9c2e9741f6590a0630f00c tracker3.patch"
diff --git a/community/gnome-photos/tracker3.patch b/community/gnome-photos/tracker3.patch
new file mode 100644
index 0000000000..406dca30fd
--- /dev/null
+++ b/community/gnome-photos/tracker3.patch
@@ -0,0 +1,4254 @@
+From b4a8de1ef79c94c1c11b730787108f305c962e38 Mon Sep 17 00:00:00 2001
+From: Sam Thursfield <sam@afuera.me.uk>
+Date: Wed, 26 Aug 2020 01:57:46 +0200
+Subject: [PATCH 1/4] photos-tracker-controller: Label unit of timing
+ measurements
+
+Otherwise, it's not clear if these are timings or some kind of ID
+number.
+---
+ src/photos-tracker-controller.c | 4 ++--
+ 1 file changed, 2 insertions(+), 2 deletions(-)
+
+diff --git a/src/photos-tracker-controller.c b/src/photos-tracker-controller.c
+index a02db077..3a62cf2d 100644
+--- a/src/photos-tracker-controller.c
++++ b/src/photos-tracker-controller.c
+@@ -237,7 +237,7 @@ photos_tracker_controller_cursor_next (GObject *source_object, GAsyncResult *res
+ }
+
+ now = g_get_monotonic_time ();
+- photos_debug (PHOTOS_DEBUG_TRACKER, "Query Cursor: %" G_GINT64_FORMAT, (now - priv->last_query_time) / 1000000);
++ photos_debug (PHOTOS_DEBUG_TRACKER, "Query Cursor: %" G_GINT64_FORMAT " seconds", (now - priv->last_query_time) / 1000000);
+
+ photos_item_manager_add_item_for_mode (PHOTOS_ITEM_MANAGER (priv->item_mngr),
+ PHOTOS_TRACKER_CONTROLLER_GET_CLASS (self)->base_item_type,
+@@ -346,7 +346,7 @@ photos_tracker_controller_set_query_status (PhotosTrackerController *self, gbool
+ else
+ {
+ photos_debug (PHOTOS_DEBUG_TRACKER,
+- "Query Elapsed: %" G_GINT64_FORMAT,
++ "Query Elapsed: %" G_GINT64_FORMAT " seconds",
+ (now - priv->last_query_time) / 1000000);
+ priv->last_query_time = 0;
+ }
+--
+GitLab
+
+
+From 7c98a884ba6a26795ea25a8780002ac101036887 Mon Sep 17 00:00:00 2001
+From: Sam Thursfield <sam@afuera.me.uk>
+Date: Wed, 27 May 2020 13:07:58 +0200
+Subject: [PATCH 2/4] Fix build failure due to undefined M_PI constant
+
+The <math.h> header needs to be included.
+
+Previously I suppose libtracker-sparql.h pulled this in.
+---
+ src/photos-utils.c | 1 +
+ 1 file changed, 1 insertion(+)
+
+diff --git a/src/photos-utils.c b/src/photos-utils.c
+index 401f52ba..c638297b 100644
+--- a/src/photos-utils.c
++++ b/src/photos-utils.c
+@@ -30,6 +30,7 @@
+ #include <glib.h>
+ #include <tracker-sparql.h>
+ #include <libgd/gd.h>
++#include <math.h>
+
+ #include "photos-application.h"
+ #include "photos-device-item.h"
+--
+GitLab
+
+
+From 0e4bd7bbc8056743a8ea07ab3713ad543c00d478 Mon Sep 17 00:00:00 2001
+From: Sam Thursfield <sam@afuera.me.uk>
+Date: Sun, 7 Jun 2020 21:53:20 +0200
+Subject: [PATCH 3/4] Generate queries using SPARQL templates
+
+The code to generate SPARQL queries was split across many different
+source files, making it difficult to make big changes. Now the
+queries are written out as templates and we use template substitution
+to build the SPARQL that we send to Tracker.
+---
+ src/meson.build | 5 +
+ src/photos-base-manager.c | 15 ++
+ src/photos-base-manager.h | 4 +
+ src/photos-filterable.c | 8 -
+ src/photos-filterable.h | 3 -
+ src/photos-query-builder.c | 207 +++++++++-----------
+ src/photos-search-type-manager.c | 59 +-----
+ src/photos-search-type.c | 67 +++----
+ src/photos-search-type.h | 6 +-
+ src/photos-sparql-template.c | 187 ++++++++++++++++++
+ src/photos-sparql-template.h | 38 ++++
+ src/photos.gresource.xml | 4 +
+ src/queries/all.sparql.template | 31 +++
+ src/queries/collections.sparql.template | 14 ++
+ src/queries/favorite-photos.sparql.template | 12 ++
+ src/queries/photos.sparql.template | 11 ++
+ 16 files changed, 446 insertions(+), 225 deletions(-)
+ create mode 100644 src/photos-sparql-template.c
+ create mode 100644 src/photos-sparql-template.h
+ create mode 100644 src/queries/all.sparql.template
+ create mode 100644 src/queries/collections.sparql.template
+ create mode 100644 src/queries/favorite-photos.sparql.template
+ create mode 100644 src/queries/photos.sparql.template
+
+diff --git a/src/meson.build b/src/meson.build
+index 9919f0cf..b5b2759c 100644
+--- a/src/meson.build
++++ b/src/meson.build
+@@ -195,6 +195,7 @@ sources = common_sources + files(
+ 'photos-source.c',
+ 'photos-source-manager.c',
+ 'photos-source-notification.c',
++ 'photos-sparql-template.c',
+ 'photos-spinner-box.c',
+ 'photos-thumbnail-factory.c',
+ 'photos-tool.c',
+@@ -290,6 +291,10 @@ resource_data = files(
+ 'photos-selection-toolbar.ui',
+ 'photos-share-dialog.ui',
+ 'photos-zoom-controls.ui',
++ 'queries/all.sparql.template',
++ 'queries/collections.sparql.template',
++ 'queries/favorite-photos.sparql.template',
++ 'queries/photos.sparql.template',
+ )
+
+ sources += gnome.compile_resources(
+diff --git a/src/photos-base-manager.c b/src/photos-base-manager.c
+index d49d19a5..052638d5 100644
+--- a/src/photos-base-manager.c
++++ b/src/photos-base-manager.c
+@@ -250,6 +250,12 @@ photos_base_manager_default_get_where (PhotosBaseManager *self, gint flags)
+ }
+
+
++static PhotosSparqlTemplate *
++photos_base_manager_default_get_sparql_template (PhotosBaseManager *self, gint flags)
++{
++ return NULL;
++}
++
+ static void
+ photos_base_manager_default_remove_object_by_id (PhotosBaseManager *self, const gchar *id)
+ {
+@@ -438,6 +444,7 @@ photos_base_manager_class_init (PhotosBaseManagerClass *class)
+ class->get_object_by_id = photos_base_manager_default_get_object_by_id;
+ class->get_previous_object = photos_base_manager_default_get_previous_object;
+ class->get_where = photos_base_manager_default_get_where;
++ class->get_sparql_template = photos_base_manager_default_get_sparql_template;
+ class->remove_object_by_id = photos_base_manager_default_remove_object_by_id;
+ class->set_active_object = photos_base_manager_default_set_active_object;
+
+@@ -714,6 +721,14 @@ photos_base_manager_get_where (PhotosBaseManager *self, gint flags)
+ }
+
+
++PhotosSparqlTemplate *
++photos_base_manager_get_sparql_template (PhotosBaseManager *self, gint flags)
++{
++ g_return_val_if_fail (PHOTOS_IS_BASE_MANAGER (self), NULL);
++ return PHOTOS_BASE_MANAGER_GET_CLASS (self)->get_sparql_template (self, flags);
++}
++
++
+ void
+ photos_base_manager_process_new_objects (PhotosBaseManager *self, GHashTable *new_objects)
+ {
+diff --git a/src/photos-base-manager.h b/src/photos-base-manager.h
+index 99d203e9..04cfa7db 100644
+--- a/src/photos-base-manager.h
++++ b/src/photos-base-manager.h
+@@ -24,6 +24,7 @@
+ #define PHOTOS_BASE_MANAGER_H
+
+ #include <glib-object.h>
++#include "photos-sparql-template.h"
+
+ G_BEGIN_DECLS
+
+@@ -46,6 +47,7 @@ struct _PhotosBaseManagerClass
+ gchar *(*get_where) (PhotosBaseManager *self, gint flags);
+ void (*remove_object_by_id) (PhotosBaseManager *self, const gchar *id);
+ gboolean (*set_active_object) (PhotosBaseManager *self, GObject *object);
++ PhotosSparqlTemplate *(*get_sparql_template) (PhotosBaseManager *self, gint flags);
+
+ /* signals */
+ void (*active_changed) (PhotosBaseManager *self, GObject *object);
+@@ -80,6 +82,8 @@ const gchar *photos_base_manager_get_title (PhotosBaseMana
+
+ gchar *photos_base_manager_get_where (PhotosBaseManager *self, gint flags);
+
++PhotosSparqlTemplate *photos_base_manager_get_sparql_template (PhotosBaseManager *self, gint flags);
++
+ void photos_base_manager_process_new_objects (PhotosBaseManager *self, GHashTable *new_objects);
+
+ void photos_base_manager_remove_object (PhotosBaseManager *self, GObject *object);
+diff --git a/src/photos-filterable.c b/src/photos-filterable.c
+index 361b1c29..aaa4109c 100644
+--- a/src/photos-filterable.c
++++ b/src/photos-filterable.c
+@@ -69,14 +69,6 @@ photos_filterable_get_id (PhotosFilterable *self)
+ }
+
+
+-gchar *
+-photos_filterable_get_where (PhotosFilterable *self)
+-{
+- g_return_val_if_fail (PHOTOS_IS_FILTERABLE (self), NULL);
+- return PHOTOS_FILTERABLE_GET_IFACE (self)->get_where (self);
+-}
+-
+-
+ gboolean
+ photos_filterable_is_search_criterion (PhotosFilterable *self)
+ {
+diff --git a/src/photos-filterable.h b/src/photos-filterable.h
+index e768bca0..8a3415fc 100644
+--- a/src/photos-filterable.h
++++ b/src/photos-filterable.h
+@@ -37,7 +37,6 @@ struct _PhotosFilterableInterface
+ gboolean (*get_builtin) (PhotosFilterable *self);
+ gchar *(*get_filter) (PhotosFilterable *self);
+ const gchar *(*get_id) (PhotosFilterable *self);
+- gchar *(*get_where) (PhotosFilterable *self);
+ gboolean (*is_search_criterion) (PhotosFilterable *self);
+ };
+
+@@ -47,8 +46,6 @@ gchar *photos_filterable_get_filter (PhotosFilterable *self
+
+ const gchar *photos_filterable_get_id (PhotosFilterable *self);
+
+-gchar *photos_filterable_get_where (PhotosFilterable *self);
+-
+ gboolean photos_filterable_is_search_criterion (PhotosFilterable *self);
+
+ G_END_DECLS
+diff --git a/src/photos-query-builder.c b/src/photos-query-builder.c
+index 6b996811..e0735bde 100644
+--- a/src/photos-query-builder.c
++++ b/src/photos-query-builder.c
+@@ -26,112 +26,76 @@
+ #include <string.h>
+
+ #include "photos-base-manager.h"
++#include "photos-query.h"
+ #include "photos-query-builder.h"
+ #include "photos-search-type.h"
+ #include "photos-source-manager.h"
+ #include "photos-search-match-manager.h"
+ #include "photos-search-type-manager.h"
+
++#define PHOTOS_QUERY_COLLECTIONS_IDENTIFIER "photos:collection:"
++#define PHOTOS_QUERY_LOCAL_COLLECTIONS_IDENTIFIER "photos:collection:local:"
+
+-static gchar *
+-photos_query_builder_filter (PhotosSearchContextState *state, gint flags)
+-{
+- gchar *sparql;
+- g_autofree gchar *src_mngr_filter = NULL;
+- g_autofree gchar *srch_mtch_mngr_filter = NULL;
+- g_autofree gchar *srch_typ_mngr_filter = NULL;
++const gchar *collections_default_filter = \
++ "(fn:starts-with (nao:identifier (?urn), '" PHOTOS_QUERY_COLLECTIONS_IDENTIFIER "')"
++ " || (?urn = nfo:image-category-screenshot))";
+
+- src_mngr_filter = photos_base_manager_get_filter (state->src_mngr, flags);
+- srch_mtch_mngr_filter = photos_base_manager_get_filter (state->srch_mtch_mngr, flags);
+- srch_typ_mngr_filter = photos_base_manager_get_filter (state->srch_typ_mngr, flags);
+
+- sparql = g_strdup_printf ("FILTER (%s && %s && %s)",
+- src_mngr_filter,
+- srch_mtch_mngr_filter,
+- srch_typ_mngr_filter);
+-
+- return sparql;
+-}
++/* This includes mimetype blocklist */
++const gchar *photos_default_filter = \
++ "(nie:mimeType(?urn) != 'image/gif' && nie:mimeType(?urn) != 'image/x-eps')";
+
+
+ static gchar *
+-photos_query_builder_optional (void)
+-{
+- return g_strdup ("OPTIONAL { ?urn nco:creator ?creator . } "
+- "OPTIONAL { ?urn nco:publisher ?publisher . }");
+-}
+-
+-
+-static gchar *
+-photos_query_builder_inner_where (PhotosSearchContextState *state, gboolean global, gint flags)
++photos_query_builder_query (PhotosSearchContextState *state,
++ gboolean global,
++ gint flags,
++ PhotosOffsetController *offset_cntrlr)
+ {
+- g_autofree gchar *item_mngr_where = NULL;
++ PhotosSparqlTemplate *template;
++ const gchar *projection = NULL;
++ g_autofree gchar *item_pattern = NULL;
++ g_autofree gchar *search_filter = NULL;
++ g_autofree gchar *source_filter = NULL;
++ const gchar *order = NULL;
++ g_autofree gchar *offset_limit = NULL;
+ gchar *sparql;
+- g_autofree gchar *srch_typ_mngr_where = NULL;
+
+- srch_typ_mngr_where = photos_base_manager_get_where (state->srch_typ_mngr, flags);
++ template = photos_base_manager_get_sparql_template (state->srch_typ_mngr, flags);
++
++ projection = "?urn "
++ "nie:url (?urn) "
++ "nfo:fileName (?urn) "
++ "nie:mimeType (?urn) "
++ "nie:title (?urn) "
++ "tracker:coalesce (nco:fullname (?creator), nco:fullname (?publisher), '') "
++ "tracker:coalesce (nfo:fileLastModified (?urn), nie:contentLastModified (?urn)) AS ?mtime "
++ "nao:identifier (?urn) "
++ "rdf:type (?urn) "
++ "nie:dataSource(?urn) "
++ "( EXISTS { ?urn nao:hasTag nao:predefined-tag-favorite } ) "
++ "( EXISTS { ?urn nco:contributor ?contributor FILTER ( ?contributor != ?creator ) } ) "
++ "tracker:coalesce(nfo:fileCreated (?urn), nie:contentCreated (?urn)) "
++ "nfo:width (?urn) "
++ "nfo:height (?urn) "
++ "nfo:equipment (?urn) "
++ "nfo:orientation (?urn) "
++ "nmm:exposureTime (?urn) "
++ "nmm:fnumber (?urn) "
++ "nmm:focalLength (?urn) "
++ "nmm:isoSpeed (?urn) "
++ "nmm:flash (?urn) "
++ "slo:location (?urn) ";
++
++ item_pattern = photos_base_manager_get_where (state->item_mngr, flags);
+
+ if (!(flags & PHOTOS_QUERY_FLAGS_UNFILTERED))
+ {
+- if (global)
+- {
+- /* TODO: SearchCategoryManager */
+-
+- item_mngr_where = photos_base_manager_get_where (state->item_mngr, flags);
+- }
++ source_filter = photos_base_manager_get_filter (state->src_mngr, flags);
++ search_filter = photos_base_manager_get_filter (state->srch_mtch_mngr, flags);
+ }
+
+- sparql = g_strdup_printf ("WHERE { %s %s }",
+- srch_typ_mngr_where,
+- (item_mngr_where != NULL) ? item_mngr_where : "");
+-
+- return sparql;
+-}
+-
+-
+-static gchar *
+-photos_query_builder_where (PhotosSearchContextState *state, gboolean global, gint flags)
+-{
+- const gchar *count_items = "COUNT (?item) AS ?count";
+- gboolean item_defined;
+- g_autofree gchar *filter = NULL;
+- g_autofree gchar *optional = NULL;
+- gchar *sparql;
+- g_autofree gchar *where_sparql = NULL;
+-
+- where_sparql = photos_query_builder_inner_where (state, global, flags);
+- item_defined = strstr (where_sparql, "?item") != NULL;
+-
+- optional = photos_query_builder_optional ();
+-
+- if (!(flags & PHOTOS_QUERY_FLAGS_UNFILTERED))
+- filter = photos_query_builder_filter (state, flags);
+-
+- sparql = g_strdup_printf ("WHERE {{"
+- " SELECT ?urn rdf:type (?urn) AS ?type %s %s GROUP BY (?urn)"
+- " }"
+- " %s %s"
+- "}",
+- item_defined ? count_items : "",
+- where_sparql,
+- optional,
+- (filter != NULL) ? filter : "");
+-
+- return sparql;
+-}
+-
+-
+-static gchar *
+-photos_query_builder_query (PhotosSearchContextState *state,
+- gboolean global,
+- gint flags,
+- PhotosOffsetController *offset_cntrlr)
+-{
+- gchar *sparql;
+- g_autofree gchar *tail_sparql = NULL;
+- g_autofree gchar *where_sparql = NULL;
+-
+- where_sparql = photos_query_builder_where (state, global, flags);
++ order = "ORDER BY DESC (?mtime)";
+
+ if (global && (flags & PHOTOS_QUERY_FLAGS_UNLIMITED) == 0)
+ {
+@@ -144,35 +108,19 @@ photos_query_builder_query (PhotosSearchContextState *state,
+ step = photos_offset_controller_get_step (offset_cntrlr);
+ }
+
+- tail_sparql = g_strdup_printf ("ORDER BY DESC (?mtime) LIMIT %d OFFSET %d", step, offset);
++ offset_limit = g_strdup_printf ("LIMIT %d OFFSET %d", step, offset);
+ }
+
+- sparql = g_strconcat ("SELECT ?urn "
+- "nie:url (?urn) "
+- "nfo:fileName (?urn) "
+- "nie:mimeType (?urn) "
+- "nie:title (?urn) "
+- "tracker:coalesce (nco:fullname (?creator), nco:fullname (?publisher), '') "
+- "tracker:coalesce (nfo:fileLastModified (?urn), nie:contentLastModified (?urn)) AS ?mtime "
+- "nao:identifier (?urn) "
+- "rdf:type (?urn) "
+- "nie:dataSource(?urn) "
+- "( EXISTS { ?urn nao:hasTag nao:predefined-tag-favorite } ) "
+- "( EXISTS { ?urn nco:contributor ?contributor FILTER ( ?contributor != ?creator ) } ) "
+- "tracker:coalesce(nfo:fileCreated (?urn), nie:contentCreated (?urn)) "
+- "nfo:width (?urn) "
+- "nfo:height (?urn) "
+- "nfo:equipment (?urn) "
+- "nfo:orientation (?urn) "
+- "nmm:exposureTime (?urn) "
+- "nmm:fnumber (?urn) "
+- "nmm:focalLength (?urn) "
+- "nmm:isoSpeed (?urn) "
+- "nmm:flash (?urn) "
+- "slo:location (?urn) ",
+- where_sparql,
+- tail_sparql,
+- NULL);
++ sparql = photos_sparql_template_get_sparql (template,
++ "projection", projection,
++ "collections_default_filter", collections_default_filter,
++ "item_pattern", item_pattern,
++ "photos_default_filter", photos_default_filter,
++ "source_filter", source_filter ? source_filter : "",
++ "search_filter", search_filter ? search_filter : "",
++ "order", order,
++ "offset_limit", offset_limit ? offset_limit : "",
++ NULL);
+
+ return sparql;
+ }
+@@ -231,12 +179,37 @@ photos_query_builder_collection_icon_query (PhotosSearchContextState *state, con
+ PhotosQuery *
+ photos_query_builder_count_query (PhotosSearchContextState *state, gint flags)
+ {
+- PhotosQuery *query;
++ PhotosSparqlTemplate *template;
++ const gchar *projection = NULL;
++ g_autofree gchar *item_pattern = NULL;
++ g_autofree gchar *search_filter = NULL;
++ g_autofree gchar *source_filter = NULL;
+ g_autofree gchar *sparql = NULL;
+- g_autofree gchar *where_sparql = NULL;
++ PhotosQuery *query;
++
++ template = photos_base_manager_get_sparql_template (state->srch_typ_mngr, flags);
++
++ projection = "COUNT(?urn) ";
++
++ item_pattern = photos_base_manager_get_where (state->item_mngr, flags);
++
++ if (! (flags & PHOTOS_QUERY_FLAGS_UNFILTERED))
++ {
++ source_filter = photos_base_manager_get_filter (state->src_mngr, flags);
++ search_filter = photos_base_manager_get_filter (state->srch_mtch_mngr, flags);
++ }
++
++ sparql = photos_sparql_template_get_sparql (template,
++ "projection", projection,
++ "collections_default_filter", collections_default_filter,
++ "item_pattern", item_pattern,
++ "photos_default_filter", photos_default_filter,
++ "source_filter", source_filter ? source_filter : "",
++ "search_filter", search_filter ? search_filter : "",
++ "order", "",
++ "offset_limit", "",
++ NULL);
+
+- where_sparql = photos_query_builder_where (state, TRUE, flags);
+- sparql = g_strconcat ("SELECT DISTINCT COUNT(?urn) ", where_sparql, NULL);
+ query = photos_query_new (state, sparql);
+
+ return query;
+diff --git a/src/photos-search-type-manager.c b/src/photos-search-type-manager.c
+index 87f441ed..5c87d4a4 100644
+--- a/src/photos-search-type-manager.c
++++ b/src/photos-search-type-manager.c
+@@ -41,13 +41,6 @@ struct _PhotosSearchTypeManager
+ G_DEFINE_TYPE (PhotosSearchTypeManager, photos_search_type_manager, PHOTOS_TYPE_BASE_MANAGER);
+
+
+-static const gchar *BLACKLISTED_MIME_TYPES[] =
+-{
+- "image/gif",
+- "image/x-eps"
+-};
+-
+-
+ static gchar *
+ photos_search_type_manager_get_filter (PhotosBaseManager *mngr, gint flags)
+ {
+@@ -69,9 +62,8 @@ photos_search_type_manager_get_filter (PhotosBaseManager *mngr, gint flags)
+ return filter;
+ }
+
+-
+-static gchar *
+-photos_search_type_manager_get_where (PhotosBaseManager *mngr, gint flags)
++static PhotosSparqlTemplate *
++photos_search_type_manager_get_sparql_template (PhotosBaseManager *mngr, gint flags)
+ {
+ GObject *search_type;
+
+@@ -86,74 +78,39 @@ photos_search_type_manager_get_where (PhotosBaseManager *mngr, gint flags)
+ else
+ search_type = photos_base_manager_get_object_by_id (mngr, PHOTOS_SEARCH_TYPE_STOCK_ALL);
+
+- return photos_filterable_get_where (PHOTOS_FILTERABLE (search_type));
++ return photos_search_type_get_sparql_template (PHOTOS_SEARCH_TYPE (search_type));
+ }
+
+-
+ static void
+ photos_search_type_manager_init (PhotosSearchTypeManager *self)
+ {
+ PhotosSearchType *search_type;
+- gchar *item_filter;
+- gchar *all_filter;
+- gchar *blacklisted_mime_types_filter;
+- gchar *col_filter;
+- gchar **strv;
+- guint i;
+- guint n_elements;
+-
+- n_elements = G_N_ELEMENTS (BLACKLISTED_MIME_TYPES);
+- strv = (gchar **) g_malloc0_n (n_elements + 1, sizeof (gchar *));
+- for (i = 0; i < n_elements; i++)
+- strv[i] = g_strdup_printf ("nie:mimeType(?urn) != '%s'", BLACKLISTED_MIME_TYPES[i]);
+-
+- blacklisted_mime_types_filter = g_strjoinv (" && ", strv);
+-
+- item_filter = g_strdup_printf ("(fn:contains (?type, 'nmm#Photo') && %s)", blacklisted_mime_types_filter);
+- col_filter = g_strdup_printf ("(fn:contains (?type, 'nfo#DataContainer')"
+- " && ?count > 0"
+- " && (fn:starts-with (nao:identifier (?urn), '%s')"
+- " || (?urn = nfo:image-category-screenshot)))",
+- PHOTOS_QUERY_COLLECTIONS_IDENTIFIER);
+- all_filter = g_strdup_printf ("(%s || %s)", col_filter, item_filter);
+
+ search_type = photos_search_type_new_full (PHOTOS_SEARCH_TYPE_STOCK_ALL,
+ _("All"),
+- "?urn a rdfs:Resource. "
+- "OPTIONAL {?item a nmm:Photo; nie:isPartOf ?urn}",
+- all_filter);
++ "resource:///org/gnome/Photos/all.sparql.template");
+ photos_base_manager_add_object (PHOTOS_BASE_MANAGER (self), G_OBJECT (search_type));
+ g_object_unref (search_type);
+
+ search_type = photos_search_type_new_full (PHOTOS_SEARCH_TYPE_STOCK_COLLECTIONS,
+ _("Albums"),
+- "?urn a nfo:DataContainer. "
+- "?item a nmm:Photo; nie:isPartOf ?urn.",
+- col_filter);
++ "resource:///org/gnome/Photos/collections.sparql.template");
+ photos_base_manager_add_object (PHOTOS_BASE_MANAGER (self), G_OBJECT (search_type));
+ g_object_unref (search_type);
+
+ search_type = photos_search_type_new_full (PHOTOS_SEARCH_TYPE_STOCK_FAVORITES,
+ _("Favorites"),
+- "?urn a nmm:Photo; nao:hasTag nao:predefined-tag-favorite. ",
+- blacklisted_mime_types_filter);
++ "resource:///org/gnome/Photos/favorite-photos.sparql.template");
+ photos_base_manager_add_object (PHOTOS_BASE_MANAGER (self), G_OBJECT (search_type));
+ g_object_unref (search_type);
+
+ search_type = photos_search_type_new_full (PHOTOS_SEARCH_TYPE_STOCK_PHOTOS,
+ _("Photos"),
+- "?urn a nmm:Photo",
+- blacklisted_mime_types_filter);
++ "resource:///org/gnome/Photos/photos.sparql.template");
+ photos_base_manager_add_object (PHOTOS_BASE_MANAGER (self), G_OBJECT (search_type));
+ g_object_unref (search_type);
+
+ photos_base_manager_set_active_object_by_id (PHOTOS_BASE_MANAGER (self), PHOTOS_SEARCH_TYPE_STOCK_ALL);
+-
+- g_free (item_filter);
+- g_free (all_filter);
+- g_free (blacklisted_mime_types_filter);
+- g_free (col_filter);
+- g_strfreev (strv);
+ }
+
+
+@@ -163,7 +120,7 @@ photos_search_type_manager_class_init (PhotosSearchTypeManagerClass *class)
+ PhotosBaseManagerClass *base_manager_class = PHOTOS_BASE_MANAGER_CLASS (class);
+
+ base_manager_class->get_filter = photos_search_type_manager_get_filter;
+- base_manager_class->get_where = photos_search_type_manager_get_where;
++ base_manager_class->get_sparql_template = photos_search_type_manager_get_sparql_template;
+ }
+
+
+diff --git a/src/photos-search-type.c b/src/photos-search-type.c
+index 44dc60eb..f3bbae15 100644
+--- a/src/photos-search-type.c
++++ b/src/photos-search-type.c
+@@ -25,24 +25,23 @@
+
+ #include "photos-filterable.h"
+ #include "photos-search-type.h"
++#include "photos-sparql-template.h"
+
+
+ struct _PhotosSearchType
+ {
+ GObject parent_instance;
+- gchar *filter;
+ gchar *id;
+ gchar *name;
+- gchar *where;
++ PhotosSparqlTemplate *sparql_template;
+ };
+
+ enum
+ {
+ PROP_0,
+- PROP_FILTER,
+ PROP_ID,
+ PROP_NAME,
+- PROP_WHERE,
++ PROP_SPARQL_TEMPLATE,
+ };
+
+ static void photos_search_type_filterable_iface_init (PhotosFilterableInterface *iface);
+@@ -53,14 +52,6 @@ G_DEFINE_TYPE_WITH_CODE (PhotosSearchType, photos_search_type, G_TYPE_OBJECT,
+ photos_search_type_filterable_iface_init));
+
+
+-static gchar *
+-photos_search_type_get_filter (PhotosFilterable *iface)
+-{
+- PhotosSearchType *self = PHOTOS_SEARCH_TYPE (iface);
+- return g_strdup (self->filter);
+-}
+-
+-
+ static const gchar *
+ photos_search_type_get_id (PhotosFilterable *filterable)
+ {
+@@ -69,14 +60,6 @@ photos_search_type_get_id (PhotosFilterable *filterable)
+ }
+
+
+-static gchar *
+-photos_search_type_get_where (PhotosFilterable *iface)
+-{
+- PhotosSearchType *self = PHOTOS_SEARCH_TYPE (iface);
+- return g_strdup (self->where);
+-}
+-
+-
+ static gboolean
+ photos_search_type_is_search_criterion (PhotosFilterable *iface)
+ {
+@@ -84,15 +67,20 @@ photos_search_type_is_search_criterion (PhotosFilterable *iface)
+ }
+
+
++PhotosSparqlTemplate *
++photos_search_type_get_sparql_template (PhotosSearchType *self)
++{
++ return self->sparql_template;
++}
++
+ static void
+ photos_search_type_finalize (GObject *object)
+ {
+ PhotosSearchType *self = PHOTOS_SEARCH_TYPE (object);
+
+- g_free (self->filter);
+ g_free (self->id);
+ g_free (self->name);
+- g_free (self->where);
++ g_clear_object (&self->sparql_template);
+
+ G_OBJECT_CLASS (photos_search_type_parent_class)->finalize (object);
+ }
+@@ -113,6 +101,10 @@ photos_search_type_get_property (GObject *object, guint prop_id, GValue *value,
+ g_value_set_string (value, self->name);
+ break;
+
++ case PROP_SPARQL_TEMPLATE:
++ g_value_set_object (value, self->sparql_template);
++ break;
++
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ break;
+@@ -127,10 +119,6 @@ photos_search_type_set_property (GObject *object, guint prop_id, const GValue *v
+
+ switch (prop_id)
+ {
+- case PROP_FILTER:
+- self->filter = g_value_dup_string (value);
+- break;
+-
+ case PROP_ID:
+ self->id = g_value_dup_string (value);
+ break;
+@@ -139,8 +127,8 @@ photos_search_type_set_property (GObject *object, guint prop_id, const GValue *v
+ self->name = g_value_dup_string (value);
+ break;
+
+- case PROP_WHERE:
+- self->where = g_value_dup_string (value);
++ case PROP_SPARQL_TEMPLATE:
++ self->sparql_template = g_object_ref (g_value_get_object (value));
+ break;
+
+ default:
+@@ -165,14 +153,6 @@ photos_search_type_class_init (PhotosSearchTypeClass *class)
+ object_class->get_property = photos_search_type_get_property;
+ object_class->set_property = photos_search_type_set_property;
+
+- g_object_class_install_property (object_class,
+- PROP_FILTER,
+- g_param_spec_string ("filter",
+- "",
+- "",
+- "(true)",
+- G_PARAM_CONSTRUCT_ONLY | G_PARAM_WRITABLE));
+-
+ g_object_class_install_property (object_class,
+ PROP_ID,
+ g_param_spec_string ("id",
+@@ -190,11 +170,11 @@ photos_search_type_class_init (PhotosSearchTypeClass *class)
+ G_PARAM_CONSTRUCT_ONLY | G_PARAM_READWRITE));
+
+ g_object_class_install_property (object_class,
+- PROP_WHERE,
+- g_param_spec_string ("where",
+- "",
++ PROP_SPARQL_TEMPLATE,
++ g_param_spec_object ("sparql-template",
+ "",
+ "",
++ PHOTOS_TYPE_SPARQL_TEMPLATE,
+ G_PARAM_CONSTRUCT_ONLY | G_PARAM_WRITABLE));
+ }
+
+@@ -202,9 +182,7 @@ photos_search_type_class_init (PhotosSearchTypeClass *class)
+ static void
+ photos_search_type_filterable_iface_init (PhotosFilterableInterface *iface)
+ {
+- iface->get_filter = photos_search_type_get_filter;
+ iface->get_id = photos_search_type_get_id;
+- iface->get_where = photos_search_type_get_where;
+ iface->is_search_criterion = photos_search_type_is_search_criterion;
+ }
+
+@@ -217,12 +195,13 @@ photos_search_type_new (const gchar *id, const gchar *name)
+
+
+ PhotosSearchType *
+-photos_search_type_new_full (const gchar *id, const gchar *name, const gchar *where, const gchar *filter)
++photos_search_type_new_full (const gchar *id, const gchar *name, const gchar *template_path)
+ {
++ g_autoptr (PhotosSparqlTemplate) template = photos_sparql_template_new (template_path);
++
+ return g_object_new (PHOTOS_TYPE_SEARCH_TYPE,
+ "id", id,
+ "name", name,
+- "filter", filter,
+- "where", where,
++ "sparql-template", template,
+ NULL);
+ }
+diff --git a/src/photos-search-type.h b/src/photos-search-type.h
+index 2f7135bd..7d699dd4 100644
+--- a/src/photos-search-type.h
++++ b/src/photos-search-type.h
+@@ -24,6 +24,7 @@
+ #define PHOTOS_SEARCH_TYPE_H
+
+ #include <glib-object.h>
++#include "photos-sparql-template.h"
+
+ G_BEGIN_DECLS
+
+@@ -39,8 +40,9 @@ PhotosSearchType *photos_search_type_new (const gchar *id, con
+
+ PhotosSearchType *photos_search_type_new_full (const gchar *id,
+ const gchar *name,
+- const gchar *where,
+- const gchar *filter);
++ const gchar *template_path);
++
++PhotosSparqlTemplate *photos_search_type_get_sparql_template (PhotosSearchType *self);
+
+ G_END_DECLS
+
+diff --git a/src/photos-sparql-template.c b/src/photos-sparql-template.c
+new file mode 100644
+index 00000000..b32437c0
+--- /dev/null
++++ b/src/photos-sparql-template.c
+@@ -0,0 +1,187 @@
++/*
++ * Photos - access, organize and share your photos on GNOME
++ * Copyright © 2020 Sam Thursfield <sam@afuera.me.uk>
++ *
++ * 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 3 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, see <http://www.gnu.org/licenses/>.
++ */
++
++#include <gio/gio.h>
++
++#include "photos-sparql-template.h"
++
++#define MAX_SPARQL_TEMPLATE_SIZE (1024 * 10)
++
++struct _PhotosSparqlTemplate {
++ GObject parent_instance;
++ gchar *template_path;
++ gchar *template_text;
++};
++
++G_DEFINE_TYPE (PhotosSparqlTemplate, photos_sparql_template, G_TYPE_OBJECT)
++
++enum {
++ PROP_0,
++ PROP_TEMPLATE_PATH,
++ N_PROPS
++};
++
++PhotosSparqlTemplate *
++photos_sparql_template_new (const gchar *template_path)
++{
++ return g_object_new (PHOTOS_TYPE_SPARQL_TEMPLATE, "template-path", template_path, NULL);
++}
++
++static void
++photos_sparql_template_constructed (GObject *object)
++{
++ PhotosSparqlTemplate *self = PHOTOS_SPARQL_TEMPLATE (object);
++ g_autoptr (GFile) file = NULL;
++ g_autoptr (GFileInputStream) stream = NULL;
++ gchar buffer[MAX_SPARQL_TEMPLATE_SIZE + 1];
++ gsize bytes_read;
++ g_autoptr (GError) error = NULL;
++
++ G_OBJECT_CLASS (photos_sparql_template_parent_class)->constructed (object);
++
++ file = g_file_new_for_uri (self->template_path);
++
++ stream = g_file_read (file, NULL, &error);
++
++ if (!stream)
++ {
++ g_critical ("Failed to open template %s: %s", self->template_path, error->message);
++ }
++
++ g_input_stream_read_all (G_INPUT_STREAM (stream), buffer, MAX_SPARQL_TEMPLATE_SIZE, &bytes_read, NULL, &error);
++
++ if (error)
++ {
++ g_critical ("Failed to read template %s: %s", self->template_path, error->message);
++ }
++
++ buffer[bytes_read] = '\0';
++
++ self->template_text = g_strdup (buffer);
++}
++
++static void
++photos_sparql_template_finalize (GObject *object)
++{
++ G_OBJECT_CLASS (photos_sparql_template_parent_class)->finalize (object);
++}
++
++static void
++photos_sparql_template_get_property (GObject *object,
++ guint prop_id,
++ GValue *value,
++ GParamSpec *pspec)
++{
++ PhotosSparqlTemplate *self = PHOTOS_SPARQL_TEMPLATE (object);
++
++ switch (prop_id)
++ {
++ case PROP_TEMPLATE_PATH:
++ g_value_set_string (value, self->template_path);
++ break;
++
++ default:
++ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
++ }
++}
++
++static void
++photos_sparql_template_set_property (GObject *object,
++ guint prop_id,
++ const GValue *value,
++ GParamSpec *pspec)
++{
++ PhotosSparqlTemplate *self = PHOTOS_SPARQL_TEMPLATE (object);
++
++ switch (prop_id)
++ {
++ case PROP_TEMPLATE_PATH:
++ self->template_path = g_value_dup_string (value);
++ break;
++
++ default:
++ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
++ }
++}
++
++static void
++photos_sparql_template_class_init (PhotosSparqlTemplateClass *klass)
++{
++ GObjectClass *object_class = G_OBJECT_CLASS (klass);
++
++ object_class->constructed = photos_sparql_template_constructed;
++ object_class->finalize = photos_sparql_template_finalize;
++ object_class->get_property = photos_sparql_template_get_property;
++ object_class->set_property = photos_sparql_template_set_property;
++
++ g_object_class_install_property (object_class,
++ PROP_TEMPLATE_PATH,
++ g_param_spec_string ("template-path",
++ "Template path",
++ "Path to the template file.",
++ NULL,
++ G_PARAM_CONSTRUCT_ONLY | G_PARAM_READWRITE));
++}
++
++static void
++photos_sparql_template_init (PhotosSparqlTemplate *self)
++{
++}
++
++
++gchar *
++photos_sparql_template_get_sparql (PhotosSparqlTemplate *self,
++ const gchar *first_binding_name, ...)
++{
++ va_list va;
++ gchar *sparql;
++
++ sparql = self->template_text;
++
++ /* FIXME: this is an inefficent way to do template substitutions
++ * because we allocate and free a copy of the string for each binding.
++ * We should check https://gitlab.gnome.org/GNOME/template-glib/
++ */
++ if (first_binding_name)
++ {
++ GRegex *regex;
++ gchar *name_regex;
++ const gchar *name;
++ const gchar *value;
++
++ va_start (va, first_binding_name);
++ name = first_binding_name;
++ do {
++ value = va_arg (va, const gchar *);
++
++ if (value == NULL)
++ {
++ g_critical ("Missing value for argument \"%s\"", name);
++ break;
++ }
++
++ name_regex = g_strdup_printf ("{{\\s?%s\\s?}}", name);
++ regex = g_regex_new (name_regex, 0, 0, NULL);
++ sparql = g_regex_replace_literal (regex, sparql, -1, 0, value, 0, NULL) ;
++ } while ((name = va_arg (va, const gchar *)));
++
++ va_end (va);
++ }
++
++ return sparql;
++}
+diff --git a/src/photos-sparql-template.h b/src/photos-sparql-template.h
+new file mode 100644
+index 00000000..66351b38
+--- /dev/null
++++ b/src/photos-sparql-template.h
+@@ -0,0 +1,38 @@
++/*
++ * Photos - access, organize and share your photos on GNOME
++ * Copyright © 2020 Sam Thursfield <sam@afuera.me.uk>
++ *
++ * 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 3 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, see <http://www.gnu.org/licenses/>.
++ */
++
++#pragma once
++
++#include <glib-object.h>
++
++G_BEGIN_DECLS
++
++#define PHOTOS_TYPE_SPARQL_TEMPLATE (photos_sparql_template_get_type())
++
++G_DECLARE_FINAL_TYPE (PhotosSparqlTemplate, photos_sparql_template, PHOTOS, SPARQL_TEMPLATE, GObject)
++
++struct _PhotosSparqlTemplateClass
++{
++ GObjectClass parent_class;
++};
++
++PhotosSparqlTemplate *photos_sparql_template_new (const gchar *template_path);
++
++gchar *photos_sparql_template_get_sparql (PhotosSparqlTemplate *self, const gchar *first_binding_name, ...) G_GNUC_NULL_TERMINATED;
++
++G_END_DECLS
+diff --git a/src/photos.gresource.xml b/src/photos.gresource.xml
+index 1015b11d..39e6d842 100644
+--- a/src/photos.gresource.xml
++++ b/src/photos.gresource.xml
+@@ -35,6 +35,10 @@
+ <file alias="selection-toolbar.ui" preprocess="xml-stripblanks" compressed="true">photos-selection-toolbar.ui</file>
+ <file alias="share-dialog.ui" preprocess="xml-stripblanks" compressed="true">photos-share-dialog.ui</file>
+ <file alias="zoom-controls.ui" preprocess="xml-stripblanks" compressed="true">photos-zoom-controls.ui</file>
++ <file alias="all.sparql.template">queries/all.sparql.template</file>
++ <file alias="collections.sparql.template">queries/collections.sparql.template</file>
++ <file alias="favorite-photos.sparql.template">queries/favorite-photos.sparql.template</file>
++ <file alias="photos.sparql.template">queries/photos.sparql.template</file>
+ </gresource>
+
+ <gresource prefix="/org/gnome/Photos/gtk">
+diff --git a/src/queries/all.sparql.template b/src/queries/all.sparql.template
+new file mode 100644
+index 00000000..1cef98e8
+--- /dev/null
++++ b/src/queries/all.sparql.template
+@@ -0,0 +1,31 @@
++SELECT {{projection}}
++{
++ {
++ SELECT {{projection}}
++ {
++ {
++ SELECT ?urn COUNT(?item) AS ?count
++ {
++ ?urn a nfo:DataContainer.
++ ?item a nmm:Photo; nie:isPartOf ?urn.
++ } GROUP BY ?urn
++ }
++ FILTER (?count > 0 && {{collections_default_filter}} && {{search_filter}})
++ }
++ GROUP BY ?urn
++ }
++ UNION
++ {
++ SELECT {{projection}}
++ {
++ ?urn a nmm:Photo .
++ OPTIONAL { ?urn nco:creator ?creator . }
++ OPTIONAL { ?urn nco:publisher ?publisher . }
++ {{item_pattern}}
++ FILTER ({{photos_default_filter}} && {{source_filter}} && {{search_filter}})
++ }
++ GROUP BY ?urn
++ }
++}
++{{order}}
++{{offset_limit}}
+diff --git a/src/queries/collections.sparql.template b/src/queries/collections.sparql.template
+new file mode 100644
+index 00000000..20b35cd6
+--- /dev/null
++++ b/src/queries/collections.sparql.template
+@@ -0,0 +1,14 @@
++SELECT {{projection}}
++{
++ {
++ SELECT ?urn COUNT(?item) AS ?count
++ {
++ ?urn a nfo:DataContainer.
++ ?item a nmm:Photo; nie:isPartOf ?urn.
++ } GROUP BY ?urn
++ }
++ FILTER (?count > 0 && {{collections_default_filter}} && {{search_filter}})
++}
++GROUP BY ?urn
++{{order}}
++{{offset_limit}}
+diff --git a/src/queries/favorite-photos.sparql.template b/src/queries/favorite-photos.sparql.template
+new file mode 100644
+index 00000000..0885a08a
+--- /dev/null
++++ b/src/queries/favorite-photos.sparql.template
+@@ -0,0 +1,12 @@
++SELECT {{projection}}
++{
++ ?urn a nmm:Photo .
++ ?urn a nmm:Photo; nao:hasTag nao:predefined-tag-favorite .
++ OPTIONAL { ?urn nco:creator ?creator . }
++ OPTIONAL { ?urn nco:publisher ?publisher . }
++ {{item_pattern}}
++ FILTER ({{photos_default_filter}} && {{source_filter}} && {{search_filter}})
++}
++GROUP BY ?urn
++{{order}}
++{{offset_limit}}
+diff --git a/src/queries/photos.sparql.template b/src/queries/photos.sparql.template
+new file mode 100644
+index 00000000..4eb10b74
+--- /dev/null
++++ b/src/queries/photos.sparql.template
+@@ -0,0 +1,11 @@
++SELECT {{projection}}
++{
++ ?urn a nmm:Photo .
++ OPTIONAL { ?urn nco:creator ?creator . }
++ OPTIONAL { ?urn nco:publisher ?publisher . }
++ {{item_pattern}}
++ FILTER ({{photos_default_filter}} && {{source_filter}} && {{search_filter}})
++}
++GROUP BY ?urn
++{{order}}
++{{offset_limit}}
+--
+GitLab
+
+
+From d61d440efe340bda4e195d640066fb29220d0cb5 Mon Sep 17 00:00:00 2001
+From: Sam Thursfield <sam@afuera.me.uk>
+Date: Sat, 23 May 2020 13:36:25 +0200
+Subject: [PATCH 4/4] Port to Tracker 3
+
+Notable changes:
+
+ * User data (favourites, albums) is now stored in a private database in
+ ~/.local/share/gnome-photos. This is combined with the Tracker Miner
+ FS index of photos at query time.
+ * Inside Flatpak, the app connects to Tracker via the new
+ xdg-tracker-portal instead of talking directly over D-Bus. Access is
+ limited by the portal so the app can only see the Pictures graph.
+ * The Flatpak build can use a bundled version of Tracker Miners, if a
+ suitable version is not available on the host.
+ * Change detection is done using TrackerNotifier instead of watching
+ the GraphUpdated D-Bus signal directly.
+
+Closes https://gitlab.gnome.org/GNOME/gnome-photos/-/issues/59
+Closes https://gitlab.gnome.org/GNOME/gnome-photos/-/issues/152
+---
+ data/meson.build | 2 +
+ data/tracker/meson.build | 33 ++
+ ...e.Photos.Tracker3.Miner.Extract.service.in | 7 +
+ ...ome.Photos.Tracker3.Miner.Files.service.in | 7 +
+ data/tracker/org.gnome.Photos.domain.rule.in | 20 +
+ flatpak/org.gnome.Photos.json | 55 +-
+ meson.build | 3 +-
+ src/meson.build | 19 +-
+ ...freedesktop.Tracker3.Miner.Files.Index.xml | 11 +
+ src/org.freedesktop.Tracker3.Miner.xml | 56 +++
+ src/photos-application.c | 184 +++----
+ src/photos-base-item.c | 18 +-
+ src/photos-indexing-notification.c | 53 +-
+ src/photos-item-manager.c | 69 +--
+ src/photos-quarks.c | 20 +-
+ src/photos-query-builder.c | 148 ++++--
+ src/photos-search-context.c | 3 +
+ src/photos-search-context.h | 1 +
+ src/photos-search-match-manager.c | 2 +-
+ src/photos-source.c | 14 +-
+ src/photos-tracker-change-event.c | 136 -----
+ src/photos-tracker-change-event.h | 64 ---
+ src/photos-tracker-change-monitor.c | 468 ------------------
+ src/photos-tracker-change-monitor.h | 42 --
+ src/photos-tracker-controller.c | 12 +-
+ src/photos-tracker-extract-priority.xml | 2 +-
+ src/photos-tracker-import-controller.c | 85 ++--
+ src/photos-tracker-queue.c | 206 +++++++-
+ src/photos-tracker-queue.h | 7 +
+ src/photos-tracker-resources.xml | 31 --
+ src/photos-utils.c | 24 +-
+ src/photos-utils.h | 2 +-
+ src/queries/all.sparql.template | 34 +-
+ src/queries/collections.sparql.template | 20 +-
+ src/queries/favorite-photos.sparql.template | 20 +-
+ src/queries/photos.sparql.template | 22 +-
+ 36 files changed, 754 insertions(+), 1146 deletions(-)
+ create mode 100644 data/tracker/meson.build
+ create mode 100644 data/tracker/org.gnome.Photos.Tracker3.Miner.Extract.service.in
+ create mode 100644 data/tracker/org.gnome.Photos.Tracker3.Miner.Files.service.in
+ create mode 100644 data/tracker/org.gnome.Photos.domain.rule.in
+ create mode 100644 src/org.freedesktop.Tracker3.Miner.Files.Index.xml
+ create mode 100644 src/org.freedesktop.Tracker3.Miner.xml
+ delete mode 100644 src/photos-tracker-change-event.c
+ delete mode 100644 src/photos-tracker-change-event.h
+ delete mode 100644 src/photos-tracker-change-monitor.c
+ delete mode 100644 src/photos-tracker-change-monitor.h
+ delete mode 100644 src/photos-tracker-resources.xml
+
+diff --git a/data/meson.build b/data/meson.build
+index 7898b0cc..f0d85d4b 100644
+--- a/data/meson.build
++++ b/data/meson.build
+@@ -54,3 +54,5 @@ install_data(
+ photos_namespace.to_lower() + '.gschema.xml',
+ install_dir: join_paths(photos_datadir, 'glib-2.0', 'schemas'),
+ )
++
++subdir('tracker')
+diff --git a/data/tracker/meson.build b/data/tracker/meson.build
+new file mode 100644
+index 00000000..accc99c2
+--- /dev/null
++++ b/data/tracker/meson.build
+@@ -0,0 +1,33 @@
++# Files needed for running Tracker inside the Flatpak sandbox, for systems
++# which don't have a suitable version of Tracker in the host OS.
++#
++# We must export the .service files from the sandbox so they work on the
++# session bus. This means the Tracker domain name must correspond with the
++# application ID.
++
++
++domain_ontologies_dir = get_option('datadir') / 'tracker3' / 'domain-ontologies'
++dbus_services_dir = get_option('datadir') / 'dbus-1' / 'services'
++
++tracker_domain_config = configuration_data()
++tracker_domain_config.set('application_id', photos_namespace)
++tracker_domain_config.set('domain_rule', get_option('prefix') / domain_ontologies_dir / photos_namespace + '.domain.rule')
++
++configure_file(
++ input: 'org.gnome.Photos.domain.rule.in',
++ output: photos_namespace + '.domain.rule',
++ configuration: tracker_domain_config,
++ install_dir: domain_ontologies_dir)
++
++configure_file(
++ input: 'org.gnome.Photos.Tracker3.Miner.Extract.service.in',
++ output: photos_namespace + '.Tracker3.Miner.Extract.service',
++ configuration: tracker_domain_config,
++ install_dir: dbus_services_dir)
++
++configure_file(
++ input: 'org.gnome.Photos.Tracker3.Miner.Files.service.in',
++ output: photos_namespace + '.Tracker3.Miner.Files.service',
++ configuration: tracker_domain_config,
++ install_dir: dbus_services_dir)
++
+diff --git a/data/tracker/org.gnome.Photos.Tracker3.Miner.Extract.service.in b/data/tracker/org.gnome.Photos.Tracker3.Miner.Extract.service.in
+new file mode 100644
+index 00000000..eb7a87aa
+--- /dev/null
++++ b/data/tracker/org.gnome.Photos.Tracker3.Miner.Extract.service.in
+@@ -0,0 +1,7 @@
++[D-BUS Service]
++Name=@application_id@.Tracker3.Miner.Extract
++Exec=/app/libexec/tracker-extract-3 --domain-ontology @domain_rule@
++
++# Miner details needed for tracker-control
++Path=/org/freedesktop/Tracker3/Miner/Extract
++NameSuffix=Miner.Files
+diff --git a/data/tracker/org.gnome.Photos.Tracker3.Miner.Files.service.in b/data/tracker/org.gnome.Photos.Tracker3.Miner.Files.service.in
+new file mode 100644
+index 00000000..4fa7371d
+--- /dev/null
++++ b/data/tracker/org.gnome.Photos.Tracker3.Miner.Files.service.in
+@@ -0,0 +1,7 @@
++[D-BUS Service]
++Name=@application_id@.Tracker3.Miner.Files
++Exec=/app/libexec/tracker-miner-fs-3 --domain-ontology @domain_rule@ --initial-sleep 0
++
++# Miner details needed for tracker-control
++Path=/org/freedesktop/Tracker3/Miner/Files
++NameSuffix=Miner.Files
+diff --git a/data/tracker/org.gnome.Photos.domain.rule.in b/data/tracker/org.gnome.Photos.domain.rule.in
+new file mode 100644
+index 00000000..8f5fc4a1
+--- /dev/null
++++ b/data/tracker/org.gnome.Photos.domain.rule.in
+@@ -0,0 +1,20 @@
++# This defines a private Tracker domain for GNOME Photos.
++#
++# It's used to run the Tracker indexer inside a Flatpak sandbox, when Photos is
++# running on a host that doesn't have a suitable version of Tracker installed.
++
++[DomainOntology]
++# Location for the Tracker database
++CacheLocation=$XDG_CACHE_HOME/gnome-photos/miner/files
++
++# Name of the ontology to use, must be one located in
++# $(sharedir)/tracker/ontologies
++OntologyName=nepomuk
++
++# DBus name for the owner (not optional). Tracker will use
++# the domain as the prefix of the DBus name for all the
++# services related to this domain ontology.
++Domain=@application_id@
++
++# List of miners we expect to run in this domain.
++Miners=Miner.Files;Miner.Extract
+diff --git a/flatpak/org.gnome.Photos.json b/flatpak/org.gnome.Photos.json
+index 5d16689c..adfdaf34 100644
+--- a/flatpak/org.gnome.Photos.json
++++ b/flatpak/org.gnome.Photos.json
+@@ -7,7 +7,7 @@
+ "tags": [ "nightly" ],
+ "desktop-file-name-prefix": "(Nightly) ",
+ "finish-args": [
+- "--env=TRACKER_SPARQL_BACKEND=bus",
++ "--add-policy=Tracker3.dbus:org.freedesktop.Tracker3.Miner.Files=tracker:Pictures",
+ "--filesystem=xdg-download",
+ "--filesystem=xdg-pictures",
+ "--metadata=X-DConf=migrate-path=/org/gnome/photos/",
+@@ -17,8 +17,8 @@
+ "--socket=wayland",
+ "--socket=x11",
+ "--talk-name=org.freedesktop.FileManager1",
+- "--talk-name=org.freedesktop.Tracker1",
+- "--talk-name=org.freedesktop.Tracker1.Miner.Extract",
++ "--talk-name=org.freedesktop.Tracker3.Miner.Files",
++ "--talk-name=org.freedesktop.Tracker3.Miner.Files.Index",
+ "--talk-name=com.intel.dleyna-renderer",
+ "--talk-name=org.gnome.ControlCenter",
+ "--talk-name=org.gnome.SettingsDaemon",
+@@ -248,56 +248,22 @@
+ }
+ ]
+ },
+- {
+- "name": "tracker",
+- "buildsystem": "meson",
+- "cleanup": [ "/bin", "/etc", "/lib/girepository-1.0", "/libexec", "/share/dbus-1", "/share/gir-1.0" ],
+- "config-opts": [ "-Dbash_completion=no", "-Ddocs=false", "-Dsystemd_user_services=no" ],
+- "sources": [
+- {
+- "type": "git",
+- "url": "https://gitlab.gnome.org/GNOME/tracker.git",
+- "branch": "tracker-2.3"
+- }
+- ]
+- },
+- {
+- "name": "intltool",
+- "cleanup": [ "*" ],
+- "sources": [
+- {
+- "type": "archive",
+- "url": "https://launchpad.net/intltool/trunk/0.51.0/+download/intltool-0.51.0.tar.gz",
+- "sha256": "67c74d94196b153b774ab9f89b2fa6c6ba79352407037c8c14d5aeb334e959cd"
+- }
+- ]
+- },
+ {
+ "name": "tracker-miners",
+ "buildsystem": "meson",
+- "cleanup": [ "/etc",
+- "/lib",
+- "/libexec",
+- "/share/dbus-1/services/org.freedesktop.Tracker1.Miner.Extract.service",
+- "/share/dbus-1/services/org.freedesktop.Tracker1.Writeback.service",
+- "/share/tracker/miners/org.freedesktop.Tracker1.Miner.Applications.service",
+- "/share/tracker/miners/org.freedesktop.Tracker1.Miner.Extract.service",
+- "/share/tracker/miners/org.freedesktop.Tracker1.Miner.RSS.service",
+- "/share/tracker-miners",
+- "/share/glib-2.0/schemas/org.freedesktop.Tracker.Extract.gschema.xml",
+- "/share/glib-2.0/schemas/org.freedesktop.Tracker.Writeback.gschema.xml" ],
+- "config-opts": [ "-Dextract=false",
+- "-Dgeneric_media_extractor=none",
+- "-Dminer_apps=false",
++ "cleanup": [ "/share/dbus-1/services/org.freedesktop.Tracker3.Miner.Extract.service",
++ "/share/dbus-1/services/org.freedesktop.Tracker3.Miner.Files.service",
++ "/share/dbus-1/services/org.freedesktop.Tracker3.Writeback.service" ],
++ "config-opts": [ "-Dman=false",
+ "-Dminer_fs=true",
+ "-Dminer_rss=false",
+- "-Dsystemd_user_services=no",
++ "-Dsystemd_user_services=false",
+ "-Dwriteback=false" ],
+ "sources": [
+ {
+ "type": "git",
+ "url": "https://gitlab.gnome.org/GNOME/tracker-miners.git",
+- "branch": "tracker-miners-2.3"
++ "branch": "master"
+ }
+ ]
+ },
+@@ -309,7 +275,8 @@
+ {
+ "type": "git",
+ "url": "https://gitlab.gnome.org/GNOME/gnome-photos.git",
+- "disable-shallow-clone": "true"
++ "disable-shallow-clone": "true",
++ "branch": "sam/tracker3"
+ }
+ ]
+ }
+diff --git a/meson.build b/meson.build
+index 70816d24..1c6f2906 100644
+--- a/meson.build
++++ b/meson.build
+@@ -172,8 +172,7 @@ libgdata_dep = dependency('libgdata', version: '>= 0.17.10')
+ libgfgraph_dep = dependency('libgfbgraph-0.2', version: '>= 0.2.1')
+ libjpeg_dep = dependency('libjpeg')
+ libpng_dep = dependency('libpng16')
+-tracker_control_dep = dependency('tracker-control-2.0')
+-tracker_sparql_dep = dependency('tracker-sparql-2.0')
++tracker_sparql_dep = dependency('tracker-sparql-3.0')
+
+ dbus_dep = dependency('dbus-1')
+ dbus_service_dir = dbus_dep.get_pkgconfig_variable(
+diff --git a/src/meson.build b/src/meson.build
+index b5b2759c..6b3b7f01 100644
+--- a/src/meson.build
++++ b/src/meson.build
+@@ -205,8 +205,6 @@ sources = common_sources + files(
+ 'photos-tool-enhance.c',
+ 'photos-tool-filter-button.c',
+ 'photos-tool-filters.c',
+- 'photos-tracker-change-event.c',
+- 'photos-tracker-change-monitor.c',
+ 'photos-tracker-collection-view-controller.c',
+ 'photos-tracker-collections-controller.c',
+ 'photos-tracker-controller.c',
+@@ -367,22 +365,18 @@ sources += gnome.gdbus_codegen(
+ autocleanup: 'all',
+ )
+
+-tracker_extract_priority = 'photos-tracker-extract-priority'
+-
+ sources += gnome.gdbus_codegen(
+- tracker_extract_priority,
+- tracker_extract_priority + '.xml',
+- interface_prefix: 'org.freedesktop.Tracker1.',
++ 'photos-tracker-miner',
++ 'org.freedesktop.Tracker3.Miner.xml',
++ interface_prefix: 'org.freedesktop.Tracker3.',
+ namespace: 'Tracker',
+ autocleanup: 'all',
+ )
+
+-tracker_resources = 'photos-tracker-resources'
+-
+ sources += gnome.gdbus_codegen(
+- tracker_resources,
+- tracker_resources + '.xml',
+- interface_prefix: 'org.freedesktop.Tracker1.',
++ 'photos-tracker-miner-index',
++ 'org.freedesktop.Tracker3.Miner.Files.Index.xml',
++ interface_prefix: 'org.freedesktop.Tracker3.',
+ namespace: 'Tracker',
+ autocleanup: 'all',
+ )
+@@ -401,7 +395,6 @@ deps = common_deps + [
+ libgdata_dep,
+ libgfgraph_dep,
+ m_dep,
+- tracker_control_dep,
+ tracker_sparql_dep,
+ ]
+
+diff --git a/src/org.freedesktop.Tracker3.Miner.Files.Index.xml b/src/org.freedesktop.Tracker3.Miner.Files.Index.xml
+new file mode 100644
+index 00000000..e368f1e2
+--- /dev/null
++++ b/src/org.freedesktop.Tracker3.Miner.Files.Index.xml
+@@ -0,0 +1,11 @@
++<node>
++ <interface name='org.freedesktop.Tracker3.Miner.Files.Index'>
++ <method name='IndexLocation'>
++ <arg type='s' name='file_uri' direction='in' />
++ <arg type='as' name='graphs' direction='in' />
++ <arg type='as' name='flags' direction='in'>"
++ <doc:doc><doc:summary>Extension flags, no allowed values at the moment</doc:summary></doc:doc>
++ </arg>
++ </method>
++ </interface>
++</node>
+diff --git a/src/org.freedesktop.Tracker3.Miner.xml b/src/org.freedesktop.Tracker3.Miner.xml
+new file mode 100644
+index 00000000..6fe09c84
+--- /dev/null
++++ b/src/org.freedesktop.Tracker3.Miner.xml
+@@ -0,0 +1,56 @@
++<?xml version="1.0" encoding="UTF-8"?>
++
++<node name="/">
++ <interface name="org.freedesktop.Tracker3.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/photos-application.c b/src/photos-application.c
+index cb114fda..5fbbbef2 100644
+--- a/src/photos-application.c
++++ b/src/photos-application.c
+@@ -36,7 +36,6 @@
+ #include <glib.h>
+ #include <glib/gi18n.h>
+ #include <grilo.h>
+-#include <libtracker-control/tracker-control.h>
+
+ #include "photos-application.h"
+ #include "photos-base-item.h"
+@@ -68,7 +67,8 @@
+ #include "photos-share-notification.h"
+ #include "photos-share-point-manager.h"
+ #include "photos-thumbnail-factory.h"
+-#include "photos-tracker-extract-priority.h"
++#include "photos-tracker-miner-index.h"
++#include "photos-tracker-queue.h"
+ #include "photos-utils.h"
+
+
+@@ -127,7 +127,7 @@ struct _PhotosApplication
+ PhotosSearchProvider *search_provider;
+ PhotosSelectionController *sel_cntrlr;
+ PhotosThumbnailFactory *factory;
+- TrackerExtractPriority *extract_priority;
++ TrackerMinerFilesIndex *miner_control_proxy;
+ gboolean empty_results;
+ gboolean main_window_deleted;
+ guint create_miners_count;
+@@ -148,7 +148,6 @@ static guint signals[LAST_SIGNAL] = { 0 };
+
+ static void photos_application_search_context_iface_init (PhotosSearchContextInterface *iface);
+
+-
+ G_DEFINE_TYPE_WITH_CODE (PhotosApplication, photos_application, GTK_TYPE_APPLICATION,
+ G_IMPLEMENT_INTERFACE (PHOTOS_TYPE_SEARCH_CONTEXT,
+ photos_application_search_context_iface_init));
+@@ -191,11 +190,11 @@ struct _PhotosApplicationCreateData
+ struct _PhotosApplicationImportData
+ {
+ PhotosApplication *application;
++ TrackerMinerFilesIndex *miner_control_proxy;
+ GFile *destination;
+ GFile *import_sub_dir;
+ GList *files;
+ PhotosBaseItem *collection;
+- TrackerMinerManager *manager;
+ gchar *collection_urn;
+ gint64 ctime_latest;
+ };
+@@ -248,7 +247,7 @@ photos_application_create_data_free (PhotosApplicationCreateData *data)
+
+ static PhotosApplicationImportData *
+ photos_application_import_data_new (PhotosApplication *application,
+- TrackerMinerManager *manager,
++ TrackerMinerFilesIndex *miner_control_proxy,
+ GList *files,
+ gint64 ctime_latest)
+ {
+@@ -257,7 +256,7 @@ photos_application_import_data_new (PhotosApplication *application,
+ data = g_slice_new0 (PhotosApplicationImportData);
+ g_application_hold (G_APPLICATION (application));
+ data->application = application;
+- data->manager = g_object_ref (manager);
++ data->miner_control_proxy = miner_control_proxy;
+ data->files = g_list_copy_deep (files, (GCopyFunc) g_object_ref, NULL);
+ data->ctime_latest = ctime_latest;
+ return data;
+@@ -277,8 +276,8 @@ photos_application_import_data_free (PhotosApplicationImportData *data)
+
+ g_clear_object (&data->destination);
+ g_clear_object (&data->import_sub_dir);
++ g_clear_object (&data->miner_control_proxy);
+ g_list_free_full (data->files, g_object_unref);
+- g_clear_object (&data->manager);
+ g_free (data->collection_urn);
+ g_slice_free (PhotosApplicationImportData, data);
+ }
+@@ -584,27 +583,6 @@ photos_application_actions_update (PhotosApplication *self)
+ }
+
+
+-static void
+-photos_application_tracker_clear_rdf_types (GObject *source_object, GAsyncResult *res, gpointer user_data)
+-{
+- PhotosApplication *self = PHOTOS_APPLICATION (user_data);
+- TrackerExtractPriority *extract_priority = TRACKER_EXTRACT_PRIORITY (source_object);
+-
+- {
+- g_autoptr (GError) error = NULL;
+-
+- if (!tracker_extract_priority_call_clear_rdf_types_finish (extract_priority, res, &error))
+- {
+- g_warning ("Unable to call ClearRdfTypes: %s", error->message);
+- goto out;
+- }
+- }
+-
+- out:
+- g_application_release (G_APPLICATION (self));
+-}
+-
+-
+ static gboolean
+ photos_application_delete_event (PhotosApplication *self)
+ {
+@@ -641,16 +619,6 @@ photos_application_destroy (PhotosApplication *self)
+ self->create_window_cancellable = g_cancellable_new ();
+
+ photos_application_stop_miners (self);
+-
+- if (self->extract_priority != NULL)
+- {
+- g_application_hold (G_APPLICATION (self));
+- tracker_extract_priority_call_clear_rdf_types (self->extract_priority,
+- NULL,
+- photos_application_tracker_clear_rdf_types,
+- self);
+- g_clear_object (&self->extract_priority);
+- }
+ }
+
+
+@@ -740,60 +708,6 @@ photos_application_gegl_init_fishes_idle (gpointer user_data)
+ }
+
+
+-static void
+-photos_application_tracker_set_rdf_types (GObject *source_object, GAsyncResult *res, gpointer user_data)
+-{
+- PhotosApplication *self = PHOTOS_APPLICATION (user_data);
+- TrackerExtractPriority *extract_priority = TRACKER_EXTRACT_PRIORITY (source_object);
+-
+- {
+- g_autoptr (GError) error = NULL;
+-
+- if (!tracker_extract_priority_call_set_rdf_types_finish (extract_priority, res, &error))
+- {
+- if (!g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED))
+- g_warning ("Unable to call SetRdfTypes: %s", error->message);
+-
+- goto out;
+- }
+- }
+-
+- out:
+- g_application_release (G_APPLICATION (self));
+-}
+-
+-
+-static void
+-photos_application_tracker_extract_priority (GObject *source_object, GAsyncResult *res, gpointer user_data)
+-{
+- PhotosApplication *self = PHOTOS_APPLICATION (user_data);
+- const gchar *const rdf_types[] = {"nfo:Image", NULL};
+-
+- {
+- g_autoptr (GError) error = NULL;
+-
+- self->extract_priority = tracker_extract_priority_proxy_new_for_bus_finish (res, &error);
+- if (error != NULL)
+- {
+- if (!g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED))
+- g_warning ("Unable to create TrackerExtractPriority proxy: %s", error->message);
+-
+- goto out;
+- }
+- }
+-
+- g_application_hold (G_APPLICATION (self));
+- tracker_extract_priority_call_set_rdf_types (self->extract_priority,
+- rdf_types,
+- self->create_window_cancellable,
+- photos_application_tracker_set_rdf_types,
+- self);
+-
+- out:
+- g_application_release (G_APPLICATION (self));
+-}
+-
+-
+ static gboolean
+ photos_application_create_window (PhotosApplication *self)
+ {
+@@ -828,13 +742,6 @@ photos_application_create_window (PhotosApplication *self)
+ self->init_fishes_id = g_idle_add (photos_application_gegl_init_fishes_idle, self);
+
+ g_application_hold (G_APPLICATION (self));
+- tracker_extract_priority_proxy_new_for_bus (G_BUS_TYPE_SESSION,
+- G_DBUS_PROXY_FLAGS_NONE,
+- "org.freedesktop.Tracker1.Miner.Extract",
+- "/org/freedesktop/Tracker1/Extract/Priority",
+- self->create_window_cancellable,
+- photos_application_tracker_extract_priority,
+- self);
+
+ photos_application_start_miners (self);
+ return TRUE;
+@@ -1113,18 +1020,18 @@ photos_application_get_state (PhotosSearchContext *context)
+
+
+ static void
+-photos_application_import_index_file (GObject *source_object, GAsyncResult *res, gpointer user_data)
++photos_application_import_index_location (GObject *source_object, GAsyncResult *res, gpointer user_data)
+ {
+ PhotosApplication *self;
+ g_autoptr (GFile) file = G_FILE (user_data);
+- TrackerMinerManager *manager = TRACKER_MINER_MANAGER (source_object);
++ TrackerMinerFilesIndex *miner_control_proxy = TRACKER_MINER_FILES_INDEX (source_object);
+
+ self = PHOTOS_APPLICATION (g_application_get_default ());
+
+ {
+ g_autoptr (GError) error = NULL;
+
+- if (!tracker_miner_manager_index_file_for_process_finish (manager, res, &error))
++ if (!tracker_miner_files_index_call_index_location_finish (miner_control_proxy, res, &error))
+ {
+ g_autofree gchar *uri = NULL;
+
+@@ -1323,7 +1230,6 @@ photos_application_import_file_copy (GObject *source_object, GAsyncResult *res,
+ PhotosApplication *self = data->application;
+ g_autoptr (GFile) destination = NULL;
+ GFile *source = G_FILE (source_object);
+- TrackerMinerManager *manager = data->manager;
+
+ {
+ g_autoptr (GError) error = NULL;
+@@ -1348,6 +1254,8 @@ photos_application_import_file_copy (GObject *source_object, GAsyncResult *res,
+ else
+ {
+ g_autofree gchar *destination_uri = NULL;
++ const gchar *tracker_priority_graphs[] = { TRACKER_PICTURES_GRAPH };
++ const gchar *tracker_index_location_flags[] = { "for-process" };
+
+ g_assert_true (G_IS_FILE (destination));
+ g_set_object (&data->destination, destination);
+@@ -1364,11 +1272,13 @@ photos_application_import_file_copy (GObject *source_object, GAsyncResult *res,
+
+ g_application_hold (G_APPLICATION (self));
+ g_application_mark_busy (G_APPLICATION (self));
+- tracker_miner_manager_index_file_for_process_async (manager,
+- destination,
+- NULL,
+- photos_application_import_index_file,
+- g_object_ref (destination));
++ tracker_miner_files_index_call_index_location (self->miner_control_proxy,
++ destination_uri,
++ tracker_priority_graphs,
++ tracker_index_location_flags,
++ NULL,
++ photos_application_import_index_location,
++ g_object_ref (destination));
+ }
+
+ out:
+@@ -1509,6 +1419,37 @@ photos_application_import_response (GtkDialog *dialog, gint response_id, gpointe
+ }
+
+
++static TrackerMinerFilesIndex *
++photos_application_get_miner_fs_control_proxy (GCancellable *cancellable)
++{
++ g_autoptr (PhotosTrackerQueue) tracker_queue = NULL;
++ const gchar *miner_fs_control_busname;
++ g_autoptr (GError) error = NULL;
++ TrackerMinerFilesIndex *miner_control_proxy;
++
++ tracker_queue = photos_tracker_queue_dup_singleton (cancellable, &error);
++
++ if (!tracker_queue) {
++ g_warning ("Error getting Tracker queue: %s. Import will not work.", error->message);
++ return NULL;
++ }
++
++ miner_fs_control_busname = photos_tracker_queue_get_miner_fs_control_busname (tracker_queue);
++ miner_control_proxy = tracker_miner_files_index_proxy_new_for_bus_sync (G_BUS_TYPE_SESSION,
++ G_DBUS_PROXY_FLAGS_NONE,
++ miner_fs_control_busname,
++ "/org/freedesktop/Tracker3/Miner/Files/Control",
++ NULL,
++ &error);
++ if (!miner_control_proxy) {
++ g_warning ("Error getting Tracker Miner FS control proxy: %s. Import will not work", error->message);
++ return NULL;
++ }
++
++ return miner_control_proxy;
++}
++
++
+ static void
+ photos_application_import (PhotosApplication *self)
+ {
+@@ -1519,8 +1460,8 @@ photos_application_import (PhotosApplication *self)
+ GtkWidget *dialog;
+ g_autoptr (PhotosApplicationImportData) data = NULL;
+ PhotosSource *source;
+- TrackerMinerManager *manager = NULL; /* TODO: use g_autoptr */
+ gint64 ctime_latest = -1;
++ TrackerMinerFilesIndex *miner_control_proxy;
+
+ source = PHOTOS_SOURCE (photos_base_manager_get_active_object (self->state->src_mngr));
+ g_return_if_fail (PHOTOS_IS_SOURCE (source));
+@@ -1533,17 +1474,9 @@ photos_application_import (PhotosApplication *self)
+ selection = photos_selection_controller_get_selection (self->sel_cntrlr);
+ g_return_if_fail (selection != NULL);
+
+- {
+- g_autoptr (GError) error = NULL;
+-
+- manager = tracker_miner_manager_new_full (FALSE, &error);
+- if (error != NULL)
+- {
+- g_warning ("Unable to create a TrackerMinerManager, importing from attached devices won't work: %s",
+- error->message);
+- goto out;
+- }
+- }
++ miner_control_proxy = photos_application_get_miner_fs_control_proxy (NULL);
++ if (!miner_control_proxy)
++ goto out;
+
+ for (l = selection; l != NULL; l = l->next)
+ {
+@@ -1572,14 +1505,14 @@ photos_application_import (PhotosApplication *self)
+ dialog = photos_import_dialog_new (GTK_WINDOW (self->main_window), ctime_latest);
+ gtk_widget_show_all (dialog);
+
+- data = photos_application_import_data_new (self, manager, files, ctime_latest);
++ data = photos_application_import_data_new (self, miner_control_proxy, files, ctime_latest);
++
+ g_signal_connect (dialog,
+ "response",
+ G_CALLBACK (photos_application_import_response),
+ g_steal_pointer (&data));
+
+ out:
+- g_clear_object (&manager);
+ g_list_free_full (files, g_object_unref);
+ }
+
+@@ -2974,6 +2907,7 @@ photos_application_dispose (GObject *object)
+ g_clear_object (&self->insta_action);
+ g_clear_object (&self->load_next_action);
+ g_clear_object (&self->load_previous_action);
++ g_clear_object (&self->miner_control_proxy);
+ g_clear_object (&self->open_action);
+ g_clear_object (&self->preview_menu_action);
+ g_clear_object (&self->primary_menu_action);
+@@ -3002,7 +2936,6 @@ photos_application_dispose (GObject *object)
+ g_clear_object (&self->camera_cache);
+ g_clear_object (&self->sel_cntrlr);
+ g_clear_object (&self->factory);
+- g_clear_object (&self->extract_priority);
+
+ if (self->state != NULL)
+ {
+@@ -3130,8 +3063,7 @@ photos_application_get_miners_running (PhotosApplication *self)
+ return self->miners_running;
+ }
+
+-
+-gint
++int
+ photos_application_get_scale_factor (PhotosApplication *self)
+ {
+ GList *windows;
+diff --git a/src/photos-base-item.c b/src/photos-base-item.c
+index 143431f9..b34706f2 100644
+--- a/src/photos-base-item.c
++++ b/src/photos-base-item.c
+@@ -2756,6 +2756,16 @@ photos_base_item_update_info_from_type (PhotosBaseItem *self)
+ }
+
+
++static gdouble
++get_double_with_default (TrackerSparqlCursor *cursor, PhotosQueryColumns column, gdouble default_value)
++{
++ if (tracker_sparql_cursor_is_bound (cursor, column)) {
++ return tracker_sparql_cursor_get_double (cursor, column);
++ } else {
++ return default_value;
++ }
++}
++
+ static void
+ photos_base_item_populate_from_cursor (PhotosBaseItem *self, TrackerSparqlCursor *cursor)
+ {
+@@ -2891,10 +2901,10 @@ photos_base_item_populate_from_cursor (PhotosBaseItem *self, TrackerSparqlCursor
+ priv->width = height;
+ }
+
+- priv->exposure_time = tracker_sparql_cursor_get_double (cursor, PHOTOS_QUERY_COLUMNS_EXPOSURE_TIME);
+- priv->fnumber = tracker_sparql_cursor_get_double (cursor, PHOTOS_QUERY_COLUMNS_FNUMBER);
+- priv->focal_length = tracker_sparql_cursor_get_double (cursor, PHOTOS_QUERY_COLUMNS_FOCAL_LENGTH);
+- priv->iso_speed = tracker_sparql_cursor_get_double (cursor, PHOTOS_QUERY_COLUMNS_ISO_SPEED);
++ priv->exposure_time = get_double_with_default (cursor, PHOTOS_QUERY_COLUMNS_EXPOSURE_TIME, 0.0);
++ priv->fnumber = get_double_with_default (cursor, PHOTOS_QUERY_COLUMNS_FNUMBER, 0.0);
++ priv->focal_length = get_double_with_default (cursor, PHOTOS_QUERY_COLUMNS_FOCAL_LENGTH, 0.0);
++ priv->iso_speed = get_double_with_default (cursor, PHOTOS_QUERY_COLUMNS_ISO_SPEED, 0.0);
+
+ flash = tracker_sparql_cursor_get_string (cursor, PHOTOS_QUERY_COLUMNS_FLASH, NULL);
+ priv->flash = g_quark_from_string (flash);
+diff --git a/src/photos-indexing-notification.c b/src/photos-indexing-notification.c
+index 7b2ea7dd..122d0d91 100644
+--- a/src/photos-indexing-notification.c
++++ b/src/photos-indexing-notification.c
+@@ -26,12 +26,13 @@
+ #include <gio/gio.h>
+ #include <glib.h>
+ #include <glib/gi18n.h>
+-#include <libtracker-control/tracker-control.h>
+
+ #include "photos-application.h"
+ #include "photos-gom-miner.h"
+ #include "photos-indexing-notification.h"
+ #include "photos-notification-manager.h"
++#include "photos-tracker-queue.h"
++#include "photos-tracker-miner.h"
+
+
+ struct _PhotosIndexingNotification
+@@ -41,7 +42,7 @@ struct _PhotosIndexingNotification
+ GtkWidget *primary_label;
+ GtkWidget *secondary_label;
+ GtkWidget *spinner;
+- TrackerMinerManager *manager;
++ TrackerMiner *miner_fs_proxy;
+ gboolean closed;
+ gboolean on_display;
+ guint timeout_id;
+@@ -56,8 +57,6 @@ enum
+ REMOTE_MINER_TIMEOUT = 10 /* s */
+ };
+
+-static const gchar *MINER_FILES = "org.freedesktop.Tracker1.Miner.Files";
+-
+
+ static void
+ photos_indexing_notification_remove_timeout (PhotosIndexingNotification *self)
+@@ -180,16 +179,18 @@ photos_indexing_notification_check_notification (PhotosIndexingNotification *sel
+ GSList *running = NULL;
+ gboolean is_indexing_local = FALSE;
+ gboolean is_indexing_remote = FALSE;
++ GError *error = NULL;
++ gdouble progress;
+
+- running = tracker_miner_manager_get_running (self->manager);
+- if (g_slist_find_custom (running, (gconstpointer) MINER_FILES, (GCompareFunc) g_strcmp0) != NULL)
+- {
+- gdouble progress;
++ tracker_miner_call_get_progress_sync (self->miner_fs_proxy, &progress, NULL, &error);
++ if (error) {
++ g_warning ("Couldn't get indexing progress from Tracker Miner FS: %s", error->message);
+
+- tracker_miner_manager_get_status (self->manager, MINER_FILES, NULL, &progress, NULL);
+- if (progress < 1)
+- is_indexing_local = TRUE;
+- }
++ g_clear_error (&error);
++ } else {
++ if (progress < 1)
++ is_indexing_local = TRUE;
++ }
+
+ app = g_application_get_default ();
+ miners_running = photos_application_get_miners_running (PHOTOS_APPLICATION (app));
+@@ -222,7 +223,6 @@ photos_indexing_notification_dispose (GObject *object)
+ photos_indexing_notification_remove_timeout (self);
+
+ g_clear_object (&self->ntfctn_mngr);
+- g_clear_object (&self->manager);
+
+ G_OBJECT_CLASS (photos_indexing_notification_parent_class)->dispose (object);
+ }
+@@ -240,13 +240,25 @@ photos_indexing_notification_init (PhotosIndexingNotification *self)
+ app = g_application_get_default ();
+
+ {
++ g_autoptr (PhotosTrackerQueue) tracker_queue;
+ g_autoptr (GError) error = NULL;
++ const gchar *miner_fs_busname;
++
++ tracker_queue = photos_tracker_queue_dup_singleton (NULL, &error);
++
++ if (tracker_queue) {
++ miner_fs_busname = photos_tracker_queue_get_miner_fs_busname (tracker_queue);
++ self->miner_fs_proxy = tracker_miner_proxy_new_for_bus_sync (G_BUS_TYPE_SESSION,
++ G_DBUS_PROXY_FLAGS_NONE,
++ miner_fs_busname,
++ "/org/freedesktop/Tracker3/Miner/Files",
++ NULL,
++ &error);
++ }
+
+- self->manager = tracker_miner_manager_new_full (FALSE, &error);
+ if (error != NULL)
+ {
+- g_warning ("Unable to create a TrackerMinerManager, indexing progress notification won't work: %s",
+- error->message);
++ g_warning ("Unable to create proxy for Tracker Miner FS, indexing progress notification won't work");
+ return;
+ }
+ }
+@@ -293,10 +305,11 @@ photos_indexing_notification_init (PhotosIndexingNotification *self)
+ self,
+ G_CONNECT_SWAPPED);
+
+- g_signal_connect_swapped (self->manager,
+- "miner-progress",
+- G_CALLBACK (photos_indexing_notification_check_notification),
+- self);
++ g_signal_connect_object (self->miner_fs_proxy,
++ "progress",
++ G_CALLBACK (photos_indexing_notification_check_notification),
++ self,
++ G_CONNECT_SWAPPED);
+ }
+
+
+diff --git a/src/photos-item-manager.c b/src/photos-item-manager.c
+index c44fbbfc..e4511bb1 100644
+--- a/src/photos-item-manager.c
++++ b/src/photos-item-manager.c
+@@ -41,12 +41,9 @@
+ #include "photos-query.h"
+ #include "photos-search-context.h"
+ #include "photos-single-item-job.h"
+-#include "photos-tracker-change-event.h"
+-#include "photos-tracker-change-monitor.h"
+ #include "photos-tracker-queue.h"
+ #include "photos-utils.h"
+
+-
+ struct _PhotosItemManager
+ {
+ PhotosBaseManager parent_instance;
+@@ -60,7 +57,7 @@ struct _PhotosItemManager
+ PhotosBaseItem *active_collection;
+ PhotosBaseManager **item_mngr_chldrn;
+ PhotosLoadState load_state;
+- PhotosTrackerChangeMonitor *monitor;
++ TrackerNotifier *notifier;
+ PhotosTrackerQueue *queue;
+ PhotosWindowMode mode;
+ gboolean fullscreen;
+@@ -311,7 +308,7 @@ photos_item_manager_add_cursor_for_mode (PhotosItemManager *self,
+ g_return_if_fail (base_item_type == G_TYPE_NONE
+ || (base_item_type != PHOTOS_TYPE_BASE_ITEM
+ && g_type_is_a (base_item_type, PHOTOS_TYPE_BASE_ITEM)));
+- g_return_if_fail (TRACKER_SPARQL_IS_CURSOR (cursor));
++ g_return_if_fail (TRACKER_IS_SPARQL_CURSOR (cursor));
+ g_return_if_fail (mode != PHOTOS_WINDOW_MODE_NONE);
+ g_return_if_fail (mode != PHOTOS_WINDOW_MODE_EDIT);
+ g_return_if_fail (mode != PHOTOS_WINDOW_MODE_PREVIEW);
+@@ -529,21 +526,23 @@ photos_item_manager_item_created (PhotosItemManager *self, const gchar *urn)
+
+
+ static void
+-photos_item_manager_changes_pending_foreach (gpointer key, gpointer value, gpointer user_data)
++photos_item_manager_changes_pending_foreach (gpointer data,
++ gpointer user_data)
+ {
+ PhotosItemManager *self = PHOTOS_ITEM_MANAGER (user_data);
+- PhotosTrackerChangeEvent *change_event = (PhotosTrackerChangeEvent *) value;
+- PhotosTrackerChangeEventType change_type;
++ TrackerNotifierEvent *event = (TrackerNotifierEvent *) data;
++ TrackerNotifierEventType change_type;
+ const gchar *change_urn;
+
+- change_type = photos_tracker_change_event_get_type (change_event);
+- change_urn = photos_tracker_change_event_get_urn (change_event);
++ change_type = tracker_notifier_event_get_event_type (event);
++ change_urn = tracker_notifier_event_get_urn (event);
+
+- if (change_type == PHOTOS_TRACKER_CHANGE_EVENT_CHANGED)
++ if (change_type == TRACKER_NOTIFIER_EVENT_UPDATE)
+ {
+ GObject *object;
+
+ object = photos_base_manager_get_object_by_id (PHOTOS_BASE_MANAGER (self), change_urn);
++ g_message ("UPDATE event for %s, object %p", change_urn, object);
+ if (object != NULL)
+ {
+ photos_base_item_refresh (PHOTOS_BASE_ITEM (object));
+@@ -557,15 +556,17 @@ photos_item_manager_changes_pending_foreach (gpointer key, gpointer value, gpoin
+ }
+ }
+ }
+- else if (change_type == PHOTOS_TRACKER_CHANGE_EVENT_CREATED)
++ else if (change_type == TRACKER_NOTIFIER_EVENT_CREATE)
+ {
++ g_message ("CREATE event for %s", change_urn);
+ photos_item_manager_item_created (self, change_urn);
+ }
+- else if (change_type == PHOTOS_TRACKER_CHANGE_EVENT_DELETED)
++ else if (change_type == TRACKER_NOTIFIER_EVENT_DELETE)
+ {
+ GObject *object;
+
+ object = photos_base_manager_get_object_by_id (PHOTOS_BASE_MANAGER (self), change_urn);
++ g_message ("DELETE event for %s, object %p", change_urn, object);
+ if (object != NULL)
+ {
+ photos_base_item_destroy (PHOTOS_BASE_ITEM (object));
+@@ -577,9 +578,15 @@ photos_item_manager_changes_pending_foreach (gpointer key, gpointer value, gpoin
+
+
+ static void
+-photos_item_manager_changes_pending (PhotosItemManager *self, GHashTable *changes)
+-{
+- g_hash_table_foreach (changes, photos_item_manager_changes_pending_foreach, self);
++photos_item_manager_changes_pending (PhotosItemManager *self,
++ const gchar *service,
++ const gchar *graph,
++ GPtrArray *events,
++ gpointer user_data)
++{
++ if (g_str_equal (graph, TRACKER_PICTURES_GRAPH)) {
++ g_ptr_array_foreach (events, photos_item_manager_changes_pending_foreach, self);
++ }
+ }
+
+
+@@ -717,7 +724,7 @@ photos_item_manager_wait_for_changes_timeout (gpointer user_data)
+ g_autoptr (PhotosQuery) query = NULL;
+ g_autofree gchar *sparql = NULL;
+
+- sparql = g_strdup_printf ("SELECT ?urn nie:url (?urn) WHERE { ?urn nie:url '%s' }", uri);
++ sparql = g_strdup_printf ("SELECT ?urn nie:isStoredAs (?urn) WHERE { ?urn nie:isStoredAs '%s' }", uri);
+ query = photos_query_new (NULL, sparql);
+ photos_tracker_queue_select (self->queue,
+ query,
+@@ -1038,7 +1045,7 @@ photos_item_manager_dispose (GObject *object)
+ g_clear_object (&self->active_object);
+ g_clear_object (&self->loader_cancellable);
+ g_clear_object (&self->active_collection);
+- g_clear_object (&self->monitor);
++ g_clear_object (&self->notifier);
+ g_clear_object (&self->queue);
+
+ G_OBJECT_CLASS (photos_item_manager_parent_class)->dispose (object);
+@@ -1093,20 +1100,24 @@ photos_item_manager_init (PhotosItemManager *self)
+
+ self->mode = PHOTOS_WINDOW_MODE_NONE;
+
+- self->monitor = photos_tracker_change_monitor_dup_singleton (NULL, NULL);
+- if (G_LIKELY (self->monitor != NULL))
+- g_signal_connect_object (self->monitor,
+- "changes-pending",
+- G_CALLBACK (photos_item_manager_changes_pending),
+- self,
+- G_CONNECT_SWAPPED);
+-
+ {
+ g_autoptr (GError) error = NULL;
+
+ self->queue = photos_tracker_queue_dup_singleton (NULL, &error);
+ if (G_UNLIKELY (error != NULL))
+- g_warning ("Unable to create PhotosTrackerQueue: %s", error->message);
++ {
++ g_warning ("Unable to create PhotosTrackerQueue: %s", error->message);
++ self->notifier = NULL;
++ }
++ else
++ {
++ self->notifier = photos_tracker_queue_get_notifier (self->queue);
++ g_signal_connect_object (self->notifier,
++ "events",
++ G_CALLBACK (photos_item_manager_changes_pending),
++ self,
++ G_CONNECT_SWAPPED);
++ }
+ }
+
+ self->fullscreen = FALSE;
+@@ -1241,7 +1252,7 @@ photos_item_manager_add_item (PhotosItemManager *self,
+ g_return_if_fail (base_item_type == G_TYPE_NONE
+ || (base_item_type != PHOTOS_TYPE_BASE_ITEM
+ && g_type_is_a (base_item_type, PHOTOS_TYPE_BASE_ITEM)));
+- g_return_if_fail (TRACKER_SPARQL_IS_CURSOR (cursor));
++ g_return_if_fail (TRACKER_IS_SPARQL_CURSOR (cursor));
+
+ id = tracker_sparql_cursor_get_string (cursor, PHOTOS_QUERY_COLUMNS_URN, NULL);
+ g_return_if_fail (id != NULL && id[0] != '\0');
+@@ -1370,7 +1381,7 @@ photos_item_manager_create_item (PhotosItemManager *self,
+ g_return_val_if_fail (base_item_type == G_TYPE_NONE
+ || (base_item_type != PHOTOS_TYPE_BASE_ITEM
+ && g_type_is_a (base_item_type, PHOTOS_TYPE_BASE_ITEM)), NULL);
+- g_return_val_if_fail (TRACKER_SPARQL_IS_CURSOR (cursor), NULL);
++ g_return_val_if_fail (TRACKER_IS_SPARQL_CURSOR (cursor), NULL);
+
+ id = tracker_sparql_cursor_get_string (cursor, PHOTOS_QUERY_COLUMNS_URN, NULL);
+ item = PHOTOS_BASE_ITEM (photos_base_manager_get_object_by_id (PHOTOS_BASE_MANAGER (self), id));
+diff --git a/src/photos-quarks.c b/src/photos-quarks.c
+index 0870ef62..5ee25745 100644
+--- a/src/photos-quarks.c
++++ b/src/photos-quarks.c
+@@ -25,68 +25,68 @@
+ GQuark
+ photos_quarks_flash_off_quark (void)
+ {
+- return g_quark_from_static_string ("http://www.tracker-project.org/temp/nmm#flash-off");
++ return g_quark_from_static_string ("http://tracker.api.gnome.org/ontology/v3/nmm#flash-off");
+ }
+
+
+ GQuark
+ photos_quarks_flash_on_quark (void)
+ {
+- return g_quark_from_static_string ("http://www.tracker-project.org/temp/nmm#flash-on");
++ return g_quark_from_static_string ("http://tracker.api.gnome.org/ontology/v3/nmm#flash-on");
+ }
+
+
+ GQuark
+ photos_quarks_orientation_bottom_quark (void)
+ {
+- return g_quark_from_static_string ("http://www.semanticdesktop.org/ontologies/2007/03/22/nfo#orientation-bottom");
++ return g_quark_from_static_string ("http://tracker.api.gnome.org/ontology/v3/nfo#orientation-bottom");
+ }
+
+
+ GQuark
+ photos_quarks_orientation_bottom_mirror_quark (void)
+ {
+- return g_quark_from_static_string ("http://www.semanticdesktop.org/ontologies/2007/03/22/nfo#orientation-bottom-mirror");
++ return g_quark_from_static_string ("http://tracker.api.gnome.org/ontology/v3/nfo#orientation-bottom-mirror");
+ }
+
+
+ GQuark
+ photos_quarks_orientation_left_quark (void)
+ {
+- return g_quark_from_static_string ("http://www.semanticdesktop.org/ontologies/2007/03/22/nfo#orientation-left");
++ return g_quark_from_static_string ("http://tracker.api.gnome.org/ontology/v3/nfo#orientation-left");
+ }
+
+
+ GQuark
+ photos_quarks_orientation_left_mirror_quark (void)
+ {
+- return g_quark_from_static_string ("http://www.semanticdesktop.org/ontologies/2007/03/22/nfo#orientation-left-mirror");
++ return g_quark_from_static_string ("http://tracker.api.gnome.org/ontology/v3/nfo#orientation-left-mirror");
+ }
+
+
+ GQuark
+ photos_quarks_orientation_right_quark (void)
+ {
+- return g_quark_from_static_string ("http://www.semanticdesktop.org/ontologies/2007/03/22/nfo#orientation-right");
++ return g_quark_from_static_string ("http://tracker.api.gnome.org/ontology/v3/nfo#orientation-right");
+ }
+
+
+ GQuark
+ photos_quarks_orientation_right_mirror_quark (void)
+ {
+- return g_quark_from_static_string ("http://www.semanticdesktop.org/ontologies/2007/03/22/nfo#orientation-right-mirror");
++ return g_quark_from_static_string ("http://tracker.api.gnome.org/ontology/v3/nfo#orientation-right-mirror");
+ }
+
+
+ GQuark
+ photos_quarks_orientation_top_quark (void)
+ {
+- return g_quark_from_static_string ("http://www.semanticdesktop.org/ontologies/2007/03/22/nfo#orientation-top");
++ return g_quark_from_static_string ("http://tracker.api.gnome.org/ontology/v3/nfo#orientation-top");
+ }
+
+
+ GQuark
+ photos_quarks_orientation_top_mirror_quark (void)
+ {
+- return g_quark_from_static_string ("http://www.semanticdesktop.org/ontologies/2007/03/22/nfo#orientation-top-mirror");
++ return g_quark_from_static_string ("http://tracker.api.gnome.org/ontology/v3/nfo#orientation-top-mirror");
+ }
+diff --git a/src/photos-query-builder.c b/src/photos-query-builder.c
+index e0735bde..46d65e8f 100644
+--- a/src/photos-query-builder.c
++++ b/src/photos-query-builder.c
+@@ -25,6 +25,7 @@
+
+ #include <string.h>
+
++#include "photos-application.h"
+ #include "photos-base-manager.h"
+ #include "photos-query.h"
+ #include "photos-query-builder.h"
+@@ -32,6 +33,7 @@
+ #include "photos-source-manager.h"
+ #include "photos-search-match-manager.h"
+ #include "photos-search-type-manager.h"
++#include "photos-tracker-queue.h"
+
+ #define PHOTOS_QUERY_COLLECTIONS_IDENTIFIER "photos:collection:"
+ #define PHOTOS_QUERY_LOCAL_COLLECTIONS_IDENTIFIER "photos:collection:local:"
+@@ -48,12 +50,14 @@ const gchar *photos_default_filter = \
+
+ static gchar *
+ photos_query_builder_query (PhotosSearchContextState *state,
+- gboolean global,
++ const gchar *values,
+ gint flags,
+ PhotosOffsetController *offset_cntrlr)
+ {
+ PhotosSparqlTemplate *template;
+- const gchar *projection = NULL;
++ const gchar *miner_fs_busname = NULL;
++ const gchar *main_projection = NULL;
++ const gchar *second_projection = NULL;
+ g_autofree gchar *item_pattern = NULL;
+ g_autofree gchar *search_filter = NULL;
+ g_autofree gchar *source_filter = NULL;
+@@ -63,29 +67,35 @@ photos_query_builder_query (PhotosSearchContextState *state,
+
+ template = photos_base_manager_get_sparql_template (state->srch_typ_mngr, flags);
+
+- projection = "?urn "
+- "nie:url (?urn) "
+- "nfo:fileName (?urn) "
+- "nie:mimeType (?urn) "
+- "nie:title (?urn) "
+- "tracker:coalesce (nco:fullname (?creator), nco:fullname (?publisher), '') "
+- "tracker:coalesce (nfo:fileLastModified (?urn), nie:contentLastModified (?urn)) AS ?mtime "
+- "nao:identifier (?urn) "
+- "rdf:type (?urn) "
+- "nie:dataSource(?urn) "
+- "( EXISTS { ?urn nao:hasTag nao:predefined-tag-favorite } ) "
+- "( EXISTS { ?urn nco:contributor ?contributor FILTER ( ?contributor != ?creator ) } ) "
+- "tracker:coalesce(nfo:fileCreated (?urn), nie:contentCreated (?urn)) "
+- "nfo:width (?urn) "
+- "nfo:height (?urn) "
+- "nfo:equipment (?urn) "
+- "nfo:orientation (?urn) "
+- "nmm:exposureTime (?urn) "
+- "nmm:fnumber (?urn) "
+- "nmm:focalLength (?urn) "
+- "nmm:isoSpeed (?urn) "
+- "nmm:flash (?urn) "
+- "slo:location (?urn) ";
++ miner_fs_busname = photos_tracker_queue_get_miner_fs_busname (state->queue);
++
++ main_projection = "?urn "
++ "?file "
++ "nfo:fileName (?file) AS ?filename "
++ "nie:mimeType (?urn) AS ?mimetype "
++ "nie:title (?urn) AS ?title "
++ "tracker:coalesce (nco:fullname (?creator), nco:fullname (?publisher), '') AS ?author_name "
++ "tracker:coalesce (nfo:fileLastModified (?file), nie:contentLastModified (?urn)) AS ?mtime "
++ "nao:identifier (?urn) AS ?identifier "
++ "rdf:type (?urn) AS ?type "
++ "nie:dataSource(?urn) AS ?datasource "
++ "( EXISTS { ?urn nco:contributor ?contributor FILTER ( ?contributor != ?creator ) } ) AS ?has_contributor "
++ "tracker:coalesce(nfo:fileCreated (?file), nie:contentCreated (?urn)) AS ?ctime "
++ "nfo:width (?urn) AS ?width "
++ "nfo:height (?urn) AS ?height "
++ "nfo:equipment (?urn) AS ?equipment "
++ "nfo:orientation (?urn) AS ?orientation "
++ "nmm:exposureTime (?urn) AS ?exposure_time "
++ "nmm:fnumber (?urn) AS ?fnumber "
++ "nmm:focalLength (?urn) AS ?focal_length "
++ "nmm:isoSpeed (?urn) AS ?isospeed "
++ "nmm:flash (?urn) AS ?flash "
++ "slo:location (?urn) AS ?location ";
++
++ second_projection = "?urn ?file ?filename ?mimetype ?title ?author_name ?mtime ?identifier ?type ?datasource "
++ "( EXISTS { ?urn nao:hasTag nao:predefined-tag-favorite } ) AS ?is_favorite "
++ "?has_contributor ?ctime ?width ?height ?equipment ?orientation ?exposure_time ?fnumber "
++ "?focal_length ?isospeed ?flash ?location ";
+
+ item_pattern = photos_base_manager_get_where (state->item_mngr, flags);
+
+@@ -97,7 +107,7 @@ photos_query_builder_query (PhotosSearchContextState *state,
+
+ order = "ORDER BY DESC (?mtime)";
+
+- if (global && (flags & PHOTOS_QUERY_FLAGS_UNLIMITED) == 0)
++ if (values == NULL && (flags & PHOTOS_QUERY_FLAGS_UNLIMITED) == 0)
+ {
+ gint offset = 0;
+ gint step = 60;
+@@ -112,12 +122,16 @@ photos_query_builder_query (PhotosSearchContextState *state,
+ }
+
+ sparql = photos_sparql_template_get_sparql (template,
+- "projection", projection,
++ "miner_fs_busname", miner_fs_busname,
++ "main_projection", main_projection,
++ "second_projection", second_projection,
++ "final_projection", second_projection,
++ "values", values ? values : "",
+ "collections_default_filter", collections_default_filter,
+ "item_pattern", item_pattern,
+ "photos_default_filter", photos_default_filter,
+- "source_filter", source_filter ? source_filter : "",
+- "search_filter", search_filter ? search_filter : "",
++ "source_filter", source_filter ? source_filter : "(true)",
++ "search_filter", search_filter ? search_filter : "(true)",
+ "order", order,
+ "offset_limit", offset_limit ? offset_limit : "",
+ NULL);
+@@ -133,6 +147,7 @@ photos_query_builder_create_collection_query (PhotosSearchContextState *state,
+ {
+ g_autoptr (GDateTime) now = NULL;
+ PhotosQuery *query;
++ g_autoptr (TrackerResource) collection = NULL;
+ g_autofree gchar *identifier = NULL;
+ g_autofree gchar *sparql = NULL;
+ g_autofree gchar *time = NULL;
+@@ -144,13 +159,14 @@ photos_query_builder_create_collection_query (PhotosSearchContextState *state,
+ now = g_date_time_new_now_utc ();
+ time = g_date_time_format_iso8601 (now);
+
+- sparql = g_strdup_printf ("INSERT { _:res a nfo:DataContainer ; a nie:DataObject ; "
+- "nie:contentLastModified '%s' ; "
+- "nie:title '%s' ; "
+- "nao:identifier '%s' }",
+- time,
+- name,
+- identifier);
++ collection = tracker_resource_new ("_:res");
++ tracker_resource_add_uri (collection, "rdf:type", "nfo:DataContainer");
++ tracker_resource_add_uri (collection, "rdf:type", "nie:DataObject");
++ tracker_resource_set_string (collection, "nie:contentLastModified", time);
++ tracker_resource_set_string (collection, "nie:title", name);
++ tracker_resource_set_string (collection, "nao:identifier", identifier);
++
++ sparql = tracker_resource_print_sparql_update (collection, NULL, "tracker:Pictures");
+
+ query = photos_query_new (state, sparql);
+
+@@ -180,7 +196,9 @@ PhotosQuery *
+ photos_query_builder_count_query (PhotosSearchContextState *state, gint flags)
+ {
+ PhotosSparqlTemplate *template;
+- const gchar *projection = NULL;
++ const gchar *miner_fs_busname = NULL;
++ const gchar *count_projection = NULL;
++ const gchar *value_projection = NULL;
+ g_autofree gchar *item_pattern = NULL;
+ g_autofree gchar *search_filter = NULL;
+ g_autofree gchar *source_filter = NULL;
+@@ -189,7 +207,10 @@ photos_query_builder_count_query (PhotosSearchContextState *state, gint flags)
+
+ template = photos_base_manager_get_sparql_template (state->srch_typ_mngr, flags);
+
+- projection = "COUNT(?urn) ";
++ miner_fs_busname = photos_tracker_queue_get_miner_fs_busname (state->queue);
++
++ value_projection = "?urn ";
++ count_projection = "COUNT(?urn) ";
+
+ item_pattern = photos_base_manager_get_where (state->item_mngr, flags);
+
+@@ -200,12 +221,16 @@ photos_query_builder_count_query (PhotosSearchContextState *state, gint flags)
+ }
+
+ sparql = photos_sparql_template_get_sparql (template,
+- "projection", projection,
++ "miner_fs_busname", miner_fs_busname,
++ "main_projection", value_projection,
++ "second_projection", value_projection,
++ "final_projection", count_projection,
++ "values", "",
+ "collections_default_filter", collections_default_filter,
+ "item_pattern", item_pattern,
+ "photos_default_filter", photos_default_filter,
+- "source_filter", source_filter ? source_filter : "",
+- "search_filter", search_filter ? search_filter : "",
++ "source_filter", source_filter ? source_filter : "(true)",
++ "search_filter", search_filter ? search_filter : "(true)",
+ "order", "",
+ "offset_limit", "",
+ NULL);
+@@ -264,7 +289,7 @@ photos_query_builder_fetch_collections_local (PhotosSearchContextState *state)
+ g_autofree gchar *sparql = NULL;
+
+ sparql = photos_query_builder_query (state,
+- TRUE,
++ NULL,
+ PHOTOS_QUERY_FLAGS_COLLECTIONS
+ | PHOTOS_QUERY_FLAGS_LOCAL
+ | PHOTOS_QUERY_FLAGS_UNLIMITED,
+@@ -284,7 +309,7 @@ photos_query_builder_global_query (PhotosSearchContextState *state,
+ PhotosQuery *query;
+ g_autofree gchar *sparql = NULL;
+
+- sparql = photos_query_builder_query (state, TRUE, flags, offset_cntrlr);
++ sparql = photos_query_builder_query (state, NULL, flags, offset_cntrlr);
+ query = photos_query_new (state, sparql);
+
+ return query;
+@@ -313,10 +338,19 @@ photos_query_builder_set_collection_query (PhotosSearchContextState *state,
+ PhotosQuery *query;
+ g_autofree gchar *sparql = NULL;
+
+- sparql = g_strdup_printf ("%s { <%s> nie:isPartOf <%s> }",
+- setting ? "INSERT" : "DELETE",
+- item_urn,
+- collection_urn);
++ if (setting)
++ sparql = g_strdup_printf ("INSERT DATA { "
++ " GRAPH tracker:Pictures {"
++ " <%s> a nie:DataObject , nmm:Photo ; "
++ " nie:isPartOf <%s> "
++ " }"
++ "}", item_urn, collection_urn);
++ else
++ sparql = g_strdup_printf ("DELETE DATA { "
++ " GRAPH tracker:Pictures {"
++ " <%s> nie:isPartOf <%s> "
++ " }"
++ "}", item_urn, collection_urn);
+ query = photos_query_new (state, sparql);
+
+ return query;
+@@ -326,17 +360,14 @@ photos_query_builder_set_collection_query (PhotosSearchContextState *state,
+ PhotosQuery *
+ photos_query_builder_single_query (PhotosSearchContextState *state, gint flags, const gchar *resource)
+ {
+- g_autoptr (GRegex) regex = NULL;
+ PhotosQuery *query;
+- g_autofree gchar *replacement = NULL;
+ g_autofree gchar *sparql = NULL;
+- g_autofree gchar *tmp = NULL;
++ g_autofree gchar *values = NULL;
++
++ values = g_strdup_printf ("VALUES ?urn { <%s> }", resource);
+
+- tmp = photos_query_builder_query (state, FALSE, flags, NULL);
++ sparql = photos_query_builder_query (state, values, flags, NULL);
+
+- regex = g_regex_new ("\\?urn", 0, 0, NULL);
+- replacement = g_strconcat ("<", resource, ">", NULL);
+- sparql = g_regex_replace (regex, tmp, -1, 0, replacement, 0, NULL);
+ query = photos_query_new (state, sparql);
+
+ return query;
+@@ -354,7 +385,14 @@ photos_query_builder_update_mtime_query (PhotosSearchContextState *state, const
+ now = g_date_time_new_now_utc ();
+ time = g_date_time_format_iso8601 (now);
+
+- sparql = g_strdup_printf ("INSERT OR REPLACE { <%s> nie:contentLastModified '%s' }", resource, time);
++ sparql = g_strdup_printf ("WITH tracker:Pictures "
++ "DELETE { <%s> nie:contentLastModified ?time } "
++ "INSERT { "
++ " <%s> a nmm:Photo ; "
++ " nie:contentLastModified '%s' "
++ "}"
++ "WHERE { <%s> nie:contentLastModified ?time }",
++ resource, resource, time, resource);
+ query = photos_query_new (state, sparql);
+
+ return query;
+diff --git a/src/photos-search-context.c b/src/photos-search-context.c
+index 4b503798..6f8694bc 100644
+--- a/src/photos-search-context.c
++++ b/src/photos-search-context.c
+@@ -31,6 +31,7 @@
+ #include "photos-search-match-manager.h"
+ #include "photos-search-type-manager.h"
+ #include "photos-source-manager.h"
++#include "photos-tracker-queue.h"
+
+
+ G_DEFINE_INTERFACE (PhotosSearchContext, photos_search_context, G_TYPE_OBJECT);
+@@ -54,6 +55,7 @@ photos_search_context_state_new (PhotosSearchContext *self)
+ state->srch_cntrlr = photos_search_controller_new ();
+ state->srch_mtch_mngr = photos_search_match_manager_new (state->srch_cntrlr);
+ state->srch_typ_mngr = photos_search_type_manager_new ();
++ state->queue = photos_tracker_queue_dup_singleton (NULL, NULL);
+
+ return state;
+ }
+@@ -68,6 +70,7 @@ photos_search_context_state_free (PhotosSearchContextState *state)
+ g_object_unref (state->srch_mtch_mngr);
+ g_object_unref (state->srch_typ_mngr);
+ g_object_unref (state->srch_cntrlr);
++ g_clear_object (&state->queue);
+ g_slice_free (PhotosSearchContextState, state);
+ }
+
+diff --git a/src/photos-search-context.h b/src/photos-search-context.h
+index 2af6cf96..5a18d386 100644
+--- a/src/photos-search-context.h
++++ b/src/photos-search-context.h
+@@ -41,6 +41,7 @@ struct _PhotosSearchContextState
+ gpointer srch_typ_mngr;
+ gpointer offset_cntrlr;
+ gpointer srch_cntrlr;
++ gpointer queue;
+ };
+
+ PhotosSearchContextState *photos_search_context_state_new (PhotosSearchContext *self);
+diff --git a/src/photos-search-match-manager.c b/src/photos-search-match-manager.c
+index e6dc429a..2ba1fd0f 100644
+--- a/src/photos-search-match-manager.c
++++ b/src/photos-search-match-manager.c
+@@ -148,7 +148,7 @@ photos_search_match_manager_init (PhotosSearchMatchManager *self)
+ " tracker:case-fold (tracker:coalesce (nco:fullname (?creator), nco:fullname(?publisher))),"
+ " \"%s\")";
+ title_filter = "fn:contains ("
+- " tracker:case-fold (tracker:coalesce (nie:title (?urn), nfo:fileName(?urn))),"
++ " tracker:case-fold (tracker:coalesce (nie:title (?urn), nfo:fileName(?file))),"
+ " \"%s\")";
+
+ search_match = photos_search_match_new (PHOTOS_SEARCH_MATCH_STOCK_ALL,
+diff --git a/src/photos-source.c b/src/photos-source.c
+index db6f2de9..5219652c 100644
+--- a/src/photos-source.c
++++ b/src/photos-source.c
+@@ -62,7 +62,7 @@ G_DEFINE_TYPE_WITH_CODE (PhotosSource, photos_source, G_TYPE_OBJECT,
+ DZL_DEFINE_COUNTER (instances, "PhotosSource", "Instances", "Number of PhotosSource instances")
+
+
+-static const gchar *TRACKER_SCHEMA = "org.freedesktop.Tracker.Miner.Files";
++static const gchar *TRACKER_SCHEMA = "org.freedesktop.Tracker3.Miner.Files";
+ static const gchar *TRACKER_KEY_RECURSIVE_DIRECTORIES = "index-recursive-directories";
+
+
+@@ -94,7 +94,7 @@ photos_source_build_filter_local (void)
+ continue;
+
+ tracker_uri = photos_utils_convert_path_to_uri (tracker_dirs[i]);
+- g_string_append_printf (tracker_filter, " || fn:contains (nie:url (?urn), '%s')", tracker_uri);
++ g_string_append_printf (tracker_filter, " || fn:contains (nie:isStoredAs (?urn), '%s')", tracker_uri);
+ }
+
+ path = g_get_user_special_dir (G_USER_DIRECTORY_DESKTOP);
+@@ -109,11 +109,11 @@ photos_source_build_filter_local (void)
+ export_path = g_build_filename (path, PHOTOS_EXPORT_SUBPATH, NULL);
+ export_uri = photos_utils_convert_path_to_uri (export_path);
+
+- filter = g_strdup_printf ("(((fn:contains (nie:url (?urn), '%s')"
+- " || fn:contains (nie:url (?urn), '%s')"
+- " || fn:contains (nie:url (?urn), '%s')"
++ filter = g_strdup_printf ("(((fn:contains (nie:isStoredAs (?urn), '%s')"
++ " || fn:contains (nie:isStoredAs (?urn), '%s')"
++ " || fn:contains (nie:isStoredAs (?urn), '%s')"
+ " %s)"
+- " && !fn:contains (nie:url (?urn), '%s'))"
++ " && !fn:contains (nie:isStoredAs (?urn), '%s'))"
+ " || fn:starts-with (nao:identifier (?urn), '%s')"
+ " || (?urn = nfo:image-category-screenshot))",
+ desktop_uri,
+@@ -146,7 +146,7 @@ photos_source_build_filter_resource (PhotosSource *self)
+
+ root = g_mount_get_root (self->mount);
+ uri = g_file_get_uri (root);
+- filter = g_strdup_printf ("(fn:starts-with (nie:url (?urn), '%s'))", uri);
++ filter = g_strdup_printf ("(fn:starts-with (nie:isStoredAs (?urn), '%s'))", uri);
+ }
+ else
+ {
+diff --git a/src/photos-tracker-change-event.c b/src/photos-tracker-change-event.c
+deleted file mode 100644
+index d5c74ebc..00000000
+--- a/src/photos-tracker-change-event.c
++++ /dev/null
+@@ -1,136 +0,0 @@
+-/*
+- * Photos - access, organize and share your photos on GNOME
+- * Copyright © 2012 – 2019 Red Hat, Inc.
+- *
+- * 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 3 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, see <http://www.gnu.org/licenses/>.
+- */
+-
+-/* Based on code from:
+- * + Documents
+- */
+-
+-
+-#include "config.h"
+-
+-#include "photos-tracker-change-event.h"
+-
+-
+-struct _PhotosTrackerChangeEvent
+-{
+- PhotosTrackerChangeEventType type;
+- gchar *predicate;
+- gchar *urn;
+- gint32 predicate_id;
+- gint32 urn_id;
+-};
+-
+-
+-static const gchar *RDF_TYPE = "http://www.w3.org/1999/02/22-rdf-syntax-ns#type";
+-
+-
+-void
+-photos_tracker_change_event_free (PhotosTrackerChangeEvent *self)
+-{
+- g_free (self->predicate);
+- g_free (self->urn);
+- g_slice_free (PhotosTrackerChangeEvent, self);
+-}
+-
+-
+-PhotosTrackerChangeEvent *
+-photos_tracker_change_event_new (gint32 urn_id, gint32 predicate_id, gboolean is_delete)
+-{
+- PhotosTrackerChangeEvent *self;
+-
+- self = g_slice_new0 (PhotosTrackerChangeEvent);
+- self->urn_id = urn_id;
+- self->predicate_id = predicate_id;
+-
+- if (is_delete)
+- self->type = PHOTOS_TRACKER_CHANGE_EVENT_DELETED;
+- else
+- self->type = PHOTOS_TRACKER_CHANGE_EVENT_CREATED;
+-
+- return self;
+-}
+-
+-
+-PhotosTrackerChangeEvent *
+-photos_tracker_change_event_copy (PhotosTrackerChangeEvent *event)
+-{
+- PhotosTrackerChangeEvent *self;
+-
+- self = g_slice_new0 (PhotosTrackerChangeEvent);
+- self->type = event->type;
+- self->predicate = g_strdup (event->predicate);
+- self->urn = g_strdup (event->urn);
+- self->predicate_id = event->predicate_id;
+- self->urn_id = event->urn_id;
+-
+- return self;
+-}
+-
+-
+-PhotosTrackerChangeEventType
+-photos_tracker_change_event_get_type (PhotosTrackerChangeEvent *self)
+-{
+- return self->type;
+-}
+-
+-
+-gint32
+-photos_tracker_change_event_get_predicate_id (PhotosTrackerChangeEvent *self)
+-{
+- return self->predicate_id;
+-}
+-
+-
+-const gchar *
+-photos_tracker_change_event_get_urn (PhotosTrackerChangeEvent *self)
+-{
+- return self->urn;
+-}
+-
+-
+-gint32
+-photos_tracker_change_event_get_urn_id (PhotosTrackerChangeEvent *self)
+-{
+- return self->urn_id;
+-}
+-
+-
+-void
+-photos_tracker_change_event_merge (PhotosTrackerChangeEvent *self, PhotosTrackerChangeEvent *event)
+-{
+- g_return_if_fail (g_strcmp0 (self->urn, event->urn) == 0);
+-
+- if (event->type == PHOTOS_TRACKER_CHANGE_EVENT_DELETED || event->type == PHOTOS_TRACKER_CHANGE_EVENT_CREATED)
+- self->type = event->type;
+-}
+-
+-
+-void
+-photos_tracker_change_event_set_resolved_values (PhotosTrackerChangeEvent *self,
+- const gchar *urn,
+- const gchar *predicate)
+-{
+- g_return_if_fail (self->predicate == NULL);
+- g_return_if_fail (self->urn == NULL);
+-
+- self->urn = g_strdup (urn);
+- self->predicate = g_strdup (predicate);
+-
+- if (g_strcmp0 (predicate, RDF_TYPE) != 0)
+- self->type = PHOTOS_TRACKER_CHANGE_EVENT_CHANGED;
+-}
+diff --git a/src/photos-tracker-change-event.h b/src/photos-tracker-change-event.h
+deleted file mode 100644
+index eee4f40c..00000000
+--- a/src/photos-tracker-change-event.h
++++ /dev/null
+@@ -1,64 +0,0 @@
+-/*
+- * Photos - access, organize and share your photos on GNOME
+- * Copyright © 2012 – 2019 Red Hat, Inc.
+- *
+- * 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 3 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, see <http://www.gnu.org/licenses/>.
+- */
+-
+-/* Based on code from:
+- * + Documents
+- */
+-
+-#ifndef PHOTOS_TRACKER_CHANGE_EVENT_H
+-#define PHOTOS_TRACKER_CHANGE_EVENT_H
+-
+-#include <glib.h>
+-
+-G_BEGIN_DECLS
+-
+-typedef enum
+-{
+- PHOTOS_TRACKER_CHANGE_EVENT_CHANGED,
+- PHOTOS_TRACKER_CHANGE_EVENT_CREATED,
+- PHOTOS_TRACKER_CHANGE_EVENT_DELETED
+-} PhotosTrackerChangeEventType;
+-
+-typedef struct _PhotosTrackerChangeEvent PhotosTrackerChangeEvent;
+-
+-PhotosTrackerChangeEvent *photos_tracker_change_event_new (gint32 urn_id,
+- gint32 predicate_id,
+- gboolean is_delete);
+-
+-PhotosTrackerChangeEvent *photos_tracker_change_event_copy (PhotosTrackerChangeEvent *event);
+-
+-void photos_tracker_change_event_free (PhotosTrackerChangeEvent *self);
+-
+-PhotosTrackerChangeEventType photos_tracker_change_event_get_type (PhotosTrackerChangeEvent *self);
+-
+-gint32 photos_tracker_change_event_get_predicate_id (PhotosTrackerChangeEvent *self);
+-
+-const gchar *photos_tracker_change_event_get_urn (PhotosTrackerChangeEvent *self);
+-
+-gint32 photos_tracker_change_event_get_urn_id (PhotosTrackerChangeEvent *self);
+-
+-void photos_tracker_change_event_merge (PhotosTrackerChangeEvent *self,
+- PhotosTrackerChangeEvent *event);
+-
+-void photos_tracker_change_event_set_resolved_values (PhotosTrackerChangeEvent *self,
+- const gchar *urn,
+- const gchar *predicate);
+-
+-G_END_DECLS
+-
+-#endif /* PHOTOS_TRACKER_CHANGE_EVENT_H */
+diff --git a/src/photos-tracker-change-monitor.c b/src/photos-tracker-change-monitor.c
+deleted file mode 100644
+index 2e9810aa..00000000
+--- a/src/photos-tracker-change-monitor.c
++++ /dev/null
+@@ -1,468 +0,0 @@
+-/*
+- * Photos - access, organize and share your photos on GNOME
+- * Copyright © 2012 – 2019 Red Hat, Inc.
+- *
+- * 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 3 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, see <http://www.gnu.org/licenses/>.
+- */
+-
+-/* Based on code from:
+- * + Documents
+- */
+-
+-
+-#include "config.h"
+-
+-#include <glib.h>
+-#include <tracker-sparql.h>
+-
+-#include "photos-tracker-change-event.h"
+-#include "photos-tracker-change-monitor.h"
+-#include "photos-tracker-queue.h"
+-#include "photos-tracker-resources.h"
+-#include "photos-query.h"
+-
+-
+-struct _PhotosTrackerChangeMonitor
+-{
+- GObject parent_instance;
+- GHashTable *pending_changes;
+- GHashTable *unresolved_ids;
+- GQueue *pending_events;
+- PhotosTrackerQueue *queue;
+- TrackerResources *resource_service;
+- guint outstanding_ops;
+- guint pending_events_id;
+-};
+-
+-enum
+-{
+- CHANGES_PENDING,
+- LAST_SIGNAL
+-};
+-
+-static guint signals[LAST_SIGNAL] = { 0 };
+-
+-static void photos_tracker_change_monitor_initable_iface_init (GInitableIface *iface);
+-
+-
+-G_DEFINE_TYPE_EXTENDED (PhotosTrackerChangeMonitor, photos_tracker_change_monitor, G_TYPE_OBJECT, 0,
+- G_IMPLEMENT_INTERFACE (G_TYPE_INITABLE, photos_tracker_change_monitor_initable_iface_init));
+-
+-
+-enum
+-{
+- CHANGE_MONITOR_TIMEOUT = 500, /* ms */
+- CHANGE_MONITOR_MAX_ITEMS = 500
+-};
+-
+-
+-typedef struct _PhotosTrackerChangeMonitorQueryData PhotosTrackerChangeMonitorQueryData;
+-typedef struct _TrackerResourcesEvent TrackerResourcesEvent;
+-
+-struct _PhotosTrackerChangeMonitorQueryData
+-{
+- PhotosTrackerChangeMonitor *self;
+- GHashTable *id_table;
+- GQueue *events;
+-};
+-
+-struct _TrackerResourcesEvent
+-{
+- gint32 graph;
+- gint32 subject;
+- gint32 predicate;
+- gint32 object;
+-};
+-
+-
+-static void
+-photos_tracker_change_monitor_query_data_free (PhotosTrackerChangeMonitorQueryData *data)
+-{
+- g_clear_object (&data->self);
+-
+- if (data->id_table != NULL)
+- g_hash_table_unref (data->id_table);
+-
+- if (data->events != NULL)
+- g_queue_free_full (data->events, (GDestroyNotify) photos_tracker_change_event_free);
+-
+- g_slice_free (PhotosTrackerChangeMonitorQueryData, data);
+-}
+-
+-
+-static PhotosTrackerChangeMonitorQueryData *
+-photos_tracker_change_monitor_query_data_new (PhotosTrackerChangeMonitor *self,
+- GHashTable *id_table,
+- GQueue *events)
+-{
+- PhotosTrackerChangeMonitorQueryData *data;
+-
+- data = g_slice_new0 (PhotosTrackerChangeMonitorQueryData);
+- data->self = g_object_ref (self);
+- data->id_table = id_table;
+- data->events = events;
+-
+- return data;
+-}
+-
+-
+-static void
+-photos_tracker_change_monitor_add_event (PhotosTrackerChangeMonitor *self, PhotosTrackerChangeEvent *change_event)
+-{
+- PhotosTrackerChangeEvent *old_change_event;
+- const gchar *urn;
+-
+- urn = photos_tracker_change_event_get_urn (change_event);
+- old_change_event = (PhotosTrackerChangeEvent *) g_hash_table_lookup (self->pending_changes, urn);
+-
+- if (old_change_event != NULL)
+- photos_tracker_change_event_merge (old_change_event, change_event);
+- else
+- g_hash_table_insert (self->pending_changes, g_strdup (urn), photos_tracker_change_event_copy (change_event));
+-}
+-
+-
+-static void
+-photos_tracker_change_monitor_remove_timeout (PhotosTrackerChangeMonitor *self)
+-{
+- if (self->pending_events_id != 0)
+- {
+- g_source_remove (self->pending_events_id);
+- self->pending_events_id = 0;
+- }
+-}
+-
+-
+-static void
+-photos_tracker_change_monitor_send_events (PhotosTrackerChangeMonitor *self, GHashTable *id_table, GQueue *events)
+-{
+- GList *l;
+-
+- for (l = events->head; l != NULL; l = l->next)
+- {
+- PhotosTrackerChangeEvent *change_event = (PhotosTrackerChangeEvent *) l->data;
+- const gchar *predicate;
+- const gchar *urn;
+- gint32 predicate_id;
+- gint32 urn_id;
+-
+- predicate_id = photos_tracker_change_event_get_predicate_id (change_event);
+- urn_id = photos_tracker_change_event_get_urn_id (change_event);
+-
+- predicate = (gchar *) g_hash_table_lookup (id_table, GINT_TO_POINTER (predicate_id));
+- if (G_UNLIKELY (predicate == NULL))
+- continue;
+-
+- urn = (gchar *) g_hash_table_lookup (id_table, GINT_TO_POINTER (urn_id));
+- if (G_UNLIKELY (urn == NULL))
+- continue;
+-
+- photos_tracker_change_event_set_resolved_values (change_event, urn, predicate);
+- photos_tracker_change_monitor_add_event (self, change_event);
+- }
+-
+- g_signal_emit (self, signals[CHANGES_PENDING], 0, self->pending_changes);
+- g_hash_table_remove_all (self->pending_changes);
+-}
+-
+-
+-static void
+-photos_tracker_change_monitor_cursor_next (GObject *source_object, GAsyncResult *res, gpointer user_data)
+-{
+- PhotosTrackerChangeMonitorQueryData *data = (PhotosTrackerChangeMonitorQueryData *) user_data;
+- PhotosTrackerChangeMonitor *self = data->self;
+- TrackerSparqlCursor *cursor = TRACKER_SPARQL_CURSOR (source_object);
+- GHashTableIter iter;
+- gboolean valid;
+-
+- {
+- g_autoptr (GError) error = NULL;
+-
+- valid = tracker_sparql_cursor_next_finish (cursor, res, &error);
+- if (error != NULL)
+- g_warning ("Unable to resolve item URNs for graph changes: %s", error->message);
+- }
+-
+- if (valid)
+- {
+- guint idx;
+-
+- idx = 0;
+- g_hash_table_iter_init (&iter, data->id_table);
+- while (g_hash_table_iter_next (&iter, NULL, NULL))
+- {
+- const gchar *str;
+-
+- str = tracker_sparql_cursor_get_string (cursor, idx, NULL);
+- g_hash_table_iter_replace (&iter, g_strdup (str));
+- idx++;
+- }
+-
+- photos_tracker_change_monitor_send_events (self, data->id_table, data->events);
+- }
+-
+- tracker_sparql_cursor_close (cursor);
+- photos_tracker_change_monitor_query_data_free (data);
+-}
+-
+-
+-static void
+-photos_tracker_change_monitor_query_executed (GObject *source_object, GAsyncResult *res, gpointer user_data)
+-{
+- PhotosTrackerChangeMonitorQueryData *data = (PhotosTrackerChangeMonitorQueryData *) user_data;
+- TrackerSparqlConnection *connection = TRACKER_SPARQL_CONNECTION (source_object);
+- TrackerSparqlCursor *cursor; /* TODO: Use g_autoptr */
+-
+- {
+- g_autoptr (GError) error = NULL;
+-
+- cursor = tracker_sparql_connection_query_finish (connection, res, &error);
+- if (error != NULL)
+- {
+- g_warning ("Unable to resolve item URNs for graph changes: %s", error->message);
+- photos_tracker_change_monitor_query_data_free (data);
+- return;
+- }
+- }
+-
+- tracker_sparql_cursor_next_async (cursor, NULL, photos_tracker_change_monitor_cursor_next, data);
+- g_object_unref (cursor);
+-}
+-
+-
+-static gboolean
+-photos_tracker_change_monitor_process_events (PhotosTrackerChangeMonitor *self)
+-{
+- GHashTable *id_table;
+- GHashTableIter iter;
+- GQueue *events;
+- g_autoptr (GString) sparql = NULL;
+- PhotosTrackerChangeMonitorQueryData *data;
+- g_autoptr (PhotosQuery) query = NULL;
+- gpointer id;
+-
+- events = self->pending_events;
+- self->pending_events = g_queue_new ();
+-
+- id_table = self->unresolved_ids;
+- self->unresolved_ids = g_hash_table_new_full (g_direct_hash, g_direct_equal, NULL, g_free);
+-
+- self->pending_events_id = 0;
+-
+- sparql = g_string_new ("SELECT");
+-
+- g_hash_table_iter_init (&iter, id_table);
+- while (g_hash_table_iter_next (&iter, &id, NULL))
+- g_string_append_printf (sparql, " tracker:uri(%d)", GPOINTER_TO_INT (id));
+-
+- g_string_append (sparql, " {}");
+-
+- query = photos_query_new (NULL, sparql->str);
+-
+- data = photos_tracker_change_monitor_query_data_new (self, id_table, events);
+- photos_tracker_queue_select (self->queue,
+- query,
+- NULL,
+- photos_tracker_change_monitor_query_executed,
+- data,
+- NULL);
+-
+- return G_SOURCE_REMOVE;
+-}
+-
+-
+-static void
+-photos_tracker_change_monitor_add_pending_event (PhotosTrackerChangeMonitor *self,
+- const TrackerResourcesEvent *event,
+- gboolean is_delete)
+-{
+- PhotosTrackerChangeEvent *change_event;
+-
+- photos_tracker_change_monitor_remove_timeout (self);
+-
+- g_hash_table_insert (self->unresolved_ids, GINT_TO_POINTER (event->subject), NULL);
+- g_hash_table_insert (self->unresolved_ids, GINT_TO_POINTER (event->predicate), NULL);
+-
+- change_event = photos_tracker_change_event_new (event->subject, event->predicate, is_delete);
+- g_queue_push_tail (self->pending_events, change_event);
+-
+- if (self->pending_events->length >= CHANGE_MONITOR_MAX_ITEMS)
+- photos_tracker_change_monitor_process_events (self);
+- else
+- self->pending_events_id = g_timeout_add (CHANGE_MONITOR_TIMEOUT,
+- (GSourceFunc) photos_tracker_change_monitor_process_events,
+- self);
+-}
+-
+-
+-static void
+-photos_tracker_change_monitor_graph_updated (TrackerResources *resource_service,
+- const gchar *class_name,
+- GVariant *delete_events,
+- GVariant *insert_events,
+- gpointer user_data)
+-{
+- PhotosTrackerChangeMonitor *self = PHOTOS_TRACKER_CHANGE_MONITOR (user_data);
+- const TrackerResourcesEvent *events;
+- gsize i;
+- gsize n_elements;
+-
+- events = (const TrackerResourcesEvent *) g_variant_get_fixed_array (delete_events,
+- &n_elements,
+- sizeof (TrackerResourcesEvent));
+- for (i = 0; i < n_elements; i++)
+- photos_tracker_change_monitor_add_pending_event (self, &events[i], TRUE);
+-
+- events = (const TrackerResourcesEvent *) g_variant_get_fixed_array (insert_events,
+- &n_elements,
+- sizeof (TrackerResourcesEvent));
+- for (i = 0; i < n_elements; i++)
+- photos_tracker_change_monitor_add_pending_event (self, &events[i], FALSE);
+-}
+-
+-
+-static GObject *
+-photos_tracker_change_monitor_constructor (GType type,
+- guint n_construct_params,
+- GObjectConstructParam *construct_params)
+-{
+- static GObject *self = NULL;
+-
+- if (self == NULL)
+- {
+- self = G_OBJECT_CLASS (photos_tracker_change_monitor_parent_class)->constructor (type,
+- n_construct_params,
+- construct_params);
+- g_object_add_weak_pointer (self, (gpointer) &self);
+- return self;
+- }
+-
+- return g_object_ref (self);
+-}
+-
+-
+-static void
+-photos_tracker_change_monitor_dispose (GObject *object)
+-{
+- PhotosTrackerChangeMonitor *self = PHOTOS_TRACKER_CHANGE_MONITOR (object);
+-
+- photos_tracker_change_monitor_remove_timeout (self);
+-
+- g_clear_object (&self->queue);
+- g_clear_object (&self->resource_service);
+-
+- G_OBJECT_CLASS (photos_tracker_change_monitor_parent_class)->dispose (object);
+-}
+-
+-
+-static void
+-photos_tracker_change_monitor_finalize (GObject *object)
+-{
+- PhotosTrackerChangeMonitor *self = PHOTOS_TRACKER_CHANGE_MONITOR (object);
+-
+- g_hash_table_unref (self->pending_changes);
+- g_hash_table_unref (self->unresolved_ids);
+-
+- g_queue_free_full (self->pending_events, (GDestroyNotify) photos_tracker_change_event_free);
+-
+- G_OBJECT_CLASS (photos_tracker_change_monitor_parent_class)->finalize (object);
+-}
+-
+-
+-static void
+-photos_tracker_change_monitor_init (PhotosTrackerChangeMonitor *self)
+-{
+- self->pending_changes = g_hash_table_new_full (g_str_hash,
+- g_str_equal,
+- g_free,
+- (GDestroyNotify) photos_tracker_change_event_free);
+-
+- self->unresolved_ids = g_hash_table_new_full (g_direct_hash, g_direct_equal, NULL, g_free);
+-
+- self->pending_events = g_queue_new ();
+-}
+-
+-
+-static void
+-photos_tracker_change_monitor_class_init (PhotosTrackerChangeMonitorClass *class)
+-{
+- GObjectClass *object_class = G_OBJECT_CLASS (class);
+-
+- object_class->constructor = photos_tracker_change_monitor_constructor;
+- object_class->dispose = photos_tracker_change_monitor_dispose;
+- object_class->finalize = photos_tracker_change_monitor_finalize;
+-
+- signals[CHANGES_PENDING] = g_signal_new ("changes-pending",
+- G_TYPE_FROM_CLASS (class),
+- G_SIGNAL_RUN_LAST,
+- 0,
+- NULL, /*accumulator */
+- NULL, /*accu_data */
+- g_cclosure_marshal_VOID__BOXED,
+- G_TYPE_NONE,
+- 1,
+- G_TYPE_HASH_TABLE);
+-}
+-
+-
+-static gboolean
+-photos_tracker_change_monitor_initable_init (GInitable *initable, GCancellable *cancellable, GError **error)
+-{
+- PhotosTrackerChangeMonitor *self = PHOTOS_TRACKER_CHANGE_MONITOR (initable);
+- gboolean ret_val = TRUE;
+-
+- if (G_LIKELY (self->queue != NULL && self->resource_service != NULL))
+- goto out;
+-
+- self->queue = photos_tracker_queue_dup_singleton (cancellable, error);
+- if (G_UNLIKELY (self->queue == NULL))
+- {
+- ret_val = FALSE;
+- goto out;
+- }
+-
+- self->resource_service = tracker_resources_proxy_new_for_bus_sync (G_BUS_TYPE_SESSION,
+- G_DBUS_PROXY_FLAGS_NONE,
+- "org.freedesktop.Tracker1",
+- "/org/freedesktop/Tracker1/Resources",
+- cancellable,
+- error);
+- if (G_UNLIKELY (self->resource_service == NULL))
+- {
+- ret_val = FALSE;
+- goto out;
+- }
+-
+- g_signal_connect (self->resource_service,
+- "graph-updated",
+- G_CALLBACK (photos_tracker_change_monitor_graph_updated),
+- self);
+-
+- out:
+- return ret_val;
+-}
+-
+-
+-static void
+-photos_tracker_change_monitor_initable_iface_init (GInitableIface *iface)
+-{
+- iface->init = photos_tracker_change_monitor_initable_init;
+-}
+-
+-
+-PhotosTrackerChangeMonitor *
+-photos_tracker_change_monitor_dup_singleton (GCancellable *cancellable, GError **error)
+-{
+- return g_initable_new (PHOTOS_TYPE_TRACKER_CHANGE_MONITOR, cancellable, error, NULL);
+-}
+diff --git a/src/photos-tracker-change-monitor.h b/src/photos-tracker-change-monitor.h
+deleted file mode 100644
+index 9740b046..00000000
+--- a/src/photos-tracker-change-monitor.h
++++ /dev/null
+@@ -1,42 +0,0 @@
+-/*
+- * Photos - access, organize and share your photos on GNOME
+- * Copyright © 2012 – 2019 Red Hat, Inc.
+- *
+- * 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 3 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, see <http://www.gnu.org/licenses/>.
+- */
+-
+-/* Based on code from:
+- * + Documents
+- */
+-
+-#ifndef PHOTOS_TRACKER_CHANGE_MONITOR_H
+-#define PHOTOS_TRACKER_CHANGE_MONITOR_H
+-
+-#include <gio/gio.h>
+-
+-G_BEGIN_DECLS
+-
+-#define PHOTOS_TYPE_TRACKER_CHANGE_MONITOR (photos_tracker_change_monitor_get_type ())
+-G_DECLARE_FINAL_TYPE (PhotosTrackerChangeMonitor,
+- photos_tracker_change_monitor,
+- PHOTOS,
+- TRACKER_CHANGE_MONITOR,
+- GObject);
+-
+-PhotosTrackerChangeMonitor *photos_tracker_change_monitor_dup_singleton (GCancellable *cancellable,
+- GError **error);
+-
+-G_END_DECLS
+-
+-#endif /* PHOTOS_TRACKER_CHANGE_MONITOR_H */
+diff --git a/src/photos-tracker-controller.c b/src/photos-tracker-controller.c
+index 3a62cf2d..f0b81e02 100644
+--- a/src/photos-tracker-controller.c
++++ b/src/photos-tracker-controller.c
+@@ -292,6 +292,12 @@ photos_tracker_controller_perform_current_query (PhotosTrackerController *self)
+
+ priv = photos_tracker_controller_get_instance_private (self);
+
++ if (G_UNLIKELY (priv->queue == NULL))
++ {
++ photos_tracker_controller_query_error (self, priv->queue_error);
++ goto out;
++ }
++
+ g_clear_object (&priv->current_query);
+ priv->current_query = PHOTOS_TRACKER_CONTROLLER_GET_CLASS (self)->get_query (self);
+ g_return_if_fail (priv->current_query != NULL);
+@@ -303,12 +309,6 @@ photos_tracker_controller_perform_current_query (PhotosTrackerController *self)
+ g_object_unref (priv->cancellable);
+ priv->cancellable = g_cancellable_new ();
+
+- if (G_UNLIKELY (priv->queue == NULL))
+- {
+- photos_tracker_controller_query_error (self, priv->queue_error);
+- goto out;
+- }
+-
+ photos_tracker_queue_select (priv->queue,
+ priv->current_query,
+ priv->cancellable,
+diff --git a/src/photos-tracker-extract-priority.xml b/src/photos-tracker-extract-priority.xml
+index 7e3b9b0c..2b562299 100644
+--- a/src/photos-tracker-extract-priority.xml
++++ b/src/photos-tracker-extract-priority.xml
+@@ -21,7 +21,7 @@
+ -->
+
+ <node name="/" xmlns:doc="http://www.freedesktop.org/dbus/1.0/doc.dtd">
+- <interface name="org.freedesktop.Tracker1.Extract.Priority">
++ <interface name="org.freedesktop.Tracker3.Extract.Priority">
+ <method name="ClearRdfTypes" />
+ <method name="SetRdfTypes">
+ <arg name="rdf_types" type="as" direction="in" />
+diff --git a/src/photos-tracker-import-controller.c b/src/photos-tracker-import-controller.c
+index 085ace3a..d7ed9b20 100644
+--- a/src/photos-tracker-import-controller.c
++++ b/src/photos-tracker-import-controller.c
+@@ -24,7 +24,6 @@
+ #include "config.h"
+
+ #include <gio/gio.h>
+-#include <libtracker-control/tracker-control.h>
+
+ #include "photos-base-manager.h"
+ #include "photos-debug.h"
+@@ -34,6 +33,8 @@
+ #include "photos-query-builder.h"
+ #include "photos-search-context.h"
+ #include "photos-tracker-import-controller.h"
++#include "photos-tracker-miner-index.h"
++#include "photos-tracker-queue.h"
+ #include "photos-utils.h"
+
+
+@@ -45,7 +46,7 @@ struct _PhotosTrackerImportController
+ PhotosBaseManager *item_mngr;
+ PhotosBaseManager *src_mngr;
+ PhotosOffsetController *offset_cntrlr;
+- TrackerMinerManager *manager;
++ TrackerMinerFilesIndex *miner_control_proxy;
+ };
+
+
+@@ -76,12 +77,12 @@ static void
+ photos_tracker_import_controller_index (GObject *source_object, GAsyncResult *res, gpointer user_data)
+ {
+ g_autoptr (GFile) file = G_FILE (user_data);
+- TrackerMinerManager *manager = TRACKER_MINER_MANAGER (source_object);
++ TrackerMinerFilesIndex *miner_control_proxy = TRACKER_MINER_FILES_INDEX (source_object);
+
+ {
+ g_autoptr (GError) error = NULL;
+
+- if (!tracker_miner_manager_index_file_for_process_finish (manager, res, &error))
++ if (!tracker_miner_files_index_call_index_location_finish (miner_control_proxy, res, &error))
+ {
+ if (!g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED))
+ {
+@@ -102,6 +103,8 @@ photos_tracker_import_controller_next_files (GObject *source_object, GAsyncResul
+ GFileEnumerator *enumerator = G_FILE_ENUMERATOR (source_object);
+ GList *infos = NULL;
+ GList *l;
++ const gchar *tracker_priority_graphs[] = { TRACKER_PICTURES_GRAPH };
++ const gchar *tracker_index_location_flags[] = { "for-process" };
+
+ {
+ g_autoptr (GError) error = NULL;
+@@ -187,11 +190,13 @@ photos_tracker_import_controller_next_files (GObject *source_object, GAsyncResul
+ if (g_content_type_equals (mime_type, IMPORTABLE_MIME_TYPES[i])
+ || g_content_type_is_a (mime_type, IMPORTABLE_MIME_TYPES[i]))
+ {
+- tracker_miner_manager_index_file_for_process_async (self->manager,
+- file,
+- self->cancellable,
+- photos_tracker_import_controller_index,
+- g_object_ref (file));
++ tracker_miner_files_index_call_index_location (self->miner_control_proxy,
++ uri,
++ tracker_priority_graphs,
++ tracker_index_location_flags,
++ self->cancellable,
++ photos_tracker_import_controller_index,
++ g_object_ref (file));
+ indexing = TRUE;
+ }
+ }
+@@ -291,28 +296,6 @@ photos_tracker_import_controller_source_active_changed (PhotosTrackerImportContr
+ {
+ g_return_if_fail (g_queue_is_empty (self->pending_directories));
+
+- if (G_LIKELY (self->manager != NULL))
+- {
+- g_autoptr (GFile) root = NULL;
+- g_autofree gchar *uri = NULL;
+-
+- root = g_mount_get_root (mount);
+- g_queue_push_tail (self->pending_directories, g_object_ref (root));
+-
+- uri = g_file_get_uri (root);
+- photos_debug (PHOTOS_DEBUG_IMPORT, "Enumerating device directory %s", uri);
+-
+- g_file_enumerate_children_async (root,
+- G_FILE_ATTRIBUTE_STANDARD_CONTENT_TYPE","
+- G_FILE_ATTRIBUTE_STANDARD_NAME","
+- G_FILE_ATTRIBUTE_STANDARD_TYPE,
+- G_FILE_QUERY_INFO_NOFOLLOW_SYMLINKS,
+- G_PRIORITY_DEFAULT,
+- self->cancellable,
+- photos_tracker_import_controller_enumerate_children,
+- self);
+- }
+-
+ photos_tracker_controller_refresh_for_object (PHOTOS_TRACKER_CONTROLLER (self));
+ }
+ }
+@@ -379,7 +362,6 @@ photos_tracker_import_controller_dispose (GObject *object)
+
+ g_clear_object (&self->src_mngr);
+ g_clear_object (&self->offset_cntrlr);
+- g_clear_object (&self->manager);
+
+ G_OBJECT_CLASS (photos_tracker_import_controller_parent_class)->dispose (object);
+ }
+@@ -397,6 +379,37 @@ photos_tracker_import_controller_finalize (GObject *object)
+ }
+
+
++static TrackerMinerFilesIndex *
++photos_tracker_import_controller_get_miner_fs_control_proxy (GCancellable *cancellable)
++{
++ g_autoptr (PhotosTrackerQueue) tracker_queue = NULL;
++ const gchar *miner_fs_control_busname;
++ g_autoptr (GError) error = NULL;
++ TrackerMinerFilesIndex *miner_control_proxy;
++
++ tracker_queue = photos_tracker_queue_dup_singleton (cancellable, &error);
++
++ if (!tracker_queue) {
++ g_warning ("Error getting Tracker queue: %s. Import will not work.", error->message);
++ return NULL;
++ }
++
++ miner_fs_control_busname = photos_tracker_queue_get_miner_fs_control_busname (tracker_queue);
++ miner_control_proxy = tracker_miner_files_index_proxy_new_for_bus_sync (G_BUS_TYPE_SESSION,
++ G_DBUS_PROXY_FLAGS_NONE,
++ miner_fs_control_busname,
++ "/org/freedesktop/Tracker3/Miner/Files/Control",
++ NULL,
++ &error);
++ if (!miner_control_proxy) {
++ g_warning ("Error getting Tracker Miner FS control proxy: %s. Import will not work", error->message);
++ return NULL;
++ }
++
++ return miner_control_proxy;
++}
++
++
+ static void
+ photos_tracker_import_controller_init (PhotosTrackerImportController *self)
+ {
+@@ -421,13 +434,7 @@ photos_tracker_import_controller_init (PhotosTrackerImportController *self)
+
+ self->offset_cntrlr = photos_offset_import_controller_dup_singleton ();
+
+- {
+- g_autoptr (GError) error = NULL;
+-
+- self->manager = tracker_miner_manager_new_full (FALSE, &error);
+- if (error != NULL)
+- g_warning ("Unable to create a TrackerMinerManager, indexing attached devices won't work: %s", error->message);
+- }
++ self->miner_control_proxy = photos_tracker_import_controller_get_miner_fs_control_proxy (NULL);
+ }
+
+
+diff --git a/src/photos-tracker-queue.c b/src/photos-tracker-queue.c
+index 6ac829cd..a5f4c91a 100644
+--- a/src/photos-tracker-queue.c
++++ b/src/photos-tracker-queue.c
+@@ -37,9 +37,20 @@ struct _PhotosTrackerQueue
+ GObject parent_instance;
+ GError *initialization_error;
+ GQueue *queue;
+- TrackerSparqlConnection *connection;
++ TrackerSparqlConnection *local_connection;
++ TrackerSparqlConnection *miner_fs_connection;
+ gboolean is_initialized;
+ gboolean running;
++ gboolean miner_fs_ready;
++ const gchar *miner_fs_busname;
++ const gchar *miner_fs_control_busname;
++};
++
++enum
++{
++ PROP_0,
++ PROP_MINER_FS_READY,
++ PROP_MINER_FS_BUSNAME
+ };
+
+ static void photos_tracker_queue_initable_iface_init (GInitableIface *iface);
+@@ -90,7 +101,6 @@ photos_tracker_queue_data_free (PhotosTrackerQueueData *data)
+
+ G_DEFINE_AUTOPTR_CLEANUP_FUNC (PhotosTrackerQueueData, photos_tracker_queue_data_free);
+
+-
+ static PhotosTrackerQueueData *
+ photos_tracker_queue_data_new (PhotosQuery *query,
+ PhotosTrackerQueryType query_type,
+@@ -179,7 +189,7 @@ photos_tracker_queue_check (PhotosTrackerQueue *self)
+ switch (data->query_type)
+ {
+ case PHOTOS_TRACKER_QUERY_SELECT:
+- tracker_sparql_connection_query_async (self->connection,
++ tracker_sparql_connection_query_async (self->local_connection,
+ sparql,
+ data->cancellable,
+ photos_tracker_queue_collector,
+@@ -187,18 +197,16 @@ photos_tracker_queue_check (PhotosTrackerQueue *self)
+ break;
+
+ case PHOTOS_TRACKER_QUERY_UPDATE:
+- tracker_sparql_connection_update_async (self->connection,
++ tracker_sparql_connection_update_async (self->local_connection,
+ sparql,
+- G_PRIORITY_DEFAULT,
+ data->cancellable,
+ photos_tracker_queue_collector,
+ g_object_ref (self));
+ break;
+
+ case PHOTOS_TRACKER_QUERY_UPDATE_BLANK:
+- tracker_sparql_connection_update_blank_async (self->connection,
++ tracker_sparql_connection_update_blank_async (self->local_connection,
+ sparql,
+- G_PRIORITY_DEFAULT,
+ data->cancellable,
+ photos_tracker_queue_collector,
+ g_object_ref (self));
+@@ -211,6 +219,53 @@ photos_tracker_queue_check (PhotosTrackerQueue *self)
+ }
+
+
++static gboolean
++photos_tracker_queue_start_session_miner_fs (PhotosTrackerQueue *self, GError **error)
++{
++ const gchar *busname = "org.freedesktop.Tracker3.Miner.Files";
++ const gchar *control_busname = "org.freedesktop.Tracker3.Miner.Files.Control";
++
++ photos_debug (PHOTOS_DEBUG_TRACKER, "Connecting to %s", busname);
++ self->miner_fs_connection = tracker_sparql_connection_bus_new (busname, NULL, NULL, error);
++ if (*error)
++ {
++ g_warning ("Unable to create connection for session-wide Tracker indexer at %s: %s", busname, (*error)->message);
++ return FALSE;
++ }
++
++ self->miner_fs_busname = busname;
++ self->miner_fs_control_busname = control_busname;
++
++ return TRUE;
++}
++
++
++static gboolean
++photos_tracker_queue_start_local_miner_fs (PhotosTrackerQueue *self, GError **error)
++{
++ const gchar *busname = "org.gnome.Photos.Tracker3.Miner.Files";
++ const gchar *control_busname = "org.gnome.Photos.Tracker3.Miner.Files.Control";
++
++ photos_debug (PHOTOS_DEBUG_TRACKER, "Connecting to %s", busname);
++ self->miner_fs_connection = tracker_sparql_connection_bus_new (busname, NULL, NULL, error);
++ if (*error)
++ {
++ g_critical ("Could not start local Tracker indexer at %s: %s", busname, (*error)->message);
++ return FALSE;
++ }
++
++ self->miner_fs_busname = busname;
++ self->miner_fs_control_busname = control_busname;
++
++ return TRUE;
++}
++
++static gboolean
++inside_flatpak (void)
++{
++ return g_file_test ("/.flatpak-info", G_FILE_TEST_EXISTS);
++}
++
+ static GObject *
+ photos_tracker_queue_constructor (GType type, guint n_construct_params, GObjectConstructParam *construct_params)
+ {
+@@ -234,7 +289,8 @@ photos_tracker_queue_dispose (GObject *object)
+ {
+ PhotosTrackerQueue *self = PHOTOS_TRACKER_QUEUE (object);
+
+- g_clear_object (&self->connection);
++ g_clear_object (&self->local_connection);
++ g_clear_object (&self->miner_fs_connection);
+
+ G_OBJECT_CLASS (photos_tracker_queue_parent_class)->dispose (object);
+ }
+@@ -258,6 +314,38 @@ photos_tracker_queue_init (PhotosTrackerQueue *self)
+ self->queue = g_queue_new ();
+ }
+
++static void
++photos_tracker_queue_get_property (GObject *object, guint prop_id, GValue *value, GParamSpec *pspec)
++{
++ PhotosTrackerQueue *self = PHOTOS_TRACKER_QUEUE (object);
++
++ switch (prop_id)
++ {
++ case PROP_MINER_FS_READY:
++ g_value_set_boolean (value, self->miner_fs_ready);
++ break;
++
++ case PROP_MINER_FS_BUSNAME:
++ g_value_set_string (value, self->miner_fs_busname);
++ break;
++
++ default:
++ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
++ break;
++ }
++}
++
++static void
++photos_tracker_queue_set_property (GObject *object, guint prop_id, const GValue *value, GParamSpec *pspec)
++{
++ switch (prop_id)
++ {
++ /* All properties are read only */
++ default:
++ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
++ break;
++ }
++}
+
+ static void
+ photos_tracker_queue_class_init (PhotosTrackerQueueClass *class)
+@@ -267,6 +355,24 @@ photos_tracker_queue_class_init (PhotosTrackerQueueClass *class)
+ object_class->constructor = photos_tracker_queue_constructor;
+ object_class->dispose = photos_tracker_queue_dispose;
+ object_class->finalize = photos_tracker_queue_finalize;
++ object_class->get_property = photos_tracker_queue_get_property;
++ object_class->set_property = photos_tracker_queue_set_property;
++
++ g_object_class_install_property (object_class,
++ PROP_MINER_FS_READY,
++ g_param_spec_boolean ("tracker-miner-fs-ready",
++ "Tracker Miner FS ready",
++ "TRUE if it is possible to query Tracker indexer",
++ FALSE,
++ G_PARAM_READABLE));
++
++ g_object_class_install_property (object_class,
++ PROP_MINER_FS_BUSNAME,
++ g_param_spec_string ("tracker-miner-fs-busname",
++ "Tracker Miner FS busname",
++ "D-Bus name of the Tracker indexer daemon",
++ "",
++ G_PARAM_READABLE));
+ }
+
+
+@@ -274,13 +380,16 @@ static gboolean
+ photos_tracker_queue_initable_init (GInitable *initable, GCancellable *cancellable, GError **error)
+ {
+ PhotosTrackerQueue *self = PHOTOS_TRACKER_QUEUE (initable);
++ TrackerSparqlConnectionFlags tracker_flags;
++ g_autoptr (GFile) store = NULL;
+ gboolean ret_val = FALSE;
++ gboolean miner_ok = FALSE;
+
+ G_LOCK (init_lock);
+
+ if (self->is_initialized)
+ {
+- if (self->connection != NULL)
++ if (self->local_connection != NULL && self->miner_fs_connection != NULL)
+ ret_val = TRUE;
+ else
+ g_assert_nonnull (self->initialization_error);
+@@ -290,11 +399,49 @@ photos_tracker_queue_initable_init (GInitable *initable, GCancellable *cancellab
+
+ g_assert_no_error (self->initialization_error);
+
+- self->connection = tracker_sparql_connection_get (cancellable, &self->initialization_error);
++ /* Connect to the local database which stores user data.
++ *
++ * Same flags that tracker-miner-fs uses by default. See:
++ * https://gitlab.gnome.org/GNOME/tracker-miners/-/blob/master/src/miners/fs/tracker-main.c#L735 and
++ * https://gitlab.gnome.org/GNOME/tracker-miners/-/blob/master/data/org.freedesktop.Tracker.FTS.gschema.xml */
++ tracker_flags = TRACKER_SPARQL_CONNECTION_FLAGS_FTS_ENABLE_UNACCENT |
++ TRACKER_SPARQL_CONNECTION_FLAGS_FTS_ENABLE_STOP_WORDS |
++ TRACKER_SPARQL_CONNECTION_FLAGS_FTS_IGNORE_NUMBERS;
++
++ store = g_file_new_build_filename (g_get_user_data_dir (), "gnome-photos", NULL);
++
++ photos_debug (PHOTOS_DEBUG_TRACKER, "Opening local database at %s", g_file_peek_path (store));
++ self->local_connection = tracker_sparql_connection_new (tracker_flags,
++ store,
++ tracker_sparql_get_ontology_nepomuk (),
++ cancellable,
++ &self->initialization_error);
++
+ if (G_UNLIKELY (self->initialization_error != NULL))
+ goto out;
+
+- ret_val = TRUE;
++ /* Connect to filesystem indexer. */
++ miner_ok = photos_tracker_queue_start_session_miner_fs (self, &self->initialization_error);
++
++ if (!miner_ok && inside_flatpak ())
++ {
++ g_clear_error (&self->initialization_error);
++ miner_ok = photos_tracker_queue_start_local_miner_fs (self, &self->initialization_error);
++ }
++
++ if (miner_ok)
++ {
++ photos_debug (PHOTOS_DEBUG_TRACKER, "Using %s as tracker-miner-fs", self->miner_fs_busname);
++ ret_val = TRUE;
++ }
++ else
++ {
++ photos_debug (PHOTOS_DEBUG_TRACKER, "Initialization failed due to %s", self->initialization_error->message);
++ g_clear_object (&self->miner_fs_connection);
++ g_clear_object (&self->local_connection);
++ ret_val = FALSE;
++ }
++
+
+ out:
+ self->is_initialized = TRUE;
+@@ -319,10 +466,44 @@ photos_tracker_queue_initable_iface_init (GInitableIface *iface)
+ PhotosTrackerQueue *
+ photos_tracker_queue_dup_singleton (GCancellable *cancellable, GError **error)
+ {
++ GObject *singleton;
++
+ g_return_val_if_fail (cancellable == NULL || G_IS_CANCELLABLE (cancellable), NULL);
+ g_return_val_if_fail (error == NULL || *error == NULL, NULL);
+
+- return g_initable_new (PHOTOS_TYPE_TRACKER_QUEUE, cancellable, error, NULL);
++ singleton = g_object_new (PHOTOS_TYPE_TRACKER_QUEUE, NULL);
++
++ if (g_initable_init (G_INITABLE (singleton), cancellable, error))
++ return PHOTOS_TRACKER_QUEUE (singleton);
++
++ /* On error we deliberately don't unref the object so that we won't try
++ * and re-initialize Tracker when called again.
++ */
++ return NULL;
++}
++
++
++const gchar *
++photos_tracker_queue_get_miner_fs_busname (PhotosTrackerQueue *self)
++{
++ return self->miner_fs_busname;
++}
++
++
++const gchar *
++photos_tracker_queue_get_miner_fs_control_busname (PhotosTrackerQueue *self)
++{
++ return self->miner_fs_control_busname;
++}
++
++
++TrackerNotifier *
++photos_tracker_queue_get_notifier (PhotosTrackerQueue *self)
++{
++ /* We want notifications on filesystem changes, we don't need them for the
++ * local database which we manage ourselves.
++ */
++ return tracker_sparql_connection_create_notifier (self->miner_fs_connection);
+ }
+
+
+@@ -351,6 +532,7 @@ photos_tracker_queue_select (PhotosTrackerQueue *self,
+ }
+
+
++
+ void
+ photos_tracker_queue_update (PhotosTrackerQueue *self,
+ PhotosQuery *query,
+diff --git a/src/photos-tracker-queue.h b/src/photos-tracker-queue.h
+index 93d97306..6e31ecf6 100644
+--- a/src/photos-tracker-queue.h
++++ b/src/photos-tracker-queue.h
+@@ -24,16 +24,23 @@
+ #define PHOTOS_TRACKER_QUEUE_H
+
+ #include <gio/gio.h>
++#include <libtracker-sparql/tracker-sparql.h>
+
+ #include "photos-query.h"
+
+ G_BEGIN_DECLS
+
++#define TRACKER_PICTURES_GRAPH "http://tracker.api.gnome.org/ontology/v3/tracker#Pictures"
++
+ #define PHOTOS_TYPE_TRACKER_QUEUE (photos_tracker_queue_get_type ())
+ G_DECLARE_FINAL_TYPE (PhotosTrackerQueue, photos_tracker_queue, PHOTOS, TRACKER_QUEUE, GObject);
+
+ PhotosTrackerQueue *photos_tracker_queue_dup_singleton (GCancellable *cancellable, GError **error);
+
++const gchar * photos_tracker_queue_get_miner_fs_busname (PhotosTrackerQueue *self);
++const gchar * photos_tracker_queue_get_miner_fs_control_busname (PhotosTrackerQueue *self);
++TrackerNotifier * photos_tracker_queue_get_notifier (PhotosTrackerQueue *self);
++
+ void photos_tracker_queue_select (PhotosTrackerQueue *self,
+ PhotosQuery *query,
+ GCancellable *cancellable,
+diff --git a/src/photos-tracker-resources.xml b/src/photos-tracker-resources.xml
+deleted file mode 100644
+index 18177aa0..00000000
+--- a/src/photos-tracker-resources.xml
++++ /dev/null
+@@ -1,31 +0,0 @@
+-<!DOCTYPE node PUBLIC
+-"-//freedesktop//DTD D-BUS Object Introspection 1.0//EN"
+-"http://www.freedesktop.org/standards/dbus/1.0/introspect.dtd">
+-
+-<!--
+- Photos - access, organize and share your photos on GNOME
+- Copyright © 2012 – 2019 Red Hat, Inc.
+-
+- 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 3 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, see <http://www.gnu.org/licenses/>.
+--->
+-
+-<node name="/" xmlns:doc="http://www.freedesktop.org/dbus/1.0/doc.dtd">
+- <interface name="org.freedesktop.Tracker1.Resources">
+- <signal name="GraphUpdated">
+- <arg name="className" type="s" />
+- <arg name="deleteEvents" type="a(iiii)" />
+- <arg name="insertEvents" type="a(iiii)" />
+- </signal>
+- </interface>
+-</node>
+diff --git a/src/photos-utils.c b/src/photos-utils.c
+index c638297b..cc8593d8 100644
+--- a/src/photos-utils.c
++++ b/src/photos-utils.c
+@@ -1261,7 +1261,13 @@ photos_utils_set_edited_name (const gchar *urn, const gchar *title)
+ g_autoptr (PhotosTrackerQueue) queue = NULL;
+ g_autofree gchar *sparql = NULL;
+
+- sparql = g_strdup_printf ("INSERT OR REPLACE { <%s> nie:title \"%s\" }", urn, title);
++ sparql = g_strdup_printf ("WITH tracker:Pictures "
++ "DELETE { <%s> nie:title ?title } "
++ "INSERT { "
++ " <%s> a nmm:Photo ; "
++ " nie:title \"%s\" . "
++ "}"
++ "WHERE { <%s> nie:title ?title }", urn, urn, title, urn);
+ query = photos_query_new (NULL, sparql);
+
+ {
+@@ -1289,9 +1295,19 @@ photos_utils_set_favorite (const gchar *urn, gboolean is_favorite)
+ g_autoptr (PhotosTrackerQueue) queue = NULL;
+ g_autofree gchar *sparql = NULL;
+
+- sparql = g_strdup_printf ("%s { <%s> nao:hasTag nao:predefined-tag-favorite }",
+- (is_favorite) ? "INSERT OR REPLACE" : "DELETE",
+- urn);
++ if (is_favorite)
++ sparql = g_strdup_printf ("INSERT DATA { "
++ " GRAPH tracker:Pictures {"
++ " <%s> a nmm:Photo ; "
++ " nao:hasTag nao:predefined-tag-favorite "
++ " } "
++ "}", urn);
++ else
++ sparql = g_strdup_printf ("DELETE DATA {"
++ " GRAPH tracker:Pictures {"
++ " <%s> nao:hasTag nao:predefined-tag-favorite "
++ " } "
++ "}", urn);
+ query = photos_query_new (NULL, sparql);
+
+ {
+diff --git a/src/photos-utils.h b/src/photos-utils.h
+index 78ec3668..f1450f7d 100644
+--- a/src/photos-utils.h
++++ b/src/photos-utils.h
+@@ -46,7 +46,7 @@ G_BEGIN_DECLS
+ #define PHOTOS_TRACKER_CONTROLLER_EXTENSION_POINT_NAME "photos-tracker-controller"
+
+ #define PHOTOS_COLLECTION_SCREENSHOT \
+- "http://www.semanticdesktop.org/ontologies/2007/03/22/nfo#image-category-screenshot"
++ "http://tracker.api.gnome.org/ontology/v3/nfo#image-category-screenshot"
+ #define PHOTOS_EXPORT_SUBPATH "Exports"
+
+ typedef enum
+diff --git a/src/queries/all.sparql.template b/src/queries/all.sparql.template
+index 1cef98e8..14e08027 100644
+--- a/src/queries/all.sparql.template
++++ b/src/queries/all.sparql.template
+@@ -1,30 +1,42 @@
+-SELECT {{projection}}
++SELECT {{final_projection}}
++FROM NAMED tracker:Pictures
+ {
+ {
+- SELECT {{projection}}
++ SELECT {{main_projection}}
+ {
+ {
+ SELECT ?urn COUNT(?item) AS ?count
+ {
++ {{values}}
++ VALUES (?file ?filename ?creator ?publisher) { ("" "" "" "") }
+ ?urn a nfo:DataContainer.
+- ?item a nmm:Photo; nie:isPartOf ?urn.
+- } GROUP BY ?urn
++ ?item a nmm:Photo ;
++ nie:isPartOf ?urn .
++ {{item_pattern}}
++ }
++ GROUP BY ?urn
+ }
+ FILTER (?count > 0 && {{collections_default_filter}} && {{search_filter}})
+ }
+- GROUP BY ?urn
+ }
+ UNION
+ {
+- SELECT {{projection}}
++ SELECT {{second_projection}}
+ {
+- ?urn a nmm:Photo .
+- OPTIONAL { ?urn nco:creator ?creator . }
+- OPTIONAL { ?urn nco:publisher ?publisher . }
+ {{item_pattern}}
+- FILTER ({{photos_default_filter}} && {{source_filter}} && {{search_filter}})
++ SERVICE <dbus:{{miner_fs_busname}}>
++ {
++ SELECT {{main_projection}}
++ {
++ {{values}}
++ ?urn a nmm:Photo ;
++ nie:isStoredAs ?file .
++ OPTIONAL { ?urn nco:creator ?creator . }
++ OPTIONAL { ?urn nco:publisher ?publisher . }
++ FILTER ({{photos_default_filter}} && {{source_filter}} && {{search_filter}})
++ }
++ }
+ }
+- GROUP BY ?urn
+ }
+ }
+ {{order}}
+diff --git a/src/queries/collections.sparql.template b/src/queries/collections.sparql.template
+index 20b35cd6..4dfb345a 100644
+--- a/src/queries/collections.sparql.template
++++ b/src/queries/collections.sparql.template
+@@ -1,14 +1,20 @@
+-SELECT {{projection}}
++SELECT {{final_projection}}
+ {
++ SELECT {{main_projection}}
+ {
+- SELECT ?urn COUNT(?item) AS ?count
+ {
+- ?urn a nfo:DataContainer.
+- ?item a nmm:Photo; nie:isPartOf ?urn.
+- } GROUP BY ?urn
++ SELECT ?urn COUNT(?item) AS ?count
++ {
++ {{values}}
++ VALUES (?file ?filename ?creator ?publisher) { ("" "" "" "") }
++ ?urn a nfo:DataContainer .
++ ?item a nmm:Photo ;
++ nie:isPartOf ?urn .
++ }
++ GROUP BY ?urn
++ }
++ FILTER (?count > 0 && {{collections_default_filter}} && {{search_filter}})
+ }
+- FILTER (?count > 0 && {{collections_default_filter}} && {{search_filter}})
+ }
+-GROUP BY ?urn
+ {{order}}
+ {{offset_limit}}
+diff --git a/src/queries/favorite-photos.sparql.template b/src/queries/favorite-photos.sparql.template
+index 0885a08a..9a0d7806 100644
+--- a/src/queries/favorite-photos.sparql.template
++++ b/src/queries/favorite-photos.sparql.template
+@@ -1,12 +1,20 @@
+-SELECT {{projection}}
++SELECT {{final_projection}}
++FROM tracker:Pictures
+ {
+ ?urn a nmm:Photo .
+ ?urn a nmm:Photo; nao:hasTag nao:predefined-tag-favorite .
+- OPTIONAL { ?urn nco:creator ?creator . }
+- OPTIONAL { ?urn nco:publisher ?publisher . }
+- {{item_pattern}}
+- FILTER ({{photos_default_filter}} && {{source_filter}} && {{search_filter}})
++ SERVICE <dbus:{{miner_fs_busname}}>
++ {
++ SELECT {{main_projection}}
++ {
++ ?urn a nmm:Photo ;
++ nie:isStoredAs ?file .
++ OPTIONAL { ?urn nco:creator ?creator . }
++ OPTIONAL { ?urn nco:publisher ?publisher . }
++ {{item_pattern}}
++ FILTER ({{photos_default_filter}} && {{source_filter}} && {{search_filter}})
++ }
++ }
+ }
+-GROUP BY ?urn
+ {{order}}
+ {{offset_limit}}
+diff --git a/src/queries/photos.sparql.template b/src/queries/photos.sparql.template
+index 4eb10b74..6ade35e2 100644
+--- a/src/queries/photos.sparql.template
++++ b/src/queries/photos.sparql.template
+@@ -1,11 +1,19 @@
+-SELECT {{projection}}
++SELECT {{final_projection}}
++FROM tracker:Pictures
+ {
+- ?urn a nmm:Photo .
+- OPTIONAL { ?urn nco:creator ?creator . }
+- OPTIONAL { ?urn nco:publisher ?publisher . }
+- {{item_pattern}}
+- FILTER ({{photos_default_filter}} && {{source_filter}} && {{search_filter}})
++ SERVICE <dbus:{{miner_fs_busname}}>
++ {
++ SELECT {{main_projection}}
++ {
++ {{values}}
++ ?urn a nmm:Photo ;
++ nie:isStoredAs ?file .
++ OPTIONAL { ?urn nco:creator ?creator . }
++ OPTIONAL { ?urn nco:publisher ?publisher . }
++ {{item_pattern}}
++ FILTER ({{photos_default_filter}} && {{source_filter}} && {{search_filter}})
++ }
++ }
+ }
+-GROUP BY ?urn
+ {{order}}
+ {{offset_limit}}
+--
+GitLab
+