|
1 /* This Source Code Form is subject to the terms of the Mozilla Public |
|
2 * License, v. 2.0. If a copy of the MPL was not distributed with this |
|
3 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ |
|
4 |
|
5 this.EXPORTED_SYMBOLS = ['FormEngine', 'FormRec']; |
|
6 |
|
7 const Cc = Components.classes; |
|
8 const Ci = Components.interfaces; |
|
9 const Cu = Components.utils; |
|
10 |
|
11 Cu.import("resource://gre/modules/XPCOMUtils.jsm"); |
|
12 Cu.import("resource://services-sync/engines.js"); |
|
13 Cu.import("resource://services-sync/record.js"); |
|
14 Cu.import("resource://services-common/async.js"); |
|
15 Cu.import("resource://services-sync/util.js"); |
|
16 Cu.import("resource://services-sync/constants.js"); |
|
17 Cu.import("resource://gre/modules/Log.jsm"); |
|
18 |
|
19 const FORMS_TTL = 5184000; // 60 days |
|
20 |
|
21 this.FormRec = function FormRec(collection, id) { |
|
22 CryptoWrapper.call(this, collection, id); |
|
23 } |
|
24 FormRec.prototype = { |
|
25 __proto__: CryptoWrapper.prototype, |
|
26 _logName: "Sync.Record.Form", |
|
27 ttl: FORMS_TTL |
|
28 }; |
|
29 |
|
30 Utils.deferGetSet(FormRec, "cleartext", ["name", "value"]); |
|
31 |
|
32 |
|
33 let FormWrapper = { |
|
34 _log: Log.repository.getLogger("Sync.Engine.Forms"), |
|
35 |
|
36 _getEntryCols: ["fieldname", "value"], |
|
37 _guidCols: ["guid"], |
|
38 |
|
39 // Do a "sync" search by spinning the event loop until it completes. |
|
40 _searchSpinningly: function(terms, searchData) { |
|
41 let results = []; |
|
42 let cb = Async.makeSpinningCallback(); |
|
43 let callbacks = { |
|
44 handleResult: function(result) { |
|
45 results.push(result); |
|
46 }, |
|
47 handleCompletion: function(reason) { |
|
48 cb(null, results); |
|
49 } |
|
50 }; |
|
51 Svc.FormHistory.search(terms, searchData, callbacks); |
|
52 return cb.wait(); |
|
53 }, |
|
54 |
|
55 _updateSpinningly: function(changes) { |
|
56 if (!Svc.FormHistory.enabled) { |
|
57 return; // update isn't going to do anything. |
|
58 } |
|
59 let cb = Async.makeSpinningCallback(); |
|
60 let callbacks = { |
|
61 handleCompletion: function(reason) { |
|
62 cb(); |
|
63 } |
|
64 }; |
|
65 Svc.FormHistory.update(changes, callbacks); |
|
66 return cb.wait(); |
|
67 }, |
|
68 |
|
69 getEntry: function (guid) { |
|
70 let results = this._searchSpinningly(this._getEntryCols, {guid: guid}); |
|
71 if (!results.length) { |
|
72 return null; |
|
73 } |
|
74 return {name: results[0].fieldname, value: results[0].value}; |
|
75 }, |
|
76 |
|
77 getGUID: function (name, value) { |
|
78 // Query for the provided entry. |
|
79 let query = { fieldname: name, value: value }; |
|
80 let results = this._searchSpinningly(this._guidCols, query); |
|
81 return results.length ? results[0].guid : null; |
|
82 }, |
|
83 |
|
84 hasGUID: function (guid) { |
|
85 // We could probably use a count function here, but searchSpinningly exists... |
|
86 return this._searchSpinningly(this._guidCols, {guid: guid}).length != 0; |
|
87 }, |
|
88 |
|
89 replaceGUID: function (oldGUID, newGUID) { |
|
90 let changes = { |
|
91 op: "update", |
|
92 guid: oldGUID, |
|
93 newGuid: newGUID, |
|
94 } |
|
95 this._updateSpinningly(changes); |
|
96 } |
|
97 |
|
98 }; |
|
99 |
|
100 this.FormEngine = function FormEngine(service) { |
|
101 SyncEngine.call(this, "Forms", service); |
|
102 } |
|
103 FormEngine.prototype = { |
|
104 __proto__: SyncEngine.prototype, |
|
105 _storeObj: FormStore, |
|
106 _trackerObj: FormTracker, |
|
107 _recordObj: FormRec, |
|
108 applyIncomingBatchSize: FORMS_STORE_BATCH_SIZE, |
|
109 |
|
110 get prefName() "history", |
|
111 |
|
112 _findDupe: function _findDupe(item) { |
|
113 return FormWrapper.getGUID(item.name, item.value); |
|
114 } |
|
115 }; |
|
116 |
|
117 function FormStore(name, engine) { |
|
118 Store.call(this, name, engine); |
|
119 } |
|
120 FormStore.prototype = { |
|
121 __proto__: Store.prototype, |
|
122 |
|
123 _processChange: function (change) { |
|
124 // If this._changes is defined, then we are applying a batch, so we |
|
125 // can defer it. |
|
126 if (this._changes) { |
|
127 this._changes.push(change); |
|
128 return; |
|
129 } |
|
130 |
|
131 // Otherwise we must handle the change synchronously, right now. |
|
132 FormWrapper._updateSpinningly(change); |
|
133 }, |
|
134 |
|
135 applyIncomingBatch: function (records) { |
|
136 // We collect all the changes to be made then apply them all at once. |
|
137 this._changes = []; |
|
138 let failures = Store.prototype.applyIncomingBatch.call(this, records); |
|
139 if (this._changes.length) { |
|
140 FormWrapper._updateSpinningly(this._changes); |
|
141 } |
|
142 delete this._changes; |
|
143 return failures; |
|
144 }, |
|
145 |
|
146 getAllIDs: function () { |
|
147 let results = FormWrapper._searchSpinningly(["guid"], []) |
|
148 let guids = {}; |
|
149 for (let result of results) { |
|
150 guids[result.guid] = true; |
|
151 } |
|
152 return guids; |
|
153 }, |
|
154 |
|
155 changeItemID: function (oldID, newID) { |
|
156 FormWrapper.replaceGUID(oldID, newID); |
|
157 }, |
|
158 |
|
159 itemExists: function (id) { |
|
160 return FormWrapper.hasGUID(id); |
|
161 }, |
|
162 |
|
163 createRecord: function (id, collection) { |
|
164 let record = new FormRec(collection, id); |
|
165 let entry = FormWrapper.getEntry(id); |
|
166 if (entry != null) { |
|
167 record.name = entry.name; |
|
168 record.value = entry.value; |
|
169 } else { |
|
170 record.deleted = true; |
|
171 } |
|
172 return record; |
|
173 }, |
|
174 |
|
175 create: function (record) { |
|
176 this._log.trace("Adding form record for " + record.name); |
|
177 let change = { |
|
178 op: "add", |
|
179 fieldname: record.name, |
|
180 value: record.value |
|
181 }; |
|
182 this._processChange(change); |
|
183 }, |
|
184 |
|
185 remove: function (record) { |
|
186 this._log.trace("Removing form record: " + record.id); |
|
187 let change = { |
|
188 op: "remove", |
|
189 guid: record.id |
|
190 }; |
|
191 this._processChange(change); |
|
192 }, |
|
193 |
|
194 update: function (record) { |
|
195 this._log.trace("Ignoring form record update request!"); |
|
196 }, |
|
197 |
|
198 wipe: function () { |
|
199 let change = { |
|
200 op: "remove" |
|
201 }; |
|
202 FormWrapper._updateSpinningly(change); |
|
203 } |
|
204 }; |
|
205 |
|
206 function FormTracker(name, engine) { |
|
207 Tracker.call(this, name, engine); |
|
208 } |
|
209 FormTracker.prototype = { |
|
210 __proto__: Tracker.prototype, |
|
211 |
|
212 QueryInterface: XPCOMUtils.generateQI([ |
|
213 Ci.nsIObserver, |
|
214 Ci.nsISupportsWeakReference]), |
|
215 |
|
216 startTracking: function() { |
|
217 Svc.Obs.add("satchel-storage-changed", this); |
|
218 }, |
|
219 |
|
220 stopTracking: function() { |
|
221 Svc.Obs.remove("satchel-storage-changed", this); |
|
222 }, |
|
223 |
|
224 observe: function (subject, topic, data) { |
|
225 Tracker.prototype.observe.call(this, subject, topic, data); |
|
226 |
|
227 switch (topic) { |
|
228 case "satchel-storage-changed": |
|
229 if (data == "formhistory-add" || data == "formhistory-remove") { |
|
230 let guid = subject.QueryInterface(Ci.nsISupportsString).toString(); |
|
231 this.trackEntry(guid); |
|
232 } |
|
233 break; |
|
234 } |
|
235 }, |
|
236 |
|
237 trackEntry: function (guid) { |
|
238 this.addChangedID(guid); |
|
239 this.score += SCORE_INCREMENT_MEDIUM; |
|
240 }, |
|
241 }; |