changeset 85:f5161c834622

store summaries in addition to content and display them in the river view
author Myk Melez <myk@mozilla.org>
date Fri, 16 May 2008 15:45:35 -0700
parents 915e41848f6d
children 4d86f720af42
files extension/content/river.js extension/modules/collection.js extension/modules/datastore.js extension/modules/feed.js extension/modules/message.js extension/modules/service.js
diffstat 6 files changed, 129 insertions(+), 95 deletions(-) [+]
line wrap: on
line diff
--- a/extension/content/river.js	Fri May 16 10:42:36 2008 -0700
+++ b/extension/content/river.js	Fri May 16 15:45:35 2008 -0700
@@ -571,11 +571,7 @@
         let body = this._document.createElementNS(HTML_NS, "div");
         body.className = "body";
 
-        // The summary is currently not stored and made available, so we can
-        // only use the content.
-        // FIXME: use the summary instead once it becomes available.
-        //var summary = message.summary || message.content;
-        let summary = message.content;
+        let summary = message.content || message.summary;
         if (summary) {
           if (summary.base)
             body.setAttributeNS(XML_NS, "base", summary.base.spec);
--- a/extension/modules/collection.js	Fri May 16 10:42:36 2008 -0700
+++ b/extension/modules/collection.js	Fri May 16 15:45:35 2008 -0700
@@ -5,9 +5,23 @@
 const Cr = Components.results;
 const Cu = Components.utils;
 
+// FIXME: factor this out into a common file.
+const PART_TYPE_CONTENT = 1;
+const PART_TYPE_SUMMARY = 2;
+
+// Media type to nsIFeedTextConstruct::type mappings.
+// FIXME: get this from message.js (or from something that both message.js
+// and collection.js import).
+const textConstructTypes = {
+  "text/html": "html",
+  "application/xhtml+xml": "xhtml",
+  "text/plain": "text"
+};
+
 Cu.import("resource://snowl/modules/log4moz.js");
 Cu.import("resource://snowl/modules/datastore.js");
 Cu.import("resource://snowl/modules/message.js");
+Cu.import("resource://snowl/modules/URI.js");
 
 /**
  * A group of messages.
@@ -120,9 +134,9 @@
   },
 
   _getContent: function() {
-    let query = "SELECT messageID, content, contentType " +
-                "FROM parts " +
-                "WHERE parts.messageID IN (" +
+    let query = "SELECT messageID, content, mediaType, baseURI, languageCode " +
+                "FROM parts WHERE partType = " + PART_TYPE_CONTENT +
+                " AND messageID IN (" +
                   this._messages.map(function(v) { return v.id }).join(",") +
                 ")";
     let statement = SnowlDatastore.createStatement(query);
@@ -132,7 +146,9 @@
         let content = Cc["@mozilla.org/feed-textconstruct;1"].
                       createInstance(Ci.nsIFeedTextConstruct);
         content.text = statement.row.content;
-        content.type = textConstructTypes[statement.row.contentType];
+        content.type = textConstructTypes[statement.row.mediaType];
+        content.base = URI.get(statement.row.baseURI);
+        content.lang = statement.row.languageCode;
         this._messageIndex[statement.row.messageID].content = content;
       }
     }
@@ -222,11 +238,3 @@
 
   return aObject;
 }
-
-// FIXME: get this from message.js (or from something that both message.js and collection.js import).
-let textConstructTypes = {
-  "text/html": "html",
-  "application/xhtml+xml": "xhtml",
-  "text/plain": "text"
-};
-
--- a/extension/modules/datastore.js	Fri May 16 10:42:36 2008 -0700
+++ b/extension/modules/datastore.js	Fri May 16 15:45:35 2008 -0700
@@ -71,8 +71,11 @@
         type: TABLE_TYPE_FULLTEXT,
         columns: [
           "messageID INTEGER NOT NULL REFERENCES messages(id)",
-          "contentType",
-          "content"
+          "partType INTEGER NOT NULL",
+          "content NOT NULL",
+          "mediaType TEXT",
+          "baseURI TEXT",
+          "languageCode TEXT",
         ]
       },
 
@@ -334,32 +337,6 @@
     return this.dbConnection.lastInsertRowID;
   },
 
-  get _insertPartStatement() {
-    let statement = this.createStatement(
-      "INSERT INTO parts(messageID, content, contentType) \
-       VALUES (:messageID, :content, :contentType)"
-    );
-    this.__defineGetter__("_insertPartStatement", function() { return statement });
-    return this._insertPartStatement;
-  },
-
-  /**
-   * Insert a record into the parts table.
-   * 
-   * @param aMessageID    {integer} the record ID of the message
-   * @param aContentType  {string}  the Internet media type of the content
-   * @param aContent      {string}  the content
-   *
-   * @returns {integer} the ID of the newly-created record
-   */
-  insertPart: function(aMessageID, aContent, aContentType) {
-    this._insertPartStatement.params.messageID = aMessageID;
-    this._insertPartStatement.params.content = aContent;
-    this._insertPartStatement.params.contentType = aContentType;
-    this._insertPartStatement.execute();
-    return this.dbConnection.lastInsertRowID;
-  },
-
   get _selectAttributeIDStatement() {
     let statement = this.createStatement(
       "SELECT id FROM attributes WHERE name = :name"
--- a/extension/modules/feed.js	Fri May 16 10:42:36 2008 -0700
+++ b/extension/modules/feed.js	Fri May 16 15:45:35 2008 -0700
@@ -5,8 +5,13 @@
 const Cr = Components.results;
 const Cu = Components.utils;
 
+// FIXME: factor this out into a common file.
+const PART_TYPE_CONTENT = 1;
+const PART_TYPE_SUMMARY = 2;
+
 Cu.import("resource://snowl/modules/log4moz.js");
 Cu.import("resource://snowl/modules/datastore.js");
+Cu.import("resource://snowl/modules/URI.js");
 
 var SnowlFeedClient = {
   // XXX Make this take a feed ID once it stores the list of subscribed feeds
@@ -120,8 +125,8 @@
     }
   },
 
-  // nsIFeedTextConstruct::type to MIME media type mappings.
-  contentTypes: { html: "text/html", xhtml: "application/xhtml+xml", text: "text/plain" },
+  // nsIFeedTextConstruct::type to media type mappings.
+  mediaTypes: { html: "text/html", xhtml: "application/xhtml+xml", text: "text/plain" },
 
   getNewMessages: function() {
     let request = Cc["@mozilla.org/xmlextras/xmlhttprequest;1"].createInstance();
@@ -180,14 +185,25 @@
     // Convert the publication date/time string into a JavaScript Date object.
     let timestamp = aEntry.published ? new Date(aEntry.published) : null;
 
-    // Convert the content type specified by nsIFeedTextConstruct, which is
-    // either "html", "xhtml", or "text", into an Internet media type.
-    let contentType = aEntry.content ? this.contentTypes[aEntry.content.type] : null;
-    let contentText = aEntry.content ? aEntry.content.text : null;
+    // FIXME: wrap all queries that add the message into a transaction?
+
+    // FIXME: handle titles that contain markup or are missing.
     let messageID = this.addSimpleMessage(this.id, aExternalID,
                                           aEntry.title.text, author,
-                                          timestamp, aEntry.link,
-                                          contentText, contentType);
+                                          timestamp, aEntry.link);
+
+    // Add parts
+    if (aEntry.content) {
+      this.addPart(messageID, PART_TYPE_CONTENT, aEntry.content.text,
+                   (aEntry.content.base ? aEntry.content.base.spec : null),
+                   aEntry.content.lang, this.mediaTypes[aEntry.content.type]);
+    }
+
+    if (aEntry.summary) {
+      this.addPart(messageID, PART_TYPE_SUMMARY, aEntry.summary.text,
+                   (aEntry.summary.base ? aEntry.summary.base.spec : null),
+                   aEntry.summary.lang, this.mediaTypes[aEntry.summary.type]);
+    }
 
     // Add metadata.
     let fields = aEntry.QueryInterface(Ci.nsIFeedContainer).
@@ -302,18 +318,11 @@
    * @param aTimestamp   {Date}    the date/time at which the message was sent
    * @param aLink        {nsIURI}  a link to the content of the message,
    *                               if the content is hosted on a server
-   * @param aContent     {string}  the content of the message, if the content
-   *                               is included with the message
-   * @param aContentType {string}  the media type of the content of the message,
-   *                               if the content is included with the message
    *
-   * FIXME: allow callers to pass a set of arbitrary metadata name/value pairs
-   * that get written to the attributes table.
-   * 
    * @returns {integer} the internal ID of the newly-created message
    */
   addSimpleMessage: function(aSourceID, aExternalID, aSubject, aAuthor,
-                             aTimestamp, aLink, aContent, aContentType) {
+                             aTimestamp, aLink) {
     // Convert the timestamp to milliseconds-since-epoch, which is how we store
     // it in the datastore.
     let timestamp = aTimestamp ? aTimestamp.getTime() : null;
@@ -326,10 +335,29 @@
       SnowlDatastore.insertMessage(aSourceID, aExternalID, aSubject, aAuthor,
                                    timestamp, link);
 
-    if (aContent)
-      SnowlDatastore.insertPart(messageID, aContent, aContentType);
+    return messageID;
+  },
+
+  get _addPartStatement() {
+    let statement = SnowlDatastore.createStatement(
+      "INSERT INTO parts(messageID, partType, content, baseURI, languageCode, mediaType) \
+       VALUES (:messageID, :partType, :content, :baseURI, :languageCode, :mediaType)"
+    );
+    this.__defineGetter__("_addPartStatement", function() { return statement });
+    return this._addPartStatement;
+  },
 
-    return messageID;
+  addPart: function(aMessageID, aPartType, aContent, aBaseURI, aLanguageCode,
+                    aMediaType) {
+    this._addPartStatement.params.messageID = aMessageID;
+    this._addPartStatement.params.partType = aPartType;
+    this._addPartStatement.params.content = aContent;
+    this._addPartStatement.params.baseURI = aBaseURI;
+    this._addPartStatement.params.languageCode = aLanguageCode;
+    this._addPartStatement.params.mediaType = aMediaType;
+    this._addPartStatement.execute();
+
+    return SnowlDatastore.dbConnection.lastInsertRowID;
   },
 
   addMetadatum: function(aMessageID, aAttributeName, aValue) {
--- a/extension/modules/message.js	Fri May 16 10:42:36 2008 -0700
+++ b/extension/modules/message.js	Fri May 16 15:45:35 2008 -0700
@@ -5,8 +5,22 @@
 const Cr = Components.results;
 const Cu = Components.utils;
 
+// FIXME: factor this out into a common file.
+const PART_TYPE_CONTENT = 1;
+const PART_TYPE_SUMMARY = 2;
+
+// Media type to nsIFeedTextConstruct::type mappings.
+// FIXME: get this from message.js (or from something that both message.js
+// and collection.js import).
+const textConstructTypes = {
+  "text/html": "html",
+  "application/xhtml+xml": "xhtml",
+  "text/plain": "text"
+};
+
 Cu.import("resource://snowl/modules/datastore.js");
 Cu.import("resource://snowl/modules/source.js");
+Cu.import("resource://snowl/modules/URI.js");
 
 function SnowlMessage(aID, aSubject, aAuthor, aLink, aTimestamp, aRead) {
   this.id = aID;
@@ -41,41 +55,57 @@
                                                  " WHERE id = " + this.id);
   },
 
-
-  // FIXME: also store and make available the summary.
+  _content: null,
+  get content() {
+    if (!this._content)
+      this._content = this._getPart(PART_TYPE_CONTENT);
+    return this._content;
+  },
+  set content(newValue) {
+    this._content = newValue;
+  },
 
-  get _contentStatement() {
-    let statement = SnowlDatastore.createStatement(
-      "SELECT content, contentType FROM parts WHERE messageID = :messageID"
-    );
-    this.__defineGetter__("_contentStatement", function() { return statement });
-    return this._contentStatement;
+  _summary: null,
+  get summary() {
+    if (!this._summary)
+      this._summary = this._getPart(PART_TYPE_SUMMARY);
+    return this._summary;
+  },
+  set summary(newValue) {
+    this._summary = newValue;
   },
 
-  _content: null,
+  get _getPartStatement() {
+    let statement = SnowlDatastore.createStatement(
+      "SELECT content, mediaType, baseURI, languageCode FROM parts " +
+      "WHERE messageID = :messageID AND partType = :partType"
+    );
+    this.__defineGetter__("_getPartStatement", function() { return statement });
+    return this._getPartStatement;
+  },
 
-  get content() {
-    if (this._content)
-      return this._content;
+  _getPart: function(aPartType) {
+    let part;
 
     try {
-      this._contentStatement.params.messageID = this.id;
-      if (this._contentStatement.step()) {
-        this._content = Cc["@mozilla.org/feed-textconstruct;1"].
-                        createInstance(Ci.nsIFeedTextConstruct);
-        this._content.text = this._contentStatement.row.content;
-        this._content.type = textConstructTypes[this._contentStatement.row.contentType];
+      this._getPartStatement.params.messageID = this.id;
+      this._getPartStatement.params.partType = aPartType;
+      if (this._getPartStatement.step()) {
+        // FIXME: instead of a text construct, return a JS object that knows
+        // its ID and part type.
+        part = Cc["@mozilla.org/feed-textconstruct;1"].
+               createInstance(Ci.nsIFeedTextConstruct);
+        part.text = this._getPartStatement.row.content;
+        part.type = textConstructTypes[this._getPartStatement.row.mediaType];
+        part.base = URI.get(this._getPartStatement.row.baseURI);
+        part.lang = this._getPartStatement.row.languageCode;
       }
     }
     finally {
-      this._contentStatement.reset();
+      this._getPartStatement.reset();
     }
 
-    return this._content;
-  },
-
-  set content(newValue) {
-    this._content = newValue;
+    return part;
   },
 
   // FIXME: for performance, make this a class property rather than an instance
@@ -104,9 +134,3 @@
   }
 
 };
-
-let textConstructTypes = {
-  "text/html": "html",
-  "application/xhtml+xml": "xhtml",
-  "text/plain": "text"
-};
--- a/extension/modules/service.js	Fri May 16 10:42:36 2008 -0700
+++ b/extension/modules/service.js	Fri May 16 15:45:35 2008 -0700
@@ -9,6 +9,7 @@
 Cu.import("resource://snowl/modules/log4moz.js");
 Cu.import("resource://snowl/modules/datastore.js");
 Cu.import("resource://snowl/modules/feed.js");
+Cu.import("resource://snowl/modules/source.js");
 Cu.import("resource://snowl/modules/URI.js");
 
 const PERMS_FILE      = 0644;
@@ -171,7 +172,7 @@
   },
 
   get _getSourcesStatement() {
-    let statement = this.createStatement(
+    let statement = SnowlDatastore.createStatement(
       "SELECT id, name, machineURI, humanURI, lastRefreshed, importance FROM sources"
     );
     delete this._getSourcesStatement;