1.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000 1.2 +++ b/services/sync/modules/engines/forms.js Wed Dec 31 06:09:35 2014 +0100 1.3 @@ -0,0 +1,241 @@ 1.4 +/* This Source Code Form is subject to the terms of the Mozilla Public 1.5 + * License, v. 2.0. If a copy of the MPL was not distributed with this 1.6 + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 1.7 + 1.8 +this.EXPORTED_SYMBOLS = ['FormEngine', 'FormRec']; 1.9 + 1.10 +const Cc = Components.classes; 1.11 +const Ci = Components.interfaces; 1.12 +const Cu = Components.utils; 1.13 + 1.14 +Cu.import("resource://gre/modules/XPCOMUtils.jsm"); 1.15 +Cu.import("resource://services-sync/engines.js"); 1.16 +Cu.import("resource://services-sync/record.js"); 1.17 +Cu.import("resource://services-common/async.js"); 1.18 +Cu.import("resource://services-sync/util.js"); 1.19 +Cu.import("resource://services-sync/constants.js"); 1.20 +Cu.import("resource://gre/modules/Log.jsm"); 1.21 + 1.22 +const FORMS_TTL = 5184000; // 60 days 1.23 + 1.24 +this.FormRec = function FormRec(collection, id) { 1.25 + CryptoWrapper.call(this, collection, id); 1.26 +} 1.27 +FormRec.prototype = { 1.28 + __proto__: CryptoWrapper.prototype, 1.29 + _logName: "Sync.Record.Form", 1.30 + ttl: FORMS_TTL 1.31 +}; 1.32 + 1.33 +Utils.deferGetSet(FormRec, "cleartext", ["name", "value"]); 1.34 + 1.35 + 1.36 +let FormWrapper = { 1.37 + _log: Log.repository.getLogger("Sync.Engine.Forms"), 1.38 + 1.39 + _getEntryCols: ["fieldname", "value"], 1.40 + _guidCols: ["guid"], 1.41 + 1.42 + // Do a "sync" search by spinning the event loop until it completes. 1.43 + _searchSpinningly: function(terms, searchData) { 1.44 + let results = []; 1.45 + let cb = Async.makeSpinningCallback(); 1.46 + let callbacks = { 1.47 + handleResult: function(result) { 1.48 + results.push(result); 1.49 + }, 1.50 + handleCompletion: function(reason) { 1.51 + cb(null, results); 1.52 + } 1.53 + }; 1.54 + Svc.FormHistory.search(terms, searchData, callbacks); 1.55 + return cb.wait(); 1.56 + }, 1.57 + 1.58 + _updateSpinningly: function(changes) { 1.59 + if (!Svc.FormHistory.enabled) { 1.60 + return; // update isn't going to do anything. 1.61 + } 1.62 + let cb = Async.makeSpinningCallback(); 1.63 + let callbacks = { 1.64 + handleCompletion: function(reason) { 1.65 + cb(); 1.66 + } 1.67 + }; 1.68 + Svc.FormHistory.update(changes, callbacks); 1.69 + return cb.wait(); 1.70 + }, 1.71 + 1.72 + getEntry: function (guid) { 1.73 + let results = this._searchSpinningly(this._getEntryCols, {guid: guid}); 1.74 + if (!results.length) { 1.75 + return null; 1.76 + } 1.77 + return {name: results[0].fieldname, value: results[0].value}; 1.78 + }, 1.79 + 1.80 + getGUID: function (name, value) { 1.81 + // Query for the provided entry. 1.82 + let query = { fieldname: name, value: value }; 1.83 + let results = this._searchSpinningly(this._guidCols, query); 1.84 + return results.length ? results[0].guid : null; 1.85 + }, 1.86 + 1.87 + hasGUID: function (guid) { 1.88 + // We could probably use a count function here, but searchSpinningly exists... 1.89 + return this._searchSpinningly(this._guidCols, {guid: guid}).length != 0; 1.90 + }, 1.91 + 1.92 + replaceGUID: function (oldGUID, newGUID) { 1.93 + let changes = { 1.94 + op: "update", 1.95 + guid: oldGUID, 1.96 + newGuid: newGUID, 1.97 + } 1.98 + this._updateSpinningly(changes); 1.99 + } 1.100 + 1.101 +}; 1.102 + 1.103 +this.FormEngine = function FormEngine(service) { 1.104 + SyncEngine.call(this, "Forms", service); 1.105 +} 1.106 +FormEngine.prototype = { 1.107 + __proto__: SyncEngine.prototype, 1.108 + _storeObj: FormStore, 1.109 + _trackerObj: FormTracker, 1.110 + _recordObj: FormRec, 1.111 + applyIncomingBatchSize: FORMS_STORE_BATCH_SIZE, 1.112 + 1.113 + get prefName() "history", 1.114 + 1.115 + _findDupe: function _findDupe(item) { 1.116 + return FormWrapper.getGUID(item.name, item.value); 1.117 + } 1.118 +}; 1.119 + 1.120 +function FormStore(name, engine) { 1.121 + Store.call(this, name, engine); 1.122 +} 1.123 +FormStore.prototype = { 1.124 + __proto__: Store.prototype, 1.125 + 1.126 + _processChange: function (change) { 1.127 + // If this._changes is defined, then we are applying a batch, so we 1.128 + // can defer it. 1.129 + if (this._changes) { 1.130 + this._changes.push(change); 1.131 + return; 1.132 + } 1.133 + 1.134 + // Otherwise we must handle the change synchronously, right now. 1.135 + FormWrapper._updateSpinningly(change); 1.136 + }, 1.137 + 1.138 + applyIncomingBatch: function (records) { 1.139 + // We collect all the changes to be made then apply them all at once. 1.140 + this._changes = []; 1.141 + let failures = Store.prototype.applyIncomingBatch.call(this, records); 1.142 + if (this._changes.length) { 1.143 + FormWrapper._updateSpinningly(this._changes); 1.144 + } 1.145 + delete this._changes; 1.146 + return failures; 1.147 + }, 1.148 + 1.149 + getAllIDs: function () { 1.150 + let results = FormWrapper._searchSpinningly(["guid"], []) 1.151 + let guids = {}; 1.152 + for (let result of results) { 1.153 + guids[result.guid] = true; 1.154 + } 1.155 + return guids; 1.156 + }, 1.157 + 1.158 + changeItemID: function (oldID, newID) { 1.159 + FormWrapper.replaceGUID(oldID, newID); 1.160 + }, 1.161 + 1.162 + itemExists: function (id) { 1.163 + return FormWrapper.hasGUID(id); 1.164 + }, 1.165 + 1.166 + createRecord: function (id, collection) { 1.167 + let record = new FormRec(collection, id); 1.168 + let entry = FormWrapper.getEntry(id); 1.169 + if (entry != null) { 1.170 + record.name = entry.name; 1.171 + record.value = entry.value; 1.172 + } else { 1.173 + record.deleted = true; 1.174 + } 1.175 + return record; 1.176 + }, 1.177 + 1.178 + create: function (record) { 1.179 + this._log.trace("Adding form record for " + record.name); 1.180 + let change = { 1.181 + op: "add", 1.182 + fieldname: record.name, 1.183 + value: record.value 1.184 + }; 1.185 + this._processChange(change); 1.186 + }, 1.187 + 1.188 + remove: function (record) { 1.189 + this._log.trace("Removing form record: " + record.id); 1.190 + let change = { 1.191 + op: "remove", 1.192 + guid: record.id 1.193 + }; 1.194 + this._processChange(change); 1.195 + }, 1.196 + 1.197 + update: function (record) { 1.198 + this._log.trace("Ignoring form record update request!"); 1.199 + }, 1.200 + 1.201 + wipe: function () { 1.202 + let change = { 1.203 + op: "remove" 1.204 + }; 1.205 + FormWrapper._updateSpinningly(change); 1.206 + } 1.207 +}; 1.208 + 1.209 +function FormTracker(name, engine) { 1.210 + Tracker.call(this, name, engine); 1.211 +} 1.212 +FormTracker.prototype = { 1.213 + __proto__: Tracker.prototype, 1.214 + 1.215 + QueryInterface: XPCOMUtils.generateQI([ 1.216 + Ci.nsIObserver, 1.217 + Ci.nsISupportsWeakReference]), 1.218 + 1.219 + startTracking: function() { 1.220 + Svc.Obs.add("satchel-storage-changed", this); 1.221 + }, 1.222 + 1.223 + stopTracking: function() { 1.224 + Svc.Obs.remove("satchel-storage-changed", this); 1.225 + }, 1.226 + 1.227 + observe: function (subject, topic, data) { 1.228 + Tracker.prototype.observe.call(this, subject, topic, data); 1.229 + 1.230 + switch (topic) { 1.231 + case "satchel-storage-changed": 1.232 + if (data == "formhistory-add" || data == "formhistory-remove") { 1.233 + let guid = subject.QueryInterface(Ci.nsISupportsString).toString(); 1.234 + this.trackEntry(guid); 1.235 + } 1.236 + break; 1.237 + } 1.238 + }, 1.239 + 1.240 + trackEntry: function (guid) { 1.241 + this.addChangedID(guid); 1.242 + this.score += SCORE_INCREMENT_MEDIUM; 1.243 + }, 1.244 +};