michael@0: /* This Source Code Form is subject to the terms of the Mozilla Public michael@0: * License, v. 2.0. If a copy of the MPL was not distributed with this michael@0: * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ michael@0: michael@0: this.EXPORTED_SYMBOLS = ['FormEngine', 'FormRec']; michael@0: michael@0: const Cc = Components.classes; michael@0: const Ci = Components.interfaces; michael@0: const Cu = Components.utils; michael@0: michael@0: Cu.import("resource://gre/modules/XPCOMUtils.jsm"); michael@0: Cu.import("resource://services-sync/engines.js"); michael@0: Cu.import("resource://services-sync/record.js"); michael@0: Cu.import("resource://services-common/async.js"); michael@0: Cu.import("resource://services-sync/util.js"); michael@0: Cu.import("resource://services-sync/constants.js"); michael@0: Cu.import("resource://gre/modules/Log.jsm"); michael@0: michael@0: const FORMS_TTL = 5184000; // 60 days michael@0: michael@0: this.FormRec = function FormRec(collection, id) { michael@0: CryptoWrapper.call(this, collection, id); michael@0: } michael@0: FormRec.prototype = { michael@0: __proto__: CryptoWrapper.prototype, michael@0: _logName: "Sync.Record.Form", michael@0: ttl: FORMS_TTL michael@0: }; michael@0: michael@0: Utils.deferGetSet(FormRec, "cleartext", ["name", "value"]); michael@0: michael@0: michael@0: let FormWrapper = { michael@0: _log: Log.repository.getLogger("Sync.Engine.Forms"), michael@0: michael@0: _getEntryCols: ["fieldname", "value"], michael@0: _guidCols: ["guid"], michael@0: michael@0: // Do a "sync" search by spinning the event loop until it completes. michael@0: _searchSpinningly: function(terms, searchData) { michael@0: let results = []; michael@0: let cb = Async.makeSpinningCallback(); michael@0: let callbacks = { michael@0: handleResult: function(result) { michael@0: results.push(result); michael@0: }, michael@0: handleCompletion: function(reason) { michael@0: cb(null, results); michael@0: } michael@0: }; michael@0: Svc.FormHistory.search(terms, searchData, callbacks); michael@0: return cb.wait(); michael@0: }, michael@0: michael@0: _updateSpinningly: function(changes) { michael@0: if (!Svc.FormHistory.enabled) { michael@0: return; // update isn't going to do anything. michael@0: } michael@0: let cb = Async.makeSpinningCallback(); michael@0: let callbacks = { michael@0: handleCompletion: function(reason) { michael@0: cb(); michael@0: } michael@0: }; michael@0: Svc.FormHistory.update(changes, callbacks); michael@0: return cb.wait(); michael@0: }, michael@0: michael@0: getEntry: function (guid) { michael@0: let results = this._searchSpinningly(this._getEntryCols, {guid: guid}); michael@0: if (!results.length) { michael@0: return null; michael@0: } michael@0: return {name: results[0].fieldname, value: results[0].value}; michael@0: }, michael@0: michael@0: getGUID: function (name, value) { michael@0: // Query for the provided entry. michael@0: let query = { fieldname: name, value: value }; michael@0: let results = this._searchSpinningly(this._guidCols, query); michael@0: return results.length ? results[0].guid : null; michael@0: }, michael@0: michael@0: hasGUID: function (guid) { michael@0: // We could probably use a count function here, but searchSpinningly exists... michael@0: return this._searchSpinningly(this._guidCols, {guid: guid}).length != 0; michael@0: }, michael@0: michael@0: replaceGUID: function (oldGUID, newGUID) { michael@0: let changes = { michael@0: op: "update", michael@0: guid: oldGUID, michael@0: newGuid: newGUID, michael@0: } michael@0: this._updateSpinningly(changes); michael@0: } michael@0: michael@0: }; michael@0: michael@0: this.FormEngine = function FormEngine(service) { michael@0: SyncEngine.call(this, "Forms", service); michael@0: } michael@0: FormEngine.prototype = { michael@0: __proto__: SyncEngine.prototype, michael@0: _storeObj: FormStore, michael@0: _trackerObj: FormTracker, michael@0: _recordObj: FormRec, michael@0: applyIncomingBatchSize: FORMS_STORE_BATCH_SIZE, michael@0: michael@0: get prefName() "history", michael@0: michael@0: _findDupe: function _findDupe(item) { michael@0: return FormWrapper.getGUID(item.name, item.value); michael@0: } michael@0: }; michael@0: michael@0: function FormStore(name, engine) { michael@0: Store.call(this, name, engine); michael@0: } michael@0: FormStore.prototype = { michael@0: __proto__: Store.prototype, michael@0: michael@0: _processChange: function (change) { michael@0: // If this._changes is defined, then we are applying a batch, so we michael@0: // can defer it. michael@0: if (this._changes) { michael@0: this._changes.push(change); michael@0: return; michael@0: } michael@0: michael@0: // Otherwise we must handle the change synchronously, right now. michael@0: FormWrapper._updateSpinningly(change); michael@0: }, michael@0: michael@0: applyIncomingBatch: function (records) { michael@0: // We collect all the changes to be made then apply them all at once. michael@0: this._changes = []; michael@0: let failures = Store.prototype.applyIncomingBatch.call(this, records); michael@0: if (this._changes.length) { michael@0: FormWrapper._updateSpinningly(this._changes); michael@0: } michael@0: delete this._changes; michael@0: return failures; michael@0: }, michael@0: michael@0: getAllIDs: function () { michael@0: let results = FormWrapper._searchSpinningly(["guid"], []) michael@0: let guids = {}; michael@0: for (let result of results) { michael@0: guids[result.guid] = true; michael@0: } michael@0: return guids; michael@0: }, michael@0: michael@0: changeItemID: function (oldID, newID) { michael@0: FormWrapper.replaceGUID(oldID, newID); michael@0: }, michael@0: michael@0: itemExists: function (id) { michael@0: return FormWrapper.hasGUID(id); michael@0: }, michael@0: michael@0: createRecord: function (id, collection) { michael@0: let record = new FormRec(collection, id); michael@0: let entry = FormWrapper.getEntry(id); michael@0: if (entry != null) { michael@0: record.name = entry.name; michael@0: record.value = entry.value; michael@0: } else { michael@0: record.deleted = true; michael@0: } michael@0: return record; michael@0: }, michael@0: michael@0: create: function (record) { michael@0: this._log.trace("Adding form record for " + record.name); michael@0: let change = { michael@0: op: "add", michael@0: fieldname: record.name, michael@0: value: record.value michael@0: }; michael@0: this._processChange(change); michael@0: }, michael@0: michael@0: remove: function (record) { michael@0: this._log.trace("Removing form record: " + record.id); michael@0: let change = { michael@0: op: "remove", michael@0: guid: record.id michael@0: }; michael@0: this._processChange(change); michael@0: }, michael@0: michael@0: update: function (record) { michael@0: this._log.trace("Ignoring form record update request!"); michael@0: }, michael@0: michael@0: wipe: function () { michael@0: let change = { michael@0: op: "remove" michael@0: }; michael@0: FormWrapper._updateSpinningly(change); michael@0: } michael@0: }; michael@0: michael@0: function FormTracker(name, engine) { michael@0: Tracker.call(this, name, engine); michael@0: } michael@0: FormTracker.prototype = { michael@0: __proto__: Tracker.prototype, michael@0: michael@0: QueryInterface: XPCOMUtils.generateQI([ michael@0: Ci.nsIObserver, michael@0: Ci.nsISupportsWeakReference]), michael@0: michael@0: startTracking: function() { michael@0: Svc.Obs.add("satchel-storage-changed", this); michael@0: }, michael@0: michael@0: stopTracking: function() { michael@0: Svc.Obs.remove("satchel-storage-changed", this); michael@0: }, michael@0: michael@0: observe: function (subject, topic, data) { michael@0: Tracker.prototype.observe.call(this, subject, topic, data); michael@0: michael@0: switch (topic) { michael@0: case "satchel-storage-changed": michael@0: if (data == "formhistory-add" || data == "formhistory-remove") { michael@0: let guid = subject.QueryInterface(Ci.nsISupportsString).toString(); michael@0: this.trackEntry(guid); michael@0: } michael@0: break; michael@0: } michael@0: }, michael@0: michael@0: trackEntry: function (guid) { michael@0: this.addChangedID(guid); michael@0: this.score += SCORE_INCREMENT_MEDIUM; michael@0: }, michael@0: };