/services/sync/modules/policies.js

http://github.com/zpao/v8monkey · JavaScript · 937 lines · 630 code · 109 blank · 198 comment · 104 complexity · 4e1b9f469eb597ef8dbe745bcf924751 MD5 · raw file

  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 Firefox Sync.
  15. *
  16. * The Initial Developer of the Original Code is
  17. * the Mozilla Foundation.
  18. * Portions created by the Initial Developer are Copyright (C) 2011
  19. * the Initial Developer. All Rights Reserved.
  20. *
  21. * Contributor(s):
  22. * Marina Samuel <msamuel@mozilla.com>
  23. * Philipp von Weitershausen <philipp@weitershausen.de>
  24. * Chenxia Liu <liuche@mozilla.com>
  25. * Richard Newman <rnewman@mozilla.com>
  26. *
  27. * Alternatively, the contents of this file may be used under the terms of
  28. * either the GNU General Public License Version 2 or later (the "GPL"), or
  29. * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
  30. * in which case the provisions of the GPL or the LGPL are applicable instead
  31. * of those above. If you wish to allow use of your version of this file only
  32. * under the terms of either the GPL or the LGPL, and not to allow others to
  33. * use your version of this file under the terms of the MPL, indicate your
  34. * decision by deleting the provisions above and replace them with the notice
  35. * and other provisions required by the GPL or the LGPL. If you do not delete
  36. * the provisions above, a recipient may use your version of this file under
  37. * the terms of any one of the MPL, the GPL or the LGPL.
  38. *
  39. * ***** END LICENSE BLOCK ***** */
  40. const EXPORTED_SYMBOLS = ["SyncScheduler",
  41. "ErrorHandler",
  42. "SendCredentialsController"];
  43. const {classes: Cc, interfaces: Ci, utils: Cu, results: Cr} = Components;
  44. Cu.import("resource://services-sync/constants.js");
  45. Cu.import("resource://services-sync/log4moz.js");
  46. Cu.import("resource://services-sync/util.js");
  47. Cu.import("resource://services-sync/engines.js");
  48. Cu.import("resource://services-sync/engines/clients.js");
  49. Cu.import("resource://services-sync/status.js");
  50. Cu.import("resource://services-sync/main.js"); // So we can get to Service for callbacks.
  51. let SyncScheduler = {
  52. _log: Log4Moz.repository.getLogger("Sync.SyncScheduler"),
  53. _fatalLoginStatus: [LOGIN_FAILED_NO_USERNAME,
  54. LOGIN_FAILED_NO_PASSWORD,
  55. LOGIN_FAILED_NO_PASSPHRASE,
  56. LOGIN_FAILED_INVALID_PASSPHRASE,
  57. LOGIN_FAILED_LOGIN_REJECTED],
  58. /**
  59. * The nsITimer object that schedules the next sync. See scheduleNextSync().
  60. */
  61. syncTimer: null,
  62. setDefaults: function setDefaults() {
  63. this._log.trace("Setting SyncScheduler policy values to defaults.");
  64. this.singleDeviceInterval = Svc.Prefs.get("scheduler.singleDeviceInterval") * 1000;
  65. this.idleInterval = Svc.Prefs.get("scheduler.idleInterval") * 1000;
  66. this.activeInterval = Svc.Prefs.get("scheduler.activeInterval") * 1000;
  67. this.immediateInterval = Svc.Prefs.get("scheduler.immediateInterval") * 1000;
  68. // A user is non-idle on startup by default.
  69. this.idle = false;
  70. this.hasIncomingItems = false;
  71. this.clearSyncTriggers();
  72. },
  73. // nextSync is in milliseconds, but prefs can't hold that much
  74. get nextSync() Svc.Prefs.get("nextSync", 0) * 1000,
  75. set nextSync(value) Svc.Prefs.set("nextSync", Math.floor(value / 1000)),
  76. get syncInterval() Svc.Prefs.get("syncInterval", this.singleDeviceInterval),
  77. set syncInterval(value) Svc.Prefs.set("syncInterval", value),
  78. get syncThreshold() Svc.Prefs.get("syncThreshold", SINGLE_USER_THRESHOLD),
  79. set syncThreshold(value) Svc.Prefs.set("syncThreshold", value),
  80. get globalScore() Svc.Prefs.get("globalScore", 0),
  81. set globalScore(value) Svc.Prefs.set("globalScore", value),
  82. get numClients() Svc.Prefs.get("numClients", 0),
  83. set numClients(value) Svc.Prefs.set("numClients", value),
  84. init: function init() {
  85. this._log.level = Log4Moz.Level[Svc.Prefs.get("log.logger.service.main")];
  86. this.setDefaults();
  87. Svc.Obs.add("weave:engine:score:updated", this);
  88. Svc.Obs.add("network:offline-status-changed", this);
  89. Svc.Obs.add("weave:service:sync:start", this);
  90. Svc.Obs.add("weave:service:sync:finish", this);
  91. Svc.Obs.add("weave:engine:sync:finish", this);
  92. Svc.Obs.add("weave:engine:sync:error", this);
  93. Svc.Obs.add("weave:service:login:error", this);
  94. Svc.Obs.add("weave:service:logout:finish", this);
  95. Svc.Obs.add("weave:service:sync:error", this);
  96. Svc.Obs.add("weave:service:backoff:interval", this);
  97. Svc.Obs.add("weave:service:ready", this);
  98. Svc.Obs.add("weave:engine:sync:applied", this);
  99. Svc.Obs.add("weave:service:setup-complete", this);
  100. Svc.Obs.add("weave:service:start-over", this);
  101. if (Status.checkSetup() == STATUS_OK) {
  102. Svc.Idle.addIdleObserver(this, Svc.Prefs.get("scheduler.idleTime"));
  103. }
  104. },
  105. observe: function observe(subject, topic, data) {
  106. this._log.trace("Handling " + topic);
  107. switch(topic) {
  108. case "weave:engine:score:updated":
  109. if (Status.login == LOGIN_SUCCEEDED) {
  110. Utils.namedTimer(this.calculateScore, SCORE_UPDATE_DELAY, this,
  111. "_scoreTimer");
  112. }
  113. break;
  114. case "network:offline-status-changed":
  115. // Whether online or offline, we'll reschedule syncs
  116. this._log.trace("Network offline status change: " + data);
  117. this.checkSyncStatus();
  118. break;
  119. case "weave:service:sync:start":
  120. // Clear out any potentially pending syncs now that we're syncing
  121. this.clearSyncTriggers();
  122. // reset backoff info, if the server tells us to continue backing off,
  123. // we'll handle that later
  124. Status.resetBackoff();
  125. this.globalScore = 0;
  126. break;
  127. case "weave:service:sync:finish":
  128. this.nextSync = 0;
  129. this.adjustSyncInterval();
  130. if (Status.service == SYNC_FAILED_PARTIAL && this.requiresBackoff) {
  131. this.requiresBackoff = false;
  132. this.handleSyncError();
  133. return;
  134. }
  135. let sync_interval;
  136. this._syncErrors = 0;
  137. if (Status.sync == NO_SYNC_NODE_FOUND) {
  138. this._log.trace("Scheduling a sync at interval NO_SYNC_NODE_FOUND.");
  139. sync_interval = NO_SYNC_NODE_INTERVAL;
  140. }
  141. this.scheduleNextSync(sync_interval);
  142. break;
  143. case "weave:engine:sync:finish":
  144. if (data == "clients") {
  145. // Update the client mode because it might change what we sync.
  146. this.updateClientMode();
  147. }
  148. break;
  149. case "weave:engine:sync:error":
  150. // `subject` is the exception thrown by an engine's sync() method.
  151. let exception = subject;
  152. if (exception.status >= 500 && exception.status <= 504) {
  153. this.requiresBackoff = true;
  154. }
  155. break;
  156. case "weave:service:login:error":
  157. this.clearSyncTriggers();
  158. if (Status.login == MASTER_PASSWORD_LOCKED) {
  159. // Try again later, just as if we threw an error... only without the
  160. // error count.
  161. this._log.debug("Couldn't log in: master password is locked.");
  162. this._log.trace("Scheduling a sync at MASTER_PASSWORD_LOCKED_RETRY_INTERVAL");
  163. this.scheduleAtInterval(MASTER_PASSWORD_LOCKED_RETRY_INTERVAL);
  164. } else if (this._fatalLoginStatus.indexOf(Status.login) == -1) {
  165. // Not a fatal login error, just an intermittent network or server
  166. // issue. Keep on syncin'.
  167. this.checkSyncStatus();
  168. }
  169. break;
  170. case "weave:service:logout:finish":
  171. // Start or cancel the sync timer depending on if
  172. // logged in or logged out
  173. this.checkSyncStatus();
  174. break;
  175. case "weave:service:sync:error":
  176. // There may be multiple clients but if the sync fails, client mode
  177. // should still be updated so that the next sync has a correct interval.
  178. this.updateClientMode();
  179. this.adjustSyncInterval();
  180. this.nextSync = 0;
  181. this.handleSyncError();
  182. break;
  183. case "weave:service:backoff:interval":
  184. let requested_interval = subject * 1000;
  185. // Leave up to 25% more time for the back off.
  186. let interval = requested_interval * (1 + Math.random() * 0.25);
  187. Status.backoffInterval = interval;
  188. Status.minimumNextSync = Date.now() + requested_interval;
  189. break;
  190. case "weave:service:ready":
  191. // Applications can specify this preference if they want autoconnect
  192. // to happen after a fixed delay.
  193. let delay = Svc.Prefs.get("autoconnectDelay");
  194. if (delay) {
  195. this.delayedAutoConnect(delay);
  196. }
  197. break;
  198. case "weave:engine:sync:applied":
  199. let numItems = subject.applied;
  200. this._log.trace("Engine " + data + " applied " + numItems + " items.");
  201. if (numItems)
  202. this.hasIncomingItems = true;
  203. break;
  204. case "weave:service:setup-complete":
  205. Svc.Idle.addIdleObserver(this, Svc.Prefs.get("scheduler.idleTime"));
  206. break;
  207. case "weave:service:start-over":
  208. SyncScheduler.setDefaults();
  209. try {
  210. Svc.Idle.removeIdleObserver(this, Svc.Prefs.get("scheduler.idleTime"));
  211. } catch (ex if (ex.result == Cr.NS_ERROR_FAILURE)) {
  212. // In all likelihood we didn't have an idle observer registered yet.
  213. // It's all good.
  214. }
  215. break;
  216. case "idle":
  217. this._log.trace("We're idle.");
  218. this.idle = true;
  219. // Adjust the interval for future syncs. This won't actually have any
  220. // effect until the next pending sync (which will happen soon since we
  221. // were just active.)
  222. this.adjustSyncInterval();
  223. break;
  224. case "back":
  225. this._log.trace("Received notification that we're back from idle.");
  226. this.idle = false;
  227. Utils.namedTimer(function onBack() {
  228. if (this.idle) {
  229. this._log.trace("... and we're idle again. " +
  230. "Ignoring spurious back notification.");
  231. return;
  232. }
  233. this._log.trace("Genuine return from idle. Syncing.");
  234. // Trigger a sync if we have multiple clients.
  235. if (this.numClients > 1) {
  236. this.scheduleNextSync(0);
  237. }
  238. }, IDLE_OBSERVER_BACK_DELAY, this, "idleDebouncerTimer");
  239. break;
  240. }
  241. },
  242. adjustSyncInterval: function adjustSyncInterval() {
  243. if (this.numClients <= 1) {
  244. this._log.trace("Adjusting syncInterval to singleDeviceInterval.");
  245. this.syncInterval = this.singleDeviceInterval;
  246. return;
  247. }
  248. // Only MULTI_DEVICE clients will enter this if statement
  249. // since SINGLE_USER clients will be handled above.
  250. if (this.idle) {
  251. this._log.trace("Adjusting syncInterval to idleInterval.");
  252. this.syncInterval = this.idleInterval;
  253. return;
  254. }
  255. if (this.hasIncomingItems) {
  256. this._log.trace("Adjusting syncInterval to immediateInterval.");
  257. this.hasIncomingItems = false;
  258. this.syncInterval = this.immediateInterval;
  259. } else {
  260. this._log.trace("Adjusting syncInterval to activeInterval.");
  261. this.syncInterval = this.activeInterval;
  262. }
  263. },
  264. calculateScore: function calculateScore() {
  265. let engines = [Clients].concat(Engines.getEnabled());
  266. for (let i = 0;i < engines.length;i++) {
  267. this._log.trace(engines[i].name + ": score: " + engines[i].score);
  268. this.globalScore += engines[i].score;
  269. engines[i]._tracker.resetScore();
  270. }
  271. this._log.trace("Global score updated: " + this.globalScore);
  272. this.checkSyncStatus();
  273. },
  274. /**
  275. * Process the locally stored clients list to figure out what mode to be in
  276. */
  277. updateClientMode: function updateClientMode() {
  278. // Nothing to do if it's the same amount
  279. let numClients = Clients.stats.numClients;
  280. if (this.numClients == numClients)
  281. return;
  282. this._log.debug("Client count: " + this.numClients + " -> " + numClients);
  283. this.numClients = numClients;
  284. if (numClients <= 1) {
  285. this._log.trace("Adjusting syncThreshold to SINGLE_USER_THRESHOLD");
  286. this.syncThreshold = SINGLE_USER_THRESHOLD;
  287. } else {
  288. this._log.trace("Adjusting syncThreshold to MULTI_DEVICE_THRESHOLD");
  289. this.syncThreshold = MULTI_DEVICE_THRESHOLD;
  290. }
  291. this.adjustSyncInterval();
  292. },
  293. /**
  294. * Check if we should be syncing and schedule the next sync, if it's not scheduled
  295. */
  296. checkSyncStatus: function checkSyncStatus() {
  297. // Should we be syncing now, if not, cancel any sync timers and return
  298. // if we're in backoff, we'll schedule the next sync.
  299. let ignore = [kSyncBackoffNotMet, kSyncMasterPasswordLocked];
  300. let skip = Weave.Service._checkSync(ignore);
  301. this._log.trace("_checkSync returned \"" + skip + "\".");
  302. if (skip) {
  303. this.clearSyncTriggers();
  304. return;
  305. }
  306. // Only set the wait time to 0 if we need to sync right away
  307. let wait;
  308. if (this.globalScore > this.syncThreshold) {
  309. this._log.debug("Global Score threshold hit, triggering sync.");
  310. wait = 0;
  311. }
  312. this.scheduleNextSync(wait);
  313. },
  314. /**
  315. * Call sync() if Master Password is not locked.
  316. *
  317. * Otherwise, reschedule a sync for later.
  318. */
  319. syncIfMPUnlocked: function syncIfMPUnlocked() {
  320. // No point if we got kicked out by the master password dialog.
  321. if (Status.login == MASTER_PASSWORD_LOCKED &&
  322. Utils.mpLocked()) {
  323. this._log.debug("Not initiating sync: Login status is " + Status.login);
  324. // If we're not syncing now, we need to schedule the next one.
  325. this._log.trace("Scheduling a sync at MASTER_PASSWORD_LOCKED_RETRY_INTERVAL");
  326. this.scheduleAtInterval(MASTER_PASSWORD_LOCKED_RETRY_INTERVAL);
  327. return;
  328. }
  329. Utils.nextTick(Weave.Service.sync, Weave.Service);
  330. },
  331. /**
  332. * Set a timer for the next sync
  333. */
  334. scheduleNextSync: function scheduleNextSync(interval) {
  335. // If no interval was specified, use the current sync interval.
  336. if (interval == null) {
  337. interval = this.syncInterval;
  338. }
  339. // Ensure the interval is set to no less than the backoff.
  340. if (Status.backoffInterval && interval < Status.backoffInterval) {
  341. this._log.trace("Requested interval " + interval +
  342. " ms is smaller than the backoff interval. " +
  343. "Using backoff interval " +
  344. Status.backoffInterval + " ms instead.");
  345. interval = Status.backoffInterval;
  346. }
  347. if (this.nextSync != 0) {
  348. // There's already a sync scheduled. Don't reschedule if there's already
  349. // a timer scheduled for sooner than requested.
  350. let currentInterval = this.nextSync - Date.now();
  351. this._log.trace("There's already a sync scheduled in " +
  352. currentInterval + " ms.");
  353. if (currentInterval < interval && this.syncTimer) {
  354. this._log.trace("Ignoring scheduling request for next sync in " +
  355. interval + " ms.");
  356. return;
  357. }
  358. }
  359. // Start the sync right away if we're already late.
  360. if (interval <= 0) {
  361. this._log.trace("Requested sync should happen right away.");
  362. this.syncIfMPUnlocked();
  363. return;
  364. }
  365. this._log.debug("Next sync in " + interval + " ms.");
  366. Utils.namedTimer(this.syncIfMPUnlocked, interval, this, "syncTimer");
  367. // Save the next sync time in-case sync is disabled (logout/offline/etc.)
  368. this.nextSync = Date.now() + interval;
  369. },
  370. /**
  371. * Incorporates the backoff/retry logic used in error handling and elective
  372. * non-syncing.
  373. */
  374. scheduleAtInterval: function scheduleAtInterval(minimumInterval) {
  375. let interval = Utils.calculateBackoff(this._syncErrors, MINIMUM_BACKOFF_INTERVAL);
  376. if (minimumInterval) {
  377. interval = Math.max(minimumInterval, interval);
  378. }
  379. this._log.debug("Starting client-initiated backoff. Next sync in " +
  380. interval + " ms.");
  381. this.scheduleNextSync(interval);
  382. },
  383. /**
  384. * Automatically start syncing after the given delay (in seconds).
  385. *
  386. * Applications can define the `services.sync.autoconnectDelay` preference
  387. * to have this called automatically during start-up with the pref value as
  388. * the argument. Alternatively, they can call it themselves to control when
  389. * Sync should first start to sync.
  390. */
  391. delayedAutoConnect: function delayedAutoConnect(delay) {
  392. if (Weave.Service._checkSetup() == STATUS_OK) {
  393. Utils.namedTimer(this.autoConnect, delay * 1000, this, "_autoTimer");
  394. }
  395. },
  396. autoConnect: function autoConnect() {
  397. if (Weave.Service._checkSetup() == STATUS_OK && !Weave.Service._checkSync()) {
  398. // Schedule a sync based on when a previous sync was scheduled.
  399. // scheduleNextSync() will do the right thing if that time lies in
  400. // the past.
  401. this.scheduleNextSync(this.nextSync - Date.now());
  402. }
  403. // Once autoConnect is called we no longer need _autoTimer.
  404. if (this._autoTimer) {
  405. this._autoTimer.clear();
  406. }
  407. },
  408. _syncErrors: 0,
  409. /**
  410. * Deal with sync errors appropriately
  411. */
  412. handleSyncError: function handleSyncError() {
  413. this._log.trace("In handleSyncError. Error count: " + this._syncErrors);
  414. this._syncErrors++;
  415. // Do nothing on the first couple of failures, if we're not in
  416. // backoff due to 5xx errors.
  417. if (!Status.enforceBackoff) {
  418. if (this._syncErrors < MAX_ERROR_COUNT_BEFORE_BACKOFF) {
  419. this.scheduleNextSync();
  420. return;
  421. }
  422. this._log.debug("Sync error count has exceeded " +
  423. MAX_ERROR_COUNT_BEFORE_BACKOFF + "; enforcing backoff.");
  424. Status.enforceBackoff = true;
  425. }
  426. this.scheduleAtInterval();
  427. },
  428. /**
  429. * Remove any timers/observers that might trigger a sync
  430. */
  431. clearSyncTriggers: function clearSyncTriggers() {
  432. this._log.debug("Clearing sync triggers and the global score.");
  433. this.globalScore = this.nextSync = 0;
  434. // Clear out any scheduled syncs
  435. if (this.syncTimer)
  436. this.syncTimer.clear();
  437. }
  438. };
  439. const LOG_PREFIX_SUCCESS = "success-";
  440. const LOG_PREFIX_ERROR = "error-";
  441. let ErrorHandler = {
  442. /**
  443. * Flag that turns on error reporting for all errors, incl. network errors.
  444. */
  445. dontIgnoreErrors: false,
  446. init: function init() {
  447. Svc.Obs.add("weave:engine:sync:applied", this);
  448. Svc.Obs.add("weave:engine:sync:error", this);
  449. Svc.Obs.add("weave:service:login:error", this);
  450. Svc.Obs.add("weave:service:sync:error", this);
  451. Svc.Obs.add("weave:service:sync:finish", this);
  452. this.initLogs();
  453. },
  454. initLogs: function initLogs() {
  455. this._log = Log4Moz.repository.getLogger("Sync.ErrorHandler");
  456. this._log.level = Log4Moz.Level[Svc.Prefs.get("log.logger.service.main")];
  457. this._cleaningUpFileLogs = false;
  458. let root = Log4Moz.repository.getLogger("Sync");
  459. root.level = Log4Moz.Level[Svc.Prefs.get("log.rootLogger")];
  460. let formatter = new Log4Moz.BasicFormatter();
  461. let capp = new Log4Moz.ConsoleAppender(formatter);
  462. capp.level = Log4Moz.Level[Svc.Prefs.get("log.appender.console")];
  463. root.addAppender(capp);
  464. let dapp = new Log4Moz.DumpAppender(formatter);
  465. dapp.level = Log4Moz.Level[Svc.Prefs.get("log.appender.dump")];
  466. root.addAppender(dapp);
  467. let fapp = this._logAppender = new Log4Moz.StorageStreamAppender(formatter);
  468. fapp.level = Log4Moz.Level[Svc.Prefs.get("log.appender.file.level")];
  469. root.addAppender(fapp);
  470. },
  471. observe: function observe(subject, topic, data) {
  472. this._log.trace("Handling " + topic);
  473. switch(topic) {
  474. case "weave:engine:sync:applied":
  475. if (subject.newFailed) {
  476. // An engine isn't able to apply one or more incoming records.
  477. // We don't fail hard on this, but it usually indicates a bug,
  478. // so for now treat it as sync error (c.f. Service._syncEngine())
  479. Status.engines = [data, ENGINE_APPLY_FAIL];
  480. this._log.debug(data + " failed to apply some records.");
  481. }
  482. break;
  483. case "weave:engine:sync:error":
  484. let exception = subject; // exception thrown by engine's sync() method
  485. let engine_name = data; // engine name that threw the exception
  486. this.checkServerError(exception);
  487. Status.engines = [engine_name, exception.failureCode || ENGINE_UNKNOWN_FAIL];
  488. this._log.debug(engine_name + " failed: " + Utils.exceptionStr(exception));
  489. break;
  490. case "weave:service:login:error":
  491. this.resetFileLog(Svc.Prefs.get("log.appender.file.logOnError"),
  492. LOG_PREFIX_ERROR);
  493. if (this.shouldReportError()) {
  494. this.notifyOnNextTick("weave:ui:login:error");
  495. } else {
  496. this.notifyOnNextTick("weave:ui:clear-error");
  497. }
  498. this.dontIgnoreErrors = false;
  499. break;
  500. case "weave:service:sync:error":
  501. if (Status.sync == CREDENTIALS_CHANGED) {
  502. Weave.Service.logout();
  503. }
  504. this.resetFileLog(Svc.Prefs.get("log.appender.file.logOnError"),
  505. LOG_PREFIX_ERROR);
  506. if (this.shouldReportError()) {
  507. this.notifyOnNextTick("weave:ui:sync:error");
  508. } else {
  509. this.notifyOnNextTick("weave:ui:sync:finish");
  510. }
  511. this.dontIgnoreErrors = false;
  512. break;
  513. case "weave:service:sync:finish":
  514. this._log.trace("Status.service is " + Status.service);
  515. // Check both of these status codes: in the event of a failure in one
  516. // engine, Status.service will be SYNC_FAILED_PARTIAL despite
  517. // Status.sync being SYNC_SUCCEEDED.
  518. // *facepalm*
  519. if (Status.sync == SYNC_SUCCEEDED &&
  520. Status.service == STATUS_OK) {
  521. // Great. Let's clear our mid-sync 401 note.
  522. this._log.trace("Clearing lastSyncReassigned.");
  523. Svc.Prefs.reset("lastSyncReassigned");
  524. }
  525. if (Status.service == SYNC_FAILED_PARTIAL) {
  526. this._log.debug("Some engines did not sync correctly.");
  527. this.resetFileLog(Svc.Prefs.get("log.appender.file.logOnError"),
  528. LOG_PREFIX_ERROR);
  529. if (this.shouldReportError()) {
  530. this.dontIgnoreErrors = false;
  531. this.notifyOnNextTick("weave:ui:sync:error");
  532. break;
  533. }
  534. } else {
  535. this.resetFileLog(Svc.Prefs.get("log.appender.file.logOnSuccess"),
  536. LOG_PREFIX_SUCCESS);
  537. }
  538. this.dontIgnoreErrors = false;
  539. this.notifyOnNextTick("weave:ui:sync:finish");
  540. break;
  541. }
  542. },
  543. notifyOnNextTick: function notifyOnNextTick(topic) {
  544. Utils.nextTick(function() {
  545. this._log.trace("Notifying " + topic +
  546. ". Status.login is " + Status.login +
  547. ". Status.sync is " + Status.sync);
  548. Svc.Obs.notify(topic);
  549. }, this);
  550. },
  551. /**
  552. * Trigger a sync and don't muffle any errors, particularly network errors.
  553. */
  554. syncAndReportErrors: function syncAndReportErrors() {
  555. this._log.debug("Beginning user-triggered sync.");
  556. this.dontIgnoreErrors = true;
  557. Utils.nextTick(Weave.Service.sync, Weave.Service);
  558. },
  559. /**
  560. * Finds all logs older than maxErrorAge and deletes them without tying up I/O.
  561. */
  562. cleanupLogs: function cleanupLogs() {
  563. let direntries = FileUtils.getDir("ProfD", ["weave", "logs"]).directoryEntries;
  564. let oldLogs = [];
  565. let index = 0;
  566. let threshold = Date.now() - 1000 * Svc.Prefs.get("log.appender.file.maxErrorAge");
  567. while (direntries.hasMoreElements()) {
  568. let logFile = direntries.getNext().QueryInterface(Ci.nsIFile);
  569. if (logFile.lastModifiedTime < threshold) {
  570. oldLogs.push(logFile);
  571. }
  572. }
  573. // Deletes a file from oldLogs each tick until there are none left.
  574. function deleteFile() {
  575. if (index >= oldLogs.length) {
  576. ErrorHandler._cleaningUpFileLogs = false;
  577. Svc.Obs.notify("weave:service:cleanup-logs");
  578. return;
  579. }
  580. try {
  581. oldLogs[index].remove(false);
  582. } catch (ex) {
  583. ErrorHandler._log._debug("Encountered error trying to clean up old log file '"
  584. + oldLogs[index].leafName + "':"
  585. + Utils.exceptionStr(ex));
  586. }
  587. index++;
  588. Utils.nextTick(deleteFile);
  589. }
  590. if (oldLogs.length > 0) {
  591. ErrorHandler._cleaningUpFileLogs = true;
  592. Utils.nextTick(deleteFile);
  593. }
  594. },
  595. /**
  596. * Generate a log file for the sync that just completed
  597. * and refresh the input & output streams.
  598. *
  599. * @param flushToFile
  600. * the log file to be flushed/reset
  601. *
  602. * @param filenamePrefix
  603. * a value of either LOG_PREFIX_SUCCESS or LOG_PREFIX_ERROR
  604. * to be used as the log filename prefix
  605. */
  606. resetFileLog: function resetFileLog(flushToFile, filenamePrefix) {
  607. let inStream = this._logAppender.getInputStream();
  608. this._logAppender.reset();
  609. if (flushToFile && inStream) {
  610. try {
  611. let filename = filenamePrefix + Date.now() + ".txt";
  612. let file = FileUtils.getFile("ProfD", ["weave", "logs", filename]);
  613. let outStream = FileUtils.openFileOutputStream(file);
  614. NetUtil.asyncCopy(inStream, outStream, function () {
  615. Svc.Obs.notify("weave:service:reset-file-log");
  616. if (filenamePrefix == LOG_PREFIX_ERROR
  617. && !ErrorHandler._cleaningUpFileLogs) {
  618. Utils.nextTick(ErrorHandler.cleanupLogs, ErrorHandler);
  619. }
  620. });
  621. } catch (ex) {
  622. Svc.Obs.notify("weave:service:reset-file-log");
  623. }
  624. } else {
  625. Svc.Obs.notify("weave:service:reset-file-log");
  626. }
  627. },
  628. /**
  629. * Translates server error codes to meaningful strings.
  630. *
  631. * @param code
  632. * server error code as an integer
  633. */
  634. errorStr: function errorStr(code) {
  635. switch (code.toString()) {
  636. case "1":
  637. return "illegal-method";
  638. case "2":
  639. return "invalid-captcha";
  640. case "3":
  641. return "invalid-username";
  642. case "4":
  643. return "cannot-overwrite-resource";
  644. case "5":
  645. return "userid-mismatch";
  646. case "6":
  647. return "json-parse-failure";
  648. case "7":
  649. return "invalid-password";
  650. case "8":
  651. return "invalid-record";
  652. case "9":
  653. return "weak-password";
  654. default:
  655. return "generic-server-error";
  656. }
  657. },
  658. shouldReportError: function shouldReportError() {
  659. if (Status.login == MASTER_PASSWORD_LOCKED) {
  660. this._log.trace("shouldReportError: false (master password locked).");
  661. return false;
  662. }
  663. if (this.dontIgnoreErrors) {
  664. return true;
  665. }
  666. let lastSync = Svc.Prefs.get("lastSync");
  667. if (lastSync && ((Date.now() - Date.parse(lastSync)) >
  668. Svc.Prefs.get("errorhandler.networkFailureReportTimeout") * 1000)) {
  669. Status.sync = PROLONGED_SYNC_FAILURE;
  670. this._log.trace("shouldReportError: true (prolonged sync failure).");
  671. return true;
  672. }
  673. // We got a 401 mid-sync. Wait for the next sync before actually handling
  674. // an error. This assumes that we'll get a 401 again on a login fetch in
  675. // order to report the error.
  676. if (!Weave.Service.clusterURL) {
  677. this._log.trace("shouldReportError: false (no cluster URL; " +
  678. "possible node reassignment).");
  679. return false;
  680. }
  681. return ([Status.login, Status.sync].indexOf(SERVER_MAINTENANCE) == -1 &&
  682. [Status.login, Status.sync].indexOf(LOGIN_FAILED_NETWORK_ERROR) == -1);
  683. },
  684. /**
  685. * Handle HTTP response results or exceptions and set the appropriate
  686. * Status.* bits.
  687. */
  688. checkServerError: function checkServerError(resp) {
  689. switch (resp.status) {
  690. case 400:
  691. if (resp == RESPONSE_OVER_QUOTA) {
  692. Status.sync = OVER_QUOTA;
  693. }
  694. break;
  695. case 401:
  696. Weave.Service.logout();
  697. this._log.info("Got 401 response; resetting clusterURL.");
  698. Svc.Prefs.reset("clusterURL");
  699. let delay = 0;
  700. if (Svc.Prefs.get("lastSyncReassigned")) {
  701. // We got a 401 in the middle of the previous sync, and we just got
  702. // another. Login must have succeeded in order for us to get here, so
  703. // the password should be correct.
  704. // This is likely to be an intermittent server issue, so back off and
  705. // give it time to recover.
  706. this._log.warn("Last sync also failed for 401. Delaying next sync.");
  707. delay = MINIMUM_BACKOFF_INTERVAL;
  708. } else {
  709. this._log.debug("New mid-sync 401 failure. Making a note.");
  710. Svc.Prefs.set("lastSyncReassigned", true);
  711. }
  712. this._log.info("Attempting to schedule another sync.");
  713. SyncScheduler.scheduleNextSync(delay);
  714. break;
  715. case 500:
  716. case 502:
  717. case 503:
  718. case 504:
  719. Status.enforceBackoff = true;
  720. if (resp.status == 503 && resp.headers["retry-after"]) {
  721. if (Weave.Service.isLoggedIn) {
  722. Status.sync = SERVER_MAINTENANCE;
  723. } else {
  724. Status.login = SERVER_MAINTENANCE;
  725. }
  726. Svc.Obs.notify("weave:service:backoff:interval",
  727. parseInt(resp.headers["retry-after"], 10));
  728. }
  729. break;
  730. }
  731. switch (resp.result) {
  732. case Cr.NS_ERROR_UNKNOWN_HOST:
  733. case Cr.NS_ERROR_CONNECTION_REFUSED:
  734. case Cr.NS_ERROR_NET_TIMEOUT:
  735. case Cr.NS_ERROR_NET_RESET:
  736. case Cr.NS_ERROR_NET_INTERRUPT:
  737. case Cr.NS_ERROR_PROXY_CONNECTION_REFUSED:
  738. // The constant says it's about login, but in fact it just
  739. // indicates general network error.
  740. if (Weave.Service.isLoggedIn) {
  741. Status.sync = LOGIN_FAILED_NETWORK_ERROR;
  742. } else {
  743. Status.login = LOGIN_FAILED_NETWORK_ERROR;
  744. }
  745. break;
  746. }
  747. },
  748. };
  749. /**
  750. * Send credentials over an active J-PAKE channel.
  751. *
  752. * This object is designed to take over as the JPAKEClient controller,
  753. * presumably replacing one that is UI-based which would either cause
  754. * DOM objects to leak or the JPAKEClient to be GC'ed when the DOM
  755. * context disappears. This object stays alive for the duration of the
  756. * transfer by being strong-ref'ed as an nsIObserver.
  757. *
  758. * Credentials are sent after the first sync has been completed
  759. * (successfully or not.)
  760. *
  761. * Usage:
  762. *
  763. * jpakeclient.controller = new SendCredentialsController(jpakeclient);
  764. *
  765. */
  766. function SendCredentialsController(jpakeclient) {
  767. this._log = Log4Moz.repository.getLogger("Sync.SendCredentialsController");
  768. this._log.level = Log4Moz.Level[Svc.Prefs.get("log.logger.service.main")];
  769. this._log.trace("Loading.");
  770. this.jpakeclient = jpakeclient;
  771. // Register ourselves as observers the first Sync finishing (either
  772. // successfully or unsuccessfully, we don't care) or for removing
  773. // this device's sync configuration, in case that happens while we
  774. // haven't finished the first sync yet.
  775. Services.obs.addObserver(this, "weave:service:sync:finish", false);
  776. Services.obs.addObserver(this, "weave:service:sync:error", false);
  777. Services.obs.addObserver(this, "weave:service:start-over", false);
  778. }
  779. SendCredentialsController.prototype = {
  780. unload: function unload() {
  781. this._log.trace("Unloading.");
  782. try {
  783. Services.obs.removeObserver(this, "weave:service:sync:finish");
  784. Services.obs.removeObserver(this, "weave:service:sync:error");
  785. Services.obs.removeObserver(this, "weave:service:start-over");
  786. } catch (ex) {
  787. // Ignore.
  788. }
  789. },
  790. observe: function observe(subject, topic, data) {
  791. switch (topic) {
  792. case "weave:service:sync:finish":
  793. case "weave:service:sync:error":
  794. Utils.nextTick(this.sendCredentials, this);
  795. break;
  796. case "weave:service:start-over":
  797. // This will call onAbort which will call unload().
  798. this.jpakeclient.abort();
  799. break;
  800. }
  801. },
  802. sendCredentials: function sendCredentials() {
  803. this._log.trace("Sending credentials.");
  804. let credentials = {account: Weave.Service.account,
  805. password: Weave.Service.password,
  806. synckey: Weave.Service.passphrase,
  807. serverURL: Weave.Service.serverURL};
  808. this.jpakeclient.sendAndComplete(credentials);
  809. },
  810. // JPAKEClient controller API
  811. onComplete: function onComplete() {
  812. this._log.debug("Exchange was completed successfully!");
  813. this.unload();
  814. // Schedule a Sync for soonish to fetch the data uploaded by the
  815. // device with which we just paired.
  816. SyncScheduler.scheduleNextSync(SyncScheduler.activeInterval);
  817. },
  818. onAbort: function onAbort(error) {
  819. // It doesn't really matter why we aborted, but the channel is closed
  820. // for sure, so we won't be able to do anything with it.
  821. this._log.debug("Exchange was aborted with error: " + error);
  822. this.unload();
  823. },
  824. // Irrelevant methods for this controller:
  825. displayPIN: function displayPIN() {},
  826. onPairingStart: function onPairingStart() {},
  827. onPaired: function onPaired() {}
  828. };