diff --git a/lib/roles/device/plugins/install.js b/lib/roles/device/plugins/install.js index 858508ad..f2c9a36f 100644 --- a/lib/roles/device/plugins/install.js +++ b/lib/roles/device/plugins/install.js @@ -16,36 +16,6 @@ module.exports = syrup.serial() .define(function(options, adb, router, push) { var log = logger.createLogger('device:plugins:install') - function fetchResource(options) { - var resolver = Promise.defer() - - function responseListener(res) { - if (res.statusCode === 200) { - resolver.resolve(res) - } - else { - resolver.reject(new Error(util.format( - 'Resource "%s" returned HTTP %d' - , options.url - , res.statusCode - ))) - } - } - - function errorListener(err) { - resolver.reject(err) - } - - var req = request(options) - .on('response', responseListener) - .on('error', errorListener) - - return resolver.promise.finally(function() { - req.removeListener('response', responseListener) - req.removeListener('error', errorListener) - }) - } - router.on(wire.InstallMessage, function(channel, message) { log.info('Installing "%s"', message.url) @@ -63,48 +33,64 @@ module.exports = syrup.serial() ]) } - sendProgress('Fetching app from server') - fetchResource({url: message.url}) - .then(function(res) { - var contentLength = parseInt(res.headers['content-length'], 10) - var source = new stream.Readable().wrap(res) - var target = '/data/local/tmp/_app.apk' - - sendProgress('Pushing app to the device', 0) - return adb.push(options.serial, source, target) - .then(function(transfer) { - var resolver = Promise.defer() - - function progressListener(stats) { - if (contentLength) { - sendProgress( - 'Pushing app to the device' - , stats.bytesTransferred / contentLength - ) - } - } - - function errorListener(err) { - resolver.reject(err) - } - - function endListener() { - resolver.resolve(target) - } - - transfer.on('progress', progressListener) - transfer.on('error', errorListener) - transfer.on('end', endListener) - - return resolver.promise.finally(function() { - transfer.removeListener('progress', progressListener) - transfer.removeListener('error', errorListener) - transfer.removeListener('end', endListener) - }) - }) + function pushApp() { + var req = request({ + url: message.url }) + + // We need to catch the Content-Length on the fly or we risk + // losing some of the initial chunks. + var contentLength = null + req.on('response', function(res) { + contentLength = parseInt(res.headers['content-length'], 10) + }) + + var source = new stream.Readable().wrap(req) + var target = '/data/local/tmp/_app.apk' + + return adb.push(options.serial, source, target) + .then(function(transfer) { + var resolver = Promise.defer() + + function progressListener(stats) { + if (contentLength) { + // Progress 0% to 70% + sendProgress( + 'Pushing app to the device' + , 70 * Math.max(0, Math.min( + 70 + , stats.bytesTransferred / contentLength + )) + ) + } + } + + function errorListener(err) { + resolver.reject(err) + } + + function endListener() { + resolver.resolve(target) + } + + transfer.on('progress', progressListener) + transfer.on('error', errorListener) + transfer.on('end', endListener) + + return resolver.promise.finally(function() { + transfer.removeListener('progress', progressListener) + transfer.removeListener('error', errorListener) + transfer.removeListener('end', endListener) + }) + }) + } + + // Progress 0% + sendProgress('Pushing app to the device', 0) + pushApp() .then(function(apk) { - sendProgress('Installing app') + // Progress 80% + sendProgress('Installing app', 80) return adb.installRemote(options.serial, apk) }) .then(function() { @@ -114,7 +100,8 @@ module.exports = syrup.serial() , message.launchActivity.action , message.launchActivity.component ) - sendProgress('Launching activity') + // Progress 90% + sendProgress('Launching activity', 90) return adb.startActivity(options.serial, message.launchActivity) } }) @@ -125,6 +112,7 @@ module.exports = syrup.serial() options.serial , seq++ , true + , 'Installation complete' )) ]) }) diff --git a/lib/wire/wire.proto b/lib/wire/wire.proto index f1495873..3434ad43 100644 --- a/lib/wire/wire.proto +++ b/lib/wire/wire.proto @@ -44,7 +44,7 @@ message TransactionProgressMessage { required string serial = 1; required uint32 seq = 2; optional string data = 3; - optional float progress = 4 [default = -1]; + optional uint32 progress = 4 [default = 0]; } message TransactionDoneMessage { diff --git a/res/app/components/stf/control/control-service.js b/res/app/components/stf/control/control-service.js index 2e9051d0..aa44d0ad 100644 --- a/res/app/components/stf/control/control-service.js +++ b/res/app/components/stf/control/control-service.js @@ -99,18 +99,18 @@ module.exports = function ControlServiceFactory( , method: 'POST' , file: files[0] }) - .success(function(data) { - console.log('success', arguments) - var app = data.manifest.application + .then(function(response) { + var manifest = response.data.manifest + var app = manifest.application var tx = TransactionService.create(devices) var params = { - url: data.url + url: response.data.url } if (app.launcherActivities.length) { var activity = app.launcherActivities[0] params.launchActivity = { action: 'android.intent.action.MAIN' - , component: data.manifest.package + '/' + activity.name + , component: manifest.package + '/' + activity.name , category: ['android.intent.category.LAUNCHER'] , flags: 0x10200000 } @@ -118,12 +118,6 @@ module.exports = function ControlServiceFactory( socket.emit('device.install', channel, tx.channel, params) return tx }) - .error(function() { - console.log('error', arguments) - }) - .progress(function() { - console.log('progress', arguments) - }) } } diff --git a/res/app/components/stf/control/transaction-service.js b/res/app/components/stf/control/transaction-service.js index 0742675c..93cc7e98 100644 --- a/res/app/components/stf/control/transaction-service.js +++ b/res/app/components/stf/control/transaction-service.js @@ -58,6 +58,7 @@ module.exports = function TransactionServiceFactory(socket) { resolver.promise.finally(function() { result.settled = true + result.progress = 100 }) function readQueue() { @@ -72,19 +73,24 @@ module.exports = function TransactionServiceFactory(socket) { if (message.success) { if (message.data) { - result.data[seq] = message.data + result.lastData = result.data[seq] = message.data } } else { - result.error = message.data + result.lastData = result.error = message.data } resolver.resolve(result) return } + else { + if (message.progress) { + result.progress = message.progress + } + } foundAny = true - result.data[seq++] = message.data + result.lastData = result.data[seq++] = message.data } if (foundAny) { @@ -111,8 +117,10 @@ module.exports = function TransactionServiceFactory(socket) { this.device = device this.settled = false this.success = false + this.progress = 0 this.error = null this.data = [] + this.lastData = null } transactionService.create = function(devices) { diff --git a/res/app/device-control/device-control-controller.js b/res/app/device-control/device-control-controller.js index 1005e9cf..7f328f5f 100644 --- a/res/app/device-control/device-control-controller.js +++ b/res/app/device-control/device-control-controller.js @@ -9,6 +9,24 @@ module.exports = function DeviceControlCtrl( $scope.control = null $scope.device = null $scope.control = null + $scope.installation = null + + $scope.install = function($files) { + return $scope.control.install($files) + .then(function(tx) { + return tx.promise + .progressed(function(results) { + $scope.$apply(function() { + $scope.installation = results[0] + }) + }) + .then(function(results) { + $scope.$apply(function() { + $scope.installation = results[0] + }) + }) + }) + } DeviceService.get($routeParams.serial, $scope) .then(function(device) { diff --git a/res/app/device-control/device-control.jade b/res/app/device-control/device-control.jade index e87e56a9..23ed8fb7 100644 --- a/res/app/device-control/device-control.jade +++ b/res/app/device-control/device-control.jade @@ -3,7 +3,10 @@ h1 {{ device.serial }} {{ device.present ? 'present' : 'absent' }} button(ng-click='showScreen = !showScreen') Show/Hide button(ng-click='control.identify()') Identify -div(ng-controller='DeviceScreenCtrl', ng-file-drop='control.install($files)') +div(ng-if='installation') + strong Installation status: {{ installation.progress}}% ({{ installation.lastData }}) + +div(ng-controller='DeviceScreenCtrl', ng-file-drop='install($files)') device-screen(style='width: 400px; height: 600px; background: gray') button(ng-click='control.menu()') Menu