Merge changes Ic5fdefe2,I32cbd027,I464d846c,Ie9ef8e2b,Ib7b02d5c am: e6ae7c629b am: 8834603a01

Original change: https://android-review.googlesource.com/c/platform/development/+/1815621

Change-Id: Ifa73b12a346a8640c29a5891e6634f6d3085225d
This commit is contained in:
Kelvin Zhang
2021-09-01 14:49:34 +00:00
committed by Automerger Merge Worker
17 changed files with 215 additions and 157 deletions

View File

@@ -1,2 +1,7 @@
node_modules/ node_modules/
Dockerfile Dockerfile
.vscode
dist
output
target
*.db

View File

@@ -28,3 +28,6 @@ pnpm-debug.log*
*.sln *.sln
*.sw? *.sw?
*.db *.db
packaged
**/.DS_Store

View File

@@ -3,7 +3,12 @@ FROM node:lts-alpine as build-stage
WORKDIR /app WORKDIR /app
COPY package*.json ./ COPY package*.json ./
RUN npm install RUN npm install
COPY . . COPY src ./src
COPY public ./public
COPY *.js .
COPY .env* .
COPY .eslint* .
RUN npm run build RUN npm run build
# production stage # production stage
@@ -14,7 +19,7 @@ WORKDIR /app
VOLUME [ "/app/target", "/app/output"] VOLUME [ "/app/target", "/app/output"]
COPY otatools.zip . COPY otatools.zip .
COPY --from=build-stage /app/dist ./dist COPY --from=build-stage /app/dist ./dist
COPY --from=build-stage /app/*.py . COPY *.py .
EXPOSE 8000 EXPOSE 8000
CMD ["python3.9", "web_server.py"] CMD ["python3.9", "web_server.py"]

8
tools/otagui/build_zip.bash Executable file
View File

@@ -0,0 +1,8 @@
#!/bin/bash
image_id=$(docker build -q .)
container_id=$(docker run -d --entrypoint /usr/bin/sleep ${image_id} 60)
docker container exec ${container_id} zip /app.zip -r /app
docker container cp ${container_id}:/app.zip .
docker container stop ${container_id}
docker container rm ${container_id}

View File

@@ -1,6 +1,5 @@
import subprocess import subprocess
import os import os
import json
import pipes import pipes
import threading import threading
from dataclasses import dataclass, asdict, field from dataclasses import dataclass, asdict, field
@@ -31,16 +30,6 @@ class JobInfo:
isIncremental: bool = False isIncremental: bool = False
def __post_init__(self): def __post_init__(self):
"""
If the output, stdout, stderr paths are not set, automatically use
the job id as the file name.
"""
if not self.output:
self.output = os.path.join('output', self.id + '.zip')
if not self.stdout:
self.stdout = os.path.join('output/stdout.'+self.id)
if not self.stderr:
self.stderr = os.path.join('output/stderr.'+self.id)
def enforce_bool(t): return t if isinstance(t, bool) else bool(t) def enforce_bool(t): return t if isinstance(t, bool) else bool(t)
self.verbose, self.downgrade = map( self.verbose, self.downgrade = map(
@@ -108,16 +97,43 @@ class JobInfo:
return detail_info return detail_info
class DependencyError(Exception):
pass
class ProcessesManagement: class ProcessesManagement:
""" """
A class manage the ota generate process A class manage the ota generate process
""" """
def __init__(self, path='output/ota_database.db'): @staticmethod
def check_external_dependencies():
try:
java_version = subprocess.check_output(["java", "--version"])
print("Java version:", java_version.decode())
except Exception as e:
raise DependencyError(
"java not found in PATH. Attempt to generate OTA might fail. " + str(e))
try:
zip_version = subprocess.check_output(["zip", "-v"])
print("Zip version:", zip_version.decode())
except Exception as e:
raise DependencyError(
"zip command not found in PATH. Attempt to generate OTA might fail. " + str(e))
def __init__(self, *, working_dir='output', db_path=None, otatools_dir=None):
""" """
create a table if not exist create a table if not exist
""" """
self.path = path ProcessesManagement.check_external_dependencies()
self.working_dir = working_dir
self.logs_dir = os.path.join(working_dir, 'logs')
self.otatools_dir = otatools_dir
os.makedirs(self.working_dir, exist_ok=True)
os.makedirs(self.logs_dir, exist_ok=True)
if not db_path:
db_path = os.path.join(self.working_dir, "ota_database.db")
self.path = db_path
with sqlite3.connect(self.path) as connect: with sqlite3.connect(self.path) as connect:
cursor = connect.cursor() cursor = connect.cursor()
cursor.execute(""" cursor.execute("""
@@ -145,8 +161,8 @@ class ProcessesManagement:
job_info: JobInfo job_info: JobInfo
""" """
with sqlite3.connect(self.path) as connect: with sqlite3.connect(self.path) as connect:
cursor = connect.cursor() cursor = connect.cursor()
cursor.execute(""" cursor.execute("""
INSERT INTO Jobs (ID, TargetPath, IncrementalPath, Verbose, Partial, OutputPath, Status, Downgrade, OtherFlags, STDOUT, STDERR, StartTime, Finishtime) INSERT INTO Jobs (ID, TargetPath, IncrementalPath, Verbose, Partial, OutputPath, Status, Downgrade, OtherFlags, STDOUT, STDERR, StartTime, Finishtime)
VALUES (:id, :target, :incremental, :verbose, :partial, :output, :status, :downgrade, :extra, :stdout, :stderr, :start_time, :finish_time) VALUES (:id, :target, :incremental, :verbose, :partial, :output, :status, :downgrade, :extra, :stdout, :stderr, :start_time, :finish_time)
""", job_info.to_sql_form_dict()) """, job_info.to_sql_form_dict())
@@ -165,7 +181,7 @@ class ProcessesManagement:
cursor.execute(""" cursor.execute("""
SELECT ID, TargetPath, IncrementalPath, Verbose, Partial, OutputPath, Status, Downgrade, OtherFlags, STDOUT, STDERR, StartTime, FinishTime SELECT ID, TargetPath, IncrementalPath, Verbose, Partial, OutputPath, Status, Downgrade, OtherFlags, STDOUT, STDERR, StartTime, FinishTime
FROM Jobs WHERE ID=(?) FROM Jobs WHERE ID=(?)
""", (id,)) """, (str(id),))
row = cursor.fetchone() row = cursor.fetchone()
status = JobInfo(*row) status = JobInfo(*row)
return status return status
@@ -202,32 +218,41 @@ class ProcessesManagement:
""", """,
(status, finish_time, id)) (status, finish_time, id))
def ota_run(self, command, id): def ota_run(self, command, id, stdout_path, stderr_path):
""" """
Initiate a subprocess to run the ota generation. Wait until it finished and update Initiate a subprocess to run the ota generation. Wait until it finished and update
the record in the database. the record in the database.
""" """
stderr_pipes = pipes.Template() stderr_pipes = pipes.Template()
stdout_pipes = pipes.Template() stdout_pipes = pipes.Template()
ferr = stderr_pipes.open(stdout_path, 'w')
fout = stdout_pipes.open(stderr_path, 'w')
env = {}
if self.otatools_dir:
env['PATH'] = os.path.join(
self.otatools_dir, "bin") + ":" + os.environ["PATH"]
# TODO(lishutong): Enable user to use self-defined stderr/stdout path # TODO(lishutong): Enable user to use self-defined stderr/stdout path
ferr = stderr_pipes.open(os.path.join(
'output', 'stderr.'+str(id)), 'w')
fout = stdout_pipes.open(os.path.join(
'output', 'stdout.'+str(id)), 'w')
try: try:
proc = subprocess.Popen( proc = subprocess.Popen(
command, stderr=ferr, stdout=fout, shell=False) command, stderr=ferr, stdout=fout, shell=False, env=env, cwd=self.otatools_dir)
except FileNotFoundError as e: except FileNotFoundError as e:
logging.error('ota_from_target_files is not set properly %s', e) logging.error('ota_from_target_files is not set properly %s', e)
self.update_status(id, 'Error', int(time.time())) self.update_status(id, 'Error', int(time.time()))
return raise
exit_code = proc.wait() except Exception as e:
if exit_code == 0: logging.error('Failed to execute ota_from_target_files %s', e)
self.update_status(id, 'Finished', int(time.time()))
else:
self.update_status(id, 'Error', int(time.time())) self.update_status(id, 'Error', int(time.time()))
raise
def ota_generate(self, args, id=0): def wait_result():
exit_code = proc.wait()
if exit_code == 0:
self.update_status(id, 'Finished', int(time.time()))
else:
self.update_status(id, 'Error', int(time.time()))
threading.Thread(target=wait_result).start()
def ota_generate(self, args, id):
""" """
Read in the arguments from the frontend and start running the OTA Read in the arguments from the frontend and start running the OTA
generation process, then update the records in database. generation process, then update the records in database.
@@ -244,40 +269,41 @@ class ProcessesManagement:
if not os.path.isfile(args['target']): if not os.path.isfile(args['target']):
raise FileNotFoundError raise FileNotFoundError
if not 'output' in args: if not 'output' in args:
args['output'] = os.path.join('output', str(id) + '.zip') args['output'] = os.path.join(self.working_dir, str(id) + '.zip')
if args['verbose']: if args['verbose']:
command.append('-v') command.append('-v')
if args['extra_keys']: if args['extra_keys']:
args['extra'] = \ args['extra'] = '--' + \
'--' + ' --'.join(args['extra_keys']) + ' ' + args['extra'] ' --'.join(args['extra_keys']) + ' ' + args['extra']
if args['extra']: if args['extra']:
command += args['extra'].strip().split(' ') command += args['extra'].strip().split(' ')
if args['isIncremental']: if args['isIncremental']:
if not os.path.isfile(args['incremental']): if not os.path.isfile(args['incremental']):
raise FileNotFoundError raise FileNotFoundError
command.append('-i') command.append('-i')
command.append(args['incremental']) command.append(os.path.realpath(args['incremental']))
if args['isPartial']: if args['isPartial']:
command.append('--partial') command.append('--partial')
command.append(' '.join(args['partial'])) command.append(' '.join(args['partial']))
command.append(args['target']) command.append(os.path.realpath(args['target']))
command.append(args['output']) command.append(os.path.realpath(args['output']))
stdout = os.path.join(self.logs_dir, 'stdout.' + str(id))
stderr = os.path.join(self.logs_dir, 'stderr.' + str(id))
job_info = JobInfo(id, job_info = JobInfo(id,
target=args['target'], target=args['target'],
incremental=args['incremental'] if args['isIncremental'] else '', incremental=args['incremental'] if args['isIncremental'] else '',
verbose=args['verbose'], verbose=args['verbose'],
partial=args['partial'] if args['isPartial'] else [], partial=args['partial'] if args['isPartial'] else [
],
output=args['output'], output=args['output'],
status='Running', status='Running',
extra=args['extra'], extra=args['extra'],
start_time=int(time.time()) start_time=int(time.time()),
stdout=stdout,
stderr=stderr
) )
try: self.ota_run(command, id, job_info.stdout, job_info.stderr)
thread = threading.Thread(target=self.ota_run, args=(command, id)) self.insert_database(job_info)
self.insert_database(job_info)
thread.start()
except AssertionError:
raise SyntaxError
logging.info( logging.info(
'Starting generating OTA package with id {}: \n {}' 'Starting generating OTA package with id {}: \n {}'
.format(id, command)) .format(id, command))

View File

@@ -74,8 +74,9 @@ export default {
*/ */
async sendForm() { async sendForm() {
try { try {
let response_messages = await this.$store.state.otaConfig.sendForms( let response_data = await this.$store.state.otaConfig.sendForms(
this.targetBuilds, this.incrementalSources) this.targetBuilds, this.incrementalSources);
let response_messages = response_data.map(d => d.msg);
alert(response_messages.join('\n')) alert(response_messages.join('\n'))
this.$store.state.otaConfig.reset() this.$store.state.otaConfig.reset()
this.$store.commit('SET_TARGETS', []) this.$store.commit('SET_TARGETS', [])

View File

@@ -3,7 +3,7 @@
<h3>Build Library</h3> <h3>Build Library</h3>
<UploadFile @file-uploaded="fetchTargetList" /> <UploadFile @file-uploaded="fetchTargetList" />
<BuildTable <BuildTable
v-if="targetDetails.length>0" v-if="targetDetails && targetDetails.length>0"
:builds="targetDetails" :builds="targetDetails"
/> />
<li <li
@@ -42,7 +42,7 @@
<script> <script>
import UploadFile from '@/components/UploadFile.vue' import UploadFile from '@/components/UploadFile.vue'
import BuildTable from '@/components/BuildTable.vue' import BuildTable from '@/components/BuildTable.vue'
import ApiService from '@/services/ApiService.js' import ApiService from '../services/ApiService.js'
import FormDate from '@/services/FormDate.js' import FormDate from '@/services/FormDate.js'
export default { export default {
@@ -72,9 +72,8 @@ export default {
*/ */
async fetchTargetList() { async fetchTargetList() {
try { try {
let response = await ApiService.getFileList('') this.targetDetails = await ApiService.getBuildList()
this.targetDetails = response.data this.$emit('update:targetDetails', this.targetDetails)
this.$emit('update:targetDetails', response.data)
} catch (err) { } catch (err) {
alert( alert(
"Cannot fetch Android Builds list from the backend, for the following reasons:" "Cannot fetch Android Builds list from the backend, for the following reasons:"
@@ -88,9 +87,8 @@ export default {
*/ */
async updateBuildLib() { async updateBuildLib() {
try { try {
let response = await ApiService.getFileList('/target') this.targetDetails = await ApiService.reconstructBuildList();
this.targetDetails = response.data this.$emit('update:targetDetails', this.targetDetails);
this.$emit('update:targetDetails', response.data)
} catch (err) { } catch (err) {
alert( alert(
"Cannot fetch Android Builds list from the backend, for the following reasons: " "Cannot fetch Android Builds list from the backend, for the following reasons: "

View File

@@ -74,12 +74,13 @@ export default {
if (this.targetBuilds.length<2) { if (this.targetBuilds.length<2) {
alert( alert(
'At least two OTA packeges has to be given!' 'At least two OTA packeges has to be given!'
) );
return return
} }
try { try {
let response_messages = await this.$store.state.otaConfig let response_data = await this.$store.state.otaConfig
.sendChainForms(this.targetBuilds) .sendChainForms(this.targetBuilds)
let response_messages = response_data.map(d => d.msg);
alert(response_messages.join('\n')) alert(response_messages.join('\n'))
this.$store.state.otaConfig.reset() this.$store.state.otaConfig.reset()
this.$store.commit('SET_TARGETS', []) this.$store.commit('SET_TARGETS', [])

View File

@@ -74,9 +74,9 @@ export default {
*/ */
async sendForm() { async sendForm() {
try { try {
let response_message = await this.$store.state.otaConfig.sendForm( let data = await this.$store.state.otaConfig.sendForm(
this.targetBuild, this.incrementalSource) this.targetBuild, this.incrementalSource)
alert(response_message) alert(data.msg);
this.$store.state.otaConfig.reset() this.$store.state.otaConfig.reset()
this.$store.commit('SET_TARGETS', []) this.$store.commit('SET_TARGETS', [])
this.$store.commit('SET_SOURCES', []) this.$store.commit('SET_SOURCES', [])

View File

@@ -1,6 +1,6 @@
import axios from 'axios' import axios from 'axios'
const baseURL = process.env.NODE_ENV === 'production' ? '' : 'http://localhost:8000'; const baseURL = process.env.NODE_ENV === 'production' ? '' : 'http://localhost:5000';
console.log(`Build mode: ${process.env.NODE_ENV}, API base url ${baseURL}`); console.log(`Build mode: ${process.env.NODE_ENV}, API base url ${baseURL}`);
@@ -23,8 +23,13 @@ export default {
getJobById(id) { getJobById(id) {
return apiClient.get("/check/" + id) return apiClient.get("/check/" + id)
}, },
getFileList(path) { async getBuildList() {
return apiClient.get("/file" + path) let resp = await apiClient.get("/file");
return resp.data || [];
},
async reconstructBuildList() {
let resp = await apiClient.get("/reconstruct_build_list");
return resp.data;
}, },
uploadTarget(file, onUploadProgress) { uploadTarget(file, onUploadProgress) {
let formData = new FormData() let formData = new FormData()
@@ -37,12 +42,15 @@ export default {
}, },
async postInput(input, id) { async postInput(input, id) {
try { try {
const response = await apiClient.post( let resp = await apiClient.post(
'/run/' + id, input) '/run/' + id, JSON.stringify(input));
return response return resp.data;
} catch (err) { } catch (error) {
console.log('err:', err) if (error.response.data) {
return return error.response.data;
} else {
throw error;
}
} }
} }
} }

View File

@@ -89,6 +89,7 @@ export class OTAConfiguration {
let jsonOptions = Object.assign({}, this) let jsonOptions = Object.assign({}, this)
jsonOptions.target = targetBuild jsonOptions.target = targetBuild
jsonOptions.incremental = incrementalSource jsonOptions.incremental = incrementalSource
jsonOptions.isIncremental = !!incrementalSource;
jsonOptions.id = uuid.v1() jsonOptions.id = uuid.v1()
for (let flag of OTAExtraFlags) { for (let flag of OTAExtraFlags) {
if (jsonOptions[flag.key]) { if (jsonOptions[flag.key]) {
@@ -97,8 +98,8 @@ export class OTAConfiguration {
} }
} }
} }
let response = await ApiServices.postInput(jsonOptions, jsonOptions.id) let data = await ApiServices.postInput(jsonOptions, jsonOptions.id)
return response.data return data;
} }
/** /**

View File

@@ -16,21 +16,21 @@
<v-divider class="my-5" /> <v-divider class="my-5" />
<div> <div>
<h3>STDERR</h3> <h3>STDERR</h3>
<div <pre
ref="stderr" ref="stderr"
class="stderr" class="stderr"
> >
{{ job.stderr }} {{ job.stderr }}
<p ref="stderrBottom" /> <p ref="stderrBottom" />
</div> </pre>
<h3>STDOUT</h3> <h3>STDOUT</h3>
<div <pre
ref="stdout" ref="stdout"
class="stdout" class="stdout"
> >
{{ job.stdout }} {{ job.stdout }}
<p ref="stdoutBottom" /> <p ref="stdoutBottom" />
</div> </pre>
</div> </div>
<v-divider class="my-5" /> <v-divider class="my-5" />
<div class="download"> <div class="download">

View File

@@ -3,21 +3,6 @@
v-if="jobs" v-if="jobs"
:jobs="jobs" :jobs="jobs"
/> />
<v-row>
<v-col
v-for="job in jobs"
:key="job.id"
cols="12"
sm="3"
>
<JobDisplay
:job="job"
:active="overStatus.get(job.id)"
@mouseover="mouseOver(job.id, true)"
@mouseout="mouseOver(job.id, false)"
/>
</v-col>
</v-row>
<v-btn <v-btn
block block
@click="updateStatus" @click="updateStatus"
@@ -27,20 +12,17 @@
</template> </template>
<script> <script>
import JobDisplay from '@/components/JobDisplay.vue'
import ApiService from '../services/ApiService.js' import ApiService from '../services/ApiService.js'
import OTAJobTable from '@/components/OTAJobTable.vue' import OTAJobTable from '@/components/OTAJobTable.vue'
export default { export default {
name: 'JobList', name: 'JobList',
components: { components: {
JobDisplay,
OTAJobTable OTAJobTable
}, },
data() { data() {
return { return {
jobs: null, jobs: null,
overStatus: new Map()
} }
}, },
created (){ created (){
@@ -55,9 +37,6 @@ export default {
console.log(err); console.log(err);
} }
}, },
mouseOver(id, status) {
this.overStatus.set(id, status)
}
} }
} }

View File

@@ -8,6 +8,10 @@ import re
import json import json
class BuildFileInvalidError(Exception):
pass
@dataclass @dataclass
class BuildInfo: class BuildInfo:
""" """
@@ -34,29 +38,26 @@ class BuildInfo:
else: else:
return '' return ''
build = zipfile.ZipFile(self.path) with zipfile.ZipFile(self.path) as build:
try: try:
with build.open('SYSTEM/build.prop', 'r') as build_prop: with build.open('SYSTEM/build.prop', 'r') as build_prop:
raw_info = build_prop.readlines() raw_info = build_prop.readlines()
pattern_id = re.compile(b'(?<=ro\.build\.id\=).+') pattern_id = re.compile(b'(?<=ro\.build\.id\=).+')
pattern_version = re.compile( pattern_version = re.compile(
b'(?<=ro\.build\.version\.incremental\=).+') b'(?<=ro\.build\.version\.incremental\=).+')
pattern_flavor = re.compile(b'(?<=ro\.build\.flavor\=).+') pattern_flavor = re.compile(b'(?<=ro\.build\.flavor\=).+')
self.build_id = extract_info( self.build_id = extract_info(
pattern_id, raw_info).decode('utf-8') pattern_id, raw_info).decode('utf-8')
self.build_version = extract_info( self.build_version = extract_info(
pattern_version, raw_info).decode('utf-8') pattern_version, raw_info).decode('utf-8')
self.build_flavor = extract_info( self.build_flavor = extract_info(
pattern_flavor, raw_info).decode('utf-8') pattern_flavor, raw_info).decode('utf-8')
except KeyError: with build.open('META/ab_partitions.txt', 'r') as partition_info:
pass raw_info = partition_info.readlines()
try: for line in raw_info:
with build.open('META/ab_partitions.txt', 'r') as partition_info: self.partitions.append(line.decode('utf-8').rstrip())
raw_info = partition_info.readlines() except KeyError as e:
for line in raw_info: raise BuildFileInvalidError("Invalid build due to " + str(e))
self.partitions.append(line.decode('utf-8').rstrip())
except KeyError:
pass
def to_sql_form_dict(self): def to_sql_form_dict(self):
""" """
@@ -79,12 +80,16 @@ class TargetLib:
""" """
A class that manages the builds in database. A class that manages the builds in database.
""" """
def __init__(self, path='target/ota_database.db'):
def __init__(self, working_dir="target", db_path=None):
""" """
Create a build table if not existing Create a build table if not existing
""" """
self.path = path self.working_dir = working_dir
with sqlite3.connect(self.path) as connect: if db_path is None:
db_path = os.path.join(working_dir, "ota_database.db")
self.db_path = db_path
with sqlite3.connect(self.db_path) as connect:
cursor = connect.cursor() cursor = connect.cursor()
cursor.execute(""" cursor.execute("""
CREATE TABLE if not exists Builds ( CREATE TABLE if not exists Builds (
@@ -107,7 +112,12 @@ class TargetLib:
""" """
build_info = BuildInfo(filename, path, int(time.time())) build_info = BuildInfo(filename, path, int(time.time()))
build_info.analyse_buildprop() build_info.analyse_buildprop()
with sqlite3.connect(self.path) as connect: # Ignore name specified by user, instead use a standard format
build_info.path = os.path.join(self.working_dir, "{}-{}-{}.zip".format(
build_info.build_flavor, build_info.build_id, build_info.build_version))
if path != build_info.path:
os.rename(path, build_info.path)
with sqlite3.connect(self.db_path) as connect:
cursor = connect.cursor() cursor = connect.cursor()
cursor.execute(""" cursor.execute("""
SELECT * FROM Builds WHERE FileName=:file_name and Path=:path SELECT * FROM Builds WHERE FileName=:file_name and Path=:path
@@ -121,19 +131,21 @@ class TargetLib:
VALUES (:file_name, :time, :path, :build_id, :build_version, :build_flavor, :partitions) VALUES (:file_name, :time, :path, :build_id, :build_version, :build_flavor, :partitions)
""", build_info.to_sql_form_dict()) """, build_info.to_sql_form_dict())
def new_build_from_dir(self, path): def new_build_from_dir(self):
""" """
Update the database using files under a directory Update the database using files under a directory
Args: Args:
path: a directory path: a directory
""" """
if os.path.isdir(path): build_dir = self.working_dir
builds_name = os.listdir(path) if os.path.isdir(build_dir):
builds_name = os.listdir(build_dir)
for build_name in builds_name: for build_name in builds_name:
if build_name.endswith(".zip"): path = os.path.join(build_dir, build_name)
self.new_build(build_name, os.path.join(path, build_name)) if build_name.endswith(".zip") and zipfile.is_zipfile(path):
elif os.path.isfile(path) and path.endswith(".zip"): self.new_build(build_name, path)
self.new_build(os.path.split(path)[-1], path) elif os.path.isfile(build_dir) and build_dir.endswith(".zip"):
self.new_build(os.path.split(build_dir)[-1], build_dir)
return self.get_builds() return self.get_builds()
def sql_to_buildinfo(self, row): def sql_to_buildinfo(self, row):
@@ -147,7 +159,7 @@ class TargetLib:
A list of build_info, each of which is an object: A list of build_info, each of which is an object:
(FileName, UploadTime, Path, Build ID, Build Version, Build Flavor, Partitions) (FileName, UploadTime, Path, Build ID, Build Version, Build Flavor, Partitions)
""" """
with sqlite3.connect(self.path) as connect: with sqlite3.connect(self.db_path) as connect:
cursor = connect.cursor() cursor = connect.cursor()
cursor.execute(""" cursor.execute("""
SELECT FileName, Path, UploadTime, BuildID, BuildVersion, BuildFlavor, Partitions SELECT FileName, Path, UploadTime, BuildID, BuildVersion, BuildFlavor, Partitions
@@ -161,7 +173,7 @@ class TargetLib:
A build_info, which is an object: A build_info, which is an object:
(FileName, UploadTime, Path, Build ID, Build Version, Build Flavor, Partitions) (FileName, UploadTime, Path, Build ID, Build Version, Build Flavor, Partitions)
""" """
with sqlite3.connect(self.path) as connect: with sqlite3.connect(self.db_path) as connect:
cursor = connect.cursor() cursor = connect.cursor()
cursor.execute(""" cursor.execute("""
SELECT FileName, Path, UploadTime, BuildID, BuildVersion, BuildFlavor, Partitions SELECT FileName, Path, UploadTime, BuildID, BuildVersion, BuildFlavor, Partitions

View File

@@ -182,7 +182,7 @@ class TestProcessesManagement(unittest.TestCase):
def setUp(self): def setUp(self):
if os.path.isfile('test_process.db'): if os.path.isfile('test_process.db'):
self.tearDown() self.tearDown()
self.processes = ProcessesManagement(path='test_process.db') self.processes = ProcessesManagement(db_path='test_process.db')
testcase_job_info = TestJobInfo() testcase_job_info = TestJobInfo()
testcase_job_info.setUp() testcase_job_info.setUp()
self.test_job_info = testcase_job_info.setup_job(incremental='target/source.zip') self.test_job_info = testcase_job_info.setup_job(incremental='target/source.zip')

View File

@@ -0,0 +1,12 @@
const path = require('path')
module.exports = {
configureWebpack: {
resolve: {
symlinks: false,
alias: {
vue: path.resolve('./node_modules/vue')
}
}
}
}

View File

@@ -33,7 +33,6 @@ import json
import cgi import cgi
import os import os
import stat import stat
import sys
import zipfile import zipfile
LOCAL_ADDRESS = '0.0.0.0' LOCAL_ADDRESS = '0.0.0.0'
@@ -78,17 +77,17 @@ class RequestHandler(CORSSimpleHTTPHandler):
self.wfile.write( self.wfile.write(
json.dumps(status.to_dict_detail(target_lib)).encode() json.dumps(status.to_dict_detail(target_lib)).encode()
) )
elif self.path.startswith('/file'): elif self.path.startswith('/file') or self.path.startswith("/reconstruct_build_list"):
if self.path == '/file' or self.path == '/file/': if self.path == '/file' or self.path == '/file/':
file_list = target_lib.get_builds() file_list = target_lib.get_builds()
else: else:
file_list = target_lib.new_build_from_dir(self.path[6:]) file_list = target_lib.new_build_from_dir()
builds_info = [build.to_dict() for build in file_list] builds_info = [build.to_dict() for build in file_list]
self._set_response(type='application/json') self._set_response(type='application/json')
self.wfile.write( self.wfile.write(
json.dumps(builds_info).encode() json.dumps(builds_info).encode()
) )
logging.info( logging.debug(
"GET request:\nPath:%s\nHeaders:\n%s\nBody:\n%s\n", "GET request:\nPath:%s\nHeaders:\n%s\nBody:\n%s\n",
str(self.path), str(self.headers), file_list str(self.path), str(self.headers), file_list
) )
@@ -115,12 +114,16 @@ class RequestHandler(CORSSimpleHTTPHandler):
post_data = json.loads(self.rfile.read(content_length)) post_data = json.loads(self.rfile.read(content_length))
try: try:
jobs.ota_generate(post_data, id=str(self.path[5:])) jobs.ota_generate(post_data, id=str(self.path[5:]))
self._set_response(code=201) self._set_response(code=200)
self.wfile.write( self.send_header("Content-Type", 'application/json')
"ota generator start running".encode('utf-8')) self.wfile.write(json.dumps(
except SyntaxError: {"success": True, "msg": "OTA Generator started running"}).encode())
self.send_error(400) except Exception as e:
logging.info( logging.warning(
"Failed to run ota_from_target_files %s", e.__traceback__)
self.send_error(
400, "Failed to run ota_from_target_files", str(e))
logging.debug(
"POST request:\nPath:%s\nHeaders:\n%s\nBody:\n%s\n", "POST request:\nPath:%s\nHeaders:\n%s\nBody:\n%s\n",
str(self.path), str(self.headers), str(self.path), str(self.headers),
json.dumps(post_data) json.dumps(post_data)
@@ -144,7 +147,8 @@ class RequestHandler(CORSSimpleHTTPHandler):
file_length -= len(self.rfile.readline()) file_length -= len(self.rfile.readline())
BUFFER_SIZE = 1024*1024 BUFFER_SIZE = 1024*1024
for offset in range(0, file_length, BUFFER_SIZE): for offset in range(0, file_length, BUFFER_SIZE):
chunk = self.rfile.read(min(file_length-offset, BUFFER_SIZE)) chunk = self.rfile.read(
min(file_length-offset, BUFFER_SIZE))
output_file.write(chunk) output_file.write(chunk)
target_lib.new_build(self.path[6:], file_name) target_lib.new_build(self.path[6:], file_name)
self._set_response(code=201) self._set_response(code=201)
@@ -171,17 +175,17 @@ def run_server(SeverClass=ThreadedHTTPServer, HandlerClass=RequestHandler, port=
except KeyboardInterrupt: except KeyboardInterrupt:
pass pass
server_instance.server_close() server_instance.server_close()
logging.basicConfig(level=logging.DEBUG)
logging.info('Server has been turned off.') logging.info('Server has been turned off.')
if __name__ == '__main__': if __name__ == '__main__':
from sys import argv from sys import argv
logging.basicConfig(level=logging.DEBUG)
print(argv) print(argv)
logging.basicConfig(level=logging.INFO)
EXTRACT_DIR = None
if os.path.exists("otatools.zip"): if os.path.exists("otatools.zip"):
logging.info("Found otatools.zip, extracting...") logging.info("Found otatools.zip, extracting...")
EXTRACT_DIR = "./" EXTRACT_DIR = "/tmp/otatools-" + str(os.getpid())
os.makedirs(EXTRACT_DIR, exist_ok=True) os.makedirs(EXTRACT_DIR, exist_ok=True)
with zipfile.ZipFile("otatools.zip", "r") as zfp: with zipfile.ZipFile("otatools.zip", "r") as zfp:
zfp.extractall(EXTRACT_DIR) zfp.extractall(EXTRACT_DIR)
@@ -189,19 +193,14 @@ if __name__ == '__main__':
bin_dir = os.path.join(EXTRACT_DIR, "bin") bin_dir = os.path.join(EXTRACT_DIR, "bin")
for filename in os.listdir(bin_dir): for filename in os.listdir(bin_dir):
os.chmod(os.path.join(bin_dir, filename), stat.S_IRWXU) os.chmod(os.path.join(bin_dir, filename), stat.S_IRWXU)
os.environ["PATH"] = os.path.join(EXTRACT_DIR, "bin") + ":" + os.environ["PATH"]
logging.info("Extracted otatools to {}".format(EXTRACT_DIR)) logging.info("Extracted otatools to {}".format(EXTRACT_DIR))
logging.info("PATH: %s", os.environ["PATH"])
if not os.path.isdir('target'): if not os.path.isdir('target'):
os.mkdir('target', 755) os.mkdir('target', 755)
if not os.path.isdir('output'): if not os.path.isdir('output'):
os.mkdir('output', 755) os.mkdir('output', 755)
target_lib = TargetLib() target_lib = TargetLib()
jobs = ProcessesManagement() jobs = ProcessesManagement(otatools_dir=EXTRACT_DIR)
try: if len(argv) == 2:
if len(argv) == 2: run_server(port=int(argv[1]))
run_server(port=int(argv[1])) else:
else: run_server()
run_server()
except KeyboardInterrupt:
sys.exit(0)