[gnome-documents] Implement a ShellSearchProvider



commit 274adca6a7e8454a2a01c1a7675c09d8da6b02e3
Author: Florian MÃllner <fmuellner gnome org>
Date:   Tue Nov 15 01:47:52 2011 +0100

    Implement a ShellSearchProvider
    
    GNOME Shell provides a DBus interface for external search provider
    implementations; add a small auto-activated service which implements
    that interface, so that search results for GNOME Documents show up
    in the Shell's overview.
    
    https://bugzilla.gnome.org/show_bug.cgi?id=662453

 .gitignore                                        |    3 +
 data/Makefile.am                                  |    9 +
 data/gnome-documents-search-provider.ini.in       |    5 +
 po/POTFILES.in                                    |    2 +
 src/Makefile-js.am                                |    1 +
 src/Makefile.am                                   |   22 ++
 src/gnome-documents-search-provider.in            |   21 ++
 src/org.gnome.Documents.SearchProvider.service.in |    3 +
 src/shellSearchProvider.js                        |  395 +++++++++++++++++++++
 9 files changed, 461 insertions(+), 0 deletions(-)
---
diff --git a/.gitignore b/.gitignore
index b3614ce..0b5662b 100644
--- a/.gitignore
+++ b/.gitignore
@@ -16,6 +16,7 @@ config.log
 config.status
 config
 configure
+data/gnome-documents-search-provider.ini
 data/gnome-documents.desktop
 data/gnome-documents.desktop.in
 data/org.gnome.Documents.GDataMiner.service
@@ -44,7 +45,9 @@ src/Makefile.in
 src/gd-tracker-gdata-miner
 src/gnome-documents
 src/gnome-documents-debug
+src/gnome-documents-search-provider
 src/path.js
+src/org.gnome.Documents.SearchProvider.service
 stamp-h1
 xmldocs.make
 *~
diff --git a/data/Makefile.am b/data/Makefile.am
index 8cd6ae9..5ce93c2 100644
--- a/data/Makefile.am
+++ b/data/Makefile.am
@@ -4,8 +4,15 @@ desktopdir = $(datadir)/applications
 desktop_DATA = $(desktop_in_files:.desktop.in=.desktop)
 desktop_in_files = gnome-documents.desktop.in
 
+searchproviderdir = $(datadir)/gnome-shell/search-providers
+searchprovider_DATA = gnome-documents-search-provider.ini
+searchprovider_in_files = gnome-documents-search-provider.ini.in
+
 @INTLTOOL_DESKTOP_RULE@
 
+%.ini: %.ini.in
+	LC_ALL=C $(INTLTOOL_MERGE) -d -u -c $(top_builddir)/po/.intltool-merge-cache $(top_srcdir)/po $< $@
+
 servicedir = $(datadir)/dbus-1/services
 service_DATA = $(service_in_files:.service.in=.service)
 $(service_DATA): $(service_in_files) Makefile
@@ -34,9 +41,11 @@ EXTRA_DIST= \
     gnome-documents.desktop \
     $(service_in_files) \
     $(desktop_in_files) \
+    $(searchprovider_in_files) \
     $(gsettingsschema_in_files)
 
 CLEANFILES = \
     $(desktop_DATA) \
+    $(searchprovider_DATA) \
     $(gsettings_SCHEMAS) \
     $(service_DATA)
diff --git a/data/gnome-documents-search-provider.ini.in b/data/gnome-documents-search-provider.ini.in
new file mode 100644
index 0000000..383e2c4
--- /dev/null
+++ b/data/gnome-documents-search-provider.ini.in
@@ -0,0 +1,5 @@
+[Shell Search Provider]
+_Title=Documents
+Icon=gnome-documents
+BusName=org.gnome.Documents.SearchProvider
+ObjectPath=/org/gnome/Documents/SearchProvider
diff --git a/po/POTFILES.in b/po/POTFILES.in
index 4137ee4..83b239c 100644
--- a/po/POTFILES.in
+++ b/po/POTFILES.in
@@ -1,4 +1,5 @@
 [encoding: UTF-8]
+data/gnome-documents-search-provider.ini.in
 data/gnome-documents.desktop.in.in
 data/org.gnome.documents.gschema.xml.in
 src/application.js
@@ -11,6 +12,7 @@ src/mainToolbar.js
 src/mainWindow.js
 src/searchbar.js
 src/selections.js
+src/shellSearchProvider.js
 src/sources.js
 src/spinnerBox.js
 src/view.js
diff --git a/src/Makefile-js.am b/src/Makefile-js.am
index 00a8351..7adbc07 100644
--- a/src/Makefile-js.am
+++ b/src/Makefile-js.am
@@ -18,6 +18,7 @@ dist_js_DATA = \
     query.js \
     searchbar.js \
     selections.js \
+    shellSearchProvider.js \
     sources.js \
     spinnerBox.js \
     trackerController.js \
diff --git a/src/Makefile.am b/src/Makefile.am
index 31078dd..f2a344a 100644
--- a/src/Makefile.am
+++ b/src/Makefile.am
@@ -19,6 +19,7 @@ INTROSPECTION_GIRS =
 gir_DATA =
 typelib_DATA =
 libexec_PROGRAMS =
+libexec_SCRIPTS =
 
 # convenience command for doing Makefile variable substitutions in non-Makefile
 # files (scripts, service files, etc.)
@@ -58,3 +59,24 @@ CLEANFILES += \
 EXTRA_DIST += \
     gnome-documents.in \
     gnome-documents-debug.in
+
+libexec_SCRIPTS += gnome-documents-search-provider
+
+gnome-documents-search-provider: gnome-documents-search-provider.in
+	$(AM_V_GEN) $(do_subst) $< > $@
+	chmod +x $@
+
+CLEANFILES += gnome-documents-search-provider
+EXTRA_DIST += gnome-documents-search-provider.in
+
+service_in_files = org.gnome.Documents.SearchProvider.service.in
+
+servicedir = $(datadir)/dbus-1/services
+service_DATA = $(service_in_files:.service.in=.service)
+
+%.service: %.service.in Makefile
+	$(AM_V_GEN) [ -d $(@D) ] || $(mkdir_p) $(@D) ; \
+	            sed -e "s|\ libexecdir\@|$(libexecdir)|" $< > $  tmp && mv $  tmp $@
+
+CLEANFILES = += $(service_DATA)
+EXTRA_DIST += $(service_in_files)
diff --git a/src/gnome-documents-search-provider.in b/src/gnome-documents-search-provider.in
new file mode 100644
index 0000000..bf75ab1
--- /dev/null
+++ b/src/gnome-documents-search-provider.in
@@ -0,0 +1,21 @@
+#!/bin/bash
+
+if test x"$GJS_PATH" = x ; then
+    export GJS_PATH= pkgdatadir@/js
+else
+    export GJS_PATH= pkgdatadir@/js:$GJS_PATH
+fi
+
+if test x"$GI_TYPELIB_PATH" = x ; then
+    export GI_TYPELIB_PATH= pkglibdir@/girepository-1.0
+else
+    export GI_TYPELIB_PATH= pkglibdir@/girepository-1.0:$GI_TYPELIB_PATH
+fi
+
+if test x"$LD_LIBRARY_PATH" = x ; then
+    export LD_LIBRARY_PATH= pkglibdir@
+else
+    export LD_LIBRARY_PATH= pkglibdir@:$LD_LIBRARY_PATH
+fi
+
+ GJS_CONSOLE@ -I @pkgdatadir@/js -c "const SearchProvider = imports.shellSearchProvider; SearchProvider.start();"
diff --git a/src/org.gnome.Documents.SearchProvider.service.in b/src/org.gnome.Documents.SearchProvider.service.in
new file mode 100644
index 0000000..0d520d6
--- /dev/null
+++ b/src/org.gnome.Documents.SearchProvider.service.in
@@ -0,0 +1,3 @@
+[D-BUS Service]
+Name=org.gnome.Documents.SearchProvider
+Exec= libexecdir@/gnome-documents-search-provider
diff --git a/src/shellSearchProvider.js b/src/shellSearchProvider.js
new file mode 100644
index 0000000..aa81df1
--- /dev/null
+++ b/src/shellSearchProvider.js
@@ -0,0 +1,395 @@
+/*
+ * Copyright (c) 2011 Red Hat, Inc.
+ *
+ * Gnome Documents is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ *
+ * Gnome Documents 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 Gnome Documents; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
+ *
+ * Author: Florian MÃllner <fmuellner redhat com>
+ *
+ */
+
+const Lang = imports.lang;
+const Mainloop = imports.mainloop;
+const Gettext = imports.gettext;
+
+const Gd = imports.gi.Gd;
+const GdkPixbuf = imports.gi.GdkPixbuf;
+const Gio = imports.gi.Gio;
+const Goa = imports.gi.Goa;
+const Gtk = imports.gi.Gtk;
+const GLib = imports.gi.GLib;
+const Tracker = imports.gi.Tracker;
+
+const ChangeMonitor = imports.changeMonitor;
+const Documents = imports.documents;
+const Error = imports.error;
+const Format = imports.format;
+const Global = imports.global;
+const Manager = imports.manager;
+const Path = imports.path;
+const OffsetController = imports.offsetController;
+const Query = imports.query;
+const Searchbar = imports.searchbar;
+const Sources = imports.sources;
+const TrackerController = imports.trackerController;
+const Utils = imports.utils;
+
+const MAINLOOP_ID = "documents-search-provider";
+const AUTOQUIT_TIMEOUT = 120;
+
+const SEARCH_PROVIDER_IFACE = 'org.gnome.Shell.SearchProvider';
+const SEARCH_PROVIDER_NAME  = 'org.gnome.Documents.SearchProvider';
+const SEARCH_PROVIDER_PATH  = '/org/gnome/Documents/SearchProvider';
+
+const SearchProviderIface = <interface name={SEARCH_PROVIDER_IFACE}>
+<method name="GetInitialResultSet">
+  <arg type="as" direction="in" />
+  <arg type="as" direction="out" />
+</method>
+<method name = "GetSubsearchResultSet">
+  <arg type="as" direction="in" />
+  <arg type="as" direction="in" />
+  <arg type="as" direction="out" />
+</method>
+<method name = "GetResultMetas">
+  <arg type="as" direction="in" />
+  <arg type="aa{sv}" direction="out" />
+</method>
+<method name = "ActivateResult">
+  <arg type="s" direction="in" />
+</method>
+</interface>;
+
+function ShellSearchProvider() {
+    this._init();
+}
+
+ShellSearchProvider.prototype = {
+    _init: function() {
+        Gio.DBus.own_name(Gio.BusType.SESSION,
+                          SEARCH_PROVIDER_NAME,
+                          Gio.BusNameOwnerFlags.NONE,
+                          Lang.bind(this, this._onBusAcquired),
+                          Lang.bind(this, this._onNameAcquired),
+                          Lang.bind(this, this._onNameLost));
+
+        this._cache = {};
+        this._initReal();
+
+        this._timeoutId = 0;
+    },
+
+    _onBusAcquired: function() {
+        let dbusImpl = Gio.DBusExportedObject.wrapJSObject(SearchProviderIface, this);
+        dbusImpl.export(Gio.DBus.session, SEARCH_PROVIDER_PATH);
+    },
+
+    _onNameAcquired: function() {
+        this._resetTimeout();
+    },
+
+    _onNameLost: function() {
+        this.quit();
+    },
+
+    _initReal: function() {
+        String.prototype.format = Format.format;
+
+        Gtk.init(null, null);
+
+        Global.application = this;
+        Global.settings = new Gio.Settings({ schema: 'org.gnome.documents' });
+        Global.offsetController = new OffsetController.OffsetController();
+        Global.searchController = new Searchbar.SearchController();
+        Global.errorHandler = new Error.ErrorHandler();
+
+        // connect to tracker
+        try {
+            Global.connection = Tracker.SparqlConnection.get(null);
+        } catch (e) {
+            log('Unable to connect to the tracker database: ' + e.toString());
+            this.quit();
+        }
+
+        try {
+            Global.goaClient = Goa.Client.new_sync(null);
+        } catch (e) {
+            log('Unable to create the GOA client: ' + e.toString());
+            this.quit();
+        }
+
+        Global.connectionQueue = new TrackerController.TrackerConnectionQueue();
+        Global.sourceManager = new Sources.SourceManager();
+        Global.searchCategoryManager = new Searchbar.SearchCategoryManager();
+        Global.searchMatchManager = new Searchbar.SearchMatchManager();
+        Global.searchTypeManager = new Searchbar.SearchTypeManager();
+        Global.queryBuilder = new Query.QueryBuilder();
+        Global.changeMonitor = new ChangeMonitor.TrackerChangeMonitor();
+        Global.collectionManager = new Manager.BaseManager();
+        Global.documentManager = new Documents.DocumentManager();
+        Global.trackerController = new TrackerController.TrackerController();
+    },
+
+    _resetTimeout: function() {
+        if (this._timeoutId) {
+            Mainloop.source_remove(this._timeoutId);
+            this._timeoutId = 0;
+        }
+
+        if (GLib.getenv('DOCUMENTS_SEARCH_PROVIDER_PERSIST'))
+            return;
+
+        this._timeoutId = Mainloop.timeout_add_seconds(AUTOQUIT_TIMEOUT,
+                                                       Lang.bind(this,
+                                                                 this.quit));
+    },
+
+    _createThumbnailIcon: function(uri) {
+        let file = Gio.file_new_for_uri(uri);
+
+        try {
+            let info = file.query_info(Gio.FILE_ATTRIBUTE_THUMBNAIL_PATH,
+                                       0, null);
+            let path = info.get_attribute_byte_string(Gio.FILE_ATTRIBUTE_THUMBNAIL_PATH);
+            if (path)
+                return new Gio.FileIcon({ file: Gio.file_new_for_path(path) });
+        } catch(e) {
+            log(e);
+        }
+        return null;
+    },
+
+    _createGIcon: function(cursor) {
+        let gicon = null;
+
+        let ident = cursor.get_string(Query.QueryColumns.IDENTIFIER)[0];
+        let isRemote = ident && (ident.indexOf('https://docs.google.com') != -1);
+
+        if (!isRemote) {
+            let uri = cursor.get_string(Query.QueryColumns.URI)[0];
+            if (uri)
+                gicon = this._createThumbnailIcon(uri);
+        }
+
+        if (gicon)
+            return gicon;
+
+        let mimetype = cursor.get_string(Query.QueryColumns.MIMETYPE)[0];
+        if (mimetype)
+            gicon = Gio.content_type_get_icon(mimetype);
+
+        if (gicon)
+            return gicon;
+
+        let rdftype = cursor.get_string(Query.QueryColumns.RDFTYPE)[0];
+        if (rdftype)
+            gicon = Utils.iconFromRdfType(rdftype);
+
+        if (!gicon)
+            gicon = new Gio.ThemedIcon({ name: 'text-x-generic' });
+
+        return gicon;
+    },
+
+    _createCollectionPixbuf: function(urn) {
+        let query = Global.queryBuilder.buildCollectionIconQuery(urn);
+        let cursor = Global.connection.query(query.sparql, null);
+
+        let collectionUrns = [];
+        while (true) {
+            try {
+                if (!cursor.next(null)) {
+                    cursor.close();
+                    break;
+                }
+            } catch(e) {
+                cursor.close();
+                break;
+            }
+
+            let urn = cursor.get_string(0)[0];
+            collectionUrns.push(urn);
+        }
+
+        let pixbufs = [];
+        collectionUrns.forEach(Lang.bind(this,
+            function(urn) {
+                let query = Global.queryBuilder.buildSingleQuery(urn);
+                let cursor = Global.connection.query(query.sparql, null);
+
+                let valid;
+                try {
+                    valid = cursor.next(null);
+                } catch(e) {
+                    log("Failed to query tracker: " + e);
+                    valid = false;
+                }
+
+                if (!valid) {
+                    cursor.close();
+                    return;
+                }
+
+                let icon = this._createGIcon(cursor);
+                cursor.close();
+
+                if (icon instanceof Gio.ThemedIcon) {
+                    let theme = Gtk.IconTheme.get_default();
+                    let flags = Gtk.IconLookupFlags.FORCE_SIZE |
+                                Gtk.IconLookupFlags.GENERIC_FALLBACK;
+                    let info = theme.lookup_by_gicon(icon, Utils.getIconSize(),
+                                                     flags);
+
+                    try {
+                        let pixbuf = info.load_icon();
+                        pixbufs.push(pixbuf);
+                    } catch(e) {
+                        log("Unable to load pixbuf: " + e);
+                    }
+                } else if (icon instanceof Gio.FileIcon) {
+                    try {
+                        let stream = icon.load(Utils.getIconSize(), null)[0];
+                        let pixbuf = GdkPixbuf.Pixbuf.new_from_stream(stream,
+                                                                      null);
+                        pixbufs.push(pixbuf);
+                    } catch(e) {
+                        log("Unable to load pixbuf: " + e);
+                    }
+                }
+            }));
+        return Gd.create_collection_icon(Utils.getIconSize(), pixbufs);
+    },
+
+    _doSearch: function(terms) {
+        Global.searchController.setString(terms.join(' '));
+        let query = Global.queryBuilder.buildGlobalQuery();
+        let cursor = Global.connection.query(query.sparql, null);
+        let ids = [];
+        while(true) {
+            try {
+                let valid = cursor.next(null);
+
+                if (!valid) {
+                    cursor.close();
+                    break;
+                }
+            } catch(e) {
+                cursor.close();
+                log('Error querying tracker: ' + e);
+                break;
+            }
+
+            ids.push(cursor.get_string(Query.QueryColumns.URN)[0]);
+        }
+        return ids;
+    },
+
+    _ensureResultMeta: function(id) {
+        if (this._cache[id])
+            return;
+
+        let query = Global.queryBuilder.buildSingleQuery(id);
+        let cursor = Global.connection.query(query.sparql, null);
+
+        try {
+            let valid = cursor.next(null);
+
+            if (!valid)
+                cursor.close();
+        } catch(e) {
+            log("Failed to query tracker: " + e);
+            cursor.close();
+        }
+
+        let title =    cursor.get_string(Query.QueryColumns.TITLE)[0];
+        let filename = cursor.get_string(Query.QueryColumns.FILENAME)[0];
+        let rdftype =  cursor.get_string(Query.QueryColumns.RDFTYPE)[0];
+
+        let gicon = null;
+        let pixbuf = null;
+
+        // Collection
+        if (rdftype.indexOf('nfo#DataContainer') != -1)
+            pixbuf = this._createCollectionPixbuf(id);
+        else
+            gicon = this._createGIcon(cursor);
+
+        if (!title || title == '')
+            title = Gd.filename_strip_extension(filename);
+
+        if (!title || title == '')
+            title = _("Untitled Document");
+
+        this._cache[id] = { id: id, title: title, icon: gicon, pixbuf: pixbuf };
+    },
+
+    GetInitialResultSet: function(terms) {
+        this._resetTimeout();
+        return this._doSearch(terms);
+    },
+
+    GetSubsearchResultSet: function(previousResults, terms) {
+        this._resetTimeout();
+        return this._doSearch(terms);
+    },
+
+    GetResultMetas: function(ids) {
+        this._resetTimeout();
+
+        let metas = [];
+        for (let i = 0; i < ids.length; i++) {
+            let id = ids[i];
+
+            this._ensureResultMeta(id);
+            let meta = { id: GLib.Variant.new('s', this._cache[id].id),
+                         name: GLib.Variant.new('s', this._cache[id].title) };
+
+            let gicon = this._cache[id].icon;
+            let pixbuf = this._cache[id].pixbuf;
+            if (gicon)
+                meta['gicon'] = GLib.Variant.new('s', gicon.to_string());
+            else if (pixbuf)
+                meta['icon-data'] = Gd.create_variant_from_pixbuf(pixbuf);
+
+            metas.push(meta);
+        }
+        return metas;
+    },
+
+    ActivateResult: function(id) {
+        let app = Gio.DesktopAppInfo.new('gnome-documents.desktop');
+        if (!app)
+            return;
+
+        try {
+            if (!app.launch_uris([id], null))
+                log('Activating result "' + id + '" failed');
+        } catch(e) {
+            log('Activating result "' + id + '" failed - ' + e);
+        }
+    },
+
+    quit: function() {
+        Mainloop.quit(MAINLOOP_ID);
+    },
+
+    run: function() {
+        Mainloop.run(MAINLOOP_ID);
+    },
+};
+
+function start() {
+    let searchProvider = new ShellSearchProvider();
+    searchProvider.run();
+}



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