Merge changes Ic5fdefe2,I32cbd027,I464d846c,Ie9ef8e2b,Ib7b02d5c
* changes: API Service should return json data directly Rename path to db_path Ignore vscode files in docker Return error in json Delegate choice of output file path to 1 place in code
This commit is contained in:
@@ -1,2 +1,7 @@
|
||||
node_modules/
|
||||
Dockerfile
|
||||
Dockerfile
|
||||
.vscode
|
||||
dist
|
||||
output
|
||||
target
|
||||
*.db
|
||||
|
||||
3
tools/otagui/.gitignore
vendored
3
tools/otagui/.gitignore
vendored
@@ -28,3 +28,6 @@ pnpm-debug.log*
|
||||
*.sln
|
||||
*.sw?
|
||||
*.db
|
||||
|
||||
packaged
|
||||
**/.DS_Store
|
||||
|
||||
@@ -3,7 +3,12 @@ FROM node:lts-alpine as build-stage
|
||||
WORKDIR /app
|
||||
COPY package*.json ./
|
||||
RUN npm install
|
||||
COPY . .
|
||||
COPY src ./src
|
||||
COPY public ./public
|
||||
COPY *.js .
|
||||
COPY .env* .
|
||||
COPY .eslint* .
|
||||
|
||||
RUN npm run build
|
||||
|
||||
# production stage
|
||||
@@ -14,7 +19,7 @@ WORKDIR /app
|
||||
VOLUME [ "/app/target", "/app/output"]
|
||||
COPY otatools.zip .
|
||||
COPY --from=build-stage /app/dist ./dist
|
||||
COPY --from=build-stage /app/*.py .
|
||||
COPY *.py .
|
||||
|
||||
EXPOSE 8000
|
||||
CMD ["python3.9", "web_server.py"]
|
||||
8
tools/otagui/build_zip.bash
Executable file
8
tools/otagui/build_zip.bash
Executable 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}
|
||||
@@ -1,6 +1,5 @@
|
||||
import subprocess
|
||||
import os
|
||||
import json
|
||||
import pipes
|
||||
import threading
|
||||
from dataclasses import dataclass, asdict, field
|
||||
@@ -31,16 +30,6 @@ class JobInfo:
|
||||
isIncremental: bool = False
|
||||
|
||||
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)
|
||||
self.verbose, self.downgrade = map(
|
||||
@@ -108,16 +97,43 @@ class JobInfo:
|
||||
return detail_info
|
||||
|
||||
|
||||
class DependencyError(Exception):
|
||||
pass
|
||||
|
||||
|
||||
class ProcessesManagement:
|
||||
"""
|
||||
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
|
||||
"""
|
||||
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:
|
||||
cursor = connect.cursor()
|
||||
cursor.execute("""
|
||||
@@ -145,8 +161,8 @@ class ProcessesManagement:
|
||||
job_info: JobInfo
|
||||
"""
|
||||
with sqlite3.connect(self.path) as connect:
|
||||
cursor = connect.cursor()
|
||||
cursor.execute("""
|
||||
cursor = connect.cursor()
|
||||
cursor.execute("""
|
||||
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)
|
||||
""", job_info.to_sql_form_dict())
|
||||
@@ -165,7 +181,7 @@ class ProcessesManagement:
|
||||
cursor.execute("""
|
||||
SELECT ID, TargetPath, IncrementalPath, Verbose, Partial, OutputPath, Status, Downgrade, OtherFlags, STDOUT, STDERR, StartTime, FinishTime
|
||||
FROM Jobs WHERE ID=(?)
|
||||
""", (id,))
|
||||
""", (str(id),))
|
||||
row = cursor.fetchone()
|
||||
status = JobInfo(*row)
|
||||
return status
|
||||
@@ -202,32 +218,41 @@ class ProcessesManagement:
|
||||
""",
|
||||
(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
|
||||
the record in the database.
|
||||
"""
|
||||
stderr_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
|
||||
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:
|
||||
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:
|
||||
logging.error('ota_from_target_files is not set properly %s', e)
|
||||
self.update_status(id, 'Error', int(time.time()))
|
||||
return
|
||||
exit_code = proc.wait()
|
||||
if exit_code == 0:
|
||||
self.update_status(id, 'Finished', int(time.time()))
|
||||
else:
|
||||
raise
|
||||
except Exception as e:
|
||||
logging.error('Failed to execute ota_from_target_files %s', e)
|
||||
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
|
||||
generation process, then update the records in database.
|
||||
@@ -244,40 +269,41 @@ class ProcessesManagement:
|
||||
if not os.path.isfile(args['target']):
|
||||
raise FileNotFoundError
|
||||
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']:
|
||||
command.append('-v')
|
||||
if args['extra_keys']:
|
||||
args['extra'] = \
|
||||
'--' + ' --'.join(args['extra_keys']) + ' ' + args['extra']
|
||||
args['extra'] = '--' + \
|
||||
' --'.join(args['extra_keys']) + ' ' + args['extra']
|
||||
if args['extra']:
|
||||
command += args['extra'].strip().split(' ')
|
||||
if args['isIncremental']:
|
||||
if not os.path.isfile(args['incremental']):
|
||||
raise FileNotFoundError
|
||||
command.append('-i')
|
||||
command.append(args['incremental'])
|
||||
command.append(os.path.realpath(args['incremental']))
|
||||
if args['isPartial']:
|
||||
command.append('--partial')
|
||||
command.append(' '.join(args['partial']))
|
||||
command.append(args['target'])
|
||||
command.append(args['output'])
|
||||
command.append(os.path.realpath(args['target']))
|
||||
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,
|
||||
target=args['target'],
|
||||
incremental=args['incremental'] if args['isIncremental'] else '',
|
||||
verbose=args['verbose'],
|
||||
partial=args['partial'] if args['isPartial'] else [],
|
||||
partial=args['partial'] if args['isPartial'] else [
|
||||
],
|
||||
output=args['output'],
|
||||
status='Running',
|
||||
extra=args['extra'],
|
||||
start_time=int(time.time())
|
||||
start_time=int(time.time()),
|
||||
stdout=stdout,
|
||||
stderr=stderr
|
||||
)
|
||||
try:
|
||||
thread = threading.Thread(target=self.ota_run, args=(command, id))
|
||||
self.insert_database(job_info)
|
||||
thread.start()
|
||||
except AssertionError:
|
||||
raise SyntaxError
|
||||
self.ota_run(command, id, job_info.stdout, job_info.stderr)
|
||||
self.insert_database(job_info)
|
||||
logging.info(
|
||||
'Starting generating OTA package with id {}: \n {}'
|
||||
.format(id, command))
|
||||
|
||||
@@ -74,8 +74,9 @@ export default {
|
||||
*/
|
||||
async sendForm() {
|
||||
try {
|
||||
let response_messages = await this.$store.state.otaConfig.sendForms(
|
||||
this.targetBuilds, this.incrementalSources)
|
||||
let response_data = await this.$store.state.otaConfig.sendForms(
|
||||
this.targetBuilds, this.incrementalSources);
|
||||
let response_messages = response_data.map(d => d.msg);
|
||||
alert(response_messages.join('\n'))
|
||||
this.$store.state.otaConfig.reset()
|
||||
this.$store.commit('SET_TARGETS', [])
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
<h3>Build Library</h3>
|
||||
<UploadFile @file-uploaded="fetchTargetList" />
|
||||
<BuildTable
|
||||
v-if="targetDetails.length>0"
|
||||
v-if="targetDetails && targetDetails.length>0"
|
||||
:builds="targetDetails"
|
||||
/>
|
||||
<li
|
||||
@@ -42,7 +42,7 @@
|
||||
<script>
|
||||
import UploadFile from '@/components/UploadFile.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'
|
||||
|
||||
export default {
|
||||
@@ -72,9 +72,8 @@ export default {
|
||||
*/
|
||||
async fetchTargetList() {
|
||||
try {
|
||||
let response = await ApiService.getFileList('')
|
||||
this.targetDetails = response.data
|
||||
this.$emit('update:targetDetails', response.data)
|
||||
this.targetDetails = await ApiService.getBuildList()
|
||||
this.$emit('update:targetDetails', this.targetDetails)
|
||||
} catch (err) {
|
||||
alert(
|
||||
"Cannot fetch Android Builds list from the backend, for the following reasons:"
|
||||
@@ -88,9 +87,8 @@ export default {
|
||||
*/
|
||||
async updateBuildLib() {
|
||||
try {
|
||||
let response = await ApiService.getFileList('/target')
|
||||
this.targetDetails = response.data
|
||||
this.$emit('update:targetDetails', response.data)
|
||||
this.targetDetails = await ApiService.reconstructBuildList();
|
||||
this.$emit('update:targetDetails', this.targetDetails);
|
||||
} catch (err) {
|
||||
alert(
|
||||
"Cannot fetch Android Builds list from the backend, for the following reasons: "
|
||||
|
||||
@@ -74,12 +74,13 @@ export default {
|
||||
if (this.targetBuilds.length<2) {
|
||||
alert(
|
||||
'At least two OTA packeges has to be given!'
|
||||
)
|
||||
);
|
||||
return
|
||||
}
|
||||
try {
|
||||
let response_messages = await this.$store.state.otaConfig
|
||||
let response_data = await this.$store.state.otaConfig
|
||||
.sendChainForms(this.targetBuilds)
|
||||
let response_messages = response_data.map(d => d.msg);
|
||||
alert(response_messages.join('\n'))
|
||||
this.$store.state.otaConfig.reset()
|
||||
this.$store.commit('SET_TARGETS', [])
|
||||
|
||||
@@ -74,9 +74,9 @@ export default {
|
||||
*/
|
||||
async sendForm() {
|
||||
try {
|
||||
let response_message = await this.$store.state.otaConfig.sendForm(
|
||||
let data = await this.$store.state.otaConfig.sendForm(
|
||||
this.targetBuild, this.incrementalSource)
|
||||
alert(response_message)
|
||||
alert(data.msg);
|
||||
this.$store.state.otaConfig.reset()
|
||||
this.$store.commit('SET_TARGETS', [])
|
||||
this.$store.commit('SET_SOURCES', [])
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
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}`);
|
||||
|
||||
@@ -23,8 +23,13 @@ export default {
|
||||
getJobById(id) {
|
||||
return apiClient.get("/check/" + id)
|
||||
},
|
||||
getFileList(path) {
|
||||
return apiClient.get("/file" + path)
|
||||
async getBuildList() {
|
||||
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) {
|
||||
let formData = new FormData()
|
||||
@@ -37,12 +42,15 @@ export default {
|
||||
},
|
||||
async postInput(input, id) {
|
||||
try {
|
||||
const response = await apiClient.post(
|
||||
'/run/' + id, input)
|
||||
return response
|
||||
} catch (err) {
|
||||
console.log('err:', err)
|
||||
return
|
||||
let resp = await apiClient.post(
|
||||
'/run/' + id, JSON.stringify(input));
|
||||
return resp.data;
|
||||
} catch (error) {
|
||||
if (error.response.data) {
|
||||
return error.response.data;
|
||||
} else {
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -89,6 +89,7 @@ export class OTAConfiguration {
|
||||
let jsonOptions = Object.assign({}, this)
|
||||
jsonOptions.target = targetBuild
|
||||
jsonOptions.incremental = incrementalSource
|
||||
jsonOptions.isIncremental = !!incrementalSource;
|
||||
jsonOptions.id = uuid.v1()
|
||||
for (let flag of OTAExtraFlags) {
|
||||
if (jsonOptions[flag.key]) {
|
||||
@@ -97,8 +98,8 @@ export class OTAConfiguration {
|
||||
}
|
||||
}
|
||||
}
|
||||
let response = await ApiServices.postInput(jsonOptions, jsonOptions.id)
|
||||
return response.data
|
||||
let data = await ApiServices.postInput(jsonOptions, jsonOptions.id)
|
||||
return data;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -16,21 +16,21 @@
|
||||
<v-divider class="my-5" />
|
||||
<div>
|
||||
<h3>STDERR</h3>
|
||||
<div
|
||||
<pre
|
||||
ref="stderr"
|
||||
class="stderr"
|
||||
>
|
||||
{{ job.stderr }}
|
||||
<p ref="stderrBottom" />
|
||||
</div>
|
||||
</pre>
|
||||
<h3>STDOUT</h3>
|
||||
<div
|
||||
<pre
|
||||
ref="stdout"
|
||||
class="stdout"
|
||||
>
|
||||
{{ job.stdout }}
|
||||
<p ref="stdoutBottom" />
|
||||
</div>
|
||||
</pre>
|
||||
</div>
|
||||
<v-divider class="my-5" />
|
||||
<div class="download">
|
||||
|
||||
@@ -3,21 +3,6 @@
|
||||
v-if="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
|
||||
block
|
||||
@click="updateStatus"
|
||||
@@ -27,20 +12,17 @@
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import JobDisplay from '@/components/JobDisplay.vue'
|
||||
import ApiService from '../services/ApiService.js'
|
||||
import OTAJobTable from '@/components/OTAJobTable.vue'
|
||||
|
||||
export default {
|
||||
name: 'JobList',
|
||||
components: {
|
||||
JobDisplay,
|
||||
OTAJobTable
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
jobs: null,
|
||||
overStatus: new Map()
|
||||
}
|
||||
},
|
||||
created (){
|
||||
@@ -55,9 +37,6 @@ export default {
|
||||
console.log(err);
|
||||
}
|
||||
},
|
||||
mouseOver(id, status) {
|
||||
this.overStatus.set(id, status)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -8,6 +8,10 @@ import re
|
||||
import json
|
||||
|
||||
|
||||
class BuildFileInvalidError(Exception):
|
||||
pass
|
||||
|
||||
|
||||
@dataclass
|
||||
class BuildInfo:
|
||||
"""
|
||||
@@ -34,29 +38,26 @@ class BuildInfo:
|
||||
else:
|
||||
return ''
|
||||
|
||||
build = zipfile.ZipFile(self.path)
|
||||
try:
|
||||
with build.open('SYSTEM/build.prop', 'r') as build_prop:
|
||||
raw_info = build_prop.readlines()
|
||||
pattern_id = re.compile(b'(?<=ro\.build\.id\=).+')
|
||||
pattern_version = re.compile(
|
||||
b'(?<=ro\.build\.version\.incremental\=).+')
|
||||
pattern_flavor = re.compile(b'(?<=ro\.build\.flavor\=).+')
|
||||
self.build_id = extract_info(
|
||||
pattern_id, raw_info).decode('utf-8')
|
||||
self.build_version = extract_info(
|
||||
pattern_version, raw_info).decode('utf-8')
|
||||
self.build_flavor = extract_info(
|
||||
pattern_flavor, raw_info).decode('utf-8')
|
||||
except KeyError:
|
||||
pass
|
||||
try:
|
||||
with build.open('META/ab_partitions.txt', 'r') as partition_info:
|
||||
raw_info = partition_info.readlines()
|
||||
for line in raw_info:
|
||||
self.partitions.append(line.decode('utf-8').rstrip())
|
||||
except KeyError:
|
||||
pass
|
||||
with zipfile.ZipFile(self.path) as build:
|
||||
try:
|
||||
with build.open('SYSTEM/build.prop', 'r') as build_prop:
|
||||
raw_info = build_prop.readlines()
|
||||
pattern_id = re.compile(b'(?<=ro\.build\.id\=).+')
|
||||
pattern_version = re.compile(
|
||||
b'(?<=ro\.build\.version\.incremental\=).+')
|
||||
pattern_flavor = re.compile(b'(?<=ro\.build\.flavor\=).+')
|
||||
self.build_id = extract_info(
|
||||
pattern_id, raw_info).decode('utf-8')
|
||||
self.build_version = extract_info(
|
||||
pattern_version, raw_info).decode('utf-8')
|
||||
self.build_flavor = extract_info(
|
||||
pattern_flavor, raw_info).decode('utf-8')
|
||||
with build.open('META/ab_partitions.txt', 'r') as partition_info:
|
||||
raw_info = partition_info.readlines()
|
||||
for line in raw_info:
|
||||
self.partitions.append(line.decode('utf-8').rstrip())
|
||||
except KeyError as e:
|
||||
raise BuildFileInvalidError("Invalid build due to " + str(e))
|
||||
|
||||
def to_sql_form_dict(self):
|
||||
"""
|
||||
@@ -79,12 +80,16 @@ class TargetLib:
|
||||
"""
|
||||
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
|
||||
"""
|
||||
self.path = path
|
||||
with sqlite3.connect(self.path) as connect:
|
||||
self.working_dir = working_dir
|
||||
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.execute("""
|
||||
CREATE TABLE if not exists Builds (
|
||||
@@ -107,7 +112,12 @@ class TargetLib:
|
||||
"""
|
||||
build_info = BuildInfo(filename, path, int(time.time()))
|
||||
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.execute("""
|
||||
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)
|
||||
""", 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
|
||||
Args:
|
||||
path: a directory
|
||||
"""
|
||||
if os.path.isdir(path):
|
||||
builds_name = os.listdir(path)
|
||||
build_dir = self.working_dir
|
||||
if os.path.isdir(build_dir):
|
||||
builds_name = os.listdir(build_dir)
|
||||
for build_name in builds_name:
|
||||
if build_name.endswith(".zip"):
|
||||
self.new_build(build_name, os.path.join(path, build_name))
|
||||
elif os.path.isfile(path) and path.endswith(".zip"):
|
||||
self.new_build(os.path.split(path)[-1], path)
|
||||
path = os.path.join(build_dir, build_name)
|
||||
if build_name.endswith(".zip") and zipfile.is_zipfile(path):
|
||||
self.new_build(build_name, 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()
|
||||
|
||||
def sql_to_buildinfo(self, row):
|
||||
@@ -147,7 +159,7 @@ class TargetLib:
|
||||
A list of build_info, each of which is an object:
|
||||
(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.execute("""
|
||||
SELECT FileName, Path, UploadTime, BuildID, BuildVersion, BuildFlavor, Partitions
|
||||
@@ -161,7 +173,7 @@ class TargetLib:
|
||||
A build_info, which is an object:
|
||||
(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.execute("""
|
||||
SELECT FileName, Path, UploadTime, BuildID, BuildVersion, BuildFlavor, Partitions
|
||||
|
||||
@@ -182,7 +182,7 @@ class TestProcessesManagement(unittest.TestCase):
|
||||
def setUp(self):
|
||||
if os.path.isfile('test_process.db'):
|
||||
self.tearDown()
|
||||
self.processes = ProcessesManagement(path='test_process.db')
|
||||
self.processes = ProcessesManagement(db_path='test_process.db')
|
||||
testcase_job_info = TestJobInfo()
|
||||
testcase_job_info.setUp()
|
||||
self.test_job_info = testcase_job_info.setup_job(incremental='target/source.zip')
|
||||
|
||||
12
tools/otagui/vue.config.js
Normal file
12
tools/otagui/vue.config.js
Normal file
@@ -0,0 +1,12 @@
|
||||
const path = require('path')
|
||||
|
||||
module.exports = {
|
||||
configureWebpack: {
|
||||
resolve: {
|
||||
symlinks: false,
|
||||
alias: {
|
||||
vue: path.resolve('./node_modules/vue')
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -33,7 +33,6 @@ import json
|
||||
import cgi
|
||||
import os
|
||||
import stat
|
||||
import sys
|
||||
import zipfile
|
||||
|
||||
LOCAL_ADDRESS = '0.0.0.0'
|
||||
@@ -78,17 +77,17 @@ class RequestHandler(CORSSimpleHTTPHandler):
|
||||
self.wfile.write(
|
||||
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/':
|
||||
file_list = target_lib.get_builds()
|
||||
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]
|
||||
self._set_response(type='application/json')
|
||||
self.wfile.write(
|
||||
json.dumps(builds_info).encode()
|
||||
)
|
||||
logging.info(
|
||||
logging.debug(
|
||||
"GET request:\nPath:%s\nHeaders:\n%s\nBody:\n%s\n",
|
||||
str(self.path), str(self.headers), file_list
|
||||
)
|
||||
@@ -115,12 +114,16 @@ class RequestHandler(CORSSimpleHTTPHandler):
|
||||
post_data = json.loads(self.rfile.read(content_length))
|
||||
try:
|
||||
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(
|
||||
self._set_response(code=200)
|
||||
self.send_header("Content-Type", 'application/json')
|
||||
self.wfile.write(json.dumps(
|
||||
{"success": True, "msg": "OTA Generator started running"}).encode())
|
||||
except Exception as e:
|
||||
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",
|
||||
str(self.path), str(self.headers),
|
||||
json.dumps(post_data)
|
||||
@@ -144,7 +147,8 @@ class RequestHandler(CORSSimpleHTTPHandler):
|
||||
file_length -= len(self.rfile.readline())
|
||||
BUFFER_SIZE = 1024*1024
|
||||
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)
|
||||
target_lib.new_build(self.path[6:], file_name)
|
||||
self._set_response(code=201)
|
||||
@@ -171,17 +175,17 @@ def run_server(SeverClass=ThreadedHTTPServer, HandlerClass=RequestHandler, port=
|
||||
except KeyboardInterrupt:
|
||||
pass
|
||||
server_instance.server_close()
|
||||
logging.basicConfig(level=logging.DEBUG)
|
||||
logging.info('Server has been turned off.')
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
from sys import argv
|
||||
logging.basicConfig(level=logging.DEBUG)
|
||||
print(argv)
|
||||
logging.basicConfig(level=logging.INFO)
|
||||
EXTRACT_DIR = None
|
||||
if os.path.exists("otatools.zip"):
|
||||
logging.info("Found otatools.zip, extracting...")
|
||||
EXTRACT_DIR = "./"
|
||||
EXTRACT_DIR = "/tmp/otatools-" + str(os.getpid())
|
||||
os.makedirs(EXTRACT_DIR, exist_ok=True)
|
||||
with zipfile.ZipFile("otatools.zip", "r") as zfp:
|
||||
zfp.extractall(EXTRACT_DIR)
|
||||
@@ -189,19 +193,14 @@ if __name__ == '__main__':
|
||||
bin_dir = os.path.join(EXTRACT_DIR, "bin")
|
||||
for filename in os.listdir(bin_dir):
|
||||
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("PATH: %s", os.environ["PATH"])
|
||||
if not os.path.isdir('target'):
|
||||
os.mkdir('target', 755)
|
||||
if not os.path.isdir('output'):
|
||||
os.mkdir('output', 755)
|
||||
target_lib = TargetLib()
|
||||
jobs = ProcessesManagement()
|
||||
try:
|
||||
if len(argv) == 2:
|
||||
run_server(port=int(argv[1]))
|
||||
else:
|
||||
run_server()
|
||||
except KeyboardInterrupt:
|
||||
sys.exit(0)
|
||||
jobs = ProcessesManagement(otatools_dir=EXTRACT_DIR)
|
||||
if len(argv) == 2:
|
||||
run_server(port=int(argv[1]))
|
||||
else:
|
||||
run_server()
|
||||
|
||||
Reference in New Issue
Block a user