You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@cordova.apache.org by ag...@apache.org on 2014/05/20 21:52:51 UTC
[1/5] git commit: Stop setting a UrlRemap reset URL (not used anymore)
Repository: cordova-app-harness
Updated Branches:
refs/heads/master 1fc7bf4a5 -> 42f7e5717
Stop setting a UrlRemap reset URL (not used anymore)
Project: http://git-wip-us.apache.org/repos/asf/cordova-app-harness/repo
Commit: http://git-wip-us.apache.org/repos/asf/cordova-app-harness/commit/f2052142
Tree: http://git-wip-us.apache.org/repos/asf/cordova-app-harness/tree/f2052142
Diff: http://git-wip-us.apache.org/repos/asf/cordova-app-harness/diff/f2052142
Branch: refs/heads/master
Commit: f20521425493e9cf09628a1f4d4e8987f32e10bc
Parents: de9b285
Author: Andrew Grieve <ag...@chromium.org>
Authored: Thu May 15 21:29:27 2014 -0400
Committer: Andrew Grieve <ag...@chromium.org>
Committed: Tue May 20 14:20:55 2014 -0400
----------------------------------------------------------------------
www/cdvah/js/Installer.js | 2 --
1 file changed, 2 deletions(-)
----------------------------------------------------------------------
http://git-wip-us.apache.org/repos/asf/cordova-app-harness/blob/f2052142/www/cdvah/js/Installer.js
----------------------------------------------------------------------
diff --git a/www/cdvah/js/Installer.js b/www/cdvah/js/Installer.js
index 5aea219..40f779c 100644
--- a/www/cdvah/js/Installer.js
+++ b/www/cdvah/js/Installer.js
@@ -130,8 +130,6 @@
startLocation = startLocation.replace(harnessDir, nativeInstallUrl + '/www');
}
- // Allow navigations back to the menu.
- UrlRemap.setResetUrl('^' + harnessUrl);
// Override cordova.js, and www/plugins to point at bundled plugins.
UrlRemap.aliasUri('^(?!app-harness://).*/www/cordova\\.js.*', '.+', 'app-harness:///cordova.js', false /* redirect */, true /* allowFurtherRemapping */);
UrlRemap.aliasUri('^(?!app-harness://).*/www/plugins/.*', '^.*?/www/plugins/' , 'app-harness:///plugins/', false /* redirect */, true /* allowFurtherRemapping */);
[5/5] git commit: Add harness-push command-line tool for pushing apps.
Posted by ag...@apache.org.
Add harness-push command-line tool for pushing apps.
Project: http://git-wip-us.apache.org/repos/asf/cordova-app-harness/repo
Commit: http://git-wip-us.apache.org/repos/asf/cordova-app-harness/commit/42f7e571
Tree: http://git-wip-us.apache.org/repos/asf/cordova-app-harness/tree/42f7e571
Diff: http://git-wip-us.apache.org/repos/asf/cordova-app-harness/diff/42f7e571
Branch: refs/heads/master
Commit: 42f7e571745d7a56b962fa0fc19c05e71934201b
Parents: da594df
Author: Andrew Grieve <ag...@chromium.org>
Authored: Tue May 20 15:51:58 2014 -0400
Committer: Andrew Grieve <ag...@chromium.org>
Committed: Tue May 20 15:51:58 2014 -0400
----------------------------------------------------------------------
.gitignore | 3 +-
harness-push/harness-push.js | 346 ++++++++++++++++++++++++++++++++++++++
harness-push/package.json | 32 ++++
3 files changed, 380 insertions(+), 1 deletion(-)
----------------------------------------------------------------------
http://git-wip-us.apache.org/repos/asf/cordova-app-harness/blob/42f7e571/.gitignore
----------------------------------------------------------------------
diff --git a/.gitignore b/.gitignore
index 496ee2c..ea23fd6 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1 +1,2 @@
-.DS_Store
\ No newline at end of file
+.DS_Store
+harness-push/node_modules
http://git-wip-us.apache.org/repos/asf/cordova-app-harness/blob/42f7e571/harness-push/harness-push.js
----------------------------------------------------------------------
diff --git a/harness-push/harness-push.js b/harness-push/harness-push.js
new file mode 100755
index 0000000..d505ea0
--- /dev/null
+++ b/harness-push/harness-push.js
@@ -0,0 +1,346 @@
+#!/usr/bin/env node
+/**
+ Licensed to the Apache Software Foundation (ASF) under one
+ or more contributor license agreements. See the NOTICE file
+ distributed with this work for additional information
+ regarding copyright ownership. The ASF licenses this file
+ to you under the Apache License, Version 2.0 (the
+ "License"); you may not use this file except in compliance
+ with the License. You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing,
+ software distributed under the License is distributed on an
+ "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ KIND, either express or implied. See the License for the
+ specific language governing permissions and limitations
+ under the License.
+ */
+
+var fs = require('fs'),
+ path = require('path'),
+ crypto = require('crypto'),
+ url = require('url'),
+ Q = require('q'),
+ request = require('request'),
+ nopt = require('nopt'),
+ shelljs = require('shelljs');
+
+// Takes a Node-style callback: function(err).
+exports.push = function(target, dir, pretend) {
+ var appId;
+ var appType = 'cordova';
+ var configXmlPath = path.join(dir, 'config.xml');
+ dir = path.join(dir, 'www');
+ if (!fs.existsSync(configXmlPath)) {
+ configXmlPath = path.join(dir, 'config.xml');
+ }
+ if (!fs.existsSync(configXmlPath)) {
+ throw new Error('Not a Cordova project.');
+ }
+ var configData = fs.readFileSync(configXmlPath, { encoding: 'utf-8' });
+ var m = /\bid="(.*?)"/.exec(configData);
+ appId = m && m[1];
+
+ // ToDo - Add ability to bootstrap with a zip file.
+ return doFileSync(target, appId, appType, configXmlPath, dir, pretend);
+}
+
+function calculateMd5(fileName) {
+ var BUF_LENGTH = 64*1024,
+ buf = new Buffer(BUF_LENGTH),
+ bytesRead = BUF_LENGTH,
+ pos = 0,
+ fdr = fs.openSync(fileName, 'r');
+
+ try {
+ var md5sum = crypto.createHash('md5');
+ while (bytesRead === BUF_LENGTH) {
+ bytesRead = fs.readSync(fdr, buf, 0, BUF_LENGTH, pos);
+ pos += bytesRead;
+ md5sum.update(buf.slice(0, bytesRead));
+ }
+ } finally {
+ fs.closeSync(fdr);
+ }
+ return md5sum.digest('hex');
+}
+
+function buildAssetManifest(configXmlPath, dir) {
+ var fileList = shelljs.find(dir).filter(function(a) {
+ return !fs.statSync(a).isDirectory();
+ });
+
+ var ret = Object.create(null);
+ for (var i = 0; i < fileList.length; ++i) {
+ // TODO: convert windows slash to unix slash here.
+ var appPath = 'www' + fileList[i].slice(dir.length);
+ ret[appPath] = {
+ path: appPath,
+ realPath: fileList[i],
+ etag: calculateMd5(fileList[i]),
+ };
+ }
+ if (configXmlPath && path.dirname(configXmlPath) != dir) {
+ ret['config.xml'] = {
+ path: 'config.xml',
+ realPath: configXmlPath,
+ etag: calculateMd5(configXmlPath)
+ };
+ }
+ return ret;
+}
+
+function buildDeleteList(existingAssetManifest, newAssetManifest) {
+ var toDelete = [];
+ for (var k in existingAssetManifest) {
+ // Don't delete top-level files ever.
+ if (k.slice(0, 4) != 'www/') {
+ continue;
+ }
+ if (!newAssetManifest[k]) {
+ toDelete.push(k);
+ }
+ }
+ return toDelete;
+}
+
+function buildPushList(existingAssetManifest, newAssetManifest) {
+ var ret = [];
+ for (var k in newAssetManifest) {
+ var entry = newAssetManifest[k];
+ if (entry.etag != existingAssetManifest[k]) {
+ if (entry.path == 'config.xml' || entry.path == 'www/config.xml') {
+ ret.unshift(entry);
+ } else {
+ ret.push(entry);
+ }
+ }
+ }
+ return ret;
+}
+
+function calculatePushBytes(pushList) {
+ var ret = 0;
+ for (var i = 0; i < pushList.length; ++i) {
+ ret += fs.statSync(pushList[i].realPath).size;
+ }
+ return ret;
+}
+
+function doFileSync(target, appId, appType, configXmlPath, dir, pretend) {
+ return exports.assetmanifest(target, appId)
+ .then(function(result) {
+ var existingAssetManifest = result.body['assetManifest'] || {};
+ var newAssetManifest = buildAssetManifest(configXmlPath, dir);
+ var deleteList = buildDeleteList(existingAssetManifest, newAssetManifest);
+ var pushList = buildPushList(existingAssetManifest, newAssetManifest);
+ var totalPushBytes = calculatePushBytes(pushList);
+ if (pretend) {
+ console.log('AppId=' + appId);
+ console.log('Would delete: ' + JSON.stringify(deleteList));
+ console.log('Would upload: ' + JSON.stringify(pushList));
+ console.log('Upload bytes: ' + totalPushBytes);
+ } else if (deleteList.length === 0 && pushList.length === 0) {
+ console.log('Application already up-to-date.');
+ } else {
+ return doRequest('POST', target, 'prepupdate', { appId: appId, appType: appType, json: {'transferSize': totalPushBytes}})
+ .then(function() {
+ if (deleteList.length > 0) {
+ return doRequest('POST', target, 'deletefiles', { appId: appId, appType: appType, json: {'paths': deleteList}})
+ }
+ })
+ .then(function pushNextFile() {
+ if (pushList.length === 0) {
+ console.log('Push complete.');
+ return;
+ }
+ var curPushEntry = pushList.shift();
+ return doRequest('PUT', target, '/putfile', {
+ appId: appId,
+ appType: appType,
+ body: fs.readFileSync(curPushEntry.realPath),
+ query: {
+ path: curPushEntry.path,
+ etag: curPushEntry.etag
+ }
+ }).then(pushNextFile);
+ });
+ }
+ });
+}
+
+function doRequest(method, target, action, options) {
+ var ret = Q.defer();
+ var targetParts = target.split(':');
+ var host = targetParts[0];
+ var port = +(targetParts[1] || 2424);
+ options = options || {};
+
+ var queryParams = {};
+ if (options.query) {
+ Object.keys(options.query).forEach(function(k) {
+ queryParams[k] = options.query[k];
+ });
+ }
+ if (options.appId) {
+ queryParams['appId'] = options.appId;
+ }
+ if (options.appType) {
+ queryParams['appType'] = options.appType;
+ }
+
+ // Send the HTTP request. crxContents is a Node Buffer, which is the payload.
+ // Prepare the form data for upload.
+ var uri = url.format({
+ protocol: 'http',
+ hostname: host,
+ port: port,
+ pathname: action,
+ query: queryParams
+ });
+
+ process.stdout.write(method + ' ' + uri);
+
+ var headers = {};
+ if (options.json) {
+ options.body = JSON.stringify(options.json);
+ headers['Content-Type'] = 'application/json';
+ }
+
+ function statusCheck(err, res, body) {
+ if (err) {
+ err.statusCode = res && res.statusCode;
+ } else if (res) {
+ process.stdout.write(' ==> ' + (res && res.statusCode) + '\n');
+ if (res.statusCode != 200) {
+ err = new Error('Server returned status code: ' + res.statusCode);
+ } else if (options.expectJson) {
+ try {
+ body = JSON.parse(body);
+ } catch (e) {
+ err = new Error('Invalid JSON: ' + body.slice(500));
+ }
+ }
+ }
+ if (err) {
+ ret.reject(err);
+ } else {
+ ret.resolve({res:res, body:body});
+ }
+ }
+ var req = request({
+ uri: uri,
+ headers: headers,
+ method: method,
+ body: options.body
+ }, statusCheck);
+
+ if (options.form) {
+ var f = options.form;
+ req.form().append(f.key, f.formBody, { filename: f.filename, contentType: f.contentType });
+ }
+ return ret.promise;
+};
+
+exports.info = function(target) {
+ return doRequest('GET', target, '/info', { expectJson: true });
+};
+
+exports.assetmanifest = function(target, appId) {
+ return doRequest('GET', target, '/assetmanifest', {expectJson: true, appId: appId});
+};
+
+exports.menu = function(target) {
+ return doRequest('POST', target, '/menu');
+};
+
+exports.eval = function(target, someJs) {
+ return doRequest('POST', target, '/exec', { query: {code: someJs} });
+};
+
+exports.launch = function(target, appId) {
+ return doRequest('POST', target, '/launch', { appId: appId});
+};
+
+exports.deleteAllApps = function(target) {
+ return doRequest('POST', target, '/deleteapp', { query: {'all': 1} });
+};
+
+exports.deleteApp = function(target, appId) {
+ return doRequest('POST', target, '/deleteApp', { appId: appId});
+};
+
+function parseArgs(argv) {
+ var opts = {
+ 'help': Boolean,
+ 'target': String
+ };
+ var ret = nopt(opts, null, argv);
+ if (!ret.target) {
+ ret.target = 'localhost:2424';
+ }
+ return ret;
+}
+
+function usage() {
+ console.log('Usage: harness-push push path/to/chrome_app --target=IP_ADDRESS:PORT');
+ console.log('Usage: harness-push menu');
+ console.log('Usage: harness-push eval "alert(1)"');
+ console.log('Usage: harness-push info');
+ console.log('Usage: harness-push launch [appId]');
+ console.log();
+ console.log('--target defaults to localhost:2424');
+ console.log('To deploy to Android over USB, use: adb forward tcp:2424 tcp:2424');
+ process.exit(1);
+}
+
+function main() {
+ var args = parseArgs(process.argv);
+
+ function onFailure(err) {
+ console.error(err);
+ }
+ function onSuccess(result) {
+ if (typeof result.body == 'object') {
+ console.log(JSON.stringify(result.body, null, 4));
+ } else if (result.body) {
+ console.log(result.body);
+ }
+ }
+
+ var cmd = args.argv.remain[0];
+ if (cmd == 'push') {
+ if (!args.argv.remain[1]) {
+ usage();
+ }
+ exports.push(args.target, args.argv.remain[1], args.pretend).then(onSuccess, onFailure);
+ } else if (cmd == 'deleteall') {
+ exports.deleteAllApps(args.target);
+ } else if (cmd == 'delete') {
+ if (!args.argv.remain[1]) {
+ usage();
+ }
+ exports.deleteApp(args.target).then(onSuccess, onFailure);
+ } else if (cmd == 'menu') {
+ exports.menu(args.target).then(onSuccess, onFailure);
+ } else if (cmd == 'eval') {
+ if (!args.argv.remain[1]) {
+ usage();
+ }
+ exports.eval(args.target, args.argv.remain[1]).then(onSuccess, onFailure);
+ } else if (cmd == 'assetmanifest') {
+ exports.assetmanifest(args.target, args.appid).then(onSuccess, onFailure);
+ } else if (cmd == 'info') {
+ exports.info(args.target).then(onSuccess, onFailure);
+ } else if (cmd == 'launch') {
+ exports.launch(args.target, args.argv.remain[1]).then(onSuccess, onFailure);
+ } else {
+ usage();
+ }
+}
+
+if (require.main === module) {
+ main();
+}
http://git-wip-us.apache.org/repos/asf/cordova-app-harness/blob/42f7e571/harness-push/package.json
----------------------------------------------------------------------
diff --git a/harness-push/package.json b/harness-push/package.json
new file mode 100644
index 0000000..4357bd4
--- /dev/null
+++ b/harness-push/package.json
@@ -0,0 +1,32 @@
+{
+ "name": "harness-push",
+ "version": "0.0.1",
+ "description": "Command-line utility for communicating with Cordova App Harness.",
+ "main": "push.js",
+ "scripts": {
+ "test": "echo \"Error: no test specified\" && exit 1"
+ },
+ "author": "",
+ "license": "Apache 2.0",
+ "repository": {
+ "type": "git",
+ "url": "https://git-wip-us.apache.org/repos/asf/cordova-app-harness.git"
+ },
+ "bugs": {
+ "url": "https://issues.apache.org/jira/browse/CB",
+ "email": "dev@cordova.apache.org"
+ },
+ "keywords": [
+ "cordova",
+ "cordova-app-harness"
+ ],
+ "bin": {
+ "harness-push": "./harness-push.js"
+ },
+ "dependencies": {
+ "request": "~2.34",
+ "nopt": "~2.2",
+ "shelljs": "0.1.x",
+ "q": "~0.9"
+ }
+}
[2/5] git commit: Delete Add / Edit screens (Allow add/update from
server only).
Posted by ag...@apache.org.
Delete Add / Edit screens (Allow add/update from server only).
Project: http://git-wip-us.apache.org/repos/asf/cordova-app-harness/repo
Commit: http://git-wip-us.apache.org/repos/asf/cordova-app-harness/commit/de9b2855
Tree: http://git-wip-us.apache.org/repos/asf/cordova-app-harness/tree/de9b2855
Diff: http://git-wip-us.apache.org/repos/asf/cordova-app-harness/diff/de9b2855
Branch: refs/heads/master
Commit: de9b2855ed01de82ea9b973486bda8eb73422179
Parents: 1fc7bf4
Author: Andrew Grieve <ag...@chromium.org>
Authored: Thu May 15 16:06:32 2014 -0400
Committer: Andrew Grieve <ag...@chromium.org>
Committed: Tue May 20 14:20:55 2014 -0400
----------------------------------------------------------------------
www/cdvah/css/style.css | 9 +++
www/cdvah/harnessmenu.html | 1 -
www/cdvah/js/AddCtrl.js | 118 -------------------------------------
www/cdvah/js/HarnessServer.js | 2 +-
www/cdvah/js/ListCtrl.js | 15 -----
www/cdvah/js/app.js | 8 ---
www/cdvah/views/add.html | 55 -----------------
www/cdvah/views/details.html | 8 +--
www/cdvah/views/list.html | 23 ++++----
www/cdvahcm/contextMenu.html | 3 -
10 files changed, 24 insertions(+), 218 deletions(-)
----------------------------------------------------------------------
http://git-wip-us.apache.org/repos/asf/cordova-app-harness/blob/de9b2855/www/cdvah/css/style.css
----------------------------------------------------------------------
diff --git a/www/cdvah/css/style.css b/www/cdvah/css/style.css
index b26fecf..daab765 100644
--- a/www/cdvah/css/style.css
+++ b/www/cdvah/css/style.css
@@ -16,6 +16,15 @@
* specific language governing permissions and limitations
* under the License.
*/
+.listen {
+ padding: 2px 20px;
+}
+
+.plugin-list-item {
+ padding-top: 0.2rem;
+ padding-bottom: 0.2rem;
+}
+
.buttons {
margin: 15px;
}
http://git-wip-us.apache.org/repos/asf/cordova-app-harness/blob/de9b2855/www/cdvah/harnessmenu.html
----------------------------------------------------------------------
diff --git a/www/cdvah/harnessmenu.html b/www/cdvah/harnessmenu.html
index 883f556..9a562fa 100644
--- a/www/cdvah/harnessmenu.html
+++ b/www/cdvah/harnessmenu.html
@@ -33,7 +33,6 @@
<script type="text/javascript" src="js/PluginMetadata.js"></script>
<script type="text/javascript" src="js/UrlRemap.js"></script>
<script type="text/javascript" src="js/ListCtrl.js"></script>
- <script type="text/javascript" src="js/AddCtrl.js"></script>
<script type="text/javascript" src="js/DetailsCtrl.js"></script>
<script type="text/javascript" src="js/Notify.js"></script>
<script type="text/javascript" src="js/HttpServer.js"></script>
http://git-wip-us.apache.org/repos/asf/cordova-app-harness/blob/de9b2855/www/cdvah/js/AddCtrl.js
----------------------------------------------------------------------
diff --git a/www/cdvah/js/AddCtrl.js b/www/cdvah/js/AddCtrl.js
deleted file mode 100644
index 021fb58..0000000
--- a/www/cdvah/js/AddCtrl.js
+++ /dev/null
@@ -1,118 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements. See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership. The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License. You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied. See the License for the
- * specific language governing permissions and limitations
- * under the License.
-*/
-(function(){
- 'use strict';
-
- /* global myApp */
- myApp.controller('AddCtrl', ['$q', 'notifier', '$location', '$rootScope', '$scope', '$window', '$routeParams', 'AppsService', 'urlCleanup', function($q, notifier, $location, $rootScope, $scope, $window, $routeParams, AppsService, urlCleanup) {
- $scope.editing = $routeParams.appId;
- var editingApp;
-
- $rootScope.appTitle = $scope.editing ? 'Edit App' : 'Add App';
-
- if ($scope.editing) {
- AppsService.getAppList().then(function(appList) {
- appList.forEach(function(app) {
- if (app.appId == $scope.editing) {
- editingApp = app;
- $scope.editingApp = app;
- $scope.appData = {
- appId: app.appId,
- appUrl: app.url,
- installerType: app.type
- };
- }
- });
- if (!$scope.appData) {
- notifier.error('Could not find app to edit');
- }
- });
- } else {
- $scope.appData = {
- appUrl: '',
- installerType: 'serve'
- };
- }
-
- $scope.selectTemplate = function() {
- $scope.appData.appUrl = $scope.appData.serveTemplateValue;
- };
-
- $scope.addApp = function() {
- if ($scope.editing) {
- // Update the app, write them out, and return to the list.
- // We deliberately disallow changing the type, since that wouldn't work at all.
- var oldUrl = editingApp.url;
- editingApp.appId = $scope.appData.appId;
- editingApp.url = urlCleanup($scope.appData.appUrl);
- var urlChanged = oldUrl != editingApp.url;
- var p = AppsService.editApp($scope.editing, editingApp).then(function() {
- notifier.success('App edited');
- $location.path('/');
- });
-
- if (urlChanged) {
- return p.then(function() {
- // If the URL changed, trigger an update.
- return AppsService.updateApp(editingApp);
- }).then(function() {
- notifier.success('Updated app due to URL change');
- }, function(err) {
- notifier.error(err);
- });
- }
- return p;
- }
- return AppsService.addApp($scope.appData.installerType, $scope.appData.appUrl)
- .then(function(handler) {
- notifier.success('App Added. Updating...');
- $location.path('/');
- return AppsService.updateApp(handler);
- })
- .then(function(){
- notifier.success('Updated successfully');
- }, function(error) {
- notifier.error(error);
- });
- };
-
- // True if the optional barcodescanner plugin is installed.
- $scope.qrEnabled = !!(cordova.plugins && cordova.plugins.barcodeScanner);
-
- // Scans a QR code, placing the URL into the currently selected of source and pattern.
- $scope.fetchQR = function() {
- var deferred = $q.defer();
- $window.cordova.plugins.barcodeScanner.scan(function(result) {
- if (!result || result.cancelled || !result.text) {
- notifier.error('No QR code received.');
- deferred.reject('No QR code received.');
- } else {
- $scope.appData.appUrl = result.text;
- notifier.success('QR code received');
- deferred.resolve();
- }
- },
- function(error) {
- notifier.error(error);
- deferred.reject(error);
- });
- return deferred.promise;
- };
- }]);
-})();
http://git-wip-us.apache.org/repos/asf/cordova-app-harness/blob/de9b2855/www/cdvah/js/HarnessServer.js
----------------------------------------------------------------------
diff --git a/www/cdvah/js/HarnessServer.js b/www/cdvah/js/HarnessServer.js
index 06d6922..26097be 100644
--- a/www/cdvah/js/HarnessServer.js
+++ b/www/cdvah/js/HarnessServer.js
@@ -153,7 +153,7 @@
function getListenAddress() {
if (listenAddress) {
- return listenAddress;
+ return $q.when(listenAddress);
}
var deferred = $q.defer();
chrome.socket.getNetworkList(function(interfaces) {
http://git-wip-us.apache.org/repos/asf/cordova-app-harness/blob/de9b2855/www/cdvah/js/ListCtrl.js
----------------------------------------------------------------------
diff --git a/www/cdvah/js/ListCtrl.js b/www/cdvah/js/ListCtrl.js
index 46eadb9..383762b 100644
--- a/www/cdvah/js/ListCtrl.js
+++ b/www/cdvah/js/ListCtrl.js
@@ -66,16 +66,6 @@
});
};
- $scope.updateApp = function(app, event) {
- event.stopPropagation();
- return AppsService.updateApp(app)
- .then(function(){
- notifier.success('Updated successfully');
- }, function(error) {
- notifier.error(error);
- });
- };
-
$scope.removeApp = function(app, event) {
event.stopPropagation();
var shouldUninstall = confirm('Are you sure you want to uninstall ' + app.appId + '?');
@@ -87,11 +77,6 @@
}
};
- $scope.editApp = function(app, event) {
- event.stopPropagation();
- $location.path('/edit/' + app.appId);
- };
-
$scope.showDetails = function(index) {
$location.path('/details/' + index);
};
http://git-wip-us.apache.org/repos/asf/cordova-app-harness/blob/de9b2855/www/cdvah/js/app.js
----------------------------------------------------------------------
diff --git a/www/cdvah/js/app.js b/www/cdvah/js/app.js
index 01b82ce..c570e82 100644
--- a/www/cdvah/js/app.js
+++ b/www/cdvah/js/app.js
@@ -25,14 +25,6 @@ myApp.config(['$routeProvider', function($routeProvider){
templateUrl: 'views/list.html',
controller: 'ListCtrl'
});
- $routeProvider.when('/add', {
- templateUrl: 'views/add.html',
- controller: 'AddCtrl'
- });
- $routeProvider.when('/edit/:appId', {
- templateUrl: 'views/add.html',
- controller: 'AddCtrl'
- });
$routeProvider.when('/details/:index', {
templateUrl: 'views/details.html',
controller: 'DetailsCtrl'
http://git-wip-us.apache.org/repos/asf/cordova-app-harness/blob/de9b2855/www/cdvah/views/add.html
----------------------------------------------------------------------
diff --git a/www/cdvah/views/add.html b/www/cdvah/views/add.html
deleted file mode 100644
index ec63bfe..0000000
--- a/www/cdvah/views/add.html
+++ /dev/null
@@ -1,55 +0,0 @@
-<!--
- Licensed to the Apache Software Foundation (ASF) under one
- or more contributor license agreements. See the NOTICE file
- distributed with this work for additional information
- regarding copyright ownership. The ASF licenses this file
- to you under the Apache License, Version 2.0 (the
- "License"); you may not use this file except in compliance
- with the License. You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing,
- software distributed under the License is distributed on an
- "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- KIND, either express or implied. See the License for the
- specific language governing permissions and limitations
- under the License.
--->
-<form name="addForm" ng-controller="AddCtrl">
- <div>
- <label ng-show="editing">App name<br />
- <div><input type="text" class="topcoat-text-input" ng-model="appData.appId" autocapitalize="on" /></div>
- </label>
- </div>
- <div>
- <label ng-show="!editing">How to retrieve the app:<br>
- <select ng-model="appData.installerType">
- <option value="serve">cordova serve</option>
- </select>
- </label>
- </div>
- <div>
- <label>Enter URL
- <div><input class="topcoat-text-input" style="width:100%;-webkit-box-sizing:border-box" type="text" ng-model="appData.appUrl" autocorrect="off" autocapitalize="off"></div>
- </label>
- </div>
- <div>
- <button class="topcoat-button" ng-click="fetchQR(appData, 'appUrl')" ng-show="qrEnabled">QR Code</button>
- </div>
- <div>
- <select ng-model="appData.serveTemplateValue" ng-change="selectTemplate()">
- <option value="">Template</option>
- <option value="goo.gl/">goo.gl</option>
- <option value="localhost:8000">localhost</option>
- <option value="KEY.t.proxylocal.com">ProxyLocal</option>
- <option value="KEY.localtunnel.com">LocalTunnel</option>
- <option value="KEY.ngrok.com">ngrok</option>
- </select>
- </div>
- <div class="buttons">
- <button ng-click="addApp()" class="topcoat-button--cta">{{ editing ? 'Edit' : 'Add' }}</button>
- <a href="#/"><button class="topcoat-button" ng-click="back()">Back</button></a>
- </div>
-</form>
-
http://git-wip-us.apache.org/repos/asf/cordova-app-harness/blob/de9b2855/www/cdvah/views/details.html
----------------------------------------------------------------------
diff --git a/www/cdvah/views/details.html b/www/cdvah/views/details.html
index e61a4cf..cc6945c 100644
--- a/www/cdvah/views/details.html
+++ b/www/cdvah/views/details.html
@@ -17,13 +17,11 @@
under the License.
-->
<h2>{{ app.appId }}</h2>
-<div>{{app.url}}</div>
-<div>Last updated: {{app.lastUpdated || 'never'}}</div>
<div class="topcoat-list__container">
<h3 class="topcoat-list__header">Missing Plugins: {{ app.plugins.missing.length }}</h3>
<ul class="topcoat-list">
- <li class="topcoat-list__item" ng-repeat="plugin in app.plugins.missing">
+ <li class="topcoat-list__item plugin-list-item" ng-repeat="plugin in app.plugins.missing">
<div><strong>{{ plugin.id }}</strong>: {{ plugin.version }}</div>
</li>
</ul>
@@ -32,7 +30,7 @@
<div class="topcoat-list__container">
<h3 class="topcoat-list__header">Older Plugins: {{ app.plugins.older.length }}</h3>
<ul class="topcoat-list">
- <li class="topcoat-list__item" ng-repeat="plugin in app.plugins.older">
+ <li class="topcoat-list__item plugin-list-item" ng-repeat="plugin in app.plugins.older">
<div><strong>{{ plugin.id }}</strong></div>
<div>App wants {{ plugin.versions.child }}</div>
<div>Harness provides {{ plugin.versions.harness }}</div>
@@ -43,7 +41,7 @@
<div class="topcoat-list__container">
<h3 class="topcoat-list__header">Newer Plugins: {{ app.plugins.newer.length }}</h3>
<ul class="topcoat-list">
- <li class="topcoat-list__item" ng-repeat="plugin in app.plugins.newer">
+ <li class="topcoat-list__item plugin-list-item" ng-repeat="plugin in app.plugins.newer">
<div><strong>{{ plugin.id }}</strong></div>
<div>App wants {{ plugin.versions.child }}</div>
<div>Harness provides {{ plugin.versions.harness }}</div>
http://git-wip-us.apache.org/repos/asf/cordova-app-harness/blob/de9b2855/www/cdvah/views/list.html
----------------------------------------------------------------------
diff --git a/www/cdvah/views/list.html b/www/cdvah/views/list.html
index 98492e8..42af081 100644
--- a/www/cdvah/views/list.html
+++ b/www/cdvah/views/list.html
@@ -16,29 +16,28 @@
specific language governing permissions and limitations
under the License.
-->
+<div class="listen">
+ Listening. IP = <strong>{{ipAddress}}</strong>
+</div>
<div class="topcoat-list__container">
<h3 class="topcoat-list__header">Installed Apps</h3>
<ul class="topcoat-list">
- <li class="topcoat-list__item" ng-repeat="app in appList" ng-click="showDetails($index)">
+ <li class="topcoat-list__item" ng-repeat="app in appList">
<div>{{app.appId}}</div>
- <div>{{app.url}}</div>
<div ng-show="app.updatingStatus === null">Last updated: {{app.lastUpdated || 'never'}}</div>
<div ng-show="app.updatingStatus !== null">Update in progress: {{app.updatingStatus}}%</div>
- <div ng-show="app.plugins.missing.length + app.plugins.newer.length + app.plugins.older.length > 0">Plugins: {{ app.plugins.missing.length }} missing, {{ app.plugins.newer.length + app.plugins.older.length }} differ</div>
+ <div ng-show="app.plugins.missing.length + app.plugins.newer.length + app.plugins.older.length > 0">
+ Plugins:
+ <span style="color:#c00" ng-show="app.plugins.missing.length">{{ app.plugins.missing.length }} missing</span><span ng-show="app.plugins.missing.length && (app.plugins.older.length || app.plugins.newer.length)">, </span>
+ <span style="color:#822" ng-show="app.plugins.older.length">{{ app.plugins.older.length }} outdated<span><span ng-show="app.plugins.older.length && app.plugins.newer.length">, </span>
+ <span ng-show="app.plugins.newer.length">{{ app.plugins.newer.length }} newer</span>
+ </div>
<div ng-show="app.plugins.missing.length + app.plugins.newer.length + app.plugins.older.length == 0">Plugins: OK</div>
<button ng-click="launchApp(app, $event)">Launch</button>
- <button ng-click="updateApp(app, $event)">Update</button>
<button ng-click="removeApp(app, $event)">Remove</button>
- <button ng-click="editApp(app, $event)">Edit</button>
+ <button ng-click="showDetails($index)">Details</button>
</li>
</ul>
</div>
-<br />
-<div class="buttons">
- <a href="#/add"><button class="topcoat-button--cta">Add app</button></a>
-</div>
-<div class="listen">
- Listening. IP = <strong>{{ipAddress}}</strong>
-</div>
http://git-wip-us.apache.org/repos/asf/cordova-app-harness/blob/de9b2855/www/cdvahcm/contextMenu.html
----------------------------------------------------------------------
diff --git a/www/cdvahcm/contextMenu.html b/www/cdvahcm/contextMenu.html
index 5311fc6..39848aa 100644
--- a/www/cdvahcm/contextMenu.html
+++ b/www/cdvahcm/contextMenu.html
@@ -65,9 +65,6 @@
<p>Tap Anywhere to Close</p>
<ul>
<li>
- <button class="fullwidthElement" onclick="sendEvent('updateApp')">Update</button>
- </li>
- <li>
<button class="fullwidthElement" onclick="sendEvent('restartApp')">Restart</button>
</li>
<li>
[3/5] git commit: Make ResourcesLoader be able to delete files (as
well as directories)
Posted by ag...@apache.org.
Make ResourcesLoader be able to delete files (as well as directories)
Project: http://git-wip-us.apache.org/repos/asf/cordova-app-harness/repo
Commit: http://git-wip-us.apache.org/repos/asf/cordova-app-harness/commit/074d388a
Tree: http://git-wip-us.apache.org/repos/asf/cordova-app-harness/tree/074d388a
Diff: http://git-wip-us.apache.org/repos/asf/cordova-app-harness/diff/074d388a
Branch: refs/heads/master
Commit: 074d388a54ba6942fdbd5b8e9315d52cd8eca7bb
Parents: f205214
Author: Andrew Grieve <ag...@chromium.org>
Authored: Tue May 20 14:42:13 2014 -0400
Committer: Andrew Grieve <ag...@chromium.org>
Committed: Tue May 20 14:42:13 2014 -0400
----------------------------------------------------------------------
www/cdvah/js/ResourcesLoader.js | 16 +++++++++++-----
1 file changed, 11 insertions(+), 5 deletions(-)
----------------------------------------------------------------------
http://git-wip-us.apache.org/repos/asf/cordova-app-harness/blob/074d388a/www/cdvah/js/ResourcesLoader.js
----------------------------------------------------------------------
diff --git a/www/cdvah/js/ResourcesLoader.js b/www/cdvah/js/ResourcesLoader.js
index 8a213d6..442c9c2 100644
--- a/www/cdvah/js/ResourcesLoader.js
+++ b/www/cdvah/js/ResourcesLoader.js
@@ -185,13 +185,19 @@
});
},
- deleteDirectory: function(url) {
+ delete: function(url) {
return resolveURL(url)
- .then(function(dirEntry) {
+ .then(function(entry) {
var deferred = $q.defer();
- dirEntry.removeRecursively(deferred.resolve, function(error) {
- deferred.reject(new Error('There was an error deleting the directory: ' + url + ' ' + JSON.stringify(error)));
- });
+ if (entry.removeRecursively) {
+ entry.removeRecursively(deferred.resolve, function(error) {
+ deferred.reject(new Error('There was an error deleting directory: ' + url + ' ' + JSON.stringify(error)));
+ });
+ } else {
+ entry.remove(deferred.resolve, function(error) {
+ deferred.reject(new Error('There was an error deleting file: ' + url + ' ' + JSON.stringify(error)));
+ });
+ }
return deferred.promise;
}, function() {});
},
[4/5] git commit: Big refactor to support pushing of apps via HTTP.
Posted by ag...@apache.org.
Big refactor to support pushing of apps via HTTP.
This removes the AppHarness' ability to updating apps by fetching from
`cordova serve`. Instead, app resources are now pushed via
HarnessServer.
Project: http://git-wip-us.apache.org/repos/asf/cordova-app-harness/repo
Commit: http://git-wip-us.apache.org/repos/asf/cordova-app-harness/commit/da594dff
Tree: http://git-wip-us.apache.org/repos/asf/cordova-app-harness/tree/da594dff
Diff: http://git-wip-us.apache.org/repos/asf/cordova-app-harness/diff/da594dff
Branch: refs/heads/master
Commit: da594dffbab000b2864f306cd3f9f15e7ef8644c
Parents: 074d388
Author: Andrew Grieve <ag...@chromium.org>
Authored: Tue May 20 15:50:26 2014 -0400
Committer: Andrew Grieve <ag...@chromium.org>
Committed: Tue May 20 15:51:05 2014 -0400
----------------------------------------------------------------------
README.md | 48 +++-
www/cdvah/harnessmenu.html | 2 +-
www/cdvah/js/AppsService.js | 119 +++++-----
www/cdvah/js/DirectoryManager.js | 100 +++++++++
www/cdvah/js/HarnessServer.js | 219 ++++++++++++------
www/cdvah/js/HttpServer.js | 403 +++++++++++++++++++++++-----------
www/cdvah/js/Installer.js | 168 ++++++++------
www/cdvah/js/PluginMetadata.js | 2 +-
www/cdvah/js/ResourcesLoader.js | 4 +-
www/cdvah/js/ServeInstaller.js | 197 -----------------
www/cdvah/js/app.js | 2 +-
11 files changed, 721 insertions(+), 543 deletions(-)
----------------------------------------------------------------------
http://git-wip-us.apache.org/repos/asf/cordova-app-harness/blob/da594dff/README.md
----------------------------------------------------------------------
diff --git a/README.md b/README.md
index a61ee2c..c3d9d40 100644
--- a/README.md
+++ b/README.md
@@ -57,27 +57,57 @@ And also use Chrome DevTool's [Reverse Port Forwarding](https://developers.googl
## Commands
-### /push
-
-Add or update an app's settings, and then update & launch:
-
- curl -X POST http://$IP_ADDRESS:2424/push?type=serve&name=com.example.YourApp&url=http://$SERVE_HOST_ADDRESS:8000
-
### /menu
Show in-app overlay menu.
- curl -X POST http://$IP_ADDRESS:2424/menu
+ curl -v -X POST "http://$IP_ADDRESS:2424/menu"
### /exec
Executes a JS snippet:
- curl -X POST http://$IP_ADDRESS:2424/exec?code='alert(1)'
+ curl -v -X POST "http://$IP_ADDRESS:2424/exec?code='alert(1)'"
+
+### /launch
+
+Starts the app with the given ID (or the first app if none is given).
+
+ curl -v -X POST "http://$IP_ADDRESS:2424/launch?appId=a.b.c"
### /info
Returns JSON of server info / app state
- curl http://$IP_ADDRESS:2424/info
+ curl -v "http://$IP_ADDRESS:2424/info"
+
+### /assetmanifest
+
+Returns JSON of the asset manifest for the given app ID (or the first app if none is given).
+
+ curl -v "http://$IP_ADDRESS:2424/assetmanifest?appId=a.b.c"
+
+### /prepupdate
+
+Tell the interface that an update is in progress for the given app ID (or the first app if none is given).
+
+ echo '{"transferSize": 100}' | curl -v -X POST -d @- "http://$IP_ADDRESS:2424/prepupdate?app=foo"
+
+### /deletefiles
+
+Deletes a set of files within the given app ID (or the first app if none is given).
+
+ echo '{"paths":["www/index.html"]}' | curl -v -X POST -d @- "http://$IP_ADDRESS:2424/deletefiles?appId=a.b.c"
+
+### /putfile
+
+Updates a single file within the given app ID (or the first app if none is given).
+
+ cat file | curl -v -X PUT -d @- "http://$IP_ADDRESS:2424/assetmanifest?appId=a.b.c&path=www/index.html&etag=1234"
+
+### /deleteapp
+
+Deletes the app with the given ID (or the first app if none is given).
+ curl -v -X POST "http://$IP_ADDRESS:2424/deleteapp?appId=a.b.c"
+ curl -v -X POST "http://$IP_ADDRESS:2424/deleteapp?all=true" # Delete all apps.
http://git-wip-us.apache.org/repos/asf/cordova-app-harness/blob/da594dff/www/cdvah/harnessmenu.html
----------------------------------------------------------------------
diff --git a/www/cdvah/harnessmenu.html b/www/cdvah/harnessmenu.html
index 9a562fa..89f5432 100644
--- a/www/cdvah/harnessmenu.html
+++ b/www/cdvah/harnessmenu.html
@@ -27,8 +27,8 @@
<script type="text/javascript" src="js/CacheClear.js"></script>
<script type="text/javascript" src="js/AppHarnessUI.js"></script>
<script type="text/javascript" src="js/Installer.js"></script>
- <script type="text/javascript" src="js/ServeInstaller.js"></script>
<script type="text/javascript" src="js/ResourcesLoader.js"></script>
+ <script type="text/javascript" src="js/DirectoryManager.js"></script>
<script type="text/javascript" src="js/AppsService.js"></script>
<script type="text/javascript" src="js/PluginMetadata.js"></script>
<script type="text/javascript" src="js/UrlRemap.js"></script>
http://git-wip-us.apache.org/repos/asf/cordova-app-harness/blob/da594dff/www/cdvah/js/AppsService.js
----------------------------------------------------------------------
diff --git a/www/cdvah/js/AppsService.js b/www/cdvah/js/AppsService.js
index c044f59..98b920a 100644
--- a/www/cdvah/js/AppsService.js
+++ b/www/cdvah/js/AppsService.js
@@ -19,34 +19,22 @@
(function() {
'use strict';
/* global myApp */
- myApp.factory('AppsService', ['$q', 'ResourcesLoader', 'INSTALL_DIRECTORY', 'APPS_JSON', 'notifier', 'PluginMetadata', 'AppHarnessUI', function($q, ResourcesLoader, INSTALL_DIRECTORY, APPS_JSON, notifier, PluginMetadata, AppHarnessUI) {
-
+ myApp.factory('AppsService', ['$q', 'ResourcesLoader', 'INSTALL_DIRECTORY', 'APPS_JSON', 'notifier', 'AppHarnessUI', function($q, ResourcesLoader, INSTALL_DIRECTORY, APPS_JSON, notifier, AppHarnessUI) {
// Map of type -> installer.
- var _installerFactories = {};
+ var _installerFactories = Object.create(null);
// Array of installer objects.
var _installers = null;
// The app that is currently running.
var activeInstaller = null;
- function createInstallHandlersFromJson(json) {
- var appList = json.appList || [];
- var ret = [];
- for (var i = 0; i < appList.length; i++) {
- var entry = appList[i];
- var factory = _installerFactories[entry.appType];
- var installer = factory.createFromJson(entry.appUrl, entry.appId);
- installer.lastUpdated = entry.lastUpdated && new Date(entry.lastUpdated);
- installer.installPath = entry.installPath;
- installer.plugins = PluginMetadata.process(entry.plugins);
- ret.push(installer);
- }
- return ret;
- }
-
function readAppsJson() {
var deferred = $q.defer();
ResourcesLoader.readJSONFileContents(APPS_JSON)
.then(function(result) {
+ if (result['fileVersion'] !== 1) {
+ console.warn('Ignoring old version of apps.json');
+ result = {};
+ }
deferred.resolve(result);
}, function() {
// Error means first run.
@@ -61,25 +49,33 @@
}
return readAppsJson()
- .then(function(appsJson) {
- _installers = createInstallHandlersFromJson(appsJson);
+ .then(function(json) {
+ var appList = json['appList'] || [];
+ _installers = [];
+ var i = -1;
+ function next() {
+ var entry = appList[++i];
+ if (!entry) {
+ return;
+ }
+ return _installerFactories[entry['appType']].createFromJson(entry)
+ .then(function(app) {
+ _installers.push(app);
+ return next();
+ }, next);
+ }
+ return next();
});
}
function createAppsJson() {
var appsJson = {
+ 'fileVersion': 1,
'appList': []
};
for (var i = 0; i < _installers.length; ++i) {
var installer = _installers[i];
- appsJson.appList.push({
- 'appId' : installer.appId,
- 'appType' : installer.type,
- 'appUrl' : installer.url,
- 'lastUpdated': installer.lastUpdated && +installer.lastUpdated,
- 'installPath': installer.installPath,
- 'plugins': installer.plugins.raw
- });
+ appsJson.appList.push(installer.toDiskJson());
}
return appsJson;
}
@@ -99,15 +95,14 @@
AppHarnessUI.createOverlay();
} else if (eventName == 'hideMenu') {
AppHarnessUI.destroyOverlay();
- } else if (eventName == 'updateApp') {
- AppsService.updateAndLaunchApp(activeInstaller)
- .then(null, notifier.error);
} else if (eventName == 'restartApp') {
// TODO: Restart in place?
AppsService.launchApp(activeInstaller)
.then(null, notifier.error);
} else if (eventName == 'quitApp') {
AppsService.quitApp();
+ } else {
+ console.warn('Unknown message from AppHarnessUI: ' + eventName);
}
});
@@ -124,6 +119,28 @@
return createAppsJson();
},
+ // If no appId, then return the first app.
+ // If appId and appType, then create it if it doesn't exist.
+ // Else: return null.
+ getAppById : function(appId, /* optional */ appType) {
+ return initHandlers()
+ .then(function() {
+ var matches = _installers;
+ if (appId) {
+ matches = _installers.filter(function(x) {
+ return x.appId == appId;
+ });
+ }
+ if (matches.length > 0) {
+ return matches[0];
+ }
+ if (appType) {
+ return AppsService.addApp(appType, appId);
+ }
+ return null;
+ });
+ },
+
quitApp : function() {
if (activeInstaller) {
activeInstaller.unlaunch();
@@ -143,10 +160,10 @@
});
},
- addApp : function(installerType, appUrl, /*optional*/ appId) {
- var installerFactory = _installerFactories[installerType];
+ addApp : function(appType, /* optional */ appId) {
+ var installPath = INSTALL_DIRECTORY + 'app' + new Date().getTime() + '/';
return initHandlers().then(function() {
- return installerFactory.createFromUrl(appUrl, appId);
+ return _installerFactories[appType].createNew(installPath, appId);
}).then(function(installer) {
_installers.push(installer);
return writeAppsJson()
@@ -156,13 +173,12 @@
});
},
- editApp : function(oldId, installer) {
- _installers.forEach(function(inst, i) {
- if (inst.appId == oldId) {
- _installers.splice(i, 1, installer);
- }
- });
- return writeAppsJson();
+ uninstallAllApps : function() {
+ var deletePromises = [];
+ for (var i = 0; i < _installers.length; ++i) {
+ deletePromises.push(AppsService.uninstallApp(_installers[i]));
+ }
+ return $q.all(deletePromises);
},
uninstallApp : function(installer) {
@@ -173,27 +189,12 @@
});
},
- getLastRunApp : function() {
- throw new Error('Not implemented.');
- },
-
- updateApp : function(installer){
- var installPath = INSTALL_DIRECTORY + '/' + encodeURIComponent(installer.appId);
- return installer.updateApp(installPath)
- .then(writeAppsJson);
- },
-
- updateAndLaunchApp : function(installer) {
- return AppsService.quitApp()
- .then(function() {
- return AppsService.updateApp(installer);
- }).then(function() {
- return AppsService.launchApp(installer);
- });
+ triggerAppListChange: function() {
+ return writeAppsJson();
},
registerInstallerFactory : function(installerFactory) {
- _installerFactories[installerFactory.type] = installerFactory;
+ _installerFactories[installerFactory.type] = installerFactory;
},
onAppListChange: null
http://git-wip-us.apache.org/repos/asf/cordova-app-harness/blob/da594dff/www/cdvah/js/DirectoryManager.js
----------------------------------------------------------------------
diff --git a/www/cdvah/js/DirectoryManager.js b/www/cdvah/js/DirectoryManager.js
new file mode 100644
index 0000000..2a85612
--- /dev/null
+++ b/www/cdvah/js/DirectoryManager.js
@@ -0,0 +1,100 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+*/
+(function(){
+ 'use strict';
+
+ /* global myApp */
+ myApp.factory('DirectoryManager', ['$q', 'ResourcesLoader', function($q, ResourcesLoader) {
+ var ASSET_MANIFEST = 'assetmanifest.json';
+ function DirectoryManager(rootURL) {
+ this.rootURL = rootURL;
+ this.lastUpdated = null;
+ this._assetManifest = null;
+ this._flushTimerId = null;
+ }
+
+ DirectoryManager.prototype.deleteAll = function() {
+ this.lastUpdated = null;
+ this._assetManifest = null;
+ window.clearTimeout(this._flushTimerId);
+ return ResourcesLoader.delete(this.rootURL);
+ };
+
+ DirectoryManager.prototype.getAssetManifest = function() {
+ if (this._assetManifest) {
+ return $q.when(this._assetManifest);
+ }
+ var deferred = $q.defer();
+ var me = this;
+ ResourcesLoader.readJSONFileContents(this.rootURL + ASSET_MANIFEST)
+ .then(function(json) {
+ me._assetManifest = json;
+ deferred.resolve(json);
+ }, function() {
+ me._assetManifest = {};
+ deferred.resolve({});
+ });
+ return deferred.promise;
+ };
+
+ DirectoryManager.prototype._lazyWriteAssetManifest = function() {
+ if (this._flushTimerId === null) {
+ this._flushTimerId = window.setTimeout(this._writeAssetManifest.bind(this), 1000);
+ }
+ };
+
+ DirectoryManager.prototype._writeAssetManifest = function() {
+ this._flushTimerId = null;
+ var stringContents = JSON.stringify(this._assetManifest);
+ return ResourcesLoader.writeFileContents(this.rootURL + ASSET_MANIFEST, stringContents);
+ };
+
+ DirectoryManager.prototype.addFile = function(srcURL, relativePath, etag) {
+ var self = this;
+ return ResourcesLoader.moveFile(srcURL, this.rootURL + relativePath)
+ .then(function() {
+ self._assetManifest[relativePath] = etag;
+ self._lazyWriteAssetManifest();
+ });
+ };
+
+ DirectoryManager.prototype.writeFile = function(data, relativePath, etag) {
+ var self = this;
+ return ResourcesLoader.writeFileContents(this.rootURL + relativePath, data)
+ .then(function() {
+ self._assetManifest[relativePath] = etag;
+ self._lazyWriteAssetManifest();
+ });
+ };
+
+ DirectoryManager.prototype.deleteFile = function(relativePath) {
+ if (!this._assetManifest[relativePath]) {
+ console.warn('Tried to delete non-existing file: ' + relativePath);
+ } else {
+ delete this._assetManifest[relativePath];
+ this._lazyWriteAssetManifest();
+ return ResourcesLoader.delete(this.rootURL + relativePath);
+ }
+ };
+
+ return DirectoryManager;
+ }]);
+
+})();
+
http://git-wip-us.apache.org/repos/asf/cordova-app-harness/blob/da594dff/www/cdvah/js/HarnessServer.js
----------------------------------------------------------------------
diff --git a/www/cdvah/js/HarnessServer.js b/www/cdvah/js/HarnessServer.js
index 26097be..8f9e45b 100644
--- a/www/cdvah/js/HarnessServer.js
+++ b/www/cdvah/js/HarnessServer.js
@@ -28,17 +28,15 @@
function ensureMethodDecorator(method, func) {
return function(req, resp) {
if (req.method != method) {
- resp.sendTextResponse(405, 'Method Not Allowed\n');
- } else {
- func(req, resp);
+ return resp.sendTextResponse(405, 'Method Not Allowed\n');
}
+ return func(req, resp);
};
}
function pipeRequestToFile(req, destUrl) {
- var outerDeferred = $q.defer();
var writer = null;
- req.onData = function(arrayBuffer) {
+ function handleChunk(arrayBuffer) {
var ret = $q.when();
if (writer == null) {
ret = ResourcesLoader.createFileWriter(destUrl)
@@ -46,48 +44,22 @@
writer = w;
});
}
- return ret
- .then(function() {
+ return ret.then(function() {
var deferred = $q.defer();
writer.onwrite = deferred.resolve;
- writer.onerror = deferred.reject;
+ writer.onerror = function() {
+ deferred.reject(writer.error);
+ };
writer.write(arrayBuffer);
return deferred.promise;
})
.then(function() {
- if (req.bytesRemaining === 0) {
- outerDeferred.resolve();
+ if (req.bytesRemaining > 0) {
+ return req.readChunk().then(handleChunk);
}
- }, outerDeferred.reject);
- };
- return outerDeferred.promise;
- }
-
- function handlePush(req, resp) {
- var type = req.getQueryParam('type');
- var name = req.getQueryParam('name');
- var url = req.getQueryParam('url');
- if (!(type && name)) {
- resp.sendTextResponse(400, 'Missing required query params type=' + type + ' name=' + name + '\n');
- return;
- }
- var ret = $q.when();
- return ret.then(function() {
- if (!url) {
- resp.sendTextResponse(400, 'Missing required query param "url"\n');
- return;
- }
- return AppHarnessUI.destroy()
- .then(function() {
- return updateApp(type, name, url);
- }).then(function() {
- notifier.success('Updated ' + name + ' from remote push.');
- resp.sendTextResponse(200, '');
- }, function(e) {
- notifier.error(e);
- resp.sendTextResponse(500, e + '\n');
});
- });
+ }
+ return req.readChunk().then(handleChunk);
}
function handleExec(req, resp) {
@@ -103,6 +75,139 @@
return AppHarnessUI.createOverlay();
}
+ function handleLaunch(req, resp) {
+ var appId = req.getQueryParam('appId');
+ return AppsService.getAppById(appId)
+ .then(function(app) {
+ if (app) {
+ return AppsService.launchApp(app)
+ .then(function() {
+ return resp.sendTextResponse(200, '');
+ });
+ }
+ return resp.sendTextResponse(412, 'No apps available for launch\n');
+ });
+ }
+
+ function handleAssetManifest(req, resp) {
+ var appId = req.getQueryParam('appId');
+ return AppsService.getAppById(appId)
+ .then(function(app) {
+ if (app) {
+ return app.directoryManager.getAssetManifest();
+ }
+ return null;
+ }).then(function(assetManifest) {
+ resp.sendJsonResponse({
+ 'assetManifest': assetManifest
+ });
+ });
+ }
+
+ function handlePrepUpdate(req, resp) {
+ var appId = req.getQueryParam('appId');
+ var appType = req.getQueryParam('appType') || 'cordova';
+ return AppsService.getAppById(appId, appType)
+ .then(function(app) {
+ return req.readAsJson()
+ .then(function(requestJson) {
+ app.updatingStatus = 0;
+ app.updateBytesTotal = +requestJson['transferSize'];
+ app.updateBytesSoFar = 0;
+ return resp.sendTextResponse(200, '');
+ });
+ });
+ }
+
+ function handleDeleteFiles(req, resp) {
+ var appId = req.getQueryParam('appId');
+ var appType = req.getQueryParam('appType') || 'cordova';
+ return AppsService.getAppById(appId, appType)
+ .then(function(app) {
+ return req.readAsJson()
+ .then(function(requestJson) {
+ var paths = requestJson['paths'];
+ for (var i = 0; i < paths.length; ++i) {
+ app.directoryManager.deleteFile(paths[i]);
+ }
+ return resp.sendTextResponse(200, '');
+ });
+ });
+ }
+
+ function handleDeleteApp(req, resp) {
+ var appId = req.getQueryParam('appId');
+ var all = req.getQueryParam('all');
+ var ret;
+ if (all) {
+ ret = AppsService.uninstallAllApps();
+ } else {
+ ret = AppsService.getAppById(appId)
+ .then(function(app) {
+ if (app) {
+ return AppsService.uninstallApp(app);
+ }
+ });
+ }
+ return ret.then(function() {
+ return resp.sendTextResponse(200, '');
+ });
+ }
+
+ function handlePutFile(req, resp) {
+ var appId = req.getQueryParam('appId');
+ var appType = req.getQueryParam('appType') || 'cordova';
+ var path = req.getQueryParam('path');
+ var etag = req.getQueryParam('etag');
+ if (!path || !etag) {
+ throw new Error('Request is missing path or etag query params');
+ }
+ return AppsService.getAppById(appId, appType)
+ .then(function(app) {
+ var tmpUrl = ResourcesLoader.createTmpFileUrl();
+ return pipeRequestToFile(req, tmpUrl)
+ .then(function() {
+ var ret = $q.when();
+ if (path == 'www/cordova_plugins.js') {
+ path = 'orig-cordova_plugins.js';
+ }
+ if (path == 'www/config.xml') {
+ ret = ret.then(function() {
+ return ResourcesLoader.downloadFromUrl(tmpUrl, tmpUrl + '-2');
+ });
+ }
+ ret = ret.then(function() {
+ return app.directoryManager.addFile(tmpUrl, path, etag);
+ });
+ if (path == 'www/config.xml') {
+ ret = ret.then(function() {
+ return app.directoryManager.addFile(tmpUrl + '-2', 'config.xml', etag);
+ });
+ }
+ if (path == 'config.xml' || path == 'www/config.xml') {
+ ret = ret.then(function() {
+ return app.readConfigXml();
+ });
+ } else if (path == 'orig-cordova_plugins.js') {
+ ret = ret.then(function() {
+ return app.readCordovaPluginsFile();
+ });
+ }
+ return ret;
+ })
+ .then(function() {
+ app.updateBytesSoFar += +req.headers['content-length'];
+ app.updatingStatus = app.updateBytesTotal / app.updateBytesSoFar;
+ if (app.updatingStatus === 1) {
+ app.updatingStatus = null;
+ app.lastUpdated = new Date();
+ notifier.success('Update complete.');
+ }
+ return resp.sendTextResponse(200, '');
+ });
+ });
+ }
+
function handleInfo(req, resp) {
var json = {
'platform': cordova.platformId,
@@ -114,40 +219,20 @@
resp.sendJsonResponse(json);
}
- function updateApp(type, name, url) {
- return AppsService.getAppList()
- .then(function(list) {
- var matches = list && list.filter(function(x) { return x.appId == name; });
- var promise;
- if (list && matches.length > 0) {
- // App exists.
- var app = matches[0];
- app.url = url;
- promise = $q.when(app);
- } else {
- // New app.
- promise = AppsService.addApp(type, url, name).then(function(handler) {
- var msg = 'Added new app ' + handler.appId + ' from push';
- notifier.success(msg);
- return handler;
- });
- }
-
- return promise.then(function(theApp) {
- return AppsService.updateAndLaunchApp(theApp);
- });
- });
- }
-
function start() {
if (server) {
return;
}
- server = HttpServer.create()
- .addRoute('/push', ensureMethodDecorator('POST', handlePush))
+ server = new HttpServer()
.addRoute('/exec', ensureMethodDecorator('POST', handleExec))
.addRoute('/menu', ensureMethodDecorator('POST', handleMenu))
- .addRoute('/info', ensureMethodDecorator('GET', handleInfo));
+ .addRoute('/launch', ensureMethodDecorator('POST', handleLaunch))
+ .addRoute('/info', ensureMethodDecorator('GET', handleInfo))
+ .addRoute('/assetmanifest', ensureMethodDecorator('GET', handleAssetManifest))
+ .addRoute('/prepupdate', ensureMethodDecorator('POST', handlePrepUpdate))
+ .addRoute('/deletefiles', ensureMethodDecorator('POST', handleDeleteFiles))
+ .addRoute('/deleteapp', ensureMethodDecorator('POST', handleDeleteApp))
+ .addRoute('/putfile', ensureMethodDecorator('PUT', handlePutFile));
return server.start();
}
http://git-wip-us.apache.org/repos/asf/cordova-app-harness/blob/da594dff/www/cdvah/js/HttpServer.js
----------------------------------------------------------------------
diff --git a/www/cdvah/js/HttpServer.js b/www/cdvah/js/HttpServer.js
index cd0ef56..9622c28 100644
--- a/www/cdvah/js/HttpServer.js
+++ b/www/cdvah/js/HttpServer.js
@@ -28,17 +28,21 @@
var STATE_HEADERS_RECEIVED = 2;
var STATE_REQUEST_RECEIVED = 3;
var STATE_RESPONSE_STARTED = 4;
- var STATE_COMPLETE = 5;
+ var STATE_RESPONSE_WAITING_FOR_FLUSH = 5;
+ var STATE_COMPLETE = 6;
function HttpRequest(requestData) {
this._requestData = requestData;
this.method = requestData.method;
this.headers = requestData.headers;
this.bytesRemaining = 0;
+ this._readChunkCalled = false;
if (requestData.method == 'POST' || requestData.method == 'PUT') {
this.bytesRemaining = parseInt(requestData.headers['content-length'] || '0');
}
- this.onData = null; // function(arrayBuffer, req, resp) : Promise
+ if (this.bytesRemaining === 0) {
+ this._requestData.state = STATE_REQUEST_RECEIVED;
+ }
var host = this.headers['host'] || 'localhost';
var queryMatch = /\?.*/.exec(requestData.resource);
@@ -53,23 +57,63 @@
return m && decodeURIComponent(m[1]);
};
- HttpRequest.prototype._feedData = function(arrayBuffer) {
- console.log('Processing request chunk of size ' + arrayBuffer.byteLength);
- this.bytesRemaining -= arrayBuffer.byteLength;
- if (this.bytesRemaining < 0) {
- throw new Error('Bytes remaining negative: ' + this.bytesRemaining);
- } else if (this.bytesRemaining === 0 && this._requestData.state === STATE_HEADERS_RECEIVED) {
- this._requestData.state = STATE_REQUEST_RECEIVED;
+ HttpRequest.prototype.readAsJson = function() {
+ var self = this;
+ return this.readEntireBody()
+ .then(function(arrayBuffer) {
+ var s = arrayBufferToString(arrayBuffer);
+ return JSON.parse(s);
+ }).then(null, function(e) {
+ return self._requestData.httpResponse.sendTextResponse(400, 'Invalid JSON received.\n')
+ .then(function() {
+ throw e;
+ });
+ });
+ };
+
+ HttpRequest.prototype.readEntireBody = function() {
+ var byteArray = null;
+ var soFar = 0;
+ var self = this;
+ function handleChunk(chunk) {
+ if (byteArray) {
+ byteArray.set(chunk, soFar);
+ soFar += chunk.byteLength;
+ }
+ if (self.bytesRemaining === 0) {
+ return byteArray ? byteArray.buffer : chunk;
+ }
+ return self.readChunk().then(handleChunk);
}
- if (arrayBuffer.byteLength > 0) {
- if (this.onData) {
- return this.onData(arrayBuffer, this, this._requestData.httpResponse);
- } else {
- // Set to an empty function to avoid this warning.
- console.warn('onData not set when callback is fired for request: ' + this.getUrl());
+ return this.readChunk().then(handleChunk);
+ };
+
+ HttpRequest.prototype.readChunk = function(/* optional */maxChunkSize) {
+ // Allow readChunk() to be called *once* after request is already received.
+ // This is convenient for empty payloads.
+ if (this._requestData.state === STATE_REQUEST_RECEIVED) {
+ if (this._readChunkCalled) {
+ throw new Error('readChunk() when request already received.');
+ }
+ this._readChunkCalled = true;
+ if (this.bytesRemaining === 0) {
+ return $q.when(new ArrayBuffer(0));
}
}
- return $q.when();
+ var self = this;
+ return this._requestData.socket.read(maxChunkSize)
+ .then(function(chunk) {
+ var chunkSize = chunk.byteLength;
+ console.log('Processing request chunk of size ' + chunkSize);
+ self.bytesRemaining -= chunkSize;
+ if (self.bytesRemaining < 0) {
+ throw new Error('Bytes remaining negative: ' + self.bytesRemaining);
+ }
+ if (self.bytesRemaining === 0 && self._requestData.state === STATE_HEADERS_RECEIVED) {
+ self._requestData.state = STATE_REQUEST_RECEIVED;
+ }
+ return chunk;
+ });
};
function HttpResponse(requestData) {
@@ -77,7 +121,13 @@
this.headers = Object.create(null);
var keepAlive = requestData.headers['connection'] === 'keep-alive';
this.headers['Connection'] = keepAlive ? 'keep-alive' : 'close';
- this._writeQueue = [];
+ var self = this;
+ requestData.socket.onClose = function(err) {
+ if (err) {
+ console.error(err);
+ }
+ self._finish(!!err);
+ };
}
HttpResponse.prototype.sendTextResponse = function(status, message, /* optional */ contentType) {
@@ -85,56 +135,37 @@
this.headers['Content-Length'] = message.length;
this._startResponse(status);
this.writeChunk(stringToArrayBuffer(message));
- this.close();
+ return this.close();
};
HttpResponse.prototype.sendJsonResponse = function(json) {
- this.sendTextResponse(200, JSON.stringify(json, null, 4), 'application/json');
+ return this.sendTextResponse(200, JSON.stringify(json, null, 4), 'application/json');
};
HttpResponse.prototype.writeChunk = function(arrayBuffer) {
if (this._requestData.state !== STATE_RESPONSE_STARTED) {
this._startResponse(200);
}
- this._addToWriteQueue(arrayBuffer);
- };
-
- HttpResponse.prototype.close = function() {
- this.writeChunk(null);
- };
-
- HttpResponse.prototype._addToWriteQueue = function(arrayBuffer) {
- this._writeQueue.push(arrayBuffer);
- if (this._writeQueue.length === 1) {
- this._pokeWriteQueue();
+ var promise = this._requestData.socket.write(arrayBuffer);
+ if (!arrayBuffer) {
+ this._requestData.state = STATE_RESPONSE_WAITING_FOR_FLUSH;
+ promise = promise.then(this._finish.bind(this, true));
}
+ return promise;
};
- HttpResponse.prototype._pokeWriteQueue = function() {
- var arrayBuffer = this._writeQueue[0];
- if (arrayBuffer) {
- var self = this;
- chrome.socket.write(this._requestData.socketId, arrayBuffer, function(writeInfo) {
- if (writeInfo.bytesWritten !== arrayBuffer.byteLength) {
- console.warn('Failed to write entire ArrayBuffer.');
- }
- self._writeQueue.shift();
- if (writeInfo.bytesWritten < 0) {
- console.error('Write error: ' + -writeInfo.bytesWritten);
- self._finish(true);
- } else {
- self._pokeWriteQueue();
- }
- });
- } else if (arrayBuffer === null) {
- this._finish();
+ HttpResponse.prototype.close = function() {
+ if (this._requestData.state < STATE_RESPONSE_WAITING_FOR_FLUSH) {
+ return this.writeChunk(null);
}
};
HttpResponse.prototype._startResponse = function(status) {
var headers = this.headers;
- if (this._requestData.httpRequest.bytesRemaining > 0) {
- headers['Connection'] = 'Close';
+ // Check if they haven't finished reading the request, and error out.
+ if (this._requestData.state < STATE_REQUEST_RECEIVED) {
+ this._requestData.socket.close(new Error('Started to write response before request data was finished.'));
+ return;
}
this._requestData.state = STATE_RESPONSE_STARTED;
var statusMsg = status === 404 ? 'Not Found' :
@@ -150,19 +181,126 @@
};
HttpResponse.prototype._finish = function(disconnect) {
+ if (this._requestData.state === STATE_COMPLETE) {
+ return;
+ }
this._requestData.state = STATE_COMPLETE;
+ this._requestData.socket.onClose = null;
var socketId = this._requestData.socketId;
if (typeof disconnect == 'undefined') {
disconnect = (this.headers['Connection'] || '').toLowerCase() != 'keep-alive';
}
delete this._requestData.httpServer._requests[socketId];
if (disconnect) {
- chrome.socket.destroy(socketId);
+ this._requestData.socket.close();
} else {
this._requestData.httpServer._onAccept(socketId);
}
};
+ function Socket(socketId) {
+ this.socketId = socketId;
+ this.alive = true;
+ this.onClose = null;
+ this._pendingReadChunk = null;
+ this._writeQueue = [];
+ this._readInProgress = false;
+ }
+
+ Socket.prototype.unread = function(chunk) {
+ if (this._pendingReadChunk) {
+ throw new Error('Socket.unread called multiple times.');
+ }
+ this._pendingReadChunk = chunk;
+ };
+
+ Socket.prototype.read = function(maxLength) {
+ if (this._readInProgress) {
+ throw new Error('Read already in progress.');
+ }
+ this._readInProgress = true;
+ maxLength = maxLength || Infinity;
+ var self = this;
+ var deferred = $q.defer();
+ var bufSize = Math.min(200 * 1024, maxLength);
+ var chunk = this._pendingReadChunk;
+ if (chunk) {
+ self._readInProgress = false;
+ if (chunk.byteLength <= maxLength) {
+ this._pendingReadChunk = null;
+ deferred.resolve(chunk);
+ } else {
+ this._pendingReadChunk = chunk.slice(maxLength);
+ deferred.resolve(chunk.slice(0, maxLength));
+ }
+ } else {
+ chrome.socket.read(this.socketId, bufSize, function(readInfo) {
+ self._readInProgress = false;
+ if (!readInfo.data) {
+ var err = new Error('Socket.read() failed with code ' + readInfo.resultCode);
+ self.close(err);
+ deferred.reject(err);
+ } else {
+ deferred.resolve(readInfo.data);
+ }
+ });
+ }
+ return deferred.promise;
+ };
+
+ // Multiple writes in are allowed at a time.
+ // A null arrayBuffer can be used as a synchronization point.
+ Socket.prototype.write = function(arrayBuffer) {
+ var deferred = $q.defer();
+ this._writeQueue.push(arrayBuffer, deferred);
+ if (this._writeQueue.length === 2) {
+ this._pokeWriteQueue();
+ }
+ return deferred.promise;
+ };
+
+ Socket.prototype.close = function(/*(optional*/ error) {
+ if (this.alive) {
+ this.alive = false;
+ chrome.socket.destroy(this.socketId);
+ if (this.onClose) {
+ this.onClose(error);
+ }
+ }
+ };
+
+ Socket.prototype._pokeWriteQueue = function() {
+ if (this._writeQueue.length === 0) {
+ return;
+ }
+ var arrayBuffer = this._writeQueue[0];
+ var deferred = this._writeQueue[1];
+ if (arrayBuffer && arrayBuffer.byteLength > 0) {
+ var self = this;
+ chrome.socket.write(this.socketId, arrayBuffer, function(writeInfo) {
+ if (writeInfo.bytesWritten !== arrayBuffer.byteLength) {
+ console.warn('Failed to write entire ArrayBuffer.');
+ }
+ self._writeQueue.shift();
+ self._writeQueue.shift();
+ if (writeInfo.bytesWritten < 0) {
+ var err = new Error('Write error: ' + -writeInfo.bytesWritten);
+ deferred.reject(err);
+ self.close(err);
+ } else {
+ deferred.resolve();
+ self._pokeWriteQueue();
+ }
+ });
+ } else {
+ this._writeQueue.shift();
+ this._writeQueue.shift();
+ deferred.resolve();
+ this._pokeWriteQueue();
+ }
+ };
+
+
function HttpServer() {
this._requests = Object.create(null); // Map of socketId -> Object
this._handlers = Object.create(null); // Map of resourcePath -> function(httpRequest, httpResponse)
@@ -197,12 +335,19 @@
return deferred.promise;
};
+ function acceptLoop(socketId, acceptCallback) {
+ chrome.socket.accept(socketId, function(acceptInfo) {
+ acceptCallback(acceptInfo.socketId);
+ acceptLoop(socketId, acceptCallback);
+ });
+ }
+
HttpServer.prototype._onAccept = function(socketId) {
console.log('Connection established on socket ' + socketId);
var requestData = {
state: STATE_NEW,
+ socket: new Socket(socketId),
dataAsStr: '', // Used only when parsing head of request.
- socketId: socketId,
method: null,
resource: null,
httpVersion: null,
@@ -212,29 +357,45 @@
httpRequest: null
};
this._requests[socketId] = requestData;
- receiveHttpData(requestData);
- };
-
- HttpServer.prototype._onReceivedRequest = function(requestData) {
- var req = new HttpRequest(requestData);
- var resp = new HttpResponse(requestData);
- requestData.httpRequest = req;
- requestData.httpResponse = resp;
- // Strip query params.
- var handler = this._handlers[req.path];
- if (handler) {
- handler(req, resp);
- } else {
- resp.sendTextResponse(404, 'Not Found');
- }
- };
-
- function acceptLoop(socketId, acceptCallback) {
- chrome.socket.accept(socketId, function(acceptInfo) {
- acceptCallback(acceptInfo.socketId);
- acceptLoop(socketId, acceptCallback);
+ var self = this;
+ return readRequestHeaders(requestData)
+ .then(function() {
+ var req = new HttpRequest(requestData);
+ var resp = new HttpResponse(requestData);
+ requestData.httpRequest = req;
+ requestData.httpResponse = resp;
+ // Strip query params.
+ var handler = self._handlers[req.path];
+ if (handler) {
+ // Wrap to catch exceptions.
+ return $q.when().then(function() {
+ return handler(req, resp);
+ }).then(function() {
+ if (requestData.state < STATE_RESPONSE_WAITING_FOR_FLUSH) {
+ if (requestData.state == STATE_REQUEST_RECEIVED) {
+ console.warn('No response was sent for action ' + requestData.resource);
+ return resp.sendTextResponse(200, '');
+ } else {
+ return requestData.socket.close();
+ }
+ }
+ }, function(err) {
+ console.error('Error while handling ' + req.path, err);
+ if (requestData.state !== STATE_RESPONSE_WAITING_FOR_FLUSH) {
+ if (requestData.state < STATE_RESPONSE_STARTED) {
+ return req.readEntireBody()
+ .then(function() {
+ return resp.sendTextResponse(500, '' + err);
+ })
+ } else {
+ return requestData.socket.close();
+ }
+ }
+ });
+ }
+ return resp.sendTextResponse(404, 'Not Found');
});
- }
+ };
function stringToArrayBuffer(str) {
var view = new Uint8Array(str.length);
@@ -253,60 +414,41 @@
return str;
}
- function receiveHttpData(requestData) {
- if (requestData.state < STATE_REQUEST_RECEIVED) {
- chrome.socket.read(requestData.socketId, function(readInfo) {
- processHttpRequest(requestData, readInfo.data);
- });
- }
- }
-
- function processHttpRequest(requestData, arrayBuffer) {
- switch (requestData.state) {
- case STATE_NEW:
- case STATE_REQUEST_DATA_RECEIVED:
- var oldLen = requestData.dataAsStr.length;
- var newData = arrayBufferToString(arrayBuffer);
- var splitPoint;
- requestData.dataAsStr += newData;
- if (requestData.state === STATE_NEW) {
- splitPoint = requestData.dataAsStr.indexOf('\r\n');
- if (splitPoint > -1) {
- var requestDataLine = requestData.dataAsStr.substring(0, splitPoint);
- requestData.dataAsStr = '';
- arrayBuffer = arrayBuffer.slice(splitPoint + 2 - oldLen);
- var requestDataParts = requestDataLine.split(' ');
- requestData.method = requestDataParts[0].toUpperCase();
- requestData.resource = requestDataParts[1];
- requestData.httpVersion = requestDataParts[2];
- console.log(requestData.method + ' requestData received for ' + requestData.resource);
- requestData.state = STATE_REQUEST_DATA_RECEIVED;
- processHttpRequest(requestData, arrayBuffer);
- return;
- }
- } else {
- splitPoint = requestData.dataAsStr.indexOf('\r\n\r\n');
- if (splitPoint > -1) {
- requestData.headers = parseHeaders(requestData.dataAsStr.substring(0, splitPoint));
- requestData.dataAsStr = '';
- arrayBuffer = arrayBuffer.slice(splitPoint + 4 - oldLen);
- requestData.state = STATE_HEADERS_RECEIVED;
- requestData.httpServer._onReceivedRequest(requestData);
- processHttpRequest(requestData, arrayBuffer);
- return;
- }
+ function readRequestHeaders(requestData) {
+ return requestData.socket.read()
+ .then(function(arrayBuffer) {
+ var oldLen = requestData.dataAsStr.length;
+ var newData = arrayBufferToString(arrayBuffer);
+ var splitPoint;
+ requestData.dataAsStr += newData;
+ if (requestData.state === STATE_NEW) {
+ splitPoint = requestData.dataAsStr.indexOf('\r\n');
+ if (splitPoint > -1) {
+ var requestDataLine = requestData.dataAsStr.substring(0, splitPoint);
+ requestData.dataAsStr = '';
+ arrayBuffer = arrayBuffer.slice(splitPoint + 2 - oldLen);
+ var requestDataParts = requestDataLine.split(' ');
+ requestData.method = requestDataParts[0].toUpperCase();
+ requestData.resource = requestDataParts[1];
+ requestData.httpVersion = requestDataParts[2];
+ console.log(requestData.method + ' requestData received for ' + requestData.resource);
+ requestData.state = STATE_REQUEST_DATA_RECEIVED;
+ requestData.socket.unread(arrayBuffer);
+ return readRequestHeaders(requestData);
}
- break;
- case STATE_HEADERS_RECEIVED:
- requestData.httpRequest._feedData(arrayBuffer)
- .then(function() {
- receiveHttpData(requestData);
- }, function(e) {
- requestData.httpResponse.sendTextResponse(500, '' + e);
- });
- return;
- }
- receiveHttpData(requestData);
+ } else {
+ splitPoint = requestData.dataAsStr.indexOf('\r\n\r\n');
+ if (splitPoint > -1) {
+ requestData.headers = parseHeaders(requestData.dataAsStr.substring(0, splitPoint));
+ requestData.dataAsStr = '';
+ arrayBuffer = arrayBuffer.slice(splitPoint + 4 - oldLen);
+ requestData.state = STATE_HEADERS_RECEIVED;
+ requestData.socket.unread(arrayBuffer);
+ return requestData;
+ }
+ }
+ return readRequestHeaders(requestData);
+ });
}
function strip(str) {
@@ -335,11 +477,6 @@
return headers;
}
- return {
- create: function() {
- return new HttpServer();
- }
- };
-
+ return HttpServer;
}]);
})();
http://git-wip-us.apache.org/repos/asf/cordova-app-harness/blob/da594dff/www/cdvah/js/Installer.js
----------------------------------------------------------------------
diff --git a/www/cdvah/js/Installer.js b/www/cdvah/js/Installer.js
index 40f779c..434fc85 100644
--- a/www/cdvah/js/Installer.js
+++ b/www/cdvah/js/Installer.js
@@ -18,108 +18,125 @@
*/
(function(){
'use strict';
- /* global myApp, cordova */
- myApp.factory('Installer', ['$q', 'UrlRemap', 'ResourcesLoader', 'PluginMetadata', 'CacheClear', function($q, UrlRemap, ResourcesLoader, PluginMetadata, CacheClear) {
- function getAppStartPageFromConfig(configFile) {
- return ResourcesLoader.readFileContents(configFile)
- .then(function(contents) {
- if(!contents) {
- throw new Error('Config file is empty. Unable to find a start page for your app.');
- } else {
- var startLocation = 'index.html';
- var parser = new DOMParser();
- var xmlDoc = parser.parseFromString(contents, 'text/xml');
- var els = xmlDoc.getElementsByTagName('content');
-
- if(els.length > 0) {
- // go through all 'content' elements looking for the 'src' attribute in reverse order
- for(var i = els.length - 1; i >= 0; i--) {
- var el = els[i];
- var srcValue = el.getAttribute('src');
- if (srcValue) {
- startLocation = srcValue;
- break;
- }
- }
- }
-
- return startLocation;
- }
- });
- }
+ /* global myApp, cordova */
+ myApp.factory('Installer', ['$q', 'UrlRemap', 'ResourcesLoader', 'PluginMetadata', 'CacheClear', 'DirectoryManager', function($q, UrlRemap, ResourcesLoader, PluginMetadata, CacheClear, DirectoryManager) {
+ var platformId = cordova.require('cordova/platform').id;
- function Installer(url, appId) {
- this.url = url;
- this.appId = appId || '';
+ function Installer(installPath) {
this.updatingStatus = null;
this.lastUpdated = null;
- this.installPath = null;
- this.plugins = {};
+ // Asset manifest is a cache of what files have been downloaded along with their etags.
+ this.directoryManager = new DirectoryManager(installPath);
+ this.appId = null; // Read from config.xml
+ this.appName = null; // Read from config.xml
+ this.startPage = null; // Read from config.xml
+ this.plugins = {}; // Read from orig-cordova_plugins.js
}
- Installer.prototype.type = '';
+ Installer.type = 'cordova';
+ Installer.prototype.type = 'cordova';
- Installer.prototype.updateApp = function(installPath) {
- var self = this;
- this.updatingStatus = 0;
- this.installPath = installPath;
- // Cache clearing necessary only for Android.
- return CacheClear.clear()
+ Installer.createNew = function(installPath, /* optional */ appId) {
+ var ret = new Installer(installPath);
+ ret.appId = appId;
+ return ret.directoryManager.getAssetManifest()
.then(function() {
- return self.doUpdateApp();
- })
+ return ret;
+ });
+ };
+
+ Installer.createFromJson = function(json) {
+ var ret = new Installer(json['installPath']);
+ ret.lastUpdated = json['lastUpdated'] && new Date(json['lastUpdated']);
+ ret.appId = json['appId'];
+ return ret.directoryManager.getAssetManifest()
.then(function() {
- self.lastUpdated = new Date();
- return self.getPluginMetadata();
- }, null, function(status) {
- self.updatingStatus = Math.round(status * 100);
- }).then(function(metadata) {
- self.plugins = PluginMetadata.process(metadata);
- var pluginIds = Object.keys(metadata);
- var newPluginsFileData = PluginMetadata.createNewPluginListFile(pluginIds);
- return ResourcesLoader.writeFileContents(installPath + '/www/cordova_plugins.js', newPluginsFileData);
- }).finally(function() {
- self.updatingStatus = null;
+ return ret.readCordovaPluginsFile();
+ }).then(function() {
+ return ret.readConfigXml();
+ }).then(function() {
+ return ret;
+ }, function(e) {
+ console.warn('Deleting broken app: ' + json['installPath']);
+ ResourcesLoader.delete(json['installPath']);
+ throw e;
+ });
+ };
+
+ Installer.prototype.toDiskJson = function() {
+ return {
+ 'appType' : this.type,
+ 'appId' : this.appId,
+ 'lastUpdated': this.lastUpdated && +this.lastUpdated,
+ 'installPath': this.directoryManager.rootURL
+ };
+ };
+
+ Installer.prototype.readCordovaPluginsFile = function(force) {
+ var self = this;
+ return this.directoryManager.getAssetManifest()
+ .then(function(assetManifest) {
+ if (!force && assetManifest['orig-cordova_plugins.js'] == assetManifest['www/cordova_plugins.js']) {
+ return null;
+ }
+ return self.getPluginMetadata()
+ .then(function(metadata) {
+ self.plugins = PluginMetadata.process(metadata);
+ var pluginIds = Object.keys(metadata);
+ var newPluginsFileData = PluginMetadata.createNewPluginListFile(pluginIds);
+ return self.directoryManager.writeFile(newPluginsFileData, 'www/cordova_plugins.js', assetManifest['orig-cordova_plugins.js']);
+ });
});
};
- Installer.prototype.doUpdateApp = function() {
- throw new Error('Installer ' + this.type + ' failed to implement doUpdateApp.');
+ Installer.prototype.readConfigXml = function() {
+ var self = this;
+ return ResourcesLoader.readFileContents(this.directoryManager.rootURL + 'config.xml')
+ .then(function(configStr) {
+ function lastEl(els) {
+ return els[els.length - 1];
+ }
+ var xmlDoc = new DOMParser().parseFromString(configStr, 'text/xml');
+ self.appId = xmlDoc.firstChild.getAttribute('id');
+ var el = lastEl(xmlDoc.getElementsByTagName('content'));
+ self.startPage = el ? el.getAttribute('src') : 'index.html';
+ el = lastEl(xmlDoc.getElementsByTagName('name'));
+ self.appName = el ? el.nodeValue : 'Unnamed';
+ });
};
Installer.prototype.getPluginMetadata = function() {
- throw new Error('Installer ' + this.type + ' failed to implement getPluginMetadata.');
+ return ResourcesLoader.readFileContents(this.directoryManager.rootURL + 'orig-cordova_plugins.js')
+ .then(function(contents) {
+ return PluginMetadata.extractPluginMetadata(contents);
+ });
};
Installer.prototype.deleteFiles = function() {
this.lastUpdated = null;
- if (this.installPath) {
- return ResourcesLoader.deleteDirectory(this.installPath);
- }
- return $q.when();
+ return this.directoryManager.deleteAll();
};
Installer.prototype.unlaunch = function() {
return UrlRemap.reset();
};
+ Installer.prototype._prepareForLaunch = function() {
+ // Cache clearing necessary only for Android.
+ return CacheClear.clear();
+ };
+
Installer.prototype.launch = function() {
- var installPath = this.installPath;
- var appId = this.appId;
- if (!installPath) {
- throw new Error('App ' + appId + ' requires an update');
- }
- var configLocation = installPath + '/config.xml';
-
- return getAppStartPageFromConfig(configLocation)
- .then(function(rawStartLocation) {
+ var self = this;
+ return $q.when(this._prepareForLaunch())
+ .then(function() {
var urlutil = cordova.require('cordova/urlutil');
var harnessUrl = urlutil.makeAbsolute(location.pathname);
var harnessDir = harnessUrl.replace(/\/[^\/]*\/[^\/]*$/, '');
- var installUrl = urlutil.makeAbsolute(installPath);
- var startLocation = urlutil.makeAbsolute(rawStartLocation).replace('/cdvah/', '/');
- var useNativeStartLocation = cordova.platformId == 'ios';
+ var installUrl = self.directoryManager.rootURL;
+ var startLocation = urlutil.makeAbsolute(self.startPage).replace('/cdvah/', '/');
+ var useNativeStartLocation = platformId == 'ios';
// Use toNativeURL() so that scheme is file:/ instead of cdvfile:/ (file: has special access).
return ResourcesLoader.toNativeURL(installUrl)
@@ -147,6 +164,11 @@
});
});
};
+
return Installer;
}]);
+ myApp.run(['Installer', 'AppsService', function(Installer, AppsService) {
+ AppsService.registerInstallerFactory(Installer);
+ }]);
})();
+
http://git-wip-us.apache.org/repos/asf/cordova-app-harness/blob/da594dff/www/cdvah/js/PluginMetadata.js
----------------------------------------------------------------------
diff --git a/www/cdvah/js/PluginMetadata.js b/www/cdvah/js/PluginMetadata.js
index 6e90eab..ead8dd8 100644
--- a/www/cdvah/js/PluginMetadata.js
+++ b/www/cdvah/js/PluginMetadata.js
@@ -46,7 +46,7 @@
// Extract the JSON data from inside the JS file.
// It's between two magic comments created by Plugman.
- var startIndex = pluginListFileContents.indexOf('TOP OF METADATA') + 16;
+ var startIndex = pluginListFileContents.indexOf('TOP OF METADATA') + 15;
var endIndex = pluginListFileContents.indexOf('// BOTTOM OF METADATA');
var target = pluginListFileContents.substring(startIndex, endIndex);
var metadata = JSON.parse(target);
http://git-wip-us.apache.org/repos/asf/cordova-app-harness/blob/da594dff/www/cdvah/js/ResourcesLoader.js
----------------------------------------------------------------------
diff --git a/www/cdvah/js/ResourcesLoader.js b/www/cdvah/js/ResourcesLoader.js
index 442c9c2..f8a0434 100644
--- a/www/cdvah/js/ResourcesLoader.js
+++ b/www/cdvah/js/ResourcesLoader.js
@@ -179,8 +179,8 @@
return ensureDirectoryExists(dirName(toUrl))
.then(function(destEntry) {
var deferred = $q.defer();
- fromEntry.moveTo(destEntry, baseName(toUrl), deferred.reslove, deferred.reject);
- return deferred;
+ fromEntry.moveTo(destEntry, baseName(toUrl), deferred.resolve, deferred.reject);
+ return deferred.promise;
});
});
},
http://git-wip-us.apache.org/repos/asf/cordova-app-harness/blob/da594dff/www/cdvah/js/ServeInstaller.js
----------------------------------------------------------------------
diff --git a/www/cdvah/js/ServeInstaller.js b/www/cdvah/js/ServeInstaller.js
deleted file mode 100644
index ed7ea98..0000000
--- a/www/cdvah/js/ServeInstaller.js
+++ /dev/null
@@ -1,197 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements. See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership. The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License. You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied. See the License for the
- * specific language governing permissions and limitations
- * under the License.
-*/
-(function(){
- 'use strict';
-
- var ASSET_MANIFEST_PATH = 'installmanifest.json';
-
- /* global myApp */
- myApp.run(['$q', 'Installer', 'AppsService', 'ResourcesLoader', 'urlCleanup', 'PluginMetadata', function($q, Installer, AppsService, ResourcesLoader, urlCleanup, PluginMetadata) {
- var platformId = cordova.require('cordova/platform').id;
-
- function ServeInstaller(url, appId) {
- Installer.call(this, url, appId);
- // Asset manifest is a cache of what files have been downloaded along with their etags.
- this._assetManifest = null;
- this._cachedProjectJson = null;
- this._cachedConfigXml = null;
- }
- ServeInstaller.prototype = Object.create(Installer.prototype);
-
- ServeInstaller.prototype.type = 'serve';
-
- ServeInstaller.prototype._readAssetManifest = function() {
- var deferred = $q.defer();
- var me = this;
- ResourcesLoader.readJSONFileContents(this.installPath + '/' + ASSET_MANIFEST_PATH)
- .then(function(result) {
- me._assetManifest = result;
- deferred.resolve();
- }, function() {
- me._assetManifest = {
- 'etagByPath': {}
- };
- deferred.resolve();
- });
- return deferred.promise;
- };
-
- ServeInstaller.prototype._writeAssetManifest = function() {
- var stringContents = JSON.stringify(this._assetManifest);
- return ResourcesLoader.writeFileContents(this.installPath + '/' + ASSET_MANIFEST_PATH, stringContents);
- };
-
- function fetchMetaServeData(url) {
- var projectJsonUrl = url + '/' + platformId + '/project.json';
- return ResourcesLoader.xhrGet(projectJsonUrl, true)
- .then(null, function() {
- // If there was no :8000, try again with one appended.
- if (!/:(\d)/.test(url)) {
- var newUrl = url.replace(/(.*?\/\/[^\/]*)/, '$1:8000');
- if (newUrl != url) {
- url = newUrl;
- projectJsonUrl = url + '/' + platformId + '/project.json';
- return ResourcesLoader.xhrGet(projectJsonUrl, true);
- }
- }
- throw new Error('Could not reach server at: ' + url);
- })
- .then(function(projectJson) {
- return ResourcesLoader.xhrGet(url + projectJson.configPath)
- .then(function(configXmlRaw) {
- var configXml = new DOMParser().parseFromString(configXmlRaw, 'text/xml');
- var appId = configXml.firstChild.getAttribute('id');
- return {
- url: url,
- projectJson: projectJson,
- configXml: configXmlRaw,
- appId: appId
- };
- });
- });
- }
- // TODO: update should be more atomic. Maybe download to a new directory?
- ServeInstaller.prototype.doUpdateApp = function() {
- if (this._assetManifest) {
- return this._doUpdateAppForReal();
- }
- var me = this;
- return this._readAssetManifest().then(function() {
- return me._doUpdateAppForReal();
- });
- };
-
- ServeInstaller.prototype._bulkDownload = function(files) {
- var installPath = this.installPath;
- var wwwPath = this._cachedProjectJson.wwwPath;
- var deferred = $q.defer();
- var self = this;
- // Write the asset manifest to disk at most every 2 seconds.
- var assetManifestDirty = 0; // 0 = false, 1 = true, 2 = terminate interval.
- var intervalId = setInterval(function() {
- if (assetManifestDirty) {
- if (assetManifestDirty == 2) {
- clearInterval(intervalId);
- }
- self._writeAssetManifest();
- assetManifestDirty = 0;
- }
- }, 2000);
-
- console.log('Number of files to fetch: ' + files.length);
- var i = 0;
- var totalFiles = files.length + 1; // + 1 for the updateAppMeta.
- deferred.notify((i + 1) / totalFiles);
- function downloadNext() {
- if (i > 0) {
- self._assetManifest[files[i - 1].path] = files[i - 1].etag;
- assetManifestDirty = 1;
- }
- if (!files[i]) {
- assetManifestDirty = 2;
- deferred.resolve();
- return;
- }
- deferred.notify((i + 1) / totalFiles);
-
- var sourceUrl = self.url + wwwPath + files[i].path;
- var destPath = installPath + '/www' + files[i].path;
- if (files[i].path == '/cordova_plugins.js') {
- destPath = installPath + '/orig-cordova_plugins.js';
- }
- console.log(destPath);
- i += 1;
- ResourcesLoader.downloadFromUrl(sourceUrl, destPath).then(downloadNext, deferred.reject);
- }
- downloadNext();
- return deferred.promise;
- };
-
- ServeInstaller.prototype._doUpdateAppForReal = function() {
- var installPath = this.installPath;
- var self = this;
-
- return fetchMetaServeData(this.url)
- .then(function(meta) {
- self._cachedProjectJson = meta.projectJson;
- self._cachedConfigXml = meta.configXml;
- self.appId = self.appId || meta.appId;
- var files = self._cachedProjectJson.wwwFileList;
- files = files.filter(function(f) {
- // Don't download cordova.js or plugins. We want to use the version bundled with the harness.
- // Do download cordova_plugins.js, since we need that to compare plugins with the harness.
- var isPlugin = /\/cordova\.js$|^\/plugins\//.exec(f.path);
- var haveAlready = self._assetManifest[f.path] == f.etag;
- return (!isPlugin && !haveAlready);
- });
- return ResourcesLoader.writeFileContents(installPath + '/config.xml', self._cachedConfigXml)
- .then(function() {
- return self._bulkDownload(files);
- });
- });
- };
-
- ServeInstaller.prototype.getPluginMetadata = function() {
- return ResourcesLoader.readFileContents(this.installPath + '/orig-cordova_plugins.js')
- .then(function(contents) {
- return PluginMetadata.extractPluginMetadata(contents);
- });
- };
-
- function createFromUrl(url, /*option*/ appId) {
- // Strip platform and trailing slash if they exist.
- url = urlCleanup(url);
- // Fetch config.xml.
- return fetchMetaServeData(url)
- .then(function(meta) {
- return new ServeInstaller(meta.url, appId || meta.appId);
- });
- }
-
- function createFromJson(url, appId) {
- return new ServeInstaller(url, appId);
- }
-
- AppsService.registerInstallerFactory({
- type: 'serve',
- createFromUrl: createFromUrl, // returns a promise.
- createFromJson: createFromJson // does not return a promise.
- });
- }]);
-})();
http://git-wip-us.apache.org/repos/asf/cordova-app-harness/blob/da594dff/www/cdvah/js/app.js
----------------------------------------------------------------------
diff --git a/www/cdvah/js/app.js b/www/cdvah/js/app.js
index c570e82..e324ab1 100644
--- a/www/cdvah/js/app.js
+++ b/www/cdvah/js/app.js
@@ -43,7 +43,7 @@ myApp.factory('urlCleanup', function() {
document.addEventListener('deviceready', function() {
cordova.filesystem.getDataDirectory(false, function(dirEntry) {
- myApp.value('INSTALL_DIRECTORY', dirEntry.toURL() + 'apps');
+ myApp.value('INSTALL_DIRECTORY', dirEntry.toURL() + 'apps/');
myApp.value('APPS_JSON', dirEntry.toURL() + 'apps.json');
window.requestFileSystem(window.TEMPORARY, 1 * 1024 * 1024, function(fs) {
myApp.value('TEMP_DIR', fs.root.toURL());