engines/clients.js

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
};