Merge "Add support for uploading and downloading." am: 316ce8acca
Original change: https://android-review.googlesource.com/c/platform/development/+/1728650 Change-Id: Ie410316f1e99a045445a5fee6e19abc799e7c547
This commit is contained in:
@@ -15,6 +15,13 @@ In this case we use `lunch 17` as an example (aosp-x86_64-cf), you can choose wh
|
||||
|
||||
Then, in this directory, please use `npm build` to install the dependencies.
|
||||
|
||||
Create a `target` directory to store the target files and a `output` directory
|
||||
to store the output files:
|
||||
```
|
||||
mkdir target
|
||||
mkdir output
|
||||
```
|
||||
|
||||
Finally, run the python http-server and vue.js server:
|
||||
```
|
||||
python3 web_server.py &
|
||||
|
||||
86
tools/otagui/ota_interface.py
Normal file
86
tools/otagui/ota_interface.py
Normal file
@@ -0,0 +1,86 @@
|
||||
import subprocess
|
||||
import os
|
||||
import json
|
||||
import pipes
|
||||
from threading import Lock
|
||||
import logging
|
||||
|
||||
class ProcessesManagement:
|
||||
def __init__(self):
|
||||
self.__container = {}
|
||||
self.__lock = Lock()
|
||||
|
||||
def set(self, name, value):
|
||||
with self.__lock:
|
||||
self.__container[name] = value
|
||||
|
||||
def get(self, name):
|
||||
with self.__lock:
|
||||
return self.__container[name]
|
||||
|
||||
def get_keys(self):
|
||||
with self.__lock:
|
||||
return self.__container.keys()
|
||||
|
||||
def get_status_by_ID(self, id=0, details=False):
|
||||
status = {}
|
||||
if not id in self.get_keys():
|
||||
return '{}'
|
||||
else:
|
||||
status['id'] = id
|
||||
if self.get(id).poll() == None:
|
||||
status['status'] = 'Running'
|
||||
elif self.get(id).poll() == 0:
|
||||
status['status'] = 'Finished'
|
||||
status['path'] = os.path.join('output', str(id) + '.zip')
|
||||
else:
|
||||
status['status'] = 'Error'
|
||||
try:
|
||||
if details:
|
||||
with open(os.path.join('output', 'stdout.' + str(id)), 'r') as fout:
|
||||
status['stdout'] = fout.read()
|
||||
with open(os.path.join('output', 'stderr.' + str(id)), 'r') as ferr:
|
||||
status['stderr'] = ferr.read()
|
||||
except FileNotFoundError:
|
||||
status['stdout'] = 'NO STD OUTPUT IS FOUND'
|
||||
status['stderr'] = 'NO STD OUTPUT IS FOUND'
|
||||
return status
|
||||
|
||||
def get_status(self):
|
||||
return [self.get_status_by_ID(id=id) for id in self.get_keys()]
|
||||
|
||||
def get_list(self, dir):
|
||||
files = os.listdir(dir)
|
||||
return files
|
||||
|
||||
def ota_generate(self, args, id=0):
|
||||
command = ['ota_from_target_files']
|
||||
# Check essential configuration is properly set
|
||||
if not os.path.isfile('target/' + args['target']):
|
||||
raise FileNotFoundError
|
||||
if not args['output']:
|
||||
raise SyntaxError
|
||||
if args['verbose']:
|
||||
command.append('-v')
|
||||
command.append('-k')
|
||||
command.append(
|
||||
'../../../build/make/target/product/security/testkey')
|
||||
if args['incremental']:
|
||||
if not os.path.isfile('target/' + args['incremental']):
|
||||
raise FileNotFoundError
|
||||
command.append('-i')
|
||||
command.append('target/' + args['incremental'])
|
||||
command.append('target/' + args['target'])
|
||||
command.append(args['output'])
|
||||
|
||||
stderr_pipes = pipes.Template()
|
||||
stdout_pipes = pipes.Template()
|
||||
ferr = stderr_pipes.open(os.path.join(
|
||||
'output', 'stderr.'+str(id)), 'w')
|
||||
fout = stdout_pipes.open(os.path.join(
|
||||
'output', 'stdout.'+str(id)), 'w')
|
||||
self.set(id, subprocess.Popen(
|
||||
command, stderr=ferr, stdout=fout))
|
||||
logging.info(
|
||||
'Starting generating OTA package with id {}: \n {}'
|
||||
.format(id, command))
|
||||
44
tools/otagui/package-lock.json
generated
44
tools/otagui/package-lock.json
generated
@@ -12,6 +12,7 @@
|
||||
"eslint-config-airbnb-base": "^14.2.1",
|
||||
"vue": "^3.0.0-0",
|
||||
"vue-router": "^4.0.0-0",
|
||||
"vue-uuid": "^2.0.2",
|
||||
"vuex": "^4.0.0-0"
|
||||
},
|
||||
"devDependencies": {
|
||||
@@ -1479,6 +1480,11 @@
|
||||
"source-map": "^0.6.1"
|
||||
}
|
||||
},
|
||||
"node_modules/@types/uuid": {
|
||||
"version": "8.3.0",
|
||||
"resolved": "https://registry.npmjs.org/@types/uuid/-/uuid-8.3.0.tgz",
|
||||
"integrity": "sha512-eQ9qFW/fhfGJF8WKHGEHZEyVWfZxrT+6CLIJGBcZPfxUh/+BnEj+UCGYMlr9qZuX/2AltsvwrGqp0LhEW8D0zQ=="
|
||||
},
|
||||
"node_modules/@types/webpack": {
|
||||
"version": "4.41.21",
|
||||
"resolved": "https://registry.npmjs.org/@types/webpack/-/webpack-4.41.21.tgz",
|
||||
@@ -13339,6 +13345,23 @@
|
||||
"integrity": "sha512-4gDntzrifFnCEvyoO8PqyJDmguXgVPxKiIxrBKjIowvL9l+N66196+72XVYR8BBf1Uv1Fgt3bGevJ+sEmxfZzw==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/vue-uuid": {
|
||||
"version": "2.0.2",
|
||||
"resolved": "https://registry.npmjs.org/vue-uuid/-/vue-uuid-2.0.2.tgz",
|
||||
"integrity": "sha512-PRf1CHg3uKi77bVRyAuW2u/T2PO9LxMr7cw9t9rNdpZTkNDyw1Fx6eJVL+8JOtM9VxxPkoZ/rwhXJ5l+X5AYzQ==",
|
||||
"dependencies": {
|
||||
"@types/uuid": "^8.0.0",
|
||||
"uuid": "^8.1.0"
|
||||
}
|
||||
},
|
||||
"node_modules/vue-uuid/node_modules/uuid": {
|
||||
"version": "8.3.2",
|
||||
"resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz",
|
||||
"integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==",
|
||||
"bin": {
|
||||
"uuid": "dist/bin/uuid"
|
||||
}
|
||||
},
|
||||
"node_modules/vuex": {
|
||||
"version": "4.0.0-beta.4",
|
||||
"resolved": "https://registry.npmjs.org/vuex/-/vuex-4.0.0-beta.4.tgz",
|
||||
@@ -15792,6 +15815,11 @@
|
||||
"source-map": "^0.6.1"
|
||||
}
|
||||
},
|
||||
"@types/uuid": {
|
||||
"version": "8.3.0",
|
||||
"resolved": "https://registry.npmjs.org/@types/uuid/-/uuid-8.3.0.tgz",
|
||||
"integrity": "sha512-eQ9qFW/fhfGJF8WKHGEHZEyVWfZxrT+6CLIJGBcZPfxUh/+BnEj+UCGYMlr9qZuX/2AltsvwrGqp0LhEW8D0zQ=="
|
||||
},
|
||||
"@types/webpack": {
|
||||
"version": "4.41.21",
|
||||
"resolved": "https://registry.npmjs.org/@types/webpack/-/webpack-4.41.21.tgz",
|
||||
@@ -25710,6 +25738,22 @@
|
||||
"integrity": "sha512-4gDntzrifFnCEvyoO8PqyJDmguXgVPxKiIxrBKjIowvL9l+N66196+72XVYR8BBf1Uv1Fgt3bGevJ+sEmxfZzw==",
|
||||
"dev": true
|
||||
},
|
||||
"vue-uuid": {
|
||||
"version": "2.0.2",
|
||||
"resolved": "https://registry.npmjs.org/vue-uuid/-/vue-uuid-2.0.2.tgz",
|
||||
"integrity": "sha512-PRf1CHg3uKi77bVRyAuW2u/T2PO9LxMr7cw9t9rNdpZTkNDyw1Fx6eJVL+8JOtM9VxxPkoZ/rwhXJ5l+X5AYzQ==",
|
||||
"requires": {
|
||||
"@types/uuid": "^8.0.0",
|
||||
"uuid": "^8.1.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"uuid": {
|
||||
"version": "8.3.2",
|
||||
"resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz",
|
||||
"integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg=="
|
||||
}
|
||||
}
|
||||
},
|
||||
"vuex": {
|
||||
"version": "4.0.0-beta.4",
|
||||
"resolved": "https://registry.npmjs.org/vuex/-/vuex-4.0.0-beta.4.tgz",
|
||||
|
||||
@@ -12,6 +12,7 @@
|
||||
"eslint-config-airbnb-base": "^14.2.1",
|
||||
"vue": "^3.0.0-0",
|
||||
"vue-router": "^4.0.0-0",
|
||||
"vue-uuid": "^2.0.2",
|
||||
"vuex": "^4.0.0-0"
|
||||
},
|
||||
"devDependencies": {
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<template>
|
||||
<label class="file-select">
|
||||
<div class="select-button">
|
||||
<span v-if="value">Selected File: {{ value.name }}</span>
|
||||
<span v-if="label">{{ label }}</span>
|
||||
<span v-else>Select File</span>
|
||||
</div>
|
||||
<input
|
||||
@@ -14,12 +14,14 @@
|
||||
<script>
|
||||
export default {
|
||||
props: {
|
||||
value: File
|
||||
label: {
|
||||
type: String,
|
||||
default: ''
|
||||
}
|
||||
},
|
||||
|
||||
methods: {
|
||||
handleFileChange(e) {
|
||||
this.$emit('input', e.target.files[0])
|
||||
this.$emit('file-select', e.target.files)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
<template>
|
||||
<label> {{ label }} </label>
|
||||
<br>
|
||||
<input
|
||||
v-bind="$attrs"
|
||||
:placeholder="label"
|
||||
|
||||
@@ -1,8 +1,12 @@
|
||||
<template>
|
||||
<div class="job-display">
|
||||
<span>Status of Job.{{ job.id }}</span>
|
||||
<h4>{{ job.status }}</h4>
|
||||
</div>
|
||||
<router-link
|
||||
:to="{ name: 'JobDetails', params: {id: job.id} }"
|
||||
>
|
||||
<div class="job-display">
|
||||
<span>Status of Job.{{ job.id }}</span>
|
||||
<h4>{{ job.status }}</h4>
|
||||
</div>
|
||||
</router-link>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
|
||||
53
tools/otagui/src/components/UploadFile.vue
Normal file
53
tools/otagui/src/components/UploadFile.vue
Normal file
@@ -0,0 +1,53 @@
|
||||
<template>
|
||||
<div>
|
||||
<BaseFile
|
||||
label="Upload a target file"
|
||||
@file-select="UploadHandler"
|
||||
/>
|
||||
<br>
|
||||
<progress
|
||||
v-if="uploadPercentage > 0"
|
||||
max="100"
|
||||
:value.prop="uploadPercentage"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import BaseFile from '@/components/BaseFile.vue'
|
||||
import ApiService from '../services/ApiService.js'
|
||||
|
||||
export default {
|
||||
components: {
|
||||
BaseFile,
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
files: '',
|
||||
uploadPercentage: 0,
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
async UploadHandler(files) {
|
||||
this.file = files[0]
|
||||
console.log(this.file.name)
|
||||
try {
|
||||
let response = await ApiService.uploadTarget(this.file, (event) => {
|
||||
this.uploadPercentage = Math.round((100 * event.loaded) / event.total)
|
||||
})
|
||||
console.log(response)
|
||||
this.$emit('file-uploaded')
|
||||
} catch (err) {
|
||||
console.log(err)
|
||||
}
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
progress {
|
||||
width: 100%;
|
||||
height: 40px;
|
||||
}
|
||||
</style>
|
||||
@@ -1,6 +1,6 @@
|
||||
import axios from 'axios'
|
||||
|
||||
const apiClient = axios.create({
|
||||
const apiClient = axios.create({
|
||||
baseURL: 'http://localhost:8000',
|
||||
withCredentials: false,
|
||||
headers: {
|
||||
@@ -13,11 +13,25 @@ export default {
|
||||
getJobs() {
|
||||
return apiClient.get("/check")
|
||||
},
|
||||
getJobById(id) {
|
||||
return apiClient.get("/check/" + id)
|
||||
},
|
||||
getFileList(path) {
|
||||
return apiClient.get("/file" + path)
|
||||
},
|
||||
uploadTarget(file, onUploadProgress) {
|
||||
let formData = new FormData()
|
||||
formData.append('file', file)
|
||||
return apiClient.post("/file/" + file.name,
|
||||
formData,
|
||||
{
|
||||
onUploadProgress
|
||||
})
|
||||
},
|
||||
async postInput(input, id) {
|
||||
try {
|
||||
const response = await apiClient.post(
|
||||
'/run/' + id, input)
|
||||
console.log('Response:', response)
|
||||
return response
|
||||
} catch (err) {
|
||||
console.log('err:', err)
|
||||
|
||||
@@ -1,23 +1,23 @@
|
||||
<template>
|
||||
<div v-if="job">
|
||||
<h3> Job. {{ job.id }} {{ job.status }}</h3>
|
||||
<h3>Job. {{ job.id }} {{ job.status }}</h3>
|
||||
<div>
|
||||
<h4> STDERR </h4>
|
||||
<h4>STDERR</h4>
|
||||
<div class="stderr">
|
||||
{{ job.stderr }}
|
||||
<p ref="stderr_bottom" />
|
||||
</div>
|
||||
<h4> STDOUT </h4>
|
||||
<h4>STDOUT</h4>
|
||||
<div class="stdout">
|
||||
{{ job.stdout }}
|
||||
<p ref="stdout_bottom" />
|
||||
</div>
|
||||
</div>
|
||||
<br>
|
||||
<a
|
||||
v-if="job.status=='Finished'"
|
||||
v-if="job.status == 'Finished'"
|
||||
:href="download"
|
||||
>
|
||||
Download
|
||||
</a>
|
||||
> Download </a>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@@ -32,33 +32,35 @@ export default {
|
||||
},
|
||||
computed: {
|
||||
download() {
|
||||
return "http://localhost:8000/download/" + this.job.path
|
||||
}
|
||||
return 'http://localhost:8000/download/' + this.job.path
|
||||
},
|
||||
},
|
||||
created() {
|
||||
this.updateStatus()
|
||||
},
|
||||
methods: {
|
||||
async updateStatus() {
|
||||
// fetch job (by id) and set local job data
|
||||
// fetch job (by id) and set local job data
|
||||
try {
|
||||
let response = await ApiService.getJobById(this.id)
|
||||
this.job = response.data
|
||||
await this.$refs.stdout_bottom.scrollIntoView({ behavior: 'smooth' })
|
||||
await this.$refs.stderr_bottom.scrollIntoView({ behavior: 'smooth' })
|
||||
} catch (err) {
|
||||
console.log(err)
|
||||
}
|
||||
catch (err) {
|
||||
console.log(error)
|
||||
}
|
||||
if (this.job.status=='Running') {
|
||||
if (this.job.status == 'Running') {
|
||||
setTimeout(this.updateStatus, 1000)
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.stderr, .stdout {
|
||||
overflow: scroll;
|
||||
height: 200px;
|
||||
.stderr,
|
||||
.stdout {
|
||||
overflow: scroll;
|
||||
height: 200px;
|
||||
}
|
||||
</style>
|
||||
@@ -1,57 +1,63 @@
|
||||
<template>
|
||||
<div>
|
||||
<form @submit.prevent="sendForm">
|
||||
<BaseInput
|
||||
<UploadFile @file-uploaded="fetchTargetList" />
|
||||
<br>
|
||||
<BaseSelect
|
||||
v-if="input.incrementalStatus"
|
||||
v-model="input.incremental"
|
||||
:disabled="!input.incrementalStatus"
|
||||
:label="'Source Package Path'"
|
||||
type="text"
|
||||
label="Select the source file"
|
||||
:options="targetList"
|
||||
/>
|
||||
|
||||
<BaseInput
|
||||
<BaseSelect
|
||||
v-model="input.target"
|
||||
label="Target File path"
|
||||
type="text"
|
||||
label="Select the target file"
|
||||
:options="targetList"
|
||||
/>
|
||||
|
||||
<BaseCheckbox
|
||||
v-model="input.verbose"
|
||||
:label="'Verbose'"
|
||||
/>
|
||||
|
||||
|
||||
<BaseCheckbox
|
||||
v-model="input.incrementalStatus"
|
||||
:label="'Incremental'"
|
||||
/>
|
||||
|
||||
<button
|
||||
type="button"
|
||||
@click="fetchTargetList"
|
||||
>
|
||||
Update File List
|
||||
</button>
|
||||
<div>
|
||||
<BaseCheckbox
|
||||
v-model="input.verbose"
|
||||
:label="'Verbose'"
|
||||
/>
|
||||
 
|
||||
<BaseCheckbox
|
||||
v-model="input.incrementalStatus"
|
||||
:label="'Incremental'"
|
||||
/>
|
||||
</div>
|
||||
<br>
|
||||
<BaseInput
|
||||
v-model="input.output"
|
||||
label="Output File path"
|
||||
type="text"
|
||||
v-model="input.extra"
|
||||
:label="'Extra Configurations'"
|
||||
/>
|
||||
|
||||
<br>
|
||||
<button type="submit">
|
||||
Submit
|
||||
</button>
|
||||
</form>
|
||||
|
||||
<pre> {{ input }} </pre>
|
||||
|
||||
<h3> Response from the server </h3>
|
||||
<div> {{ response_message }}</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import BaseInput from '@/components/BaseInput.vue'
|
||||
import BaseCheckbox from '@/components/BaseCheckbox.vue'
|
||||
import BaseSelect from '@/components/BaseSelect.vue'
|
||||
import ApiService from '../services/ApiService.js'
|
||||
import UploadFile from '@/components/UploadFile.vue'
|
||||
import { uuid } from 'vue-uuid'
|
||||
|
||||
export default {
|
||||
components: {
|
||||
BaseInput,
|
||||
BaseCheckbox
|
||||
BaseCheckbox,
|
||||
UploadFile,
|
||||
BaseSelect,
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
@@ -59,31 +65,61 @@ export default {
|
||||
input: {
|
||||
verbose: false,
|
||||
target: '',
|
||||
output: '',
|
||||
output: 'output/',
|
||||
incremental: '',
|
||||
incrementalStatus: false
|
||||
incrementalStatus: false,
|
||||
extra: '',
|
||||
},
|
||||
inputs: [],
|
||||
response_message : ''
|
||||
response_message: '',
|
||||
targetList: [],
|
||||
}
|
||||
},
|
||||
methods:{
|
||||
computed: {
|
||||
updateOutput() {
|
||||
return 'output/' + String(this.id) + '.zip'
|
||||
},
|
||||
},
|
||||
created() {
|
||||
this.fetchTargetList()
|
||||
this.updateUUID()
|
||||
},
|
||||
methods: {
|
||||
sendForm(e) {
|
||||
// console.log(this.input)
|
||||
ApiService.postInput(this.input, this.id)
|
||||
.then(Response => {
|
||||
.then((Response) => {
|
||||
this.response_message = Response.data
|
||||
alert(this.response_message)
|
||||
})
|
||||
.catch(err => {
|
||||
.catch((err) => {
|
||||
this.response_message = 'Error! ' + err
|
||||
})
|
||||
this.input = {
|
||||
verbose: false,
|
||||
target: '',
|
||||
output: '',
|
||||
output: 'output/',
|
||||
incremental: '',
|
||||
incrementalStatus: false
|
||||
incrementalStatus: false,
|
||||
extra: '',
|
||||
}
|
||||
}
|
||||
this.updateUUID()
|
||||
},
|
||||
async fetchTargetList() {
|
||||
try {
|
||||
let response = await ApiService.getFileList('/target')
|
||||
this.targetList = response.data
|
||||
} catch (err) {
|
||||
console.log('Fetch Error', err)
|
||||
}
|
||||
},
|
||||
updateUUID() {
|
||||
this.id = uuid.v1()
|
||||
this.input.output += String(this.id) + '.zip'
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
</style>
|
||||
@@ -1,20 +1,27 @@
|
||||
"""
|
||||
A simple local HTTP server for Android OTA package generation.
|
||||
Based on OTA_from_target_files.
|
||||
A local HTTP server for Android OTA package generation.
|
||||
Based on OTA_from_target_files.py
|
||||
|
||||
Usage::
|
||||
python ./web_server.py [<port>]
|
||||
|
||||
API::
|
||||
GET /check : check the status of all jobs
|
||||
[TODO] GET /check/id : check the status of the job with <id>
|
||||
POST /run/id : submit a job with <id>,
|
||||
GET /check/<id> : check the status of the job with <id>
|
||||
GET /file : fetch the target file list
|
||||
GET /download/<id> : download the ota package with <id>
|
||||
POST /run/<id> : submit a job with <id>,
|
||||
arguments set in a json uploaded together
|
||||
[TODO] POST /cancel/id : cancel a job with <id>
|
||||
POST /file/<filename> : upload a target file
|
||||
[TODO] POST /cancel/<id> : cancel a job with <id>
|
||||
|
||||
Other GET request will be redirected to the static request under 'dist' directory
|
||||
"""
|
||||
|
||||
from http.server import BaseHTTPRequestHandler, HTTPServer
|
||||
from http.server import BaseHTTPRequestHandler, SimpleHTTPRequestHandler, HTTPServer
|
||||
from socketserver import ThreadingMixIn
|
||||
from threading import Lock
|
||||
from ota_interface import ProcessesManagement
|
||||
import logging
|
||||
import json
|
||||
import pipes
|
||||
@@ -25,123 +32,114 @@ import os
|
||||
LOCAL_ADDRESS = '0.0.0.0'
|
||||
|
||||
|
||||
class ThreadSafeContainer:
|
||||
def __init__(self):
|
||||
self.__container = {}
|
||||
self.__lock = Lock()
|
||||
|
||||
def set(self, name, value):
|
||||
with self.__lock:
|
||||
self.__container[name] = value
|
||||
|
||||
def get(self, name):
|
||||
with self.__lock:
|
||||
return self.__container[name]
|
||||
|
||||
def get_keys(self):
|
||||
with self.__lock:
|
||||
return self.__container.keys()
|
||||
|
||||
|
||||
class RequestHandler(BaseHTTPRequestHandler):
|
||||
def get_status(self):
|
||||
statusList = []
|
||||
for id in PROCESSES.get_keys():
|
||||
status = {}
|
||||
status['id'] = id
|
||||
if PROCESSES.get(id).poll() == None:
|
||||
status['status'] = 'Running'
|
||||
elif PROCESSES.get(id).poll() == 0:
|
||||
status['status'] = 'Finished'
|
||||
else:
|
||||
status['status'] = 'Error'
|
||||
statusList.append(json.dumps(status))
|
||||
return '['+','.join(statusList)+']'
|
||||
|
||||
def ota_generate(self, args, id=0):
|
||||
command = ['ota_from_target_files']
|
||||
# Check essential configuration is properly set
|
||||
if not os.path.isfile(args['target']):
|
||||
raise FileNotFoundError
|
||||
if not args['output']:
|
||||
raise SyntaxError
|
||||
if args['verbose']:
|
||||
command.append('-v')
|
||||
command.append('-k')
|
||||
command.append(
|
||||
'../../../build/make/target/product/security/testkey')
|
||||
if args['incremental']:
|
||||
command.append('-i')
|
||||
command.append(args['incremental'])
|
||||
command.append(args['target'])
|
||||
command.append(args['output'])
|
||||
stderr_pipes = pipes.Template()
|
||||
stdout_pipes = pipes.Template()
|
||||
ferr = stderr_pipes.open('stderr', 'w')
|
||||
fout = stdout_pipes.open('stdout', 'w')
|
||||
PROCESSES.set(id, subprocess.Popen(
|
||||
command, stderr=ferr, stdout=fout))
|
||||
logging.info(
|
||||
'Starting generating OTA package with id {}: \n {}'
|
||||
.format(id, command))
|
||||
|
||||
def _set_response(self, type='text/html'):
|
||||
self.send_response(200)
|
||||
self.send_header('Content-type', type)
|
||||
class CORSSimpleHTTPHandler(SimpleHTTPRequestHandler):
|
||||
def end_headers(self):
|
||||
try:
|
||||
origin_address, _ = cgi.parse_header(self.headers['Origin'])
|
||||
self.send_header('Access-Control-Allow-Credentials', 'true')
|
||||
self.send_header('Access-Control-Allow-Origin', origin_address)
|
||||
except TypeError:
|
||||
pass
|
||||
super().end_headers()
|
||||
|
||||
|
||||
class RequestHandler(CORSSimpleHTTPHandler):
|
||||
def _set_response(self, code=200, type='text/html'):
|
||||
self.send_response(code)
|
||||
self.send_header('Content-type', type)
|
||||
self.end_headers()
|
||||
|
||||
def do_OPTIONS(self):
|
||||
self.send_response(200)
|
||||
self.send_header('Access-Control-Allow-Origin', '*')
|
||||
self.send_header('Access-Control-Allow-Methods', 'GET, OPTIONS')
|
||||
self.send_header("Access-Control-Allow-Headers", "X-Requested-With")
|
||||
self.send_header("Access-Control-Allow-Headers", "Content-Type")
|
||||
self.end_headers()
|
||||
|
||||
def do_GET(self):
|
||||
if str(self.path) == '/check':
|
||||
status = self.get_status()
|
||||
self._set_response('application/json')
|
||||
self.wfile.write(
|
||||
status.encode()
|
||||
if self.path.startswith('/check'):
|
||||
if self.path == '/check' or self.path == '/check/':
|
||||
status = jobs.get_status()
|
||||
self._set_response(type='application/json')
|
||||
self.wfile.write(
|
||||
json.dumps(status).encode()
|
||||
)
|
||||
else:
|
||||
id = self.path[7:]
|
||||
status = jobs.get_status_by_ID(id=id, details=True)
|
||||
self._set_response(type='application/json')
|
||||
self.wfile.write(
|
||||
json.dumps(status).encode()
|
||||
)
|
||||
logging.info(
|
||||
"GET request:\nPath:%s\nHeaders:\n%s\nBody:\n%s\n",
|
||||
str(self.path), str(self.headers), status
|
||||
)
|
||||
else:
|
||||
self.send_error(404)
|
||||
return
|
||||
logging.info(
|
||||
"GET request:\nPath:%s\nHeaders:\n%s\nBody:\n%s\n",
|
||||
str(self.path), str(self.headers), status
|
||||
)
|
||||
elif self.path.startswith('/file'):
|
||||
file_list = jobs.get_list(self.path[6:])
|
||||
self._set_response(type='application/json')
|
||||
self.wfile.write(
|
||||
json.dumps(file_list).encode()
|
||||
)
|
||||
logging.info(
|
||||
"GET request:\nPath:%s\nHeaders:\n%s\nBody:\n%s\n",
|
||||
str(self.path), str(self.headers), file_list
|
||||
)
|
||||
return
|
||||
elif self.path.startswith('/download'):
|
||||
self.path = self.path[10:]
|
||||
return CORSSimpleHTTPHandler.do_GET(self)
|
||||
else:
|
||||
self.path = '/dist' + self.path
|
||||
return CORSSimpleHTTPHandler.do_GET(self)
|
||||
|
||||
def do_POST(self):
|
||||
content_type, _ = cgi.parse_header(self.headers['content-type'])
|
||||
if content_type != 'application/json':
|
||||
self.send_response(400)
|
||||
self.end_headers()
|
||||
return
|
||||
content_length = int(self.headers['Content-Length'])
|
||||
post_data = json.loads(self.rfile.read(content_length))
|
||||
if str(self.path)[:4] == '/run':
|
||||
if self.path.startswith('/run'):
|
||||
content_type, _ = cgi.parse_header(self.headers['content-type'])
|
||||
if content_type != 'application/json':
|
||||
self.send_response(400)
|
||||
self.end_headers()
|
||||
return
|
||||
content_length = int(self.headers['Content-Length'])
|
||||
post_data = json.loads(self.rfile.read(content_length))
|
||||
try:
|
||||
self.ota_generate(post_data, id=str(self.path[5:]))
|
||||
self._set_response()
|
||||
jobs.ota_generate(post_data, id=str(self.path[5:]))
|
||||
self._set_response(code=201)
|
||||
self.wfile.write(
|
||||
"ota generator start running".encode('utf-8'))
|
||||
except SyntaxError:
|
||||
self.send_error(400)
|
||||
logging.info(
|
||||
"POST request:\nPath:%s\nHeaders:\n%s\nBody:\n%s\n",
|
||||
str(self.path), str(self.headers),
|
||||
json.dumps(post_data)
|
||||
)
|
||||
elif self.path.startswith('/file'):
|
||||
file_name = os.path.join('target', self.path[6:])
|
||||
file_length = int(self.headers['Content-Length'])
|
||||
with open(file_name, 'wb') as output_file:
|
||||
# Unwrap the uploaded file first (due to the usage of FormData)
|
||||
# The wrapper has a boundary line at the top and bottom
|
||||
# and some file information in the beginning
|
||||
# There are a file content line, a file name line, and an empty line
|
||||
# The boundary line in the bottom is 4 bytes longer than the top one
|
||||
# Please refer to the following links for more details:
|
||||
# https://stackoverflow.com/questions/8659808/how-does-http-file-upload-work
|
||||
# https://datatracker.ietf.org/doc/html/rfc1867
|
||||
upper_boundary = self.rfile.readline()
|
||||
file_length -= len(upper_boundary) * 2 + 4
|
||||
file_length -= len(self.rfile.readline())
|
||||
file_length -= len(self.rfile.readline())
|
||||
file_length -= len(self.rfile.readline())
|
||||
output_file.write(self.rfile.read(file_length))
|
||||
self._set_response(code=201)
|
||||
self.wfile.write(
|
||||
"File received, saved into {}".format(
|
||||
file_name).encode('utf-8')
|
||||
)
|
||||
else:
|
||||
self.send_error(400)
|
||||
logging.info(
|
||||
"POST request:\nPath:%s\nHeaders:\n%s\nBody:\n%s\n",
|
||||
str(self.path), str(self.headers),
|
||||
json.dumps(post_data)
|
||||
)
|
||||
|
||||
|
||||
class ThreadedHTTPServer(ThreadingMixIn, HTTPServer):
|
||||
@@ -166,7 +164,7 @@ def run_server(SeverClass=ThreadedHTTPServer, HandlerClass=RequestHandler, port=
|
||||
if __name__ == '__main__':
|
||||
from sys import argv
|
||||
print(argv)
|
||||
PROCESSES = ThreadSafeContainer()
|
||||
jobs = ProcessesManagement()
|
||||
if len(argv) == 2:
|
||||
run_server(port=int(argv[1]))
|
||||
else:
|
||||
|
||||
Reference in New Issue
Block a user