1
0
Fork 0
mirror of https://github.com/openstf/stf synced 2025-10-04 18:29:17 +02:00

Show progress for URL installation. Temporarily break upload installation.

This commit is contained in:
Simo Kinnunen 2014-04-04 17:27:19 +09:00
parent 1d2afa65b4
commit 44378e625f
9 changed files with 288 additions and 88 deletions

View file

@ -431,11 +431,26 @@ program
, 'where to save files'
, String
, os.tmpdir())
.option('--id <id>'
, 'communication identifier'
, String
, 'storage')
.option('--connect-push <endpoint>'
, 'push endpoint'
, cliutil.list)
.action(function(options) {
if (!options.connectPush) {
this.missingArgument('--connect-push')
}
require('./roles/storage/temp')({
port: options.port
, publicIp: options.publicIp
, saveDir: options.saveDir
, id: options.id
, endpoints: {
push: options.connectPush
}
})
})
@ -591,6 +606,7 @@ program
, procutil.fork(__filename, [
'storage-temp'
, '--port', options.storagePort
, '--connect-push', options.bindDevPull
])
]

View file

@ -1,6 +1,7 @@
var http = require('http')
var events = require('events')
var path = require('path')
var util = require('util')
var express = require('express')
var validator = require('express-validator')
@ -9,6 +10,7 @@ var zmq = require('zmq')
var Promise = require('bluebird')
var httpProxy = require('http-proxy')
var _ = require('lodash')
var request = Promise.promisifyAll(require('request'))
var logger = require('../util/logger')
var pathutil = require('../util/pathutil')
@ -446,6 +448,30 @@ module.exports = function(options) {
)
])
})
.on('storage.upload', function(channel, responseChannel, data) {
joinChannel(responseChannel)
request.postAsync({
url: util.format('%sapi/v1/resources', options.storageUrl)
, json: true
, body: {
url: data.url
, channel: responseChannel
}
})
.catch(function(err) {
log.error('Storage upload had an error', err.stack)
leaveChannel(responseChannel)
push.send([
channel
, wireutil.envelope(new wire.TransactionDoneMessage(
'storage'
, 0
, false
, 'fail'
))
])
})
})
})
.finally(function() {
// Clean up all listeners and subscriptions

View file

@ -94,7 +94,7 @@ module.exports = syrup.serial()
sendProgress('installing_app', 80)
return adb.installRemote(options.serial, apk)
})
.timeout(10000)
.timeout(30000)
.then(function() {
if (message.launchActivity) {
log.info(

View file

@ -7,11 +7,15 @@ var formidable = require('formidable')
var Promise = require('bluebird')
var ApkReader = require('adbkit-apkreader')
var request = require('request')
var progress = require('request-progress')
var temp = require('temp')
var zmq = require('zmq')
var logger = require('../../util/logger')
var requtil = require('../../util/requtil')
var Storage = require('../../util/storage')
var wire = require('../../wire')
var wireutil = require('../../wire/util')
module.exports = function(options) {
var log = logger.createLogger('storage-temp')
@ -19,6 +23,13 @@ module.exports = function(options) {
, server = http.createServer(app)
, storage = new Storage()
// Output
var push = zmq.socket('push')
options.endpoints.push.forEach(function(endpoint) {
log.info('Sending output to %s', endpoint)
push.connect(endpoint)
})
app.set('strict routing', true)
app.set('case sensitive routing', true)
app.set('trust proxy', true)
@ -27,22 +38,40 @@ module.exports = function(options) {
log.info('Cleaning up inactive resource "%s"', id)
})
function process(file) {
log.info('Processing "%s"', file.path)
function processFile(file) {
var resolver = Promise.defer()
var reader = ApkReader.readFile(file.path)
var manifest = reader.readManifestSync()
log.info('Processing file "%s"', file.path)
resolver.progress({
percent: 0
})
process.nextTick(function() {
try {
var reader = ApkReader.readFile(file.path)
var manifest = reader.readManifestSync()
resolver.resolve(manifest)
}
catch (err) {
resolver.reject(err)
}
})
return resolver.promise
}
function storeFile(file) {
var id = storage.store(file)
return {
url: util.format(
return Promise.resolve({
id: id
, url: util.format(
'http://%s:%s/api/v1/resources/%s'
, options.publicIp
, options.port
, id
)
, manifest: manifest
}
})
}
function download(url) {
@ -57,15 +86,25 @@ module.exports = function(options) {
resolver.reject(err)
}
function progressListener(state) {
resolver.progress(state)
}
function closeListener() {
resolver.resolve({
path: path
})
}
resolver.progress({
percent: 0
})
try {
var dl = request(url)
.pipe(fs.createWriteStream(path))
var req = progress(request(url))
.on('progress', progressListener)
var save = req.pipe(fs.createWriteStream(path))
.on('error', errorListener)
.on('close', closeListener)
}
@ -74,25 +113,97 @@ module.exports = function(options) {
}
return resolver.promise.finally(function() {
dl.removeListener('error', errorListener)
dl.removeListener('close', closeListener)
req.removeListener('progress', progressListener)
save.removeListener('error', errorListener)
save.removeListener('close', closeListener)
})
}
app.post('/api/v1/resources', function(req, res) {
function handle(fields, files) {
var seq = 0
function sendProgress(data, progress) {
if (fields.channel) {
push.send([
fields.channel
, wireutil.envelope(new wire.TransactionProgressMessage(
options.id
, seq++
, data
, progress
))
])
}
}
function sendDone(success, data, body) {
if (fields.channel) {
push.send([
fields.channel
, wireutil.envelope(new wire.TransactionDoneMessage(
options.id
, seq++
, success
, data
, body ? JSON.stringify(body) : null
))
])
}
}
if (files.file) {
return processFile(files.file)
.progressed(function(progress) {
sendProgress('processing', 0.9 * progress.percent)
})
.then(function(manifest) {
sendProgress('storing', 90)
return storeFile(files.file)
.then(function(data) {
data.manifest = manifest
sendDone(true, 'success', data)
return data
})
})
.catch(function(err) {
sendDone(false, 'fail')
return Promise.reject(err)
})
}
else if (fields.url) {
return download(fields.url)
.progressed(function(progress) {
sendProgress('uploading', 0.7 * progress.percent)
})
.then(function(file) {
return processFile(file)
.progressed(function(progress) {
sendProgress('processing', 70 + 0.2 * progress.percent)
})
.then(function(manifest) {
sendProgress('storing', 90)
return storeFile(file)
.then(function(data) {
data.manifest = manifest
sendDone(true, 'success', data)
return data
})
})
})
.catch(function(err) {
sendDone(false, 'fail')
return Promise.reject(err)
})
}
else {
throw new requtil.ValidationError('"file" or "url" is required')
}
}
var form = Promise.promisifyAll(new formidable.IncomingForm())
form.parseAsync(req)
.spread(function(fields, files) {
if (files.file) {
return process(files.file)
}
else if (fields.url) {
return download(fields.url).then(process)
}
else {
throw new requtil.ValidationError('"file" or "url" is required')
}
})
.spread(handle)
.then(function(data) {
data.success = true
res.json(201, data)

View file

@ -44,17 +44,18 @@ message Envelope {
}
message TransactionProgressMessage {
required string serial = 1;
required string source = 1;
required uint32 seq = 2;
optional string data = 3;
optional uint32 progress = 4 [default = 0];
}
message TransactionDoneMessage {
required string serial = 1;
required string source = 1;
required uint32 seq = 2;
required bool success = 3;
optional string data = 4;
optional string body = 5;
}
// Heartbeat

View file

@ -109,7 +109,29 @@ module.exports = function ControlServiceFactory(
return tx
}
function install(options) {
this.upload = function(files) {
var tx = TransactionService.create({
id: 'storage'
})
if (typeof files === 'string') {
socket.emit('storage.upload', channel, tx.channel, {
url: files
})
return tx
}
else {
return $upload.upload({
url: '/api/v1/resources'
, method: 'POST'
, file: files[0]
})
.then(function(response) {
return install(response.data)
})
}
}
this.install = function(options) {
var app = options.manifest.application
var tx = TransactionService.create(target)
var params = {
@ -129,31 +151,6 @@ module.exports = function ControlServiceFactory(
return tx
}
this.install = function(files) {
if (typeof files === 'string') {
return $http({
url: '/api/v1/resources'
, method: 'POST'
, data: {
url: files
}
})
.then(function(response) {
return install(response.data)
})
}
else {
return $upload.upload({
url: '/api/v1/resources'
, method: 'POST'
, file: files[0]
})
.then(function(response) {
return install(response.data)
})
}
}
this.uninstall = function(pkg) {
var tx = TransactionService.create(target)
socket.emit('device.uninstall', channel, tx.channel, {

View file

@ -8,20 +8,20 @@ module.exports = function TransactionServiceFactory(socket) {
return 'tx.' + uuid.v4()
}
function MultiDeviceTransaction(devices) {
function MultiTargetTransaction(targets, options) {
var pending = Object.create(null)
, results = []
, channel = createChannel()
function doneListener(someChannel, data) {
if (someChannel === channel) {
pending[data.serial].done(data)
pending[data.source].done(data)
}
}
function progressListener(someChannel, data) {
if (someChannel === channel) {
pending[data.serial].progress(data)
pending[data.source].progress(data)
}
}
@ -30,10 +30,11 @@ module.exports = function TransactionServiceFactory(socket) {
this.channel = channel
this.results = results
this.promise = Promise.settle(devices.map(function(device) {
var pendingResult = new PendingTransactionResult(device)
pending[device.serial] = pendingResult
results.push(pendingResult.result)
this.promise = Promise.settle(targets.map(function(target) {
var result = new options.result(target)
var pendingResult = new PendingTransactionResult(result)
pending[options.id ? target[options.id] : target.id] = pendingResult
results.push(result)
return pendingResult.promise
}))
.finally(function() {
@ -49,9 +50,9 @@ module.exports = function TransactionServiceFactory(socket) {
})
}
function SingleDeviceTransaction(device) {
var pending = new PendingTransactionResult(device)
, result = pending.result
function SingleTargetTransaction(target, options) {
var result = new options.result(target)
, pending = new PendingTransactionResult(result)
, channel = createChannel()
function doneListener(someChannel, data) {
@ -86,9 +87,8 @@ module.exports = function TransactionServiceFactory(socket) {
})
}
function PendingTransactionResult(device) {
function PendingTransactionResult(result) {
var resolver = Promise.defer()
, result = new TransactionResult(device)
, seq = 0
, last = null
, unplaced = []
@ -112,6 +112,9 @@ module.exports = function TransactionServiceFactory(socket) {
if (message.data) {
result.lastData = result.data[seq] = message.data
}
if (message.body) {
result.body = JSON.parse(message.body)
}
}
else {
result.lastData = result.error = message.data
@ -150,24 +153,42 @@ module.exports = function TransactionServiceFactory(socket) {
this.promise = resolver.promise
}
function TransactionResult(device) {
this.device = device
function TransactionResult(source) {
this.source = source
this.settled = false
this.success = false
this.progress = 0
this.error = null
this.data = []
this.lastData = null
this.body = null
}
transactionService.create = function(target) {
function DeviceTransactionResult(device) {
TransactionResult.call(this, device)
this.device = this.source
}
transactionService.create = function(target, options) {
if (options && !options.result) {
options.result = TransactionResult
}
if (Array.isArray(target)) {
return new MultiDeviceTransaction(target)
return new MultiTargetTransaction(target, options || {
result: DeviceTransactionResult
, id: 'serial'
})
}
else {
return new SingleDeviceTransaction(target)
return new SingleTargetTransaction(target, options || {
result: DeviceTransactionResult
, id: 'serial'
})
}
}
transactionService.TransactionResult = TransactionResult
return transactionService
}

View file

@ -1,34 +1,48 @@
module.exports = function UploadCtrl($scope, $rootScope, SettingsService, gettext) {
$scope.upload = null
$scope.installation = null
$scope.clear = function () {
$scope.upload = null
$scope.installation = null
}
$rootScope.install = function ($files) {
$scope.installation = {
$scope.upload = {
progress: 0,
lastData: 'uploading'
}
return $rootScope.control.install($files)
.then(function (tx) {
var manifest = tx.manifest
return tx.promise
.progressed(function (result) {
$scope.$apply(function () {
result.manifest = manifest
$scope.installation = result
var upload = $rootScope.control.upload($files)
$scope.installation = null
return upload.promise
.progressed(function(uploadResult) {
$scope.$apply(function() {
$scope.upload = uploadResult
})
})
.then(function(uploadResult) {
$scope.$apply(function() {
$scope.upload = uploadResult
})
if (uploadResult.success) {
var install = $rootScope.control.install(uploadResult.body)
return install.promise
.progressed(function(installResult) {
$scope.$apply(function() {
installResult.manifest = uploadResult.body.manifest
$scope.installation = installResult
})
})
})
.then(function (result) {
$scope.$apply(function () {
result.manifest = manifest
$scope.treeData = manifest
$scope.installation = result
.then(function(installResult) {
$scope.$apply(function() {
installResult.manifest = uploadResult.body.manifest
$scope.treeData = installResult.manifest
$scope.installation = installResult
})
})
})
}
})
}

View file

@ -32,6 +32,22 @@
//treecontrol.tree-classic(tree-model='treeData', options='treeOptions')
span employee: {{node.name}} age {{node.age}}
.upload-status(ng-if='upload && !upload.settled')
div(ng-switch='upload.lastData')
strong(ng-switch-when='uploading')
span(translate) Uploading...
strong(ng-switch-when='processing')
span(translate) Processing...
strong(ng-switch-when='storing')
span(translate) Storing...
strong(ng-switch-when='fail')
span(translate) Upload failed
span(ng-hide='upload.settled') ({{upload.progress}}%)
progressbar(max='100', value='upload.progress', ng-if='!upload.settled',
ng-class='{"active": !upload.settled}').progress-striped
.upload-status(ng-if='installation')
accordion(close-others='false')
@ -41,8 +57,6 @@
span {{installation.manifest.package || "App" }}
div(ng-switch='installation.lastData')
strong(ng-switch-when='uploading')
span(translate) Starting to upload...
strong(ng-switch-when='pushing_app')
span(translate) Pushing app...
strong(ng-switch-when='installing_app')