[gnome-documents] search-provider: implement the interface asynchronously



commit 96e344fd9354378bbd55a28808422a03fa4979f3
Author: Cosimo Cecchi <cosimoc gnome org>
Date:   Mon Mar 12 02:53:06 2012 -0400

    search-provider: implement the interface asynchronously
    
    Matches better the behavior of the other code in Documents.
    This also fixes a couple of bugs when the tracker cursor was not
    properly closed after fetching values from it, leading to an error
    condition in sqlite.

 src/shellSearchProvider.js |  525 ++++++++++++++++++++++++++++----------------
 1 files changed, 333 insertions(+), 192 deletions(-)
---
diff --git a/src/shellSearchProvider.js b/src/shellSearchProvider.js
index 54ab0d2..f4ac53c 100644
--- a/src/shellSearchProvider.js
+++ b/src/shellSearchProvider.js
@@ -73,6 +73,288 @@ const SearchProviderIface = <interface name={SEARCH_PROVIDER_IFACE}>
 </method>
 </interface>;
 
+function _createThumbnailIcon(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;
+}
+
+function _createGIcon(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 = _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;
+}
+
+function CreateCollectionIconJob(id) {
+    this._init(id);
+}
+
+CreateCollectionIconJob.prototype = {
+    _init: function(id) {
+        this._id = id;
+        this._itemIcons = [];
+        this._itemIds = [];
+        this._itemJobs = 0;
+    },
+
+    run: function(callback) {
+        this._callback = callback;
+
+        let query = Global.queryBuilder.buildCollectionIconQuery(this._id);
+        Global.connectionQueue.add(query.sparql, null, Lang.bind(this,
+            function(object, res) {
+                let cursor = null;
+
+                try {
+                    cursor = object.query_finish(res);
+                    cursor.next_async(null, Lang.bind(this, this._onCursorNext));
+                } catch (e) {
+                    log('Error querying tracker: ' + e);
+                    this._hasItemIds();
+                }
+            }));
+    },
+
+    _createItemIcon: function(cursor) {
+        let pixbuf = null;
+        let icon = _createGIcon(cursor);
+
+        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, _SHELL_SEARCH_ICON_SIZE,
+                                      flags);
+
+            try {
+                pixbuf = info.load_icon();
+            } catch(e) {
+                log("Unable to load pixbuf: " + e);
+            }
+        } else if (icon instanceof Gio.FileIcon) {
+            try {
+                let stream = icon.load(_SHELL_SEARCH_ICON_SIZE, null)[0];
+                pixbuf = GdkPixbuf.Pixbuf.new_from_stream(stream,
+                                                          null);
+            } catch(e) {
+                log("Unable to load pixbuf: " + e);
+            }
+        }
+
+        return pixbuf;
+    },
+
+    _onCursorNext: function(cursor, res) {
+        let valid = false;
+
+        try {
+            valid = cursor.next_finish(res);
+        } catch (e) {
+            cursor.close();
+            log('Error querying tracker: ' + e);
+
+            this._hasItemIds();
+        }
+
+        if (valid) {
+            this._itemIds.push(cursor.get_string(0)[0]);
+            cursor.next_async(null, Lang.bind(this, this._onCursorNext));
+        } else {
+            cursor.close();
+            this._hasItemIds();
+        }
+    },
+
+    _hasItemIds: function() {
+        if (this._itemIds.length == 0) {
+            this._returnPixbuf();
+            return;
+        }
+
+        this._itemIds.forEach(Lang.bind(this,
+            function(itemId) {
+                let job = new Documents.SingleItemJob(itemId);
+                this._itemJobs++;
+                job.run(Query.QueryFlags.UNFILTERED, Lang.bind(this,
+                    function(cursor) {
+                        let icon = this._createItemIcon(cursor);
+                        if (icon)
+                            this._itemIcons.push(icon);
+                        this._itemJobCollector();
+                    }));
+            }));
+    },
+
+    _itemJobCollector: function() {
+        this._itemJobs--;
+
+        if (this._itemJobs == 0)
+            this._returnPixbuf();
+    },
+
+    _returnPixbuf: function() {
+        this._callback(Gd.create_collection_icon(_SHELL_SEARCH_ICON_SIZE, this._itemIcons));
+    }
+};
+
+function FetchMetasJob(ids) {
+    this._init(ids);
+}
+
+FetchMetasJob.prototype = {
+    _init: function(ids) {
+        this._ids = ids;
+        this._metas = [];
+    },
+
+    _jobCollector: function() {
+        this._activeJobs--;
+
+        if (this._activeJobs == 0)
+            this._callback(this._metas);
+    },
+
+    _createCollectionPixbuf: function(meta) {
+        let job = new CreateCollectionIconJob(meta.id);
+        job.run(Lang.bind(this,
+            function(icon) {
+                if (icon)
+                    meta.pixbuf = icon;
+
+                this._metas.push(meta);
+                this._jobCollector();
+            }));
+    },
+
+    run: function(callback) {
+        this._callback = callback;
+        this._activeJobs = this._ids.length;
+
+        this._ids.forEach(Lang.bind(this,
+            function(id) {
+                let single = new Documents.SingleItemJob(id);
+                single.run(Query.QueryFlags.UNFILTERED, Lang.bind(this,
+                    function(cursor) {
+                        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
+                        let isCollection = (rdftype.indexOf('nfo#DataContainer') != -1);
+
+                        if (!isCollection)
+                            gicon = _createGIcon(cursor);
+
+                        if (!title || title == '')
+                            title = Gd.filename_strip_extension(filename);
+
+                        if (!title || title == '')
+                            title = _("Untitled Document");
+
+                        let meta = { id: id, title: title, icon: gicon };
+
+                        if (isCollection) {
+                            this._createCollectionPixbuf(meta);
+                        } else {
+                            this._metas.push(meta);
+                            this._jobCollector();
+                        }
+                    }));
+            }));
+    }
+};
+
+function FetchIdsJob(terms) {
+    this._init(terms);
+}
+
+FetchIdsJob.prototype = {
+    _init: function(terms) {
+        this._terms = terms;
+        this._ids = [];
+    },
+
+    run: function(callback) {
+        this._callback = callback;
+        Global.searchController.setString(this._terms.join(' ').toLowerCase());
+
+        let query = Global.queryBuilder.buildGlobalQuery();
+        Global.connectionQueue.add(query.sparql, null, Lang.bind(this,
+            function(object, res) {
+                let cursor = null;
+
+                try {
+                    cursor = object.query_finish(res);
+                    cursor.next_async(null, Lang.bind(this, this._onCursorNext));
+                } catch (e) {
+                    log('Error querying tracker: ' + e);
+                    callback(this._ids);
+                }
+            }));
+    },
+
+    _onCursorNext: function(cursor, res) {
+        let valid = false;
+
+        try {
+            valid = cursor.next_finish(res);
+        } catch (e) {
+            cursor.close();
+            log('Error querying tracker: ' + e);
+
+            this._callback(this._ids);
+        }
+
+        if (valid) {
+            this._ids.push(cursor.get_string(Query.QueryColumns.URN)[0]);
+            cursor.next_async(null, Lang.bind(this, this._onCursorNext));
+        } else {
+            cursor.close();
+            this._callback(this._ids);
+        }
+    }
+};
+
 function ShellSearchProvider() {
     this._init();
 }
@@ -157,216 +439,75 @@ ShellSearchProvider.prototype = {
                                                                  this.quit));
     },
 
-    _createThumbnailIcon: function(uri) {
-        let file = Gio.file_new_for_uri(uri);
+    _returnMetasFromCache: function(ids, invocation) {
+        let metas = [];
+        for (let i = 0; i < ids.length; i++) {
+            let id = ids[i];
 
-        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;
-    },
+            if (!this._cache[id])
+                continue;
 
-    _createGIcon: function(cursor) {
-        let gicon = null;
+            let meta = { id: GLib.Variant.new('s', this._cache[id].id),
+                         name: GLib.Variant.new('s', this._cache[id].title) };
 
-        let ident = cursor.get_string(Query.QueryColumns.IDENTIFIER)[0];
-        let isRemote = ident && (ident.indexOf('https://docs.google.com') != -1);
+            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);
 
-        if (!isRemote) {
-            let uri = cursor.get_string(Query.QueryColumns.URI)[0];
-            if (uri)
-                gicon = this._createThumbnailIcon(uri);
+            metas.push(meta);
         }
-
-        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;
+        invocation.return_value(GLib.Variant.new('(aa{sv})', [ metas ]));
     },
 
-    _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;
-                }
+    GetInitialResultSetAsync: function(params, invocation) {
+        let terms = params[0];
+        this._resetTimeout();
 
-                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, _SHELL_SEARCH_ICON_SIZE,
-                                                     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(_SHELL_SEARCH_ICON_SIZE, null)[0];
-                        let pixbuf = GdkPixbuf.Pixbuf.new_from_stream(stream,
-                                                                      null);
-                        pixbufs.push(pixbuf);
-                    } catch(e) {
-                        log("Unable to load pixbuf: " + e);
-                    }
-                }
+        let job = new FetchIdsJob(terms);
+        job.run(Lang.bind(this,
+            function(ids) {
+                invocation.return_value(GLib.Variant.new('(as)', [ ids ]));
             }));
-        return Gd.create_collection_icon(_SHELL_SEARCH_ICON_SIZE, pixbufs);
-    },
-
-    _doSearch: function(terms) {
-        Global.searchController.setString(terms.join(' ').toLowerCase());
-        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) {
+    GetSubsearchResultSetAsync: function(params, invocation) {
+        let [previousResults, terms] = params;
         this._resetTimeout();
-        return this._doSearch(terms);
-    },
 
-    GetSubsearchResultSet: function(previousResults, terms) {
-        this._resetTimeout();
-        return this._doSearch(terms);
+        let job = new FetchIdsJob(terms);
+        job.run(Lang.bind(this,
+            function(ids) {
+                invocation.return_value(GLib.Variant.new('(as)', [ ids ]));
+            }));
     },
 
-    GetResultMetas: function(ids) {
+    GetResultMetasAsync: function(params, invocation) {
+        let ids = params[0];
         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);
+        let toFetch = ids.filter(Lang.bind(this,
+            function(id) {
+                return !(this._cache[id]);
+            }));
 
-            metas.push(meta);
+        if (toFetch.length > 0) {
+            let job = new FetchMetasJob(toFetch);
+            job.run(Lang.bind(this,
+                function(metas) {
+                    // cache the newly fetched results
+                    metas.forEach(Lang.bind(this,
+                        function(meta) {
+                            this._cache[meta.id] = meta;
+                        }));
+
+                    this._returnMetasFromCache(ids, invocation);
+                }));
+        } else {
+            this._returnMetasFromCache(ids, invocation);
         }
-        return metas;
     },
 
     ActivateResult: function(id) {
@@ -388,7 +529,7 @@ ShellSearchProvider.prototype = {
 
     run: function() {
         Mainloop.run(MAINLOOP_ID);
-    },
+    }
 };
 
 function start() {



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