[gnome-documents] all: implement refresh information for single elements
- From: Cosimo Cecchi <cosimoc src gnome org>
- To: commits-list gnome org
- Cc:
- Subject: [gnome-documents] all: implement refresh information for single elements
- Date: Fri, 26 Aug 2011 05:54:16 +0000 (UTC)
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]