232 lines, 118 LOC, 60 covered (50%)
3 | 1 | /* ***** BEGIN LICENSE BLOCK ***** |
2 | * Version: MPL 1.1/GPL 2.0/LGPL 2.1 |
|
3 | * |
|
4 | * The contents of this file are subject to the Mozilla Public License Version |
|
5 | * 1.1 (the "License"); you may not use this file except in compliance with |
|
6 | * the License. You may obtain a copy of the License at |
|
7 | * http://www.mozilla.org/MPL/ |
|
8 | * |
|
9 | * Software distributed under the License is distributed on an "AS IS" basis, |
|
10 | * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License |
|
11 | * for the specific language governing rights and limitations under the |
|
12 | * License. |
|
13 | * |
|
14 | * The Original Code is Weave |
|
15 | * |
|
16 | * The Initial Developer of the Original Code is Mozilla. |
|
17 | * Portions created by the Initial Developer are Copyright (C) 2009 |
|
18 | * the Initial Developer. All Rights Reserved. |
|
19 | * |
|
20 | * Contributor(s): |
|
21 | * Dan Mills <thunder@mozilla.com> |
|
22 | * |
|
23 | * Alternatively, the contents of this file may be used under the terms of |
|
24 | * either the GNU General Public License Version 2 or later (the "GPL"), or |
|
25 | * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), |
|
26 | * in which case the provisions of the GPL or the LGPL are applicable instead |
|
27 | * of those above. If you wish to allow use of your version of this file only |
|
28 | * under the terms of either the GPL or the LGPL, and not to allow others to |
|
29 | * use your version of this file under the terms of the MPL, indicate your |
|
30 | * decision by deleting the provisions above and replace them with the notice |
|
31 | * and other provisions required by the GPL or the LGPL. If you do not delete |
|
32 | * the provisions above, a recipient may use your version of this file under |
|
33 | * the terms of any one of the MPL, the GPL or the LGPL. |
|
34 | * |
|
35 | * ***** END LICENSE BLOCK ***** */ |
|
36 | ||
21 | 37 | const EXPORTED_SYMBOLS = ["Clients"]; |
38 | ||
12 | 39 | const Cc = Components.classes; |
12 | 40 | const Ci = Components.interfaces; |
12 | 41 | const Cu = Components.utils; |
42 | ||
15 | 43 | Cu.import("resource://weave/constants.js"); |
15 | 44 | Cu.import("resource://weave/util.js"); |
15 | 45 | Cu.import("resource://weave/engines.js"); |
15 | 46 | Cu.import("resource://weave/stores.js"); |
15 | 47 | Cu.import("resource://weave/type_records/clients.js"); |
48 | ||
21 | 49 | Utils.lazy(this, "Clients", ClientEngine); |
50 | ||
9 | 51 | function ClientEngine() { |
18 | 52 | SyncEngine.call(this, "Clients"); |
53 | ||
54 | // Reset the client on every startup so that we fetch recent clients |
|
15 | 55 | this._resetClient(); |
56 | } |
|
6 | 57 | ClientEngine.prototype = { |
9 | 58 | __proto__: SyncEngine.prototype, |
6 | 59 | _storeObj: ClientStore, |
6 | 60 | _recordObj: ClientsRec, |
61 | ||
62 | // Always sync client data as it controls other sync behavior |
|
6 | 63 | get enabled() true, |
64 | ||
65 | // Aggregate some stats on the composition of clients on this account |
|
6 | 66 | get stats() { |
67 | let stats = { |
|
68 | hasMobile: this.localType == "mobile", |
|
69 | names: [this.localName], |
|
70 | numClients: 1, |
|
71 | }; |
|
72 | ||
73 | for each (let {name, type} in this._store._remoteClients) { |
|
74 | stats.hasMobile = stats.hasMobile || type == "mobile"; |
|
75 | stats.names.push(name); |
|
76 | stats.numClients++; |
|
77 | } |
|
78 | ||
79 | return stats; |
|
80 | }, |
|
81 | ||
82 | // Remove any commands for the local client and mark it for upload |
|
6 | 83 | clearCommands: function clearCommands() { |
84 | delete this.localCommands; |
|
85 | this._tracker.addChangedID(this.localID); |
|
86 | }, |
|
87 | ||
88 | // Send a command+args pair to each remote client |
|
6 | 89 | sendCommand: function sendCommand(command, args) { |
90 | // Helper to determine if the client already has this command |
|
91 | let notDupe = function(other) other.command != command || |
|
92 | JSON.stringify(other.args) != JSON.stringify(args); |
|
93 | ||
94 | // Package the command/args pair into an object |
|
95 | let action = { |
|
96 | command: command, |
|
97 | args: args, |
|
98 | }; |
|
99 | ||
100 | // Send the command to each remote client |
|
101 | for (let [id, client] in Iterator(this._store._remoteClients)) { |
|
102 | // Set the action to be a new commands array if none exists |
|
103 | if (client.commands == null) |
|
104 | client.commands = [action]; |
|
105 | // Add the new action if there are no duplicates |
|
106 | else if (client.commands.every(notDupe)) |
|
107 | client.commands.push(action); |
|
108 | // Must have been a dupe.. skip! |
|
109 | else |
|
110 | continue; |
|
111 | ||
112 | this._log.trace("Client " + id + " got a new action: " + [command, args]); |
|
113 | this._tracker.addChangedID(id); |
|
114 | } |
|
115 | }, |
|
116 | ||
8 | 117 | get localID() { |
118 | // Generate a random GUID id we don't have one |
|
14 | 119 | let localID = Svc.Prefs.get("client.GUID", ""); |
10 | 120 | return localID == "" ? this.localID = Utils.makeGUID() : localID; |
121 | }, |
|
14 | 122 | set localID(value) Svc.Prefs.set("client.GUID", value), |
123 | ||
8 | 124 | get localName() { |
14 | 125 | let localName = Svc.Prefs.get("client.name", ""); |
6 | 126 | if (localName != "") |
4 | 127 | return localName; |
128 | ||
129 | // Generate a client name if we don't have a useful one yet |
|
130 | let user = Svc.Env.get("USER") || Svc.Env.get("USERNAME"); |
|
131 | let app = Svc.AppInfo.name; |
|
132 | let host = Svc.SysInfo.get("host"); |
|
133 | ||
134 | // Try figuring out the name of the current profile |
|
135 | let prof = Svc.Directory.get("ProfD", Components.interfaces.nsIFile).path; |
|
136 | let profiles = Svc.Profiles.profiles; |
|
137 | while (profiles.hasMoreElements()) { |
|
138 | let profile = profiles.getNext().QueryInterface(Ci.nsIToolkitProfile); |
|
139 | if (prof == profile.rootDir.path) { |
|
140 | // Only bother adding the profile name if it's not "default" |
|
141 | if (profile.name != "default") |
|
142 | host = profile.name + "-" + host; |
|
143 | break; |
|
144 | } |
|
145 | } |
|
146 | ||
147 | return this.localName = Str.sync.get("client.name", [user, app, host]); |
|
148 | }, |
|
14 | 149 | set localName(value) Svc.Prefs.set("client.name", value), |
150 | ||
8 | 151 | get localType() { |
152 | // Figure out if we have a type previously set |
|
14 | 153 | let localType = Svc.Prefs.get("client.type", ""); |
6 | 154 | if (localType == "") { |
155 | // Assume we're desktop-like unless the app is for mobiles |
|
156 | localType = "desktop"; |
|
157 | switch (Svc.AppInfo.ID) { |
|
158 | case FENNEC_ID: |
|
159 | localType = "mobile"; |
|
160 | break; |
|
161 | } |
|
162 | this.localType = localType; |
|
163 | } |
|
4 | 164 | return localType; |
165 | }, |
|
6 | 166 | set localType(value) Svc.Prefs.set("client.type", value), |
167 | ||
6 | 168 | isMobile: function isMobile(id) { |
169 | if (this._store._remoteClients[id]) |
|
170 | return this._store._remoteClients[id].type == "mobile"; |
|
171 | return false; |
|
172 | }, |
|
173 | ||
174 | // Always process incoming items because they might have commands |
|
6 | 175 | _reconcile: function _reconcile() { |
176 | return true; |
|
177 | }, |
|
178 | ||
179 | // Treat reset the same as wiping for locally cached clients |
|
21 | 180 | _resetClient: function _resetClient() this._wipeClient(), |
181 | ||
18 | 182 | _wipeClient: function _wipeClient() { |
21 | 183 | SyncEngine.prototype._resetClient.call(this); |
18 | 184 | this._store.wipe(); |
185 | } |
|
186 | }; |
|
187 | ||
9 | 188 | function ClientStore(name) { |
21 | 189 | Store.call(this, name); |
190 | } |
|
6 | 191 | ClientStore.prototype = { |
9 | 192 | __proto__: Store.prototype, |
193 | ||
6 | 194 | create: function create(record) this.update(record), |
195 | ||
6 | 196 | update: function update(record) { |
197 | // Only grab commands from the server; local name/type always wins |
|
198 | if (record.id == Clients.localID) |
|
199 | Clients.localCommands = record.commands; |
|
200 | else |
|
201 | this._remoteClients[record.id] = record.cleartext; |
|
202 | }, |
|
203 | ||
8 | 204 | createRecord: function createRecord(guid) { |
6 | 205 | let record = new ClientsRec(); |
206 | ||
207 | // Package the individual components into a record for the local client |
|
8 | 208 | if (guid == Clients.localID) { |
8 | 209 | record.name = Clients.localName; |
8 | 210 | record.type = Clients.localType; |
10 | 211 | record.commands = Clients.localCommands; |
212 | } |
|
213 | else |
|
214 | record.cleartext = this._remoteClients[guid]; |
|
215 | ||
4 | 216 | return record; |
217 | }, |
|
218 | ||
6 | 219 | itemExists: function itemExists(id) id in this.getAllIDs(), |
220 | ||
6 | 221 | getAllIDs: function getAllIDs() { |
222 | let ids = {}; |
|
223 | ids[Clients.localID] = true; |
|
224 | for (let id in this._remoteClients) |
|
225 | ids[id] = true; |
|
226 | return ids; |
|
227 | }, |
|
228 | ||
18 | 229 | wipe: function wipe() { |
15 | 230 | this._remoteClients = {}; |
231 | }, |
|
3 | 232 | }; |