[gnome-documents] all: implement refresh information for single elements



commit d39d9c49f3732535b1922ae20683feb4de75d726
Author: Cosimo Cecchi <cosimoc gnome org>
Date:   Fri Aug 26 01:47:22 2011 -0400

    all: implement refresh information for single elements
    
    This way we can be notified live of the changes in the model on one of
    the displayed resources.
    The code seems to hit a weird bug where toggling the favorite tag only
    works once; still to investigate why, but the wrong results seem to come
    directly from the TrackerSparqlCursor.

 src/Makefile-js.am   |    2 +
 src/application.js   |    7 +++-
 src/changeMonitor.js |  108 ++++++++++++++++++++++++++++++++++++++++++++++
 src/docFactory.js    |   89 ++++++++++++++++++++++++++++++--------
 src/global.js        |    1 +
 src/query.js         |  115 +++++++++++++++++++++++++++++++++++++++++++++++++
 src/trackerModel.js  |  117 ++++++++------------------------------------------
 7 files changed, 321 insertions(+), 118 deletions(-)
---
diff --git a/src/Makefile-js.am b/src/Makefile-js.am
index e5e4f55..09af567 100644
--- a/src/Makefile-js.am
+++ b/src/Makefile-js.am
@@ -2,6 +2,7 @@ jsdir = $(pkgdatadir)/js/
 dist_js_DATA = \
     application.js \
     categories.js \
+    changeMonitor.js \
     docFactory.js \
     filterController.js \
     gDataMiner.js \
@@ -14,6 +15,7 @@ dist_js_DATA = \
     mainWindow.js \
     offsetController.js \
     preview.js \
+    query.js \
     selectionController.js \
     sidebar.js \
     sources.js \
diff --git a/src/application.js b/src/application.js
index b5c6aae..a7d8660 100644
--- a/src/application.js
+++ b/src/application.js
@@ -31,6 +31,7 @@ const GLib = imports.gi.GLib;
 const Tracker = imports.gi.Tracker;
 
 const Categories = imports.categories;
+const ChangeMonitor = imports.changeMonitor;
 const FilterController = imports.filterController;
 const Format = imports.format;
 const Global = imports.global;
@@ -38,6 +39,7 @@ const Main = imports.main;
 const MainWindow = imports.mainWindow;
 const OffsetController = imports.offsetController;
 const Path = imports.path;
+const Query = imports.query;
 const SelectionController = imports.selectionController;
 const Sources = imports.sources;
 const TrackerModel = imports.trackerModel;
@@ -128,7 +130,10 @@ Application.prototype = {
 
     _onSourceManagerCreated: function() {
         Global.selectionController = new SelectionController.SelectionController();
-        Global.model = new TrackerModel.TrackerModel(Global.connection);
+        Global.queryBuilder = new Query.QueryBuilder();
+        Global.model = new TrackerModel.TrackerModel();
+        Global.changeMonitor = new ChangeMonitor.TrackerChangeMonitor();
+
         this._mainWindow = new MainWindow.MainWindow();
         this.activate();
     },
diff --git a/src/changeMonitor.js b/src/changeMonitor.js
new file mode 100644
index 0000000..ae9e09d
--- /dev/null
+++ b/src/changeMonitor.js
@@ -0,0 +1,108 @@
+/*
+ * 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: Cosimo Cecchi <cosimoc redhat com>
+ *
+ */
+
+const DBus = imports.dbus;
+const Lang = imports.lang;
+const Signals = imports.signals;
+
+const Global = imports.global;
+
+const TrackerResourcesServiceIface = {
+    name: 'org.freedesktop.Tracker1.Resources',
+    signals: [{ name: 'GraphUpdated',
+                inSignature: 'sa(iiii)a(iiii)' }]
+};
+
+function TrackerResourcesService() {
+    this._init();
+}
+
+TrackerResourcesService.prototype = {
+    _init: function() {
+        DBus.session.proxifyObject(this,
+                                   'org.freedesktop.Tracker1',
+                                   '/org/freedesktop/Tracker1/Resources');
+    }
+};
+DBus.proxifyPrototype(TrackerResourcesService.prototype, TrackerResourcesServiceIface);
+
+function TrackerChangeMonitor() {
+    this._init();
+}
+
+TrackerChangeMonitor.prototype = {
+    _init: function() {
+        this._outstandingOps = 0;
+        this._pendingChanges = [];
+
+        this._resourceService = new TrackerResourcesService();
+        this._resourceService.connect('GraphUpdated', Lang.bind(this, this._onGraphUpdated));
+    },
+
+    _onGraphUpdated: function(proxy, className, deleteEvents, insertEvents) {
+        deleteEvents.forEach(Lang.bind(this,
+            function(event) {
+                this._outstandingOps++;
+                this._updateIterator(event);
+            }));
+
+        insertEvents.forEach(Lang.bind(this,
+            function(event) {
+                this._outstandingOps++;
+                this._updateIterator(event);
+            }));
+    },
+
+    _updateIterator: function(event) {
+        // we're only interested in the resource URN, as we will query for
+        // the item properties again, but we still want to compress deletes and inserts
+        Global.connection.query_async(
+            ('SELECT tracker:uri(%d) {}').format(event[1]),
+            null, Lang.bind(this,
+                function(object, res) {
+                    let cursor = object.query_finish(res);
+
+                    cursor.next_async(null, Lang.bind(this,
+                        function(object, res) {
+                            let valid = cursor.next_finish(res);
+
+                            if (valid) {
+                                let subject = cursor.get_string(0)[0];
+                                if (this._pendingChanges.indexOf(subject) == -1)
+                                    this._pendingChanges.push(subject);
+                            }
+
+                            this._updateCollector();
+                        }));
+                }));
+    },
+
+    _updateCollector: function() {
+        this._outstandingOps--;
+
+        if (this._outstandingOps == 0) {
+            this.emit('changes-pending', this._pendingChanges.slice(0));
+            this._pendingChanges = [];
+        }
+    }
+
+};
+Signals.addSignalMethods(TrackerChangeMonitor.prototype);
diff --git a/src/docFactory.js b/src/docFactory.js
index 44ac5ff..23f8cda 100644
--- a/src/docFactory.js
+++ b/src/docFactory.js
@@ -27,6 +27,7 @@ const Gtk = imports.gi.Gtk;
 const Lang = imports.lang;
 const Signals = imports.signals;
 
+const ChangeMonitor = imports.changeMonitor;
 const Global = imports.global;
 const TrackerModel = imports.trackerModel;
 const Utils = imports.utils;
@@ -37,6 +38,50 @@ function DocCommon(cursor) {
 
 DocCommon.prototype = {
     _init: function(cursor) {
+        this.urn = null;
+        this.title = null;
+        this.author = null;
+        this.mtime = null;
+        this.resourceUrn = null;
+        this.favorite = null;
+        this._type = null;
+        this.pixbuf = null;
+
+        this._populateFromCursor(cursor);
+
+        this._refreshIconId =
+            Global.settings.connect('changed::list-view',
+                                    Lang.bind(this, this.refreshIcon));
+
+        this._changesId =
+            Global.changeMonitor.connect('changes-pending',
+                                         Lang.bind(this, this._onChangesPending));
+    },
+
+    _onChangesPending: function(monitor, changes) {
+        if (changes[0] == this.urn)
+            this._refresh();
+    },
+
+    _refresh: function() {
+        let sparql = Global.queryBuilder.buildSingleQuery(this.urn);
+        Global.connection.query_async(sparql, null, Lang.bind(this,
+            function(object, res) {
+                try {
+                    let cursor = object.query_finish(res);
+                    cursor.next_async(null, Lang.bind(this,
+                        function(object, res) {
+                            let valid = object.next_finish(res);
+                            this._populateFromCursor(object);
+                        }));
+                } catch (e) {
+                    log('Unable to refresh file information: ' + e.toString());
+                    return;
+                }
+            }));
+    },
+
+    _populateFromCursor: function(cursor) {
         this.urn = cursor.get_string(TrackerModel.TrackerColumns.URN)[0];
         this.title = cursor.get_string(TrackerModel.TrackerColumns.TITLE)[0];
         this.author = cursor.get_string(TrackerModel.TrackerColumns.AUTHOR)[0];
@@ -51,20 +96,15 @@ DocCommon.prototype = {
         if (!this.author)
             this.author = '';
 
-        // overridden in subclasses
-        this.uri = null;
-
-        this._refreshIconId =
-            Global.settings.connect('changed::list-view',
-                                    Lang.bind(this, this.refreshIcon));
+        this.refreshIcon();
     },
 
     refreshIcon: function() {
         this.pixbuf = Utils.pixbufFromRdfType(this._type);
-        this.checkEmblemsAndUpdateIcon();
+        this.checkEmblemsAndUpdateInfo();
     },
 
-    checkEmblemsAndUpdateIcon: function() {
+    checkEmblemsAndUpdateInfo: function() {
         if (this.favorite) {
             let emblemIcon = new Gio.ThemedIcon({ name: 'emblem-favorite' });
             let emblem = new Gio.Emblem({ icon: emblemIcon });
@@ -83,11 +123,12 @@ DocCommon.prototype = {
             }
         }
 
-        this.emit('icon-updated');
+        this.emit('info-updated');
     },
 
     destroy: function() {
         Global.settings.disconnect(this._refreshIconId);
+        Global.changeMonitor.disconnect(this._changesId);
     }
 };
 Signals.addSignalMethods(DocCommon.prototype);
@@ -102,11 +143,10 @@ LocalDocument.prototype = {
     __proto__: DocCommon.prototype,
 
     _init: function(cursor) {
-        DocCommon.prototype._init.call(this, cursor);
-
         // overridden
         this.uri = cursor.get_string(TrackerModel.TrackerColumns.URI)[0];
-        this.refreshIcon();
+
+        DocCommon.prototype._init.call(this, cursor);
     },
 
     refreshIcon: function() {
@@ -156,7 +196,7 @@ LocalDocument.prototype = {
         }
 
         if (haveNewIcon)
-            this.checkEmblemsAndUpdateIcon();
+            this.checkEmblemsAndUpdateInfo();
     },
 
     _onQueueThumbnailJob: function(object, res) {
@@ -189,7 +229,7 @@ LocalDocument.prototype = {
                                                        Utils.getIconSize(),
                                                        Utils.getIconSize());
 
-            this.checkEmblemsAndUpdateIcon();
+            this.checkEmblemsAndUpdateInfo();
         }
     }
 };
@@ -202,10 +242,10 @@ GoogleDocument.prototype = {
     __proto__: DocCommon.prototype,
 
     _init: function(cursor) {
-        DocCommon.prototype._init.call(this, cursor);
-
         // overridden
         this.uri = cursor.get_string(TrackerModel.TrackerColumns.IDENTIFIER)[0];
+
+        DocCommon.prototype._init.call(this, cursor);
     }
 };
 
@@ -215,6 +255,7 @@ function DocFactory() {
 
 DocFactory.prototype = {
     _init: function() {
+        this._docs = [];
     },
 
     _identifierIsGoogle: function(identifier) {
@@ -224,10 +265,22 @@ DocFactory.prototype = {
 
     newDocument: function(cursor) {
         let identifier = cursor.get_string(TrackerModel.TrackerColumns.IDENTIFIER)[0];
+        let doc;
 
         if (this._identifierIsGoogle(identifier))
-            return new GoogleDocument(cursor);
+            doc = new GoogleDocument(cursor);
+        else
+            doc = new LocalDocument(cursor);
+
+        this._docs.push(doc);
+
+        return doc;
+    },
 
-        return new LocalDocument(cursor);
+    clear: function() {
+        this._docs.forEach(function(doc) {
+            doc.destroy();
+        });
+        this._docs = [];
     }
 };
diff --git a/src/global.js b/src/global.js
index 077aff2..5e0450c 100644
--- a/src/global.js
+++ b/src/global.js
@@ -24,6 +24,7 @@ let categoryManager = null;
 let connection = null;
 let model = null;
 let offsetController = null;
+let queryBuilder = null;
 let selectionController = null;
 let settings = null;
 let sourceManager = null;
diff --git a/src/query.js b/src/query.js
new file mode 100644
index 0000000..c780687
--- /dev/null
+++ b/src/query.js
@@ -0,0 +1,115 @@
+/*
+ * 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: Cosimo Cecchi <cosimoc redhat com>
+ *
+ */
+
+const Global = imports.global;
+
+function QueryBuilder() {
+    this._init();
+}
+
+QueryBuilder.prototype = {
+    _init: function() {
+    },
+
+    _buildFilterSearch: function(subject) {
+        let filter =
+            ('fn:contains ' +
+             '(fn:lower-case (tracker:coalesce(nie:title(%s), nfo:fileName(%s))), ' +
+             '"%s")').format(subject, subject, Global.filterController.getFilter());
+
+        return filter;
+    },
+
+    _buildFilterString: function(subject) {
+        let sparql = 'FILTER ((';
+
+        sparql += this._buildFilterSearch(subject);
+        sparql += ') && (';
+        sparql += Global.sourceManager.getActiveSourceFilter(subject);
+
+        sparql += '))';
+
+        return sparql;
+    },
+
+    _buildTypeFilter: function(subject) {
+        let sparql =
+            ('{ %s a nfo:PaginatedTextDocument } ' +
+             'UNION ' +
+             '{ %s a nfo:Spreadsheet } ' +
+             'UNION ' +
+             '{ %s a nfo:Presentation } ').format(subject, subject, subject);
+
+        return sparql;
+    },
+
+    _buildTotalCounter: function() {
+        let sparql =
+            '(SELECT DISTINCT COUNT(?doc) WHERE { ' +
+            this._buildTypeFilter('?doc') +
+            this._buildFilterString('?doc') +
+            '}) ';
+
+        return sparql;
+    },
+
+    _buildQueryInternal: function(global) {
+        let globalSparql = '{}';
+
+        if (global) {
+            globalSparql =
+                (this._buildTotalCounter() + // totalCount
+                 'WHERE { ' +
+                 this._buildTypeFilter('?urn') +
+                 'OPTIONAL { ?urn nco:creator ?creator . } ' +
+                 'OPTIONAL { ?urn nco:publisher ?publisher . } ' +
+                 Global.categoryManager.getActiveCategoryFilter() +
+                 this._buildFilterString('?urn') +
+                 ' } ' +
+                 'ORDER BY DESC (?mtime)' +
+                 'LIMIT %d OFFSET %d').format(Global.offsetController.getOffsetStep(),
+                                              Global.offsetController.getOffset());
+        }
+
+        let sparql =
+            'SELECT DISTINCT ?urn ' + // urn
+             'nie:url(?urn) ' + // uri
+             'tracker:coalesce(nie:title(?urn), nfo:fileName(?urn)) ' + // title
+             'tracker:coalesce(nco:fullname(?creator), nco:fullname(?publisher)) ' + // author
+             'tracker:coalesce(nfo:fileLastModified(?urn), nie:contentLastModified(?urn)) AS ?mtime ' + // mtime
+             'nao:identifier(?urn) ' + // identifier
+             'rdf:type(?urn) ' + // type
+             'nie:dataSource(?urn) ' + // resource URN
+             '( EXISTS { ?urn nao:hasTag nao:predefined-tag-favorite } )' + // favorite
+             globalSparql;
+
+        return sparql;
+    },
+
+    buildSingleQuery: function(resource) {
+        let sparql = this._buildQueryInternal(false);
+        return sparql.replace('?urn', '<' + resource + '>', 'g');
+    },
+
+    buildGlobalQuery: function() {
+        return this._buildQueryInternal(true);
+    }
+};
diff --git a/src/trackerModel.js b/src/trackerModel.js
index 82d7751..015703e 100644
--- a/src/trackerModel.js
+++ b/src/trackerModel.js
@@ -28,10 +28,12 @@ const Gio = imports.gi.Gio;
 const GLib = imports.gi.GLib;
 const Tracker = imports.gi.Tracker;
 const Gd = imports.gi.Gd;
+const Gtk = imports.gi.Gtk;
 
 const DocFactory = imports.docFactory;
 const GDataMiner = imports.gDataMiner;
 const Global = imports.global;
+const Query = imports.query;
 const TrackerUtils = imports.trackerUtils;
 const Utils = imports.utils;
 
@@ -54,103 +56,22 @@ const TrackerColumns = {
     TITLE: 2,
     AUTHOR: 3,
     MTIME: 4,
-    TOTAL_COUNT: 5,
-    IDENTIFIER: 6,
-    TYPE: 7,
-    RESOURCE_URN: 8,
-    FAVORITE: 9
+    IDENTIFIER: 5,
+    TYPE: 6,
+    RESOURCE_URN: 7,
+    FAVORITE: 8,
+    TOTAL_COUNT: 9
 };
 
-function QueryBuilder() {
+function TrackerModel() {
     this._init();
 }
 
-QueryBuilder.prototype = {
-    _init: function() {
-    },
-
-    _buildFilterSearch: function(subject) {
-        let filter =
-            ('fn:contains ' +
-             '(fn:lower-case (tracker:coalesce(nie:title(%s), nfo:fileName(%s))), ' +
-             '"%s")').format(subject, subject, Global.filterController.getFilter());
-
-        return filter;
-    },
-
-    _buildFilterString: function(subject) {
-        let sparql = 'FILTER ((';
-
-        sparql += this._buildFilterSearch(subject);
-        sparql += ') && (';
-        sparql += Global.sourceManager.getActiveSourceFilter(subject);
-
-        sparql += '))';
-
-        return sparql;
-    },
-
-    _buildTypeFilter: function(subject) {
-        let sparql =
-            ('{ %s a nfo:PaginatedTextDocument } ' +
-             'UNION ' +
-             '{ %s a nfo:Spreadsheet } ' +
-             'UNION ' +
-             '{ %s a nfo:Presentation } ').format(subject, subject, subject);
-
-        return sparql;
-    },
-
-    _buildTotalCounter: function() {
-        let sparql =
-            '(SELECT DISTINCT COUNT(?doc) WHERE { ' +
-            this._buildTypeFilter('?doc') +
-            this._buildFilterString('?doc') +
-            '}) ';
-
-        return sparql;
-    },
-
-    buildQuery: function() {
-        let sparql =
-            ('SELECT DISTINCT ?urn ' + // urn
-             'nie:url(?urn) ' + // uri
-             'tracker:coalesce(nie:title(?urn), nfo:fileName(?urn)) ' + // title
-             'tracker:coalesce(nco:fullname(?creator), nco:fullname(?publisher)) ' + // author
-             'tracker:coalesce(nfo:fileLastModified(?urn), nie:contentLastModified(?urn)) AS ?mtime ' + // mtime
-             this._buildTotalCounter() + // totalCount
-             'nao:identifier(?urn) ' + // identifier
-             'rdf:type(?urn) ' + // type
-             'nie:dataSource(?urn) ' + // resource URN
-             '( EXISTS { ?urn nao:hasTag nao:predefined-tag-favorite } )' + // favorite
-             'WHERE { ' +
-             this._buildTypeFilter('?urn') +
-             'OPTIONAL { ?urn nco:creator ?creator . } ' +
-             'OPTIONAL { ?urn nco:publisher ?publisher . } ' +
-             Global.categoryManager.getActiveCategoryFilter() +
-             this._buildFilterString('?urn') +
-             ' } ' +
-             'ORDER BY DESC (?mtime)' +
-             'LIMIT %d OFFSET %d').format(Global.offsetController.getOffsetStep(),
-                                          Global.offsetController.getOffset());
-
-        return sparql;
-    }
-};
-
-function TrackerModel(connection) {
-    this._init(connection);
-}
-
 TrackerModel.prototype = {
-    _init: function(connection) {
-        this._docs = [];
-
-        this._builder = new QueryBuilder();
+    _init: function() {
         this._factory = new DocFactory.DocFactory();
 
         this.model = Gd.create_list_store();
-        this._connection = connection;
 
         // startup a refresh of the gdocs cache
         this._miner = new GDataMiner.GDataMiner();
@@ -209,14 +130,16 @@ TrackerModel.prototype = {
                      newDoc.mtime, newDoc.pixbuf,
                      newDoc.resourceUrn, newDoc.favorite);
 
-        newDoc.connect('icon-updated', Lang.bind(this,
+        newDoc.connect('info-updated', Lang.bind(this,
             function() {
                 let objectIter = this.model.get_iter(treePath)[1];
                 if (objectIter)
-                    Gd.store_update_icon(this.model, objectIter, newDoc.pixbuf);
+                    Gd.store_set(this.model, iter,
+                                 newDoc.urn, newDoc.uri,
+                                 newDoc.title, newDoc.author,
+                                 newDoc.mtime, newDoc.pixbuf,
+                                 newDoc.resourceUrn, newDoc.favorite);
             }));
-
-        this._docs.push(newDoc);
     },
 
     _onQueryFinished: function() {
@@ -256,8 +179,8 @@ TrackerModel.prototype = {
     },
 
     _performCurrentQuery: function() {
-        this._connection.query_async(this._builder.buildQuery(),
-                                     null, Lang.bind(this, this._onQueryExecuted));
+        Global.connection.query_async(Global.queryBuilder.buildGlobalQuery(),
+                                      null, Lang.bind(this, this._onQueryExecuted));
     },
 
     _emitModelUpdateDone: function() {
@@ -267,11 +190,7 @@ TrackerModel.prototype = {
     _refresh: function() {
         Global.selectionController.freezeSelection(true);
         this.model.clear();
-
-        this._docs.forEach(function(doc) {
-            doc.destroy();
-        });
-        this._docs = [];
+        this._factory.clear();
 
         this._performCurrentQuery();
     },



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