--- a/browser/components/loop/LoopStorage.jsm
+++ b/browser/components/loop/LoopStorage.jsm
@@ -20,19 +20,17 @@ Cu.import("resource://gre/modules/Servic
Cu.import("resource://gre/modules/XPCOMUtils.jsm");
XPCOMUtils.defineLazyGetter(this, "eventEmitter", function() {
const {EventEmitter} = Cu.import("resource://gre/modules/devtools/event-emitter.js", {});
return new EventEmitter();
});
this.EXPORTED_SYMBOLS = ["LoopStorage"];
-const kDatabasePrefix = "loop-";
-const kDefaultDatabaseName = "default";
-let gDatabaseName = kDatabasePrefix + kDefaultDatabaseName;
+const kDatabaseName = "loop";
const kDatabaseVersion = 1;
let gWaitForOpenCallbacks = new Set();
let gDatabase = null;
let gClosed = false;
/**
* Properly shut the database instance down. This is done on application shutdown.
@@ -80,26 +78,26 @@ const ensureDatabaseOpen = function(onOp
let invokeCallbacks = err => {
for (let callback of gWaitForOpenCallbacks) {
callback(err, gDatabase);
}
gWaitForOpenCallbacks.clear();
};
- let openRequest = indexedDB.open(gDatabaseName, kDatabaseVersion);
+ let openRequest = indexedDB.open(kDatabaseName, kDatabaseVersion);
openRequest.onblocked = function(event) {
invokeCallbacks(new Error("Database cannot be upgraded cause in use: " + event.target.error));
};
openRequest.onerror = function(event) {
// Try to delete the old database so that we can start this process over
// next time.
- indexedDB.deleteDatabase(gDatabaseName);
+ indexedDB.deleteDatabase(kDatabaseName);
invokeCallbacks(new Error("Error while opening database: " + event.target.errorCode));
};
openRequest.onupgradeneeded = function(event) {
let db = event.target.result;
eventEmitter.emit("upgrade", db, event.oldVersion, kDatabaseVersion);
};
@@ -107,39 +105,16 @@ const ensureDatabaseOpen = function(onOp
gDatabase = event.target.result;
invokeCallbacks();
// Close the database instance properly on application shutdown.
Services.obs.addObserver(closeDatabase, "quit-application", false);
};
};
/**
- * Switch to a database with a different name by closing the current connection
- * and making sure that the next connection attempt will be made using the updated
- * name.
- *
- * @param {String} name New name of the database to switch to.
- */
-const switchDatabase = function(name) {
- if (name == gDatabaseName) {
- // This is already the current database, so there's no need to switch.
- return;
- }
-
- gDatabaseName = name;
- if (gDatabase) {
- try {
- gDatabase.close();
- } finally {
- gDatabase = null;
- }
- }
-};
-
-/**
* Start a transaction on the loop database and return it.
*
* @param {String} store Name of the object store to start a transaction on
* @param {Function} callback Callback to be invoked once a database connection
* is established and a transaction can be started.
* It takes an Error object as first argument and the
* transaction object as second argument.
* @param {String} mode Mode of the transaction. May be 'readonly' or 'readwrite'
@@ -200,45 +175,28 @@ const getStore = function(store, callbac
*
* LoopStorage implements the EventEmitter interface by exposing two methods, `on`
* and `off`, to subscribe to events.
* At this point only the `upgrade` event will be emitted. This happens when the
* database is loaded in memory and consumers will be able to change its structure.
*/
this.LoopStorage = Object.freeze({
/**
- * @var {String} databaseName The name of the database that is currently active.
- */
- get databaseName() {
- return gDatabaseName;
- },
-
- /**
* Open a connection to the IndexedDB database and return the database object.
*
* @param {Function} callback Callback to be invoked once a database connection
* is established. It takes an Error object as first
* argument and the database connection object as
* second argument, if successful.
*/
getSingleton: function(callback) {
ensureDatabaseOpen(callback);
},
/**
- * Switch to a database with a different name.
- *
- * @param {String} name New name of the database to switch to. Defaults to
- * `kDefaultDatabaseName`
- */
- switchDatabase: function(name = kDefaultDatabaseName) {
- switchDatabase(name);
- },
-
- /**
* Start a transaction on the loop database and return it.
* If only two arguments are passed, the default mode will be assumed and the
* second argument is assumed to be a callback.
*
* @param {String} store Name of the object store to start a transaction on
* @param {Function} callback Callback to be invoked once a database connection
* is established and a transaction can be started.
* It takes an Error object as first argument and the
--- a/browser/components/loop/MozLoopService.jsm
+++ b/browser/components/loop/MozLoopService.jsm
@@ -51,19 +51,16 @@ XPCOMUtils.defineLazyModuleGetter(this,
"resource://gre/modules/FxAccountsProfileClient.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "HawkClient",
"resource://services-common/hawkclient.js");
XPCOMUtils.defineLazyModuleGetter(this, "deriveHawkCredentials",
"resource://services-common/hawkrequest.js");
-XPCOMUtils.defineLazyModuleGetter(this, "LoopStorage",
- "resource:///modules/loop/LoopStorage.jsm");
-
XPCOMUtils.defineLazyModuleGetter(this, "MozLoopPushHandler",
"resource:///modules/loop/MozLoopPushHandler.jsm");
XPCOMUtils.defineLazyServiceGetter(this, "uuidgen",
"@mozilla.org/uuid-generator;1",
"nsIUUIDGenerator");
XPCOMUtils.defineLazyServiceGetter(this, "gDNSService",
@@ -365,17 +362,16 @@ let MozLoopServiceInternal = {
*/
set doNotDisturb(aFlag) {
Services.prefs.setBoolPref("loop.do_not_disturb", Boolean(aFlag));
this.notifyStatusChanged();
},
notifyStatusChanged: function(aReason = null) {
log.debug("notifyStatusChanged with reason:", aReason);
- LoopStorage.switchDatabase(gFxAOAuthProfile ? gFxAOAuthProfile.uid : null);
Services.obs.notifyObservers(null, "loop-status-changed", aReason);
},
/**
* Record an error and notify interested UI with the relevant user-facing strings attached.
*
* @param {String} errorType a key to identify the type of error. Only one
* error of a type will be saved at a time. This value may be used to
--- a/browser/components/loop/content/js/contacts.js
+++ b/browser/components/loop/content/js/contacts.js
@@ -148,24 +148,24 @@ loop.contacts = (function(_, mozL10n) {
this.setState({showMenu: false});
}
},
componentWillUnmount: function() {
document.body.removeEventListener("click", this._onBodyClick);
},
- shouldComponentUpdate: function(nextProps, nextState) {
+ componentShouldUpdate: function(nextProps, nextState) {
let currContact = this.props.contact;
let nextContact = nextProps.contact;
return (
currContact.name[0] !== nextContact.name[0] ||
currContact.blocked !== nextContact.blocked ||
- getPreferredEmail(currContact).value !== getPreferredEmail(nextContact).value ||
- nextState.showMenu !== this.state.showMenu
+ getPreferredEmail(currContact).value !==
+ getPreferredEmail(nextContact).value
);
},
handleAction: function(actionName) {
if (this.props.handleContactAction) {
this.props.handleContactAction(this.props.contact, actionName);
}
},
@@ -210,78 +210,45 @@ loop.contacts = (function(_, mozL10n) {
)
);
}
});
const ContactsList = React.createClass({displayName: 'ContactsList',
mixins: [React.addons.LinkedStateMixin],
- /**
- * Contacts collection object
- */
- contacts: null,
-
- /**
- * User profile
- */
- _userProfile: null,
-
getInitialState: function() {
return {
+ contacts: {},
importBusy: false,
filter: "",
};
},
- refresh: function(callback = function() {}) {
+ componentDidMount: function() {
let contactsAPI = navigator.mozLoop.contacts;
- this.handleContactRemoveAll();
-
contactsAPI.getAll((err, contacts) => {
if (err) {
- callback(err);
- return;
+ throw err;
}
// Add contacts already present in the DB. We do this in timed chunks to
// circumvent blocking the main event loop.
let addContactsInChunks = () => {
contacts.splice(0, CONTACTS_CHUNK_SIZE).forEach(contact => {
this.handleContactAddOrUpdate(contact, false);
});
if (contacts.length) {
setTimeout(addContactsInChunks, 0);
- } else {
- callback();
}
this.forceUpdate();
};
addContactsInChunks(contacts);
- });
- },
-
- componentWillMount: function() {
- // Take the time to initialize class variables that are used outside
- // `this.state`.
- this.contacts = {};
- this._userProfile = navigator.mozLoop.userProfile;
- },
-
- componentDidMount: function() {
- window.addEventListener("LoopStatusChanged", this._onStatusChanged);
-
- this.refresh(err => {
- if (err) {
- throw err;
- }
-
- let contactsAPI = navigator.mozLoop.contacts;
// Listen for contact changes/ updates.
contactsAPI.on("add", (eventName, contact) => {
this.handleContactAddOrUpdate(contact);
});
contactsAPI.on("remove", (eventName, contact) => {
this.handleContactRemove(contact);
});
@@ -289,55 +256,37 @@ loop.contacts = (function(_, mozL10n) {
this.handleContactRemoveAll();
});
contactsAPI.on("update", (eventName, contact) => {
this.handleContactAddOrUpdate(contact);
});
});
},
- componentWillUnmount: function() {
- window.removeEventListener("LoopStatusChanged", this._onStatusChanged);
- },
-
- _onStatusChanged: function() {
- let profile = navigator.mozLoop.userProfile;
- let currUid = this._userProfile ? this._userProfile.uid : null;
- let newUid = profile ? profile.uid : null;
- if (currUid != newUid) {
- // On profile change (login, logout), reload all contacts.
- this._userProfile = profile;
- // The following will do a forceUpdate() for us.
- this.refresh();
- }
- },
-
handleContactAddOrUpdate: function(contact, render = true) {
- let contacts = this.contacts;
+ let contacts = this.state.contacts;
let guid = String(contact._guid);
contacts[guid] = contact;
if (render) {
this.forceUpdate();
}
},
handleContactRemove: function(contact) {
- let contacts = this.contacts;
+ let contacts = this.state.contacts;
let guid = String(contact._guid);
if (!contacts[guid]) {
return;
}
delete contacts[guid];
this.forceUpdate();
},
handleContactRemoveAll: function() {
- // Do not allow any race conditions when removing all contacts.
- this.contacts = {};
- this.forceUpdate();
+ this.setState({contacts: {}});
},
handleImportButtonClick: function() {
this.setState({ importBusy: true });
navigator.mozLoop.startImport({
service: "google"
}, (err, stats) => {
this.setState({ importBusy: false });
@@ -410,21 +359,21 @@ loop.contacts = (function(_, mozL10n) {
},
render: function() {
let viewForItem = item => {
return ContactDetail({key: item._guid, contact: item,
handleContactAction: this.handleContactAction})
};
- let shownContacts = _.groupBy(this.contacts, function(contact) {
+ let shownContacts = _.groupBy(this.state.contacts, function(contact) {
return contact.blocked ? "blocked" : "available";
});
- let showFilter = Object.getOwnPropertyNames(this.contacts).length >=
+ let showFilter = Object.getOwnPropertyNames(this.state.contacts).length >=
MIN_CONTACTS_FOR_FILTERING;
if (showFilter) {
let filter = this.state.filter.trim().toLocaleLowerCase();
if (filter) {
let filterFn = contact => {
return contact.name[0].toLocaleLowerCase().contains(filter) ||
getPreferredEmail(contact).value.toLocaleLowerCase().contains(filter);
};
--- a/browser/components/loop/content/js/contacts.jsx
+++ b/browser/components/loop/content/js/contacts.jsx
@@ -148,24 +148,24 @@ loop.contacts = (function(_, mozL10n) {
this.setState({showMenu: false});
}
},
componentWillUnmount: function() {
document.body.removeEventListener("click", this._onBodyClick);
},
- shouldComponentUpdate: function(nextProps, nextState) {
+ componentShouldUpdate: function(nextProps, nextState) {
let currContact = this.props.contact;
let nextContact = nextProps.contact;
return (
currContact.name[0] !== nextContact.name[0] ||
currContact.blocked !== nextContact.blocked ||
- getPreferredEmail(currContact).value !== getPreferredEmail(nextContact).value ||
- nextState.showMenu !== this.state.showMenu
+ getPreferredEmail(currContact).value !==
+ getPreferredEmail(nextContact).value
);
},
handleAction: function(actionName) {
if (this.props.handleContactAction) {
this.props.handleContactAction(this.props.contact, actionName);
}
},
@@ -210,78 +210,45 @@ loop.contacts = (function(_, mozL10n) {
</li>
);
}
});
const ContactsList = React.createClass({
mixins: [React.addons.LinkedStateMixin],
- /**
- * Contacts collection object
- */
- contacts: null,
-
- /**
- * User profile
- */
- _userProfile: null,
-
getInitialState: function() {
return {
+ contacts: {},
importBusy: false,
filter: "",
};
},
- refresh: function(callback = function() {}) {
+ componentDidMount: function() {
let contactsAPI = navigator.mozLoop.contacts;
- this.handleContactRemoveAll();
-
contactsAPI.getAll((err, contacts) => {
if (err) {
- callback(err);
- return;
+ throw err;
}
// Add contacts already present in the DB. We do this in timed chunks to
// circumvent blocking the main event loop.
let addContactsInChunks = () => {
contacts.splice(0, CONTACTS_CHUNK_SIZE).forEach(contact => {
this.handleContactAddOrUpdate(contact, false);
});
if (contacts.length) {
setTimeout(addContactsInChunks, 0);
- } else {
- callback();
}
this.forceUpdate();
};
addContactsInChunks(contacts);
- });
- },
-
- componentWillMount: function() {
- // Take the time to initialize class variables that are used outside
- // `this.state`.
- this.contacts = {};
- this._userProfile = navigator.mozLoop.userProfile;
- },
-
- componentDidMount: function() {
- window.addEventListener("LoopStatusChanged", this._onStatusChanged);
-
- this.refresh(err => {
- if (err) {
- throw err;
- }
-
- let contactsAPI = navigator.mozLoop.contacts;
// Listen for contact changes/ updates.
contactsAPI.on("add", (eventName, contact) => {
this.handleContactAddOrUpdate(contact);
});
contactsAPI.on("remove", (eventName, contact) => {
this.handleContactRemove(contact);
});
@@ -289,55 +256,37 @@ loop.contacts = (function(_, mozL10n) {
this.handleContactRemoveAll();
});
contactsAPI.on("update", (eventName, contact) => {
this.handleContactAddOrUpdate(contact);
});
});
},
- componentWillUnmount: function() {
- window.removeEventListener("LoopStatusChanged", this._onStatusChanged);
- },
-
- _onStatusChanged: function() {
- let profile = navigator.mozLoop.userProfile;
- let currUid = this._userProfile ? this._userProfile.uid : null;
- let newUid = profile ? profile.uid : null;
- if (currUid != newUid) {
- // On profile change (login, logout), reload all contacts.
- this._userProfile = profile;
- // The following will do a forceUpdate() for us.
- this.refresh();
- }
- },
-
handleContactAddOrUpdate: function(contact, render = true) {
- let contacts = this.contacts;
+ let contacts = this.state.contacts;
let guid = String(contact._guid);
contacts[guid] = contact;
if (render) {
this.forceUpdate();
}
},
handleContactRemove: function(contact) {
- let contacts = this.contacts;
+ let contacts = this.state.contacts;
let guid = String(contact._guid);
if (!contacts[guid]) {
return;
}
delete contacts[guid];
this.forceUpdate();
},
handleContactRemoveAll: function() {
- // Do not allow any race conditions when removing all contacts.
- this.contacts = {};
- this.forceUpdate();
+ this.setState({contacts: {}});
},
handleImportButtonClick: function() {
this.setState({ importBusy: true });
navigator.mozLoop.startImport({
service: "google"
}, (err, stats) => {
this.setState({ importBusy: false });
@@ -410,21 +359,21 @@ loop.contacts = (function(_, mozL10n) {
},
render: function() {
let viewForItem = item => {
return <ContactDetail key={item._guid} contact={item}
handleContactAction={this.handleContactAction} />
};
- let shownContacts = _.groupBy(this.contacts, function(contact) {
+ let shownContacts = _.groupBy(this.state.contacts, function(contact) {
return contact.blocked ? "blocked" : "available";
});
- let showFilter = Object.getOwnPropertyNames(this.contacts).length >=
+ let showFilter = Object.getOwnPropertyNames(this.state.contacts).length >=
MIN_CONTACTS_FOR_FILTERING;
if (showFilter) {
let filter = this.state.filter.trim().toLocaleLowerCase();
if (filter) {
let filterFn = contact => {
return contact.name[0].toLocaleLowerCase().contains(filter) ||
getPreferredEmail(contact).value.toLocaleLowerCase().contains(filter);
};
--- a/browser/components/loop/content/js/panel.js
+++ b/browser/components/loop/content/js/panel.js
@@ -620,19 +620,17 @@ loop.panel = (function(_, mozL10n) {
});
} else {
this.props.notifications.remove(this.props.notifications.get("service-error"));
}
},
_onStatusChanged: function() {
var profile = navigator.mozLoop.userProfile;
- var currUid = this.state.userProfile ? this.state.userProfile.uid : null;
- var newUid = profile ? profile.uid : null;
- if (currUid != newUid) {
+ if (profile != this.state.userProfile) {
// On profile change (login, logout), switch back to the default tab.
this.selectTab("call");
}
this.setState({userProfile: profile});
this.updateServiceErrors();
},
/**
--- a/browser/components/loop/content/js/panel.jsx
+++ b/browser/components/loop/content/js/panel.jsx
@@ -620,19 +620,17 @@ loop.panel = (function(_, mozL10n) {
});
} else {
this.props.notifications.remove(this.props.notifications.get("service-error"));
}
},
_onStatusChanged: function() {
var profile = navigator.mozLoop.userProfile;
- var currUid = this.state.userProfile ? this.state.userProfile.uid : null;
- var newUid = profile ? profile.uid : null;
- if (currUid != newUid) {
+ if (profile != this.state.userProfile) {
// On profile change (login, logout), switch back to the default tab.
this.selectTab("call");
}
this.setState({userProfile: profile});
this.updateServiceErrors();
},
/**
--- a/browser/components/loop/test/mochitest/browser_LoopContacts.js
+++ b/browser/components/loop/test/mochitest/browser_LoopContacts.js
@@ -1,17 +1,12 @@
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
const {LoopContacts} = Cu.import("resource:///modules/loop/LoopContacts.jsm", {});
-const {LoopStorage} = Cu.import("resource:///modules/loop/LoopStorage.jsm", {});
-
-XPCOMUtils.defineLazyServiceGetter(this, "uuidgen",
- "@mozilla.org/uuid-generator;1",
- "nsIUUIDGenerator");
const kContacts = [{
id: 1,
name: ["Ally Avocado"],
email: [{
"pref": true,
"type": ["work"],
"value": "ally@mail.com"
@@ -400,36 +395,8 @@ add_task(function* () {
// Test if the event emitter implementation doesn't leak and is working as expected.
add_task(function* () {
yield promiseLoadContacts();
Assert.strictEqual(gExpectedAdds.length, 0, "No contact additions should be expected anymore");
Assert.strictEqual(gExpectedRemovals.length, 0, "No contact removals should be expected anymore");
Assert.strictEqual(gExpectedUpdates.length, 0, "No contact updates should be expected anymore");
});
-
-// Test switching between different databases.
-add_task(function* () {
- Assert.equal(LoopStorage.databaseName, "loop-default", "First active partition should be the default");
- yield promiseLoadContacts();
-
- let uuid = uuidgen.generateUUID().toString().replace(/[{}]+/g, "");
- LoopStorage.switchDatabase(uuid);
- Assert.equal(LoopStorage.databaseName, uuid, "The active partition should have changed");
-
- yield promiseLoadContacts();
-
- let contacts = yield promiseLoadContacts();
- for (let i = 0, l = contacts.length; i < l; ++i) {
- compareContacts(contacts[i], kContacts[i]);
- }
-
- LoopStorage.switchDatabase();
- Assert.equal(LoopStorage.databaseName, "loop-default", "The active partition should have changed");
-
- LoopContacts.getAll(function(err, contacts) {
- Assert.equal(err, null, "There shouldn't be an error");
-
- for (let i = 0, l = contacts.length; i < l; ++i) {
- compareContacts(contacts[i], kContacts[i]);
- }
- });
-});