[gnome-documents] all: implement listening to create/delete events from the tracker DB



commit 1a730c44a555f46b8f791561b79549330e1ce82c
Author: Cosimo Cecchi <cosimoc gnome org>
Date:   Fri Sep 9 18:44:33 2011 -0400

    all: implement listening to create/delete events from the tracker DB
    
    We now associate a type for each change, and add/remove elements from
    the view if we detect such a change in the graph (and the changed
    elements match what we're interested in).
    
    This way of ensuring change events are always propagated to the view
    allows us to remove a refresh of the view after the GData miner has been
    spawned (as we will receive the changes through the GraphUpdated
    signal and update single elements on the view) but to work properly this
    will need changes in the GData miner itself, which for instance, doesn't
    track deletes right now.

 src/application.js       |    2 +-
 src/changeMonitor.js     |   65 ++++++++++++++++++++++++++----
 src/documents.js         |  100 +++++++++++++++++++++++++++++++++++++--------
 src/lib/gd-utils.c       |   23 +++++++++-
 src/lib/gd-utils.h       |    6 ++-
 src/trackerController.js |    6 ---
 6 files changed, 164 insertions(+), 38 deletions(-)
---
diff --git a/src/application.js b/src/application.js
index 138c416..ed09b1e 100644
--- a/src/application.js
+++ b/src/application.js
@@ -143,9 +143,9 @@ Application.prototype = {
                         Global.sourceManager = new Sources.SourceManager();
                         Global.selectionController = new SelectionController.SelectionController();
                         Global.queryBuilder = new Query.QueryBuilder();
+                        Global.changeMonitor = new ChangeMonitor.TrackerChangeMonitor();
                         Global.documentManager = new Documents.DocumentManager();
                         Global.trackerController = new TrackerController.TrackerController();
-                        Global.changeMonitor = new ChangeMonitor.TrackerChangeMonitor();
                         Global.modeController = new WindowMode.ModeController();
                         Global.focusController = new WindowMode.FocusController();
 
diff --git a/src/changeMonitor.js b/src/changeMonitor.js
index f218c3b..6f85ce6 100644
--- a/src/changeMonitor.js
+++ b/src/changeMonitor.js
@@ -44,6 +44,41 @@ TrackerResourcesService.prototype = {
 };
 DBus.proxifyPrototype(TrackerResourcesService.prototype, TrackerResourcesServiceIface);
 
+const ChangeEventType = {
+    CHANGED: 0,
+    CREATED: 1,
+    DELETED: 2
+};
+
+const _RDF_TYPE = "http://www.w3.org/1999/02/22-rdf-syntax-ns#type";;
+
+function ChangeEvent(urn, predicate, isDelete) {
+    this._init(urn, predicate, isDelete);
+}
+
+ChangeEvent.prototype = {
+    _init: function(urn, predicate, isDelete) {
+        this.urn = urn;
+
+        if (predicate == _RDF_TYPE) {
+            if (isDelete)
+                this.type = ChangeEventType.DELETED;
+            else
+                this.type = ChangeEventType.CREATED;
+        } else {
+            this.type = ChangeEventType.CHANGED;
+        }
+    },
+
+    merge: function(event) {
+        // deletions or creations override the current type
+        if (event.type == ChangeEventType.DELETED ||
+            event.type == ChangeEventType.CREATED) {
+            this.type = event.type;
+        }
+    }
+};
+
 function TrackerChangeMonitor() {
     this._init();
 }
@@ -61,21 +96,21 @@ TrackerChangeMonitor.prototype = {
         deleteEvents.forEach(Lang.bind(this,
             function(event) {
                 this._outstandingOps++;
-                this._updateIterator(event);
+                this._updateIterator(event, true);
             }));
 
         insertEvents.forEach(Lang.bind(this,
             function(event) {
                 this._outstandingOps++;
-                this._updateIterator(event);
+                this._updateIterator(event, false);
             }));
     },
 
-    _updateIterator: function(event) {
+    _updateIterator: function(event, isDelete) {
         // 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]),
+            ('SELECT tracker:uri(%d) tracker:uri(%d) {}').format(event[1], event[2]),
             null, Lang.bind(this,
                 function(object, res) {
                     let cursor = object.query_finish(res);
@@ -86,8 +121,9 @@ TrackerChangeMonitor.prototype = {
 
                             if (valid) {
                                 let subject = cursor.get_string(0)[0];
-                                if (this._pendingChanges.indexOf(subject) == -1)
-                                    this._pendingChanges.push(subject);
+                                let predicate = cursor.get_string(1)[0];
+
+                                this._addEvent(subject, predicate, isDelete);
                             }
 
                             cursor.close();
@@ -97,14 +133,25 @@ TrackerChangeMonitor.prototype = {
                 }));
     },
 
+    _addEvent: function(subject, predicate, isDelete) {
+        let event = new ChangeEvent(subject, predicate, isDelete);
+        let oldEvent = this._pendingChanges[subject];
+
+        if (oldEvent != null) {
+            oldEvent.merge(event);
+            this._pendingChanges[subject] = oldEvent;
+        } else {
+            this._pendingChanges[subject] = event;
+        }
+    },
+
     _updateCollector: function() {
         this._outstandingOps--;
 
         if (this._outstandingOps == 0) {
-            this.emit('changes-pending', this._pendingChanges.slice(0));
-            this._pendingChanges = [];
+            this.emit('changes-pending', this._pendingChanges);
+            this._pendingChanges = {};
         }
     }
-
 };
 Signals.addSignalMethods(TrackerChangeMonitor.prototype);
diff --git a/src/documents.js b/src/documents.js
index e9934e2..7955afc 100644
--- a/src/documents.js
+++ b/src/documents.js
@@ -72,20 +72,12 @@ DocCommon.prototype = {
         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));
         this._categoryId =
             Global.categoryManager.connect('active-category-changed',
                                            Lang.bind(this, this.refreshIcon));
     },
 
-    _onChangesPending: function(monitor, changes) {
-        if (changes[0] == this.urn)
-            this._refresh();
-    },
-
-    _refresh: function() {
+    refresh: function() {
         let sparql = Global.queryBuilder.buildSingleQuery(this.urn);
 
         Global.connection.query_async(sparql, null, Lang.bind(this,
@@ -117,10 +109,13 @@ DocCommon.prototype = {
         this.uri = cursor.get_string(Query.QueryColumns.URI)[0];
         this.urn = cursor.get_string(Query.QueryColumns.URN)[0];
         this.author = cursor.get_string(Query.QueryColumns.AUTHOR)[0];
-        this.mtime = cursor.get_string(Query.QueryColumns.MTIME)[0];
         this.resourceUrn = cursor.get_string(Query.QueryColumns.RESOURCE_URN)[0];
         this.favorite = cursor.get_boolean(Query.QueryColumns.FAVORITE);
 
+        let mtime = cursor.get_string(Query.QueryColumns.MTIME)[0];
+        let timeVal = Gd.time_val_from_iso8601(mtime)[1];
+        this.mtime = timeVal.tv_sec;
+
         this.mimeType = cursor.get_string(Query.QueryColumns.MIMETYPE)[0];
         this.rdfType = cursor.get_string(Query.QueryColumns.RDFTYPE)[0];
         this.updateIconFromType();
@@ -239,7 +234,6 @@ DocCommon.prototype = {
 
     destroy: function() {
         Global.settings.disconnect(this._refreshIconId);
-        Global.changeMonitor.disconnect(this._changesId);
         Global.categoryManager.disconnect(this._categoryId);
     },
 
@@ -522,9 +516,60 @@ DocumentManager.prototype = {
         this._docs = {};
         this._activeDocument = null;
 
+        Global.changeMonitor.connect('changes-pending',
+                                     Lang.bind(this, this._onChangesPending));
+
         this._pixbufFrame = GdkPixbuf.Pixbuf.new_from_file(Path.ICONS_DIR + 'thumbnail-frame.png');
     },
 
+    _onChangesPending: function(monitor, changes) {
+        for (idx in changes) {
+            let changeEvent = changes[idx];
+
+            if (changeEvent.type == ChangeMonitor.ChangeEventType.CHANGED) {
+                let doc = this.lookupDocument(changeEvent.urn);
+
+                if (doc)
+                    doc.refresh();
+            } else if (changeEvent.type == ChangeMonitor.ChangeEventType.CREATED) {
+                this._onDocumentCreated(changeEvent.urn);
+            } else if (changeEvent.type == ChangeMonitor.ChangeEventType.DELETED) {
+                let doc = this.lookupDocument(changeEvent.urn);
+
+                if (doc) {
+                    this.emit('document-removed', doc);
+
+                    doc.destroy();
+                    delete this._docs[changeEvent.urn];
+                }
+            }
+        }
+    },
+
+    _onDocumentCreated: function(urn) {
+        let sparql = Global.queryBuilder.buildSingleQuery(urn);
+
+        Global.connection.query_async(sparql, null, Lang.bind(this,
+            function(object, res) {
+                let cursor = null;
+
+                try {
+                    cursor = object.query_finish(res);
+                    cursor.next_async(null, Lang.bind(this,
+                        function(object, res) {
+                            let valid = object.next_finish(res);
+                            if (valid)
+                                this.addDocument(object);
+
+                            cursor.close();
+                        }));
+                } catch (e) {
+                    log('Unable to add new document: ' + e.toString());
+                    return;
+                }
+            }));
+    },
+
     _identifierIsGoogle: function(identifier) {
         return (identifier &&
                 (identifier.indexOf('https://docs.google.com') != -1));
@@ -544,7 +589,7 @@ DocumentManager.prototype = {
             doc = new LocalDocument(cursor);
 
         this._docs[doc.urn] = doc;
-        this.emit('new-document', doc);
+        this.emit('document-added', doc);
     },
 
     clear: function() {
@@ -585,7 +630,8 @@ const ModelColumns = {
     URN: 0,
     TITLE: 1,
     AUTHOR: 2,
-    ICON: 3
+    ICON: 3,
+    MTIME: 4
 };
 
 function DocumentModel() {
@@ -595,28 +641,32 @@ function DocumentModel() {
 DocumentModel.prototype = {
     _init: function() {
         this.model = Gd.create_list_store();
+        this.model.set_sort_column_id(ModelColumns.MTIME,
+                                      Gtk.SortType.DESCENDING);
+
         this._documentManager = Global.documentManager;
 
         this._documentManager.connect('clear', Lang.bind(this, this._onManagerClear));
-        this._documentManager.connect('new-document', Lang.bind(this, this._onNewDocument));
+        this._documentManager.connect('document-added', Lang.bind(this, this._onDocumentAdded));
+        this._documentManager.connect('document-removed', Lang.bind(this, this._onDocumentRemoved));
 
         let documents = this._documentManager.getDocuments();
         for (idx in this._documentManager.getDocuments())
-            this._onNewDocument(this._documentManager, documents[idx]);
+            this._onDocumentAdded(this._documentManager, documents[idx]);
     },
 
     _onManagerClear: function() {
         this.model.clear();
     },
 
-    _onNewDocument: function(manager, doc) {
+    _onDocumentAdded: function(manager, doc) {
         let iter = this.model.append();
         let treePath = this.model.get_path(iter);
 
         Gd.store_set(this.model, iter,
                      doc.urn,
                      doc.title, doc.author,
-                     doc.pixbuf);
+                     doc.pixbuf, doc.mtime);
 
         doc.connect('info-updated', Lang.bind(this,
             function() {
@@ -625,7 +675,21 @@ DocumentModel.prototype = {
                     Gd.store_set(this.model, iter,
                                  doc.urn,
                                  doc.title, doc.author,
-                                 doc.pixbuf);
+                                 doc.pixbuf, doc.mtime);
+            }));
+    },
+
+    _onDocumentRemoved: function(manager, doc) {
+        this.model.foreach(Lang.bind(this,
+            function(model, path, iter) {
+                let urn = model.get_value(iter, ModelColumns.URN);
+
+                if (urn == doc.urn) {
+                    this.model.remove(iter);
+                    return true;
+                }
+
+                return false;
             }));
     }
 };
diff --git a/src/lib/gd-utils.c b/src/lib/gd-utils.c
index 6085505..7898ca2 100644
--- a/src/lib/gd-utils.c
+++ b/src/lib/gd-utils.c
@@ -38,11 +38,12 @@
 GtkListStore *
 gd_create_list_store (void)
 {
-  return gtk_list_store_new (4,
+  return gtk_list_store_new (5,
                              G_TYPE_STRING, // URN
                              G_TYPE_STRING, // TITLE
                              G_TYPE_STRING, // AUTHOR
-                             GDK_TYPE_PIXBUF); // ICON
+                             GDK_TYPE_PIXBUF, // ICON
+                             G_TYPE_LONG); // MTIME
 }
 
 void
@@ -51,13 +52,15 @@ gd_store_set (GtkListStore *store,
               const gchar *urn,
               const gchar *title,
               const gchar *author,
-              GdkPixbuf *icon)
+              GdkPixbuf *icon,
+              glong mtime)
 {
   gtk_list_store_set (store, iter,
                       0, urn,
                       1, title,
                       2, author,
                       3, icon,
+                      4, mtime,
                       -1);
 }
 
@@ -433,3 +436,17 @@ gd_filename_strip_extension (const char * filename_with_extension)
 
 	return filename;
 }
+
+/**
+ * gd_time_val_from_iso8601:
+ * @string:
+ * @timeval: (out):
+ *
+ * Returns:
+ */
+gboolean
+gd_time_val_from_iso8601 (const gchar *string,
+                          GTimeVal *timeval)
+{
+  return g_time_val_from_iso8601 (string, timeval);
+}
diff --git a/src/lib/gd-utils.h b/src/lib/gd-utils.h
index 82f6806..67304e0 100644
--- a/src/lib/gd-utils.h
+++ b/src/lib/gd-utils.h
@@ -30,7 +30,8 @@ void gd_store_set (GtkListStore *store,
                    const gchar *urn,
                    const gchar *title,
                    const gchar *author,
-                   GdkPixbuf *icon);
+                   GdkPixbuf *icon,
+                   glong mtime);
 
 GtkListStore* gd_create_sources_store (void);
 void gd_sources_store_set (GtkListStore *store,
@@ -65,5 +66,8 @@ GdkPixbuf * gd_embed_image_in_frame (GdkPixbuf *source_image,
 
 char *gd_filename_strip_extension (const char * filename_with_extension);
 
+gboolean gd_time_val_from_iso8601 (const gchar *string,
+                                   GTimeVal *timeval);
+
 #endif /* __GD_UTILS_H__ */
                                   
diff --git a/src/trackerController.js b/src/trackerController.js
index 8bba318..508c5e1 100644
--- a/src/trackerController.js
+++ b/src/trackerController.js
@@ -68,12 +68,6 @@ TrackerController.prototype = {
                     return;
                 }
 
-                // FIXME: we must have a way to know from the miner if there were
-                // no changes processed, to avoid uselessly refreshing the view.
-                // That requires support for the Changes feed in libgdata, see
-                // https://bugzilla.gnome.org/show_bug.cgi?id=654652
-                this._refresh();
-
                 Mainloop.timeout_add_seconds(MINER_REFRESH_TIMEOUT,
                                              Lang.bind(this, this._refreshMinerNow));
             }));



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