diff --git a/lib/roles/device/plugins/install.js b/lib/roles/device/plugins/install.js index bacb9845..858508ad 100644 --- a/lib/roles/device/plugins/install.js +++ b/lib/roles/device/plugins/install.js @@ -1,7 +1,9 @@ var stream = require('stream') +var util = require('util') var syrup = require('syrup') var request = require('request') +var Promise = require('bluebird') var logger = require('../../../util/logger') var wire = require('../../../wire') @@ -14,39 +16,109 @@ 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) - var source = new stream.Readable().wrap(request(message.url)) + var seq = 0 - push.send([ - channel - , wireutil.envelope(new wire.TransactionProgressMessage( - options.serial - , seq++ - , 'installing' - )) - ]) - adb.install(options.serial, source) + + function sendProgress(data, progress) { + push.send([ + channel + , wireutil.envelope(new wire.TransactionProgressMessage( + options.serial + , seq++ + , data + , progress + )) + ]) + } + + 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) + }) + }) + }) + .then(function(apk) { + sendProgress('Installing app') + return adb.installRemote(options.serial, apk) + }) .then(function() { if (message.launchActivity) { - push.send([ - channel - , wireutil.envelope(new wire.TransactionProgressMessage( - options.serial - , seq++ - , 'launching activity' - )) - ]) log.info( 'Launching activity with action "%s" on component "%s"' , message.launchActivity.action , message.launchActivity.component ) + sendProgress('Launching activity') return adb.startActivity(options.serial, message.launchActivity) } }) .then(function() { - log.info('Installed "%s"', message.url) push.send([ channel , wireutil.envelope(new wire.TransactionDoneMessage( @@ -64,7 +136,7 @@ module.exports = syrup.serial() options.serial , seq++ , false - , err.message + , 'Installation failed' )) ]) }) diff --git a/lib/wire/wire.proto b/lib/wire/wire.proto index 00d668df..f1495873 100644 --- a/lib/wire/wire.proto +++ b/lib/wire/wire.proto @@ -44,6 +44,7 @@ message TransactionProgressMessage { required string serial = 1; required uint32 seq = 2; optional string data = 3; + optional float progress = 4 [default = -1]; } message TransactionDoneMessage {