/* ***** 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 bandwagon. * * The Initial Developer of the Original Code is * Mozilla Corporation. * Portions created by the Initial Developer are Copyright (C) 2008 * the Initial Developer. All Rights Reserved. * * Contributor(s): David McNamara * Brian King * * 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 ***** */ const nsISupports = Components.interfaces.nsISupports; const CLASS_ID = Components.ID("5c896f09-126c-466d-b28a-4e8b87a29916"); const CLASS_NAME = ""; const CONTRACT_ID = "@addons.mozilla.org/bandwagonservice;1"; const Cc = Components.classes; const Ci = Components.interfaces; const WindowMediator = Cc["@mozilla.org/appshell/window-mediator;1"]; const Timer = Cc["@mozilla.org/timer;1"]; const ExtensionsManager = Cc["@mozilla.org/extensions/manager;1"]; const Storage = Cc["@mozilla.org/storage/service;1"]; const DirectoryService = Cc["@mozilla.org/file/directory_service;1"]; const ObserverService = Cc["@mozilla.org/observer-service;1"]; const CookieManager = Cc["@mozilla.org/cookiemanager;1"]; const LoginManager = Cc["@mozilla.org/login-manager;1"]; const UpdateItem = Cc["@mozilla.org/updates/item;1"]; const nsIWindowMediator = Ci.nsIWindowMediator; const nsITimer = Ci.nsITimer; const nsIExtensionManager = Ci.nsIExtensionManager; const mozIStorageService = Ci.mozIStorageService; const nsIProperties = Ci.nsIProperties; const nsIFile = Ci.nsIFile; const nsIObserverService = Ci.nsIObserverService; const nsICookieManager = Ci.nsICookieManager; const nsILoginManager = Ci.nsILoginManager; const nsIUpdateItem = Ci.nsIUpdateItem; var Bandwagon; var bandwagonService; var gEmGUID; var gUninstallObserverInited = false; /* Restore settings added or changed by the extension: * - extension preferences * - logins stored in the Login Manager? */ function cleanupSettings() { // Cleanup preferences var prefs = Components.classes["@mozilla.org/preferences-service;1"] .getService(Components.interfaces.nsIPrefBranch); try { prefs.deleteBranch("extensions.bandwagon"); } catch(e) {} } /* Returns host application string */ function getAppName() { var info = Components.classes["@mozilla.org/xre/app-info;1"] .getService(Components.interfaces.nsIXULAppInfo); // Get the name of the application running us return info.name; // Returns "Firefox" for Firefox } function BandwagonService() { this.wrappedJSObject = this; gEmGUID = "sharing@addons.mozilla.org"; } BandwagonService.prototype = { collections: {}, _initialized: false, _service: null, _collectionUpdateObservers: [], _collectionListChangeObservers: [], _authenticationStatusChangeObservers: [], _storageConnection: null, _collectionFactory: null, _collectionUpdateTimer: null, _bwObserver: null, _serviceDocument: null, init: function() { if (this._initialized) return; // get access to Bandwagon.* singletons var app = getAppName(); var appWinString = "navigator:browser"; // default, Firefox if (app == "Thunderbird") appWinString = "mail:3pane"; var appWindow = WindowMediator.getService(nsIWindowMediator).getMostRecentWindow(appWinString); if (!appWindow || !appWindow.Bandwagon) { debug("Bandwagon: could not get access to Bandwagon singletons from last window"); return; } Bandwagon = appWindow.Bandwagon; bandwagonService = this; Bandwagon.Logger.info("Initializing Bandwagon"); // init rpc service this._service = new Bandwagon.RPC.Service(); this._service.registerLogger(Bandwagon.Logger); this._service.registerObserver(this._getCollectionObserver); this._service.registerObserver(this._getServiceDocumentObserver); this._service.registerObserver(this._notAuthorizedObserver); this.registerCollectionUpdateObserver(this._collectionUpdateObserver); // init sqlite storage (also creating tables in sqlite if needed). create factory objects. this._initStorage(); // first run stuff if (Bandwagon.Preferences.getPreference("firstrun") == true) { Bandwagon.Preferences.setPreference("firstrun", false); this.firstrun(); } // start the update timer this._initUpdateTimer(); // observe when the app shuts down so we can uninit ObserverService.getService(nsIObserverService).addObserver(this._bwObserver, "quit-application", false); // storage initialized, tables created - open the collections and service document var callback = function() { // kick off the auto-publish functionality bandwagonService.autopublishExtensions(); // kick off the auto-install functionality bandwagonService.autoinstallExtensions(); bandwagonService._initialized = true; Bandwagon.Logger.info("Bandwagon has been initialized"); } this._initCollections(callback); }, _initCollections: function(callback) { this._collectionFactory.openCollections(function(result, storageCollections) { if (result == Bandwagon.STMT_OK) { for (var id in storageCollections) { bandwagonService.collections[id] = storageCollections[id]; bandwagonService.collections[id].setAllNotified(); if (bandwagonService.collections[id].isLocalAutoPublisher()) { bandwagonService.collections[id].autoPublishExtensions = Bandwagon.Preferences.getPreference("local.autopublisher.publish.extensions"); bandwagonService.collections[id].autoPublishThemes = Bandwagon.Preferences.getPreference("local.autopublisher.publish.themes"); bandwagonService.collections[id].autoPublishDicts = Bandwagon.Preferences.getPreference("local.autopublisher.publish.dictionaries"); bandwagonService.collections[id].autoPublishLangPacks = Bandwagon.Preferences.getPreference("local.autopublisher.publish.language.packs"); bandwagonService.collections[id].autoPublishDisabled = !Bandwagon.Preferences.getPreference("local.autopublisher.only.publish.enabled"); } Bandwagon.Logger.debug("opened collection from storage: " + id); } } bandwagonService._collectionFactory.openServiceDocument(function(result, serviceDocument) { bandwagonService._service._serviceDocument = serviceDocument; if (result != Bandwagon.STMT_OK || serviceDocument == null) { // no service document in storage, we never had it or we've lost it - go fetch it Bandwagon.Logger.info("No service document found in storage, fetching..."); bandwagonService.updateCollectionsList(); } }); if (callback) callback(); }); }, _initUpdateTimer: function() { this._bwObserver = { observe: function(aSubject, aTopic, aData) { if (aTopic == "timer-callback") { bandwagonService.checkAllForUpdates(); } else if (aTopic == "quit-application") { bandwagonService.uninit(); } } }; this._collectionUpdateTimer = Timer.createInstance(nsITimer); this._collectionUpdateTimer.init( this._bwObserver, (Bandwagon.Preferences.getPreference("debug")?240*1000:Bandwagon.COLLECTION_UPDATE_TIMER_DELAY*1000), nsITimer.TYPE_REPEATING_SLACK ); }, uninit: function() { this._collectionUpdateTimer = null; this.commitAll(); this._service = null; this._collectionFactory = null; Bandwagon = null; bandwagonService = null; }, getLocalAutoInstaller: function() { for (var id in bandwagonService.collections) { if (bandwagonService.collections[id].isLocalAutoInstaller()) { return bandwagonService.collections[id]; } } return null; }, autoinstallExtensions: function(callback) { Bandwagon.Logger.debug("in autoinstallExtensions()"); if (!Bandwagon.Preferences.getGlobalPreference("xpinstall.enabled", true)) { Bandwagon.Logger.warn("Can't auto install extensions because xp installs are disabled"); return; } var localAutoInstaller = bandwagonService.getLocalAutoInstaller(); if (localAutoInstaller == null) { Bandwagon.Logger.debug("No auto installer collection found"); return; } var installedExtensions = Bandwagon.Util.getInstalledExtensions(); var doAutoInstall = function(addon) { for (var i=0; i 0) { for (var i=0; i now.getTime()) { return; } else { this.checkForUpdates(collection); } } else { // use per-collection setting var dateLastCheck = null; var dateNextCheck = null; if (collection.dateLastCheck != null) { dateLastCheck = collection.dateLastCheck; dateNextCheck = new Date(dateLastCheck.getTime() + collection.updateInterval*1000); } else { dateLastCheck = null; dateNextCheck = now; } if (dateLastCheck == null || dateNextCheck.getTime() <= now.getTime()) { this.checkForUpdates(collection); } } } Bandwagon.Preferences.setPreference("updateall.datelastcheck", now.getTime()/1000); }, forceCheckForUpdates: function(collection) { if (!this.isAuthenticated()) return; this._service.getCollection(collection); collection.dateLastCheck = new Date(); }, forceCheckAllForUpdates: function() { Bandwagon.Logger.debug("in forceCheckAllForUpdates()"); for (var id in this.collections) { var collection = this.collections[id]; Bandwagon.Logger.debug("in forceCheckAllForUpdates() with collection = " + collection.toString() + ", subscribed = " + collection.subscribed); if (!collection.subscribed) continue; this.forceCheckForUpdates(collection); } }, forceCheckAllForUpdatesAndUpdateCollectionsList: function(callback) { // All updates to the collections list are forced, i.e. they are always // caused by *some* user interaction, never in the background. // Updating the collections list also forces the collections to be updated. this.updateCollectionsList(callback); }, firstrun: function() { Bandwagon.Logger.info("This is bandwagon's firstrun. Welcome!"); // the last check date is now var now = new Date(); Bandwagon.Preferences.setPreference("updateall.datelastcheck", now.getTime()/1000); // open the firstrun landing page Bandwagon.Controller.BrowserOverlay.openFirstRunLandingPage(); }, _addDefaultCollection: function(url, name) { var collection = this._collectionFactory.newCollection(); collection.resourceURL = url; collection.name = name; collection.showNotifications = 0; this.collections[collection.resourceURL] = collection; if (Bandwagon.COMMIT_NOW) this.commit(collection); this.forceCheckForUpdates(collection); this.subscribe(collection); }, uninstall: function() { // TODO }, commit: function(collection) { this.commitAll(); }, commitAll: function() { Bandwagon.Logger.debug("In commitAll()"); bandwagonService._collectionFactory.commitCollections(bandwagonService.collections); if (bandwagonService._serviceDocument) bandwagonService._collectionFactory.commitServiceDocument(bandwagonService._serviceDocument); }, removeAddonFromCollection: function(guid, collection, callback) { Bandwagon.Logger.debug("In removeAddonFromCollection()"); if (!this.isAuthenticated()) return; this._service.removeAddonFromCollection(guid, collection, callback); }, newCollection: function(collection, callback) { Bandwagon.Logger.debug("In newCollection()"); if (!this.isAuthenticated()) return; var internalCallback = function(event) { if (!event.isError()) { var collection = event.collection; bandwagonService.collections[collection.resourceURL] = collection; //bandwagonService._notifyCollectionUpdateObservers(collection); bandwagonService._notifyListChangeObservers(); } if (callback) { callback(event); } } this._service.newCollection(collection, internalCallback); }, unlinkCollection: function(collection) { this._collectionFactory.deleteCollection(collection, function(aReason) { if (aReason == Bandwagon.STMT_OK) { for (var id in bandwagonService.collections) { if (collection.equals(bandwagonService.collections[id])) { delete bandwagonService.collections[id]; bandwagonService._notifyListChangeObservers(); break; } } } }); }, deleteCollection: function(collection, callback) { if (!this.isAuthenticated()) return; this._service.deleteCollection(collection, callback); }, subscribeToCollection: function(collection, callback) { if (!this.isAuthenticated()) return; var internalCallback = function(event) { if (!event.isError()) { collection.subscribed = true; bandwagonService._notifyListChangeObservers(); } if (callback) { callback(event); } } this._service.subscribeToCollection(collection, internalCallback); }, unsubscribeFromCollection: function(collection, callback) { if (!this.isAuthenticated()) return; var internalCallback = function(event) { if (!event.isError()) { bandwagonService.unlinkCollection(collection); } if (callback) { callback(event); } } this._service.unsubscribeFromCollection(collection, internalCallback); }, updateCollectionDetails: function(collection, callback) { if (!this.isAuthenticated()) return; this._service.updateCollectionDetails(collection, callback); }, /** This function is called when a 3rd party extension is uninstalled by * the user. If this extension is part of a local auto publisher, we * remove it from that autopublisher. */ processOtherExtensionUninstall: function(guid, callback) { Bandwagon.Logger.debug("In processOtherExtensionUninstall() with guid = " + guid); var localAutoPublisher = bandwagonService.getLocalAutoPublisher(); if (!localAutoPublisher) return; var internalCallback = function(event) { if (!event.isError()) { // update list of autopublished extensions to remove this one var autopublishedExtensions = Bandwagon.Preferences.getPreferenceList("autopublished.extensions"); for (var j=0; j 0) { var app = getAppName(); var appWinString = "navigator:browser"; // default, Firefox if (app == "Thunderbird") appWinString = "mail:3pane"; var appWindow = WindowMediator.getService(nsIWindowMediator).getMostRecentWindow(appWinString); if (appWindow) { appWindow.Bandwagon.Controller.BrowserOverlay.showNewAddonsAlert(collection); } else { Bandwagon.Logger.error("Can't find a browser window to notify the user"); } collection.setAllNotified(); } // commit the collection if (Bandwagon.COMMIT_NOW) bandwagonService.commit(collection); }, _initStorage: function() { var storageService = Storage.getService(mozIStorageService); var file = DirectoryService.getService(nsIProperties).get("ProfD", nsIFile); file.append(Bandwagon.EMID); if (!file.exists() || !file.isDirectory()) { file.create(nsIFile.DIRECTORY_TYPE, 0777); } file.append(Bandwagon.SQLITE_FILENAME); try { this._storageConnection = storageService.openUnsharedDatabase(file); } catch (e) { Bandwagon.Logger.error("Error opening Storage connection: " + e); return; } if (this._storageConnection.executeAsync) this._collectionFactory = new Bandwagon.Factory.CollectionFactory2(this._storageConnection, Bandwagon); else this._collectionFactory = new Bandwagon.Factory.CollectionFactory(this._storageConnection, Bandwagon); this._initStorageTables(); }, _initStorageTables: function() { if (!this._storageConnection) return; // create tables (if they're not already created) this._storageConnection.beginTransaction(); try { this._storageConnection.executeSimpleSQL( "CREATE TABLE IF NOT EXISTS serviceDocument " + "(emailResourceURL TEXT NOT NULL, " + "collectionListResourceURL TEXT NOT NULL)" ); this._storageConnection.executeSimpleSQL( "CREATE TABLE IF NOT EXISTS collections " + "(id INTEGER PRIMARY KEY AUTOINCREMENT, " + "url TEXT NOT NULL UNIQUE, " + "name TEXT NOT NULL, " + "description TEXT, " + "dateAdded INTEGER NOT NULL, " + "dateLastCheck INTEGER, " + "updateInterval INTEGER NOT NULL, " + "showNotifications INTEGER NOT NULL, " + "autoPublish INTEGER NOT NULL, " + "active INTEGER NOT NULL DEFAULT 1, " + "addonsPerPage INTEGER NOT NULL, " + "creator TEXT, " + "listed INTEGER NOT NULL DEFAULT 1, " + "writable INTEGER NOT NULL DEFAULT 0, " + "subscribed INTEGER NOT NULL DEFAULT 1, " + "lastModified INTEGER, " + "addonsResourceURL TEXT, " + "type TEXT)" ); this._storageConnection.executeSimpleSQL( "CREATE TABLE IF NOT EXISTS collectionsLinks " + "(id INTEGER PRIMARY KEY AUTOINCREMENT, " + "collection INTEGER NOT NULL, " + "name TEXT NOT NULL, " + "href TEXT NOT NULL)" ); this._storageConnection.executeSimpleSQL( "CREATE TABLE IF NOT EXISTS collectionsAddons " + "(id INTEGER PRIMARY KEY AUTOINCREMENT, " + "collection INTEGER NOT NULL, " + "addon INTEGER NOT NULL, " + "read INTEGER NOT NULL DEFAULT 0)" ); this._storageConnection.executeSimpleSQL( "CREATE TABLE IF NOT EXISTS addons " + "(id INTEGER PRIMARY KEY AUTOINCREMENT, " + "guid TEXT NOT NULL UNIQUE, " + "name TEXT NOT NULL, " + "type INTEGER NOT NULL, " + "version TEXT NOT NULL, " + "status INTEGER NOT NULL, " + "summary TEXT, " + "description TEXT, " + "icon TEXT, " + "eula TEXT, " + "thumbnail TEXT, " + "learnmore TEXT NOT NULL, " + "author TEXT, " + "category TEXT, " + "dateAdded INTEGER NOT NULL)" ); this._storageConnection.executeSimpleSQL( "CREATE TABLE IF NOT EXISTS addonCompatibleApplications " + "(addon INTEGER NOT NULL, " + "name TEXT NOT NULL, " + "applicationId INTEGER NOT NULL, " + "minVersion TEXT NOT NULL, " + "maxVersion TEXT NOT NULL, " + "guid TEXT NOT NULL)" ); this._storageConnection.executeSimpleSQL( "CREATE TABLE IF NOT EXISTS addonCompatibleOS " + "(addon INTEGER NOT NULL, " + "name TEXT NOT NULL)" ); this._storageConnection.executeSimpleSQL( "CREATE TABLE IF NOT EXISTS addonInstalls " + "(addon INTEGER NOT NULL, " + "url TEXT NOT NULL, " + "hash TEXT NOT NULL, " + "os TEXT NOT NULL)" ); this._storageConnection.executeSimpleSQL( "CREATE TABLE IF NOT EXISTS addonComments " + "(addon INTEGER NOT NULL, " + "comment TEXT NOT NULL, " + "author TEXT NOT NULL)" ); this._storageConnection.executeSimpleSQL( "CREATE TABLE IF NOT EXISTS addonAuthors " + "(addon INTEGER NOT NULL, " + "author TEXT NOT NULL)" ); } catch (e) { Bandwagon.Logger.error("Error creating sqlite table: " + e); this._storageConnection.rollbackTransaction(); return; } if (this._storageConnection.schemaVersion < 103) { // sql schema updates for bandwagon 1.0.3 try { this._storageConnection.executeSimpleSQL("ALTER TABLE collections ADD COLUMN iconURL TEXT"); } catch (e) { Bandwagon.Logger.warn("Error updating sqlite schema (possibly harmless): " + e); } this._storageConnection.schemaVersion = 103; } if (this._storageConnection.schemaVersion < 105) { // sql schema updates for bandwagon 1.0.5 try { this._storageConnection.executeSimpleSQL("ALTER TABLE addons ADD COLUMN type2 TEXT"); } catch (e) { Bandwagon.Logger.warn("Error updating sqlite schema (possibly harmless): " + e); } this._storageConnection.schemaVersion = 105; } if (this._storageConnection.schemaVersion < 106) { // XXX future 1.0.6 schema updates go here // this._storageConnection.schemaVersion = 106; } this._storageConnection.commitTransaction(); }, startUninstallObserver : function () { if (gUninstallObserverInited) return; var extService = Components.classes["@mozilla.org/extensions/manager;1"] .getService(Components.interfaces.nsIExtensionManager); if (extService && ("uninstallItem" in extService)) { var observerService = Components.classes["@mozilla.org/observer-service;1"] .getService(Components.interfaces.nsIObserverService); observerService.addObserver(this.addonsAction, "em-action-requested", false); gUninstallObserverInited = true; } else { try { extService.datasource.AddObserver(this.addonsObserver); gUninstallObserverInited = true; } catch (e) { } } }, addonsObserver: { onAssert: function (ds, subject, predicate, target) { if ((predicate.Value == "http://www.mozilla.org/2004/em-rdf#toBeUninstalled") && (target instanceof Components.interfaces.nsIRDFLiteral) && (target.Value == "true")) { if (subject.Value == "urn:mozilla:extension:" + gEmGUID) { // This is case where bandwagon is being uninstalled - clean up cleanupSettings(); } else { // This is case where some other extension is being uninstalled var val = subject.Value; val = val.replace(/urn:mozilla:extension:/, ""); bandwagonService.processOtherExtensionUninstall(val); } } }, onUnassert: function (ds, subject, predicate, target) {}, onChange: function (ds, subject, predicate, oldtarget, newtarget) {}, onMove: function (ds, oldsubject, newsubject, predicate, target) {}, onBeginUpdateBatch: function() {}, onEndUpdateBatch: function() {} }, addonsAction: { observe: function (subject, topic, data) { if ((data == "item-uninstalled") && (subject instanceof Components.interfaces.nsIUpdateItem)) { if (subject.id == gEmGUID) { // This is case where bandwagon is being uninstalled - clean up cleanupSettings(); } else { // This is case where some other extension is being uninstalled bandwagonService.processOtherExtensionUninstall(subject.id); } } } }, // for nsISupports QueryInterface: function(aIID) { // add any other interfaces you support here if (!aIID.equals(nsISupports)) throw Components.results.NS_ERROR_NO_INTERFACE; return this; } } var BandwagonServiceFactory = { singleton: null, createInstance: function (aOuter, aIID) { if (aOuter != null) throw Components.results.NS_ERROR_NO_AGGREGATION; if (this.singleton == null) this.singleton = new BandwagonService(); return this.singleton.QueryInterface(aIID); } }; var BandwagonServiceModule = { registerSelf: function(aCompMgr, aFileSpec, aLocation, aType) { aCompMgr = aCompMgr.QueryInterface(Components.interfaces.nsIComponentRegistrar); aCompMgr.registerFactoryLocation(CLASS_ID, CLASS_NAME, CONTRACT_ID, aFileSpec, aLocation, aType); }, unregisterSelf: function(aCompMgr, aLocation, aType) { aCompMgr = aCompMgr.QueryInterface(Components.interfaces.nsIComponentRegistrar); aCompMgr.unregisterFactoryLocation(CLASS_ID, aLocation); }, getClassObject: function(aCompMgr, aCID, aIID) { if (!aIID.equals(Components.interfaces.nsIFactory)) throw Components.results.NS_ERROR_NOT_IMPLEMENTED; if (aCID.equals(CLASS_ID)) return BandwagonServiceFactory; throw Components.results.NS_ERROR_NO_INTERFACE; }, canUnload: function(aCompMgr) { return true; } }; //module initialization function NSGetModule(aCompMgr, aFileSpec) { return BandwagonServiceModule; }