Bug 761045: Upload locally installed apps on first run; r=gps

This commit is contained in:
Anant Narayanan 2012-07-13 19:52:30 -07:00
Родитель 3b2bac6d14
Коммит d9769d1d70
7 изменённых файлов: 196 добавлений и 33 удалений

Просмотреть файл

@ -609,7 +609,7 @@ let DOMApplicationRegistry = {
getAppById: function(aId) {
if (!this.webapps[aId])
return null;
let app = this._cloneAppObject(this.webapps[aId]);
return app;
},

Просмотреть файл

@ -138,6 +138,11 @@ BrowserIDService.prototype = {
_getEmails: function _getEmails(cb, options, sandbox) {
let self = this;
if (!sandbox) {
cb(new Error("Sandbox not created"), null);
return;
}
function callback(res) {
let emails = {};
try {
@ -382,8 +387,18 @@ BrowserIDService.prototype = {
*/
function Sandbox(cb, uri) {
this._uri = uri;
this._createFrame();
this._createSandbox(cb, uri);
// Put in a try/catch block because Services.wm.getMostRecentWindow, called in
// _createFrame will be null in XPCShell.
try {
this._createFrame();
this._createSandbox(cb, uri);
} catch(e) {
this._log = Log4Moz.repository.getLogger("Service.AITC.BrowserID.Sandbox");
this._log.level = Log4Moz.Level[PREFS.get("log")];
this._log.error("Could not create Sandbox " + e);
cb(null);
}
}
Sandbox.prototype = {
/**

Просмотреть файл

@ -191,7 +191,7 @@ AitcClient.prototype = {
try {
cb(null, apps);
// Don't update lastModified until we know cb succeeded.
this._appsLastModified = parseInt(req.response.headers["X-Timestamp"], 10);
this._appsLastModified = parseInt(req.response.headers["x-timestamp"], 10);
this._state.set("lastModified", "" + this._appsLastModified);
} catch (e) {
this._log.error("Exception in getApps callback " + e);
@ -376,7 +376,7 @@ AitcClient.prototype = {
// Set values from X-Backoff and Retry-After headers, if present.
_setBackoff: function _setBackoff(req) {
let backoff = 0;
let successfulStatusCodes = [200, 201, 204, 304, 401];
let statusCodesWithoutBackoff = [200, 201, 204, 304, 401];
let val;
if (req.response.headers["Retry-After"]) {

Просмотреть файл

@ -28,14 +28,27 @@ function Aitc() {
Preferences.get("services.aitc.dashboard.url")
).prePath;
this._manager = new AitcManager(this._init.bind(this));
let self = this;
this._manager = new AitcManager(function managerDone() {
CommonUtils.nextTick(self._init, self);
});
}
Aitc.prototype = {
// The goal of the init function is to be ready to activate the AITC
// client whenever the user is looking at the dashboard.
_init: function init() {
// client whenever the user is looking at the dashboard. It also calls
// the initialSchedule function on the manager.
_init: function _init() {
let self = this;
// Do an initial upload.
this._manager.initialSchedule(function queueDone(num) {
if (num == -1) {
self._log.debug("No initial upload was required");
return;
}
self._log.debug(num + " initial apps queued successfully");
});
// This is called iff the user is currently looking the dashboard.
function dashboardLoaded(browser) {
let win = browser.contentWindow;

Просмотреть файл

@ -42,7 +42,7 @@ function AitcManager(cb, premadeClient, premadeToken) {
this._tokenDuration = INITIAL_TOKEN_DURATION;
this._premadeToken = premadeToken || null;
this._invalidTokenFlag = false;
this._lastEmail = null;
this._dashboardWindow = null;
@ -67,13 +67,9 @@ function AitcManager(cb, premadeClient, premadeToken) {
cb(null, true);
return;
}
// Schedule them, but only if we can get a silent assertion.
self._makeClient(function(err, client) {
if (!err && client) {
self._client = client;
self._processQueue();
}
}, false);
// Caller will invoke initialSchedule which will process any items in the
// queue, if present.
});
}
AitcManager.prototype = {
@ -169,6 +165,71 @@ AitcManager.prototype = {
this._dashboardWindow = null;
},
/**
* Initial schedule for the manager. It is the responsibility of the
* caller who created this object to call this function if it wants to
* do an initial sync (i.e. upload local apps on a device that has never
* communicated with AITC before).
*
* The callback will be invoked with the number of local apps that were
* queued to be uploaded, or -1 if this client has already synced and a
* local upload is not required.
*
* Try to schedule PUTs but only if we can get a silent assertion, and if
* the queue in non-empty, or we've never done a GET (first run).
*/
initialSchedule: function initialSchedule(cb) {
let self = this;
function startProcessQueue(num) {
self._makeClient(function(err, client) {
if (!err && client) {
self._client = client;
self._processQueue();
return;
}
});
cb(num);
}
// If we've already done a sync with AITC, it means we've already done
// an initial upload. Resume processing the queue, if there are items in it.
if (Preferences.get("services.aitc.client.lastModified", "0") != "0") {
if (this._pending.length) {
startProcessQueue(-1);
} else {
cb(-1);
}
return;
}
DOMApplicationRegistry.getAllWithoutManifests(function gotAllApps(apps) {
let done = 0;
let appids = Object.keys(apps);
let total = appids.length;
self._log.info("First run, queuing all local apps: " + total + " found");
function appQueued(err) {
if (err) {
self._log.error("Error queuing app " + apps[appids[done]].origin);
}
if (done == total) {
self._log.info("Finished queuing all initial local apps");
startProcessQueue(total);
return;
}
let app = apps[appids[done]];
let obj = {type: "install", app: app, retries: 0, lastTime: 0};
done += 1;
self._pending.enqueue(obj, appQueued);
}
appQueued();
});
},
/**
* Poll the AITC server for any changes and process them. It is safe to call
* this function multiple times. Last caller wins. The function will
@ -197,7 +258,7 @@ AitcManager.prototype = {
this._processQueue();
return;
}
// Do one GET soon, but only if user is active.
let getFreq;
if (this._state == this._ACTIVE) {
@ -481,14 +542,14 @@ AitcManager.prototype = {
cb(err, null);
return;
}
// Prompt user to login.
self._makeClient(function(err, client) {
if (err) {
cb(err, null);
return;
}
// makeClient sets an updated token.
self._client = client;
self._invalidTokenFlag = false;
@ -541,7 +602,7 @@ AitcManager.prototype = {
return;
}
let msg = err.name + " in _getToken: " + err.error;
let msg = "Error in _getToken: " + err;
this._log.error(msg);
cb(msg, null);
},

Просмотреть файл

@ -3,10 +3,13 @@
"use strict";
Cu.import("resource://services-common/utils.js");
Cu.import("resource://services-common/preferences.js");
Cu.import("resource://gre/modules/Webapps.jsm");
Cu.import("resource://services-aitc/client.js");
Cu.import("resource://services-aitc/manager.js");
Cu.import("resource://services-common/utils.js");
Cu.import("resource://services-common/preferences.js");
Cu.import("resource://testing-common/services-common/aitcserver.js");
const PREFS = new Preferences("services.aitc.");
@ -77,6 +80,70 @@ function do_check_lt(a, b) {
do_check_true(a < b);
}
add_test(function test_manager_localapps() {
// Install two fake apps into the DOM registry.
let fakeApp1 = get_mock_app();
fakeApp1.manifest = {
name: "Appasaurus 1",
description: "One of the best fake apps ever",
launch_path: "/",
fullscreen: true,
required_features: ["webgl"]
};
let fakeApp2 = get_mock_app();
fakeApp2.manifest = {
name: "Appasaurus 2",
description: "The other best fake app ever",
launch_path: "/",
fullscreen: true,
required_features: ["geolocation"]
};
DOMApplicationRegistry.confirmInstall({app: fakeApp1});
DOMApplicationRegistry.confirmInstall({app: fakeApp2});
// Create an instance of the manager and check if it put the app in the queue.
// We put doInitialUpload in nextTick, because maanger will not be defined
// in the callback. This pattern is used everywhere, AitcManager is created.
let manager = new AitcManager(function() {
CommonUtils.nextTick(doInitialUpload);
});
function doInitialUpload() {
manager.initialSchedule(function(num) {
// 2 apps should have been queued.
do_check_eq(num, 2);
do_check_eq(manager._pending.length, 2);
let entry = manager._pending.peek();
do_check_eq(entry.type, "install");
do_check_eq(entry.app.origin, fakeApp1.origin);
// Remove one app from queue.
manager._pending.dequeue(run_next_test);
});
}
});
add_test(function test_manager_alreadysynced() {
// The manager should ignore any local apps if we've already synced before.
Preferences.set("services.aitc.client.lastModified", "" + Date.now());
let manager = new AitcManager(function() {
CommonUtils.nextTick(doCheck);
});
function doCheck() {
manager.initialSchedule(function(num) {
do_check_eq(num, -1);
do_check_eq(manager._pending.length, 1);
// Clear queue for next test.
manager._pending.dequeue(run_next_test);
});
}
});
add_test(function test_401_responses() {
PREFS.set("client.backoff", "50");
PREFS.set("manager.putFreq", 50);
@ -89,19 +156,18 @@ add_test(function test_401_responses() {
uid: "uid",
duration: "5000"
};
let server = get_server_with_user(username);
let client = get_client_for_server(username, server);
server.mockStatus = {
code: 401,
method: "Unauthorized"
}
let client = get_client_for_server(username, server);
let manager = new AitcManager(function () {}, client, premadeToken);
// Assume first token is not out dated.
manager._lastTokenTime = Date.now();
};
let mockRequestCount = 0;
let clientFirstToken = null;
server.onRequest = function mockstatus () {
server.onRequest = function mockstatus() {
mockRequestCount++;
switch (mockRequestCount) {
case 1:
@ -121,7 +187,15 @@ add_test(function test_401_responses() {
}
}
manager.appEvent("install", get_mock_app());
let manager = new AitcManager(function() {
CommonUtils.nextTick(gotManager);
}, client, premadeToken);
function gotManager() {
// Assume first token is not outdated.
manager._lastTokenTime = Date.now();
manager.appEvent("install", get_mock_app());
}
});
add_test(function test_client_exponential_backoff() {

Просмотреть файл

@ -3,7 +3,7 @@ head = ../../../common/tests/unit/head_global.js ../../../common/tests/unit/head
tail =
[test_load_modules.js]
[test_storage_queue.js]
[test_storage_registry.js]
[test_aitc_client.js]
[test_aitc_manager.js]
[test_aitc_manager.js]
[test_storage_queue.js]
[test_storage_registry.js]