Mercurial > snowl
view extension/modules/collection.js @ 174:88b162978988
collection object rearch: substantiate conditions top-level object
author | Myk Melez <myk@mozilla.org> |
---|---|
date | Thu, 17 Jul 2008 16:11:30 -0700 |
parents | 4d59aa183d57 |
children | 5409a8759186 |
line wrap: on
line source
const EXPORTED_SYMBOLS = ["SnowlCollection"]; const Cc = Components.classes; const Ci = Components.interfaces; 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. */ function SnowlCollection(aSourceID, aFilter, aCurrent, aRead, aAuthorID, conditions) { this._sourceID = aSourceID; this._authorID = aAuthorID; this._filter = aFilter; this._current = aCurrent; this._read = aRead; this.conditions = conditions || []; } SnowlCollection.prototype = { get _log() { let log = Log4Moz.Service.getLogger("Snowl.Collection"); this.__defineGetter__("_log", function() { return log }); return this._log; }, _sourceID: null, get sourceID() { return this._sourceID; }, set sourceID(newVal) { if (this._sourceID == newVal) return; this._sourceID = newVal; this.invalidate(); }, _authorID: null, get authorID() { return this._authorID; }, set authorID(newVal) { if (this._authorID == newVal) return; this._authorID = newVal; this.invalidate(); }, _filter: null, get filter() { return this._filter; }, set filter(newVal) { if (this._filter == newVal) return; this._filter = newVal; this.invalidate(); }, _current: undefined, get current() { return this._current; }, set current(newValue) { if (this._current === newValue) return; this._current = (typeof newValue == "undefined") ? undefined : newValue ? true : false; // Invalidate the message cache. this._messages = null; }, _read: undefined, get read() { return this._read; }, set read(newValue) { if (this._read === newValue) return; this._read = (typeof newValue == "undefined") ? undefined : newValue ? true : false; // Invalidate the message cache. this._messages = null; }, //**************************************************************************// // Grouping nameGroupField: null, uriGroupField: null, isOpen: false, _groups: null, get groups() { if (!this.nameGroupField) return null; if (this._groups) return this._groups; let groups = []; let statement = this._generateGetGroupsStatement(); try { while (statement.step()) { let conditions = [ { expression: this.nameGroupField + " = :groupValue", parameters: { groupValue: statement.row.name } } ]; let group = new SnowlCollection(this.sourceID, this.filter, this.current, this.read, this.authorID, conditions); group.name = statement.row.name; group.uri = URI.get(statement.row.uri); groups.push(group); } } finally { statement.reset(); } this._log.info("got " + groups.length + " groups"); return this._groups = groups; }, _generateGetGroupsStatement: function() { let query = "SELECT DISTINCT(" + this.nameGroupField + ") AS name, " + this.uriGroupField + " AS uri " + "FROM sources JOIN messages ON sources.id = messages.sourceID " + "LEFT JOIN people AS authors ON messages.authorID = authors.id"; let conditions = this._generateConditions(); if (conditions.length > 0) query += " WHERE " + conditions.join(" AND "); query += " ORDER BY " + this.nameGroupField; this._log.info("groups query: " + query); let statement = SnowlDatastore.createStatement(query); if (this.sourceID) statement.params.sourceID = this.sourceID; if (this.authorID) statement.params.authorID = this.authorID; if (this.filter) statement.params.filter = this.filter; return statement; }, _generateConditions: function() { let conditions = []; if (this.sourceID) conditions.push("messages.sourceID = :sourceID"); if (this.authorID) conditions.push("messages.authorID = :authorID"); // FIXME: use a left join here once the SQLite bug breaking left joins to // virtual tables has been fixed (i.e. after we upgrade to SQLite 3.5.7+). if (this.filter) conditions.push("messages.id IN (SELECT messageID FROM parts WHERE content MATCH :filter)"); if (typeof this.current != "undefined") conditions.push("current = " + (this.current ? "1" : "0")); if (typeof this.read != "undefined") conditions.push("read = " + (this.read ? "1" : "0")); return conditions; }, //**************************************************************************// // Retrieval sortProperty: "timestamp", sortOrder: 1, _messages: null, get messages() { if (this._messages) return this._messages; this._messages = []; this._messageIndex = {}; let statement = this._generateStatement(); try { while (statement.step()) { let message = new SnowlMessage(statement.row.id, statement.row.subject, statement.row.author, statement.row.link, statement.row.timestamp, (statement.row.read ? true : false)); this._messages.push(message); this._messageIndex[message.id] = message; } } finally { statement.reset(); } this.sort(this.sortProperty, this.sortOrder); // A bug in SQLite breaks relating a virtual table via a LEFT JOIN, so we // can't pull content with our initial query. Instead we do it here. // FIXME: stop doing this once we upgrade to a version of SQLite that does // not have this problem (i.e. 3.5.6+). this._getContent(); this._log.info("Retrieved " + this._messages.length + " messages."); return this._messages; }, invalidate: function() { this._messages = null; }, _getContent: function() { 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); try { while (statement.step()) { let content = Cc["@mozilla.org/feed-textconstruct;1"]. createInstance(Ci.nsIFeedTextConstruct); content.text = statement.row.content; 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; } } finally { statement.reset(); } }, _generateStatement: function() { let query = //"SELECT subject, author, link, timestamp, content \ // FROM sources JOIN messages ON sources.id = messages.sourceID \ // LEFT JOIN parts on messages.id = parts.messageID"; "SELECT messages.id, subject, authors.name AS author, link, timestamp, read " + "FROM sources JOIN messages ON sources.id = messages.sourceID " + "LEFT JOIN people AS authors ON messages.authorID = authors.id"; let conditions = []; if (this.sourceID) conditions.push("messages.sourceID = :sourceID"); if (this.authorID) conditions.push("messages.authorID = :authorID"); // FIXME: use a left join here once the SQLite bug breaking left joins to // virtual tables has been fixed (i.e. after we upgrade to SQLite 3.5.7+). if (this.filter) conditions.push("messages.id IN (SELECT messageID FROM parts WHERE content MATCH :filter)"); if (typeof this.current != "undefined") conditions.push("current = " + (this.current ? "1" : "0")); if (typeof this.read != "undefined") conditions.push("read = " + (this.read ? "1" : "0")); for each (let condition in this.conditions) conditions.push(condition.expression); if (conditions.length > 0) query += " WHERE " + conditions.join(" AND "); this._log.info(query); let statement = SnowlDatastore.createStatement(query); if (this.sourceID) statement.params.sourceID = this.sourceID; if (this.authorID) statement.params.authorID = this.authorID; if (this.filter) statement.params.filter = this.filter; for each (let condition in this.conditions) for (let [name, value] in Iterator(condition.parameters)) statement.params[name] = value; return statement; }, sort: function(aProperty, aOrder) { this.sortProperty = aProperty; this.sortOrder = aOrder; let compare = function(a, b) { if (prepareObjectForComparison(a[aProperty]) > prepareObjectForComparison(b[aProperty])) return 1 * aOrder; if (prepareObjectForComparison(a[aProperty]) < prepareObjectForComparison(b[aProperty])) return -1 * aOrder; // Fall back on the "subject" aProperty. if (aProperty != "subject") { if (prepareObjectForComparison(a.subject) > prepareObjectForComparison(b.subject)) return 1 * aOrder; if (prepareObjectForComparison(a.subject) < prepareObjectForComparison(b.subject)) return -1 * aOrder; } // Return an inconclusive result. return 0; }; this.messages.sort(compare); } } function prepareObjectForComparison(aObject) { if (typeof aObject == "string") return aObject.toLowerCase(); // Null values are neither greater than nor less than strings, so we // convert them into empty strings, which is how they appear to users. if (aObject == null) return ""; return aObject; } function Group(name, uri) { this.name = name; this.uri = uri; } Group.prototype = { // Favicon Service get _faviconSvc() { let faviconSvc = Cc["@mozilla.org/browser/favicon-service;1"]. getService(Ci.nsIFaviconService); delete this.__proto__._faviconSvc; this.__proto__._faviconSvc = faviconSvc; return this._faviconSvc; }, get faviconURI() { if (this.uri) { try { return this._faviconSvc.getFaviconForPage(this.uri); } catch(ex) { /* no known favicon; use the default */ } } // The default favicon for feed sources. // FIXME: make this group-specific. return URI.get("chrome://browser/skin/feeds/feedIcon16.png"); } }