зеркало из https://github.com/mozilla/snowl.git
672 строки
26 KiB
JavaScript
672 строки
26 KiB
JavaScript
/* ***** BEGIN LICENSE BLOCK *****
|
|
* Version: MPL 1.1/GPL 2.0/LGPL 2.1
|
|
*
|
|
* The contents of this file are subject to the Mozilla Public License Version
|
|
* 1.1 (the "License"); you may not use this file except in compliance with
|
|
* the License. You may obtain a copy of the License at
|
|
* http://www.mozilla.org/MPL/
|
|
*
|
|
* Software distributed under the License is distributed on an "AS IS" basis,
|
|
* WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
|
|
* for the specific language governing rights and limitations under the
|
|
* License.
|
|
*
|
|
* The Original Code is Snowl.
|
|
*
|
|
* The Initial Developer of the Original Code is Mozilla.
|
|
* Portions created by the Initial Developer are Copyright (C) 2008
|
|
* the Initial Developer. All Rights Reserved.
|
|
*
|
|
* Contributor(s):
|
|
* Myk Melez <myk@mozilla.org>
|
|
*
|
|
* Alternatively, the contents of this file may be used under the terms of
|
|
* either the GNU General Public License Version 2 or later (the "GPL"), or
|
|
* the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
|
|
* in which case the provisions of the GPL or the LGPL are applicable instead
|
|
* of those above. If you wish to allow use of your version of this file only
|
|
* under the terms of either the GPL or the LGPL, and not to allow others to
|
|
* use your version of this file under the terms of the MPL, indicate your
|
|
* decision by deleting the provisions above and replace them with the notice
|
|
* and other provisions required by the GPL or the LGPL. If you do not delete
|
|
* the provisions above, a recipient may use your version of this file under
|
|
* the terms of any one of the MPL, the GPL or the LGPL.
|
|
*
|
|
* ***** END LICENSE BLOCK ***** */
|
|
|
|
let EXPORTED_SYMBOLS = ["SnowlSource"];
|
|
|
|
const Cc = Components.classes;
|
|
const Ci = Components.interfaces;
|
|
const Cr = Components.results;
|
|
const Cu = Components.utils;
|
|
|
|
// modules that are generic
|
|
Cu.import("resource://snowl/modules/log4moz.js");
|
|
Cu.import("resource://snowl/modules/Observers.js");
|
|
Cu.import("resource://snowl/modules/Sync.js");
|
|
Cu.import("resource://snowl/modules/URI.js");
|
|
|
|
// modules that are Snowl-specific
|
|
Cu.import("resource://snowl/modules/constants.js");
|
|
Cu.import("resource://snowl/modules/datastore.js");
|
|
Cu.import("resource://snowl/modules/message.js");
|
|
Cu.import("resource://snowl/modules/service.js");
|
|
Cu.import("resource://snowl/modules/utils.js");
|
|
|
|
// FIXME: make strands.js into a module.
|
|
let loader = Cc["@mozilla.org/moz/jssubscript-loader;1"].getService(Ci.mozIJSSubScriptLoader);
|
|
loader.loadSubScript("chrome://snowl/content/strands.js");
|
|
|
|
/**
|
|
* SnowlSource: a source of messages.
|
|
*
|
|
* FIXME: update this documentation now that we're using it via mixins
|
|
* instead of inheritance.
|
|
*
|
|
* This is an abstract class that should not be instantiated. Rather, objects
|
|
* should inherit it via one of two methods (depending on whether or not they
|
|
* also inherit other functionality):
|
|
*
|
|
* Objects that only inherit SnowlSource may assign it to their prototype
|
|
* (or to their prototype's prototype) and then declare overridden attributes
|
|
* as appropriate, with the prototype chain automatically delegating other
|
|
* attributes to SnowlSource:
|
|
*
|
|
* function MySource = {
|
|
* SnowlSource.init.call(this, ...);
|
|
* this.overriddenMethod: function(...) {...},
|
|
* this.overriddenProperty: "foo",
|
|
* this.__defineGetter("overriddenGetter", function() {...}),
|
|
* this.__defineSetter("overriddenSetter", function(newVal) {...}),
|
|
* }
|
|
* MySource.prototype = SnowlSource;
|
|
*
|
|
* -- or --
|
|
*
|
|
* function MySource = {
|
|
* SnowlSource.init.call(this, ...);
|
|
* }
|
|
* MySource.prototype = {
|
|
* __proto__: SnowlSource,
|
|
* overriddenMethod: function(...) {...},
|
|
* overriddenProperty: "foo",
|
|
* get overriddenGetter() {...},
|
|
* set overriddenSetter(newVal) {...}
|
|
* };
|
|
*
|
|
* Objects that inherit other functionality should redeclare every attribute
|
|
* in SnowlSource, manually delegating to SnowlSource as appropriate:
|
|
* FIXME: make it possible to import attributes instead of redeclaring them.
|
|
*
|
|
* function MyThing = {
|
|
* SnowlSource.init.call(this, ...);
|
|
* }
|
|
*
|
|
* MyThing.prototype = {
|
|
* overriddenMethod: function(...) {...},
|
|
* overriddenProperty: "foo",
|
|
* get overriddenGetter() {...},
|
|
* set overriddenSetter(newVal) {...}
|
|
*
|
|
* delegatedMethod: function(...) {
|
|
* SnowlSource.call(this, ...);
|
|
* },
|
|
*
|
|
* get delegatedProperty: function() {
|
|
* return SnowlSource.delegatedProperty;
|
|
* },
|
|
*
|
|
* // It's dangerous to set the base class's properties; don't do this!!!
|
|
* set delegatedProperty: function(newVal) {
|
|
* SnowlSource.delegatedProperty = newVal;
|
|
* },
|
|
*
|
|
* get delegatedGetter: function() {
|
|
* return SnowlSource.__lookupGetter__("delegatedGetter").call(this);
|
|
* },
|
|
*
|
|
* set delegatedSetter: function(newVal) {
|
|
* SnowlSource.__lookupSetter__("delegatedSetter").call(this, newVal);
|
|
* }
|
|
* };
|
|
*
|
|
* Memoizing unary getters in this object must memoize to another getter
|
|
* so that subclasses can call the getters directly without causing trouble
|
|
* for other subclasses that access them via __lookupGetter__.
|
|
*/
|
|
function SnowlSource() {}
|
|
|
|
SnowlSource.retrieve = function(id) {
|
|
let source = null;
|
|
|
|
// FIXME: memoize this.
|
|
let statement = SnowlDatastore.createStatement(
|
|
"SELECT type, name, machineURI, humanURI, username, lastRefreshed, " +
|
|
" importance, placeID, attributes " +
|
|
"FROM sources " +
|
|
"WHERE id = :id"
|
|
);
|
|
|
|
try {
|
|
statement.params.id = id;
|
|
if (statement.step()) {
|
|
let row = statement.row;
|
|
let constructor;
|
|
// Bleh, this function is called within the JS context for this module,
|
|
// which means it doesn't know anything about other modules it doesn't
|
|
// import (like SnowlFeed and SnowlTwitter). The current hack to deal
|
|
// with this is to set the constructor to |this| hoping that |this| is
|
|
// the right constructor (which it is as long as this function got mixed
|
|
// into the right constructor), but this isn't going to work when we want
|
|
// to use this to pull all accounts and make them available in the service,
|
|
// so we'll have to figure out something better to do then.
|
|
try { constructor = eval(row.type) } catch(ex) { constructor = this };
|
|
source = new constructor(id,
|
|
row.name,
|
|
URI.get(row.machineURI),
|
|
URI.get(row.humanURI),
|
|
row.username,
|
|
row.lastRefreshed ? SnowlDateUtils.julianToJSDate(row.lastRefreshed) : null,
|
|
row.importance,
|
|
row.placeID,
|
|
JSON.parse(row.attributes));
|
|
}
|
|
}
|
|
finally {
|
|
statement.reset();
|
|
}
|
|
|
|
return source;
|
|
}
|
|
|
|
SnowlSource.prototype = {
|
|
init: function(aID, aName, aMachineURI, aHumanURI, aUsername,
|
|
aLastRefreshed, aImportance, aPlaceID, aAttributes) {
|
|
this.id = aID;
|
|
this.name = aName;
|
|
this.machineURI = aMachineURI;
|
|
this.humanURI = aHumanURI;
|
|
this.username = aUsername;
|
|
this.lastRefreshed = aLastRefreshed;
|
|
// FIXME: make it so I don't have to set importance to null if it isn't
|
|
// specified in order for its non-set value to remain null.
|
|
this.importance = aImportance || null;
|
|
this.placeID = aPlaceID;
|
|
this.attributes = aAttributes || this.Attributes;
|
|
},
|
|
|
|
get _log() {
|
|
let logger = Log4Moz.repository.getLogger(this._logName);
|
|
this.__defineGetter__("_log", function() logger);
|
|
return this._log;
|
|
},
|
|
|
|
// For adding isBusy property to collections tree.
|
|
busy: false,
|
|
|
|
// For adding hasError property to collections tree.
|
|
error: false,
|
|
|
|
id: null,
|
|
|
|
name: null,
|
|
|
|
/**
|
|
* The URL at which to find a machine-processable representation of the data
|
|
* provided by the source. For a feed source, this is the URL of its RSS/Atom
|
|
* document; for an email source, it's the URL of its POP/IMAP server.
|
|
*/
|
|
machineURI: null,
|
|
|
|
/**
|
|
* The codebase principal for the machine URI. We use this to determine
|
|
* whether or not the source can link to the links it provides, so we can
|
|
* prevent sources from linking to javascript: and data: links that would
|
|
* run with chrome privileges if inserted into our views.
|
|
*/
|
|
get principal() {
|
|
let securityManager = Cc["@mozilla.org/scriptsecuritymanager;1"].
|
|
getService(Ci.nsIScriptSecurityManager);
|
|
let principal = securityManager.getCodebasePrincipal(this.machineURI);
|
|
this.__defineGetter__("principal", function() principal);
|
|
return this.principal;
|
|
},
|
|
|
|
// The URL at which to find a human-readable representation of the data
|
|
// provided by the source. For a feed source, this is the website that
|
|
// publishes the feed; for an email source, it might be the webmail interface.
|
|
humanURI: null,
|
|
|
|
// The username with which the user gets authorized to access the account.
|
|
username: null,
|
|
|
|
// A JavaScript Date object representing the last time this source
|
|
// was checked for updates to its set of messages. If source attributes not
|
|
// set to use default source type value *and* source type setting does not
|
|
// override customizations on individual sources, then use the custom value.
|
|
lastRefreshed: null,
|
|
|
|
get refreshInterval() {
|
|
return this.attributes.refresh["useDefault"] ||
|
|
SnowlService._accountTypesByType[this.constructor.name].
|
|
attributes.refresh["useDefault"] ?
|
|
SnowlService._accountTypesByType[this.constructor.name].
|
|
attributes.refresh["interval"] :
|
|
this.attributes.refresh["interval"];
|
|
},
|
|
|
|
// An integer representing how important this source is to the user
|
|
// relative to other sources to which the user is subscribed.
|
|
importance: null,
|
|
|
|
// The ID of the place representing this source in a list of collections.
|
|
placeID: null,
|
|
|
|
//**************************************************************************//
|
|
// The default global attributes for a all sources. Source types may override
|
|
// and add their own attributes (but need to consider such exceptions in
|
|
// generic handling). The attributes objects are combined by Mixins.meld().
|
|
// If a global or source type attribute is changed, db maintenance must remove
|
|
// that source type record (SnowlFeed, SnowlTwitter) so it may be rebuilt with
|
|
// all new values (also will overwrite user changes, if any), or update only
|
|
// those attributes changing.
|
|
attributes: {
|
|
refresh: {
|
|
// If true for the default Type, overrides individual setting; if
|
|
// true for individual source, overrides default if default is false.
|
|
useDefault: true,
|
|
// 30 minutes
|
|
interval: 1000 * 60 * 30,
|
|
// Status determines behavior of auto and user refreshes:
|
|
// 'active' - auto refresh on interval, immediately on user action
|
|
// 'paused' - no refresh on auto or user action, set by user
|
|
// 'disabled' - refresh only on user action, auto set on permanent error
|
|
status: "active",
|
|
// Code, usually response code, or internal error code.
|
|
code: "",
|
|
// Descriptive error message.
|
|
text: ""
|
|
},
|
|
retention: {
|
|
// If true for the default Type, overrides individual setting; if
|
|
// true for individual source, overrides default if default is false.
|
|
useDefault: true,
|
|
// If 0, do not delete any messaged; if 1, delete by number of messages;
|
|
// if 2, delete by days old.
|
|
deleteBy: 0,
|
|
// If radio checked, delete messages older than number (of days).
|
|
deleteDays: 30,
|
|
// If radio checked, delete messages greater than number (of messages).
|
|
deleteNumber: 500,
|
|
// If true, messages will never be auto deleted.
|
|
keepFlagged: true
|
|
}
|
|
},
|
|
|
|
// Retrieve the melded global and source default attributes, and customizations,
|
|
// for seeding newly subscribed source attributes.
|
|
get Attributes() {
|
|
delete this._Attributes;
|
|
return this._Attributes =
|
|
SnowlService._accountTypesByType[this.constructor.name].attributes;
|
|
},
|
|
|
|
// The collection of messages from this source.
|
|
messages: null,
|
|
|
|
// Favicon Service
|
|
get faviconSvc() {
|
|
let faviconSvc = Cc["@mozilla.org/browser/favicon-service;1"].
|
|
getService(Ci.nsIFaviconService);
|
|
this.__defineGetter__("faviconSvc", function() faviconSvc);
|
|
return this.faviconSvc;
|
|
},
|
|
|
|
// If a favicon is not in places, getFaviconForPage throws, but we do not want
|
|
// to try getFaviconImageForPage as that returns a default moz image. Best
|
|
// efforts and tricks to get a favicon.
|
|
get faviconURI() {
|
|
if (this.humanURI) {
|
|
try {
|
|
// Due to private browsing changes, the favicon will only be in places
|
|
// if the page has been bookmarked.
|
|
return this.faviconSvc.getFaviconForPage(this.humanURI);
|
|
}
|
|
catch(ex) {
|
|
// Not bookmarked.
|
|
if (this.constructor.name == "SnowlFeed")
|
|
// Get the favicon - they're usually here for feeds.. If not, an
|
|
// atom is set for defaultFeedIcon and css handles it.
|
|
return URI.get('http://' + this.humanURI.host + '/favicon.ico');
|
|
|
|
if (this.constructor.name == "SnowlTwitter")
|
|
// Get the favicon - Twitter.
|
|
return URI.get("http://static.twitter.com/images/favicon.ico");
|
|
}
|
|
}
|
|
},
|
|
|
|
/**
|
|
* Check for new messages and update the local store of messages to reflect
|
|
* the latest updates available from the source. This method is a stub that
|
|
* is expected to be overridden by subclass implementations.
|
|
*
|
|
* @param refreshTime {Date}
|
|
* the time at which a refresh currently in progress began
|
|
* Note: we use this as the received time when adding messages to
|
|
* the datastore. We get it from the caller instead of generating it
|
|
* ourselves to allow the caller to synchronize received times
|
|
* across refreshes of multiple sources, which makes message views
|
|
* sorted by received, then published look better for messages
|
|
* received in the same refresh cycle.
|
|
*/
|
|
refresh: function(refreshTime) {},
|
|
|
|
onRefreshError: function() {
|
|
this.error = true;
|
|
if (this.attributes.refresh["code"] == 401 ||
|
|
this.attributes.refresh["code"] == 404) {
|
|
this.attributes.refresh["status"] = "disabled";
|
|
SnowlService.sourcesByID[this.id].attributes.refresh["status"] = "disabled";
|
|
}
|
|
|
|
this._log.error("Refresh error: " + this.attributes.refresh["text"]);
|
|
},
|
|
|
|
onDbCompleted: function() {
|
|
// Database source record updated, set notifications and states.
|
|
if (this.id) {
|
|
// Only for existing stored sources; notify refreshes collections tree state.
|
|
SnowlService.sourcesByID[this.id].busy = false;
|
|
SnowlService.sourcesByID[this.id].error = this.error;
|
|
SnowlService.refreshingCount = --SnowlService.refreshingCount;
|
|
}
|
|
Observers.notify("snowl:messages:completed", this.id);
|
|
},
|
|
|
|
onDbError: function() {
|
|
this.error = true;
|
|
if (this.id) {
|
|
// Only for existing stored sources; notify refreshes collections tree state.
|
|
SnowlService.sourcesByID[this.id].busy = false;
|
|
SnowlService.sourcesByID[this.id].error = this.error;
|
|
SnowlService.refreshingCount = --SnowlService.refreshingCount;
|
|
Observers.notify("snowl:messages:completed", this.id);
|
|
}
|
|
this._log.error("Database error: " + this.attributes.refresh["text"]);
|
|
},
|
|
|
|
retrieveMessages: function() {
|
|
// FIXME: memoize this.
|
|
let messagesStatement = SnowlDatastore.createStatement(
|
|
"SELECT id FROM messages WHERE sourceID = :id"
|
|
);
|
|
|
|
try {
|
|
messagesStatement.params.id = id;
|
|
this.messages = [];
|
|
// FIXME: retrieve all messages at once instead of one at a time.
|
|
while (messagesStatement.step())
|
|
this.messages.push(SnowlMessage.retrieve(messagesStatement.row.id));
|
|
}
|
|
finally {
|
|
messagesStatement.reset();
|
|
}
|
|
},
|
|
|
|
/**
|
|
* Insert a record for this source into the database, or update an existing
|
|
* record; store placeID back into sources table.
|
|
*
|
|
* @param pauseBetweenMessages {Boolean}
|
|
* whether or not to pause between each message we persist; useful for
|
|
* mixing up messages received at the same time from different sources
|
|
* when refreshing multiple sources at once
|
|
*
|
|
* FIXME: move this to a SnowlAccount interface.
|
|
*/
|
|
persist: function(pauseBetweenMessages) {
|
|
let statement, placeID;
|
|
if (this.id) {
|
|
statement = SnowlDatastore.createStatement(
|
|
"UPDATE sources " +
|
|
"SET name = :name, " +
|
|
" type = :type, " +
|
|
" machineURI = :machineURI, " +
|
|
" humanURI = :humanURI, " +
|
|
" username = :username, " +
|
|
"lastRefreshed = :lastRefreshed, " +
|
|
" importance = :importance, " +
|
|
" attributes = :attributes " +
|
|
"WHERE id = :id"
|
|
);
|
|
}
|
|
else {
|
|
statement = SnowlDatastore.createStatement(
|
|
"INSERT INTO sources ( name, type, machineURI, humanURI, username, " +
|
|
" lastRefreshed, importance, attributes) " +
|
|
"VALUES ( :name, :type, :machineURI, :humanURI, :username, " +
|
|
" :lastRefreshed, :importance, :attributes)"
|
|
);
|
|
}
|
|
|
|
// Need to get a transaction lock.
|
|
if (SnowlDatastore.dbConnection.transactionInProgress) {
|
|
this.attributes.refresh["code"] = "db:transactionInProgress";
|
|
this.attributes.refresh["text"] = "Database temporarily busy, could not get transaction lock";
|
|
if (this.id) {
|
|
// Only for existing stored sources; notify refreshes collections tree state.
|
|
SnowlService.sourcesByID[this.id].busy = false;
|
|
SnowlService.sourcesByID[this.id].error = this.error;
|
|
SnowlService.refreshingCount = --SnowlService.refreshingCount;
|
|
// Observers.notify("snowl:messages:completed", this.id);
|
|
}
|
|
else {
|
|
// New subscriptions need to return feedback.
|
|
this.error = true;
|
|
this._log.info("persist: " + this.attributes.refresh["text"]);
|
|
}
|
|
|
|
return;
|
|
}
|
|
|
|
SnowlDatastore.dbConnection.beginTransaction();
|
|
try {
|
|
statement.params.name = this.name;
|
|
statement.params.type = this.constructor.name;
|
|
statement.params.machineURI = this.machineURI.spec;
|
|
statement.params.humanURI = this.humanURI ? this.humanURI.spec : null;
|
|
statement.params.username = this.username;
|
|
statement.params.lastRefreshed = this.lastRefreshed ? SnowlDateUtils.jsToJulianDate(this.lastRefreshed) : null;
|
|
statement.params.importance = this.importance;
|
|
statement.params.attributes = JSON.stringify(this.attributes);
|
|
if (this.id)
|
|
statement.params.id = this.id;
|
|
statement.step();
|
|
if (!this.id) {
|
|
// Extract the ID of the source from the newly-created database record.
|
|
this.id = SnowlDatastore.dbConnection.lastInsertRowID;
|
|
// New source, bump refreshing count.
|
|
SnowlService.refreshingCount = ++SnowlService.refreshingCount;
|
|
|
|
// Update message authors to include the source ID.
|
|
// FIXME: make SnowlIdentity records have a source property
|
|
// referencing a source object rather than a sourceID property
|
|
// referencing a source object's ID.
|
|
if (this.messages)
|
|
for each (let message in this.messages)
|
|
if (message.author)
|
|
message.author.sourceID = this.id;
|
|
|
|
// Create places record
|
|
this.placeID = SnowlPlaces.persistPlace("sources",
|
|
this.id,
|
|
this.name,
|
|
this.machineURI,
|
|
null, // this.username,
|
|
this.faviconURI,
|
|
this.id); // aSourceID
|
|
|
|
// Store placeID back into messages for db integrity
|
|
SnowlDatastore.dbConnection.executeSimpleSQL(
|
|
"UPDATE sources " +
|
|
"SET placeID = " + this.placeID +
|
|
" WHERE id = " + this.id);
|
|
this._log.debug("persist placeID:sources.id - " + this.placeID + " : " + this.id);
|
|
|
|
// Use 'added' here for collections observer for more specificity
|
|
Observers.notify("snowl:source:added", this.placeID);
|
|
}
|
|
|
|
if (this.messages) {
|
|
// Sort the messages by date, so we insert them from oldest to newest,
|
|
// which makes them show up in the correct order in views that expect
|
|
// messages to be inserted in that order and sort messages by their IDs.
|
|
this.messages.sort(function(a, b) a.timestamp < b.timestamp ? -1 :
|
|
a.timestamp > b.timestamp ? 1 : 0);
|
|
|
|
let currentMessageIDs = [];
|
|
let messagesChanged = false;
|
|
|
|
for each (let message in this.messages) {
|
|
let added = false;
|
|
message.id = message._getInternalID();
|
|
if (!message.id) {
|
|
// Persist only new messages, ie without an id.
|
|
this._log.info("persisting new message " + message.externalID);
|
|
|
|
try {
|
|
added = message.persist();
|
|
}
|
|
catch(ex) {
|
|
this._log.error("couldn't persist " + message.externalID + ": " + ex);
|
|
continue;
|
|
}
|
|
}
|
|
|
|
if (messagesChanged == false && added)
|
|
messagesChanged = true;
|
|
currentMessageIDs.push(message.id);
|
|
|
|
// Sleep for a bit to give other sources that are being refreshed
|
|
// at the same time the opportunity to insert messages themselves,
|
|
// so the messages appear mixed together in views that display them
|
|
// by the order in which they are received, which is more pleasing
|
|
// than if the messages were clumped together by source.
|
|
// As a side effect, this might reduce horkage of the UI thread
|
|
// during refreshes.
|
|
if (pauseBetweenMessages)
|
|
Sync.sleep(50);
|
|
}
|
|
|
|
// Update the current flag.
|
|
this.updateCurrentMessages(currentMessageIDs);
|
|
|
|
if (messagesChanged)
|
|
// Invalidate stats cache on completion of refresh with new messages.
|
|
SnowlService._collectionStatsByCollectionID = null;
|
|
}
|
|
|
|
SnowlDatastore.dbConnection.commitTransaction();
|
|
|
|
// Source successfully stored/updated.
|
|
this.onDbCompleted();
|
|
}
|
|
catch(ex) {
|
|
SnowlDatastore.dbConnection.rollbackTransaction();
|
|
this.attributes.refresh["code"] = "db:error";
|
|
this.attributes.refresh["text"] = ex;
|
|
this.onDbError();
|
|
}
|
|
finally {
|
|
statement.reset();
|
|
}
|
|
|
|
return this.id;
|
|
},
|
|
|
|
get _persistAttributesStmt() {
|
|
let statement = SnowlDatastore.createStatement(
|
|
"UPDATE sources SET attributes = :attributes WHERE id = :id");
|
|
this.__defineGetter__("_persistAttributesStmt", function() statement);
|
|
return this._persistAttributesStmt;
|
|
},
|
|
|
|
persistAttributes: function() {
|
|
try {
|
|
this._persistAttributesStmt.params.id = this.id;
|
|
this._persistAttributesStmt.params.attributes = JSON.stringify(this.attributes);
|
|
this._persistAttributesStmt.step()
|
|
}
|
|
finally {
|
|
this._persistAttributesStmt.reset();
|
|
}
|
|
},
|
|
|
|
unstore: function() {
|
|
SnowlDatastore.dbConnection.beginTransaction();
|
|
try {
|
|
// FIXME: delegate unstorage of messages and people to their respective
|
|
// JavaScript representations.
|
|
SnowlDatastore.dbConnection.executeSimpleSQL("DELETE FROM partsText " +
|
|
"WHERE docid IN " +
|
|
"(SELECT id FROM parts WHERE messageID IN " +
|
|
"(SELECT id FROM messages WHERE sourceID = " + this.id + "))");
|
|
SnowlDatastore.dbConnection.executeSimpleSQL("DELETE FROM parts " +
|
|
"WHERE messageID IN " +
|
|
"(SELECT id FROM messages WHERE sourceID = " + this.id + ")");
|
|
SnowlDatastore.dbConnection.executeSimpleSQL("DELETE FROM messages " +
|
|
"WHERE sourceID = " + this.id);
|
|
// FIXME: don't delete people unless the only identities with which
|
|
// they are associated are identities associated with this source.
|
|
SnowlDatastore.dbConnection.executeSimpleSQL("DELETE FROM people " +
|
|
"WHERE id IN " +
|
|
"(SELECT personID FROM identities WHERE sourceID = " + this.id + ")");
|
|
SnowlDatastore.dbConnection.executeSimpleSQL("DELETE FROM identities " +
|
|
"WHERE sourceID = " + this.id);
|
|
SnowlDatastore.dbConnection.executeSimpleSQL("DELETE FROM sources " +
|
|
"WHERE id = " + this.id);
|
|
|
|
// Finally, clean up Places bookmarks with sourceID in its prefixed uri.
|
|
SnowlPlaces.removePlacesItemsByURI("snowl:sId=" + this.id + "&", true);
|
|
|
|
SnowlDatastore.dbConnection.commitTransaction();
|
|
}
|
|
catch(ex) {
|
|
SnowlDatastore.dbConnection.rollbackTransaction();
|
|
throw ex;
|
|
}
|
|
|
|
Observers.notify("snowl:source:unstored", this.id);
|
|
this.id = null;
|
|
},
|
|
|
|
/**
|
|
* Update the current flag for messages in a source, after a refresh.
|
|
* If message's current flag = 1 set to 0, then set current flag for messages
|
|
* in the current refresh list to 1. Purge current and marked deleted
|
|
* placeholder message records if no longer current.
|
|
*
|
|
* @param aCurrentMessageIDs {array} messages table ids of the current list
|
|
*/
|
|
updateCurrentMessages: function(aCurrentMessageIDs) {
|
|
SnowlDatastore.dbConnection.executeSimpleSQL(
|
|
"UPDATE messages SET current = " + MESSAGE_NON_CURRENT +
|
|
" WHERE sourceID = " + this.id + " AND current = " + MESSAGE_CURRENT
|
|
);
|
|
SnowlDatastore.dbConnection.executeSimpleSQL(
|
|
"UPDATE messages SET current = " + MESSAGE_CURRENT +
|
|
" WHERE sourceID = " + this.id + " AND id IN" +
|
|
" (" + aCurrentMessageIDs.join(", ") + ") AND current = " + MESSAGE_NON_CURRENT
|
|
);
|
|
SnowlDatastore.dbConnection.executeSimpleSQL(
|
|
"DELETE FROM messages" +
|
|
" WHERE sourceID = " + this.id + " AND" +
|
|
" current = " + MESSAGE_CURRENT_PENDING_PURGE + " AND" +
|
|
" id NOT IN (" + aCurrentMessageIDs.join(", ") + ")"
|
|
);
|
|
}
|
|
|
|
};
|