From 646ee1c57c0defb43b9348cf80d1c3f190936906 Mon Sep 17 00:00:00 2001 From: keeramis Date: Thu, 16 Jan 2025 10:11:23 -0800 Subject: [PATCH] Refactor qdl flasher. Improve output logging --- src/cli/flash.js | 6 +- src/cmd/flash.js | 17 ++-- src/lib/qdl.js | 211 +++++++++++++++++++++++++++++------------------ 3 files changed, 145 insertions(+), 89 deletions(-) diff --git a/src/cli/flash.js b/src/cli/flash.js index 6a67a1079..472047662 100644 --- a/src/cli/flash.js +++ b/src/cli/flash.js @@ -41,7 +41,10 @@ module.exports = ({ commandProcessor, root }) => { 'tachyon' : { boolean: true, description: 'Flash Tachyon' - } + }, + 'output': { + describe: 'Folder to output the log file. Only available for Tachyon' + }, }, handler: (args) => { const FlashCommand = require('../cmd/flash'); @@ -61,6 +64,7 @@ module.exports = ({ commandProcessor, root }) => { '$0 $command --usb firmware.bin': 'Flash the binary over USB', '$0 $command --tachyon': 'Flash Tachyon from the files in the current directory', '$0 $command --tachyon /path/to/unpackaged-tool-and-files': 'Flash Tachyon from the files in the specified directory', + '$0 $command --tachyon /path/to/package.zip --output /path/to/log-folder': 'Flash Tachyon using the specified zip file and save the log to the given folder', }, epilogue: unindent(` When passing the --local flag, Device OS will be updated if the version on the device is outdated. diff --git a/src/cmd/flash.js b/src/cmd/flash.js index 5194a91e5..e98d012f3 100644 --- a/src/cmd/flash.js +++ b/src/cmd/flash.js @@ -27,7 +27,7 @@ const { const createApiCache = require('../lib/api-cache'); const { validateDFUSupport } = require('./device-util'); const unzip = require('unzipper'); -const qdl = require('../lib/qdl'); +const QdlFlasher = require('../lib/qdl'); const TACHYON_MANIFEST_FILE = 'manifest.json'; @@ -44,8 +44,8 @@ module.exports = class FlashCommand extends CLICommandBase { target, port, yes, - verbose, tachyon, + output, 'application-only': applicationOnly }) { if (!tachyon && !device && !binary && !local) { @@ -64,13 +64,13 @@ module.exports = class FlashCommand extends CLICommandBase { await this.flashLocal({ files: allFiles, applicationOnly, target }); } else if (tachyon) { let allFiles = binary ? [binary, ...files] : files; - await this.flashTachyon({ verbose, files: allFiles }); + await this.flashTachyon({ files: allFiles, output }); } else { await this.flashCloud({ device, files, target }); } } - async flashTachyon({ verbose, files }) { + async flashTachyon({ files, output }) { this.ui.write(`${os.EOL}Ensure that only one device is connected to the computer before proceeding.${os.EOL}`); let zipFile; @@ -102,19 +102,22 @@ module.exports = class FlashCommand extends CLICommandBase { } this.ui.write(`Starting download. This may take several minutes...${os.EOL}`); - const outputLog = path.join(process.cwd(), `qdl-output-${Date.now()}.log`); + if (output && !fs.existsSync(output)) { + fs.mkdirSync(output); + } + const outputLog = path.join(output ? output : process.cwd(), `tachyon_flash_${Date.now()}.log`); try { // put the output in a log file if not verbose this.ui.write(`Logs are being written to: ${outputLog}${os.EOL}`); - await qdl.run({ + const qdl = new QdlFlasher({ files: filesToProgram, includeDir, updateFolder, zip: zipFile, - verbose, ui: this.ui, outputLogFile: outputLog }); + await qdl.run(); fs.appendFileSync(outputLog, 'Download complete.'); } catch (error) { this.ui.write('Download failed'); diff --git a/src/lib/qdl.js b/src/lib/qdl.js index 321514071..d7ff01c4c 100644 --- a/src/lib/qdl.js +++ b/src/lib/qdl.js @@ -9,108 +9,157 @@ const mkdirTemp = util.promisify(temp.mkdir); const TACHYON_STORAGE_TYPE = 'ufs'; -async function getExecutable() { - const archType = utilities.getArchType(); - const archName = utilities.getOs(); - const qdlDir = path.join(__dirname, `../../assets/qdl/${archName}/${archType}`); - if (!await fs.pathExists(qdlDir)) { - throw new Error('Flashing Tachyon is not suppported on your OS'); +class QdlFlasher { + constructor({ files, includeDir, updateFolder, zip, ui, outputLogFile }) { + this.files = files; + this.includeDir = includeDir; + this.updateFolder = updateFolder; + this.zip = zip; + this.ui = ui; + this.outputLogFile = outputLogFile; + this.progressBar = null; + this.totalSectorsInAllFiles = 0; + this.totalSectorsFlashed = 0; + this.currentModuleName = ''; + this.currentModuleSectors = 0; + this.progressBarInitialized = false; + this.preparingDownload = false; } - // Copy qdl to a temporary directory, so it can run outside the pkg snapshot - const tmpDir = await mkdirTemp('qdl'); - await fs.copy(qdlDir, tmpDir); + async run() { + try { + const qdlPath = await this.getExecutable(); + const qdlArguments = this.buildArgs({ files: this.files, includeDir: this.includeDir, zip: this.zip }); + this.progressBar = this.ui.createProgressBar(); + const command = `${qdlPath} ${qdlArguments.join(' ')}`; + fs.appendFileSync(this.outputLogFile, `Command: ${command}\n`); + + const qdlProcess = execa(qdlPath, qdlArguments, { + cwd: this.updateFolder || process.cwd(), + stdio: 'pipe' + }); - return path.join(tmpDir, 'qdl' + (archName === 'win32' ? '.exe' : '')); -} + const handleStream = (stream) => { + stream.on('data', chunk => { + chunk.toString().split('\n').map(line => line.trim()).filter(Boolean).forEach(line => { + this.processLogLine(line, qdlProcess); + }); + }); + }; -async function run({ files, includeDir, updateFolder, zip, ui, outputLogFile }) { - const qdlPath = await getExecutable(); + handleStream(qdlProcess.stdout); + handleStream(qdlProcess.stderr); - const qdlArguments = [ - '--storage', TACHYON_STORAGE_TYPE, - ...(zip ? ['--zip', zip] : []), - ...(includeDir ? ['--include', includeDir] : []), - ...files - ]; + await qdlProcess; + } finally { + if (this.progressBarInitialized) { + this.progressBar.stop(); + } + } + } - const progressBar = ui.createProgressBar(); - let currentModuleName = '', currentModuleSectors = 0; - let totalSectorsInAllFiles = 0, totalSectorsFlashed = 0, progressBarInitialized = false; + async getExecutable() { + const archType = utilities.getArchType(); + const archName = utilities.getOs(); + const qdlDir = path.join(__dirname, `../../assets/qdl/${archName}/${archType}`); + if (!await fs.pathExists(qdlDir)) { + throw new Error('Flashing Tachyon is not suppported on your OS'); + } - const handleError = (process, message) => { - progressBar.stop(); - ui.stdout.write(message); - process.kill(); - }; + // Copy qdl to a temporary directory, so it can run outside the pkg snapshot + const tmpDir = await mkdirTemp('qdl'); + await fs.copy(qdlDir, tmpDir); - const processLogLine = (line, process) => { - fs.appendFileSync(outputLogFile, `${line}\n`); + return path.join(tmpDir, 'qdl' + (archName === 'win32' ? '.exe' : '')); + } + + buildArgs({ files, includeDir, zip }) { + return [ + '--storage', TACHYON_STORAGE_TYPE, + ...(zip ? ['--zip', zip] : []), + ...(includeDir ? ['--include', includeDir] : []), + ...files + ]; + } + + processLogLine(line, process) { + fs.appendFileSync(this.outputLogFile, `${line}\n`); if (line.includes('Waiting for EDL device')) { - handleError(process, `Device is not in EDL mode${os.EOL}`); + this.handleError(process, `Ensure your device is connected and in EDL mode${os.EOL}`); } else if (line.includes('[ERROR]')) { - handleError(process, `${os.EOL}Error detected: ${line}${os.EOL}`); - } else if (line.includes('status=getProgramInfo')) { - const match = line.match(/sectors_total=(\d+)/); - if (match) { - totalSectorsInAllFiles += parseInt(match[1], 10); - } + this.handleError(process, `${os.EOL}Error detected: ${line}${os.EOL}`); + } else { + this.processFlashingLogs(line); + } + } + + handleError(process, message) { + this.ui.stdout.write(message); + process.kill(); + } + + processFlashingLogs(line) { + if (line.includes('status=getProgramInfo')) { + this.handleProgramInfo(line); } else if (line.includes('status=Start flashing module')) { - const moduleNameMatch = line.match(/module=(.*?),/); - const sectorsTotalMatch = line.match(/sectors_total=(\d+)/); - if (moduleNameMatch && sectorsTotalMatch) { - currentModuleName = moduleNameMatch[1]; - currentModuleSectors = parseInt(sectorsTotalMatch[1], 10); - - if (!progressBarInitialized) { - progressBarInitialized = true; - progressBar.start(totalSectorsInAllFiles, totalSectorsFlashed, { description: `Flashing ${currentModuleName}` }); - } else { - progressBar.update(totalSectorsFlashed, { description: `Flashing ${currentModuleName}` }); - } - } + this.handleStartFlashingModule(line); } else if (line.includes('status=Flashing module')) { - const sectorsFlashedMatch = line.match(/sectors_done=(\d+)/); - if (sectorsFlashedMatch) { - const sectorsFlashed = parseInt(sectorsFlashedMatch[1], 10); - progressBar.update(totalSectorsFlashed + sectorsFlashed, { description: `Flashing module: ${currentModuleName} (${sectorsFlashed}/${currentModuleSectors} sectors)` }); - - if (sectorsFlashed === currentModuleSectors) { - totalSectorsFlashed += currentModuleSectors; - progressBar.update({ description: `Flashed ${currentModuleName}` }); - } - - if (totalSectorsFlashed === totalSectorsInAllFiles) { - progressBar.update({ description: 'Flashing complete' }); - progressBar.stop(); - } - } + this.handleFlashingModule(line); } - }; + } - try { - const qdlProcess = execa(qdlPath, qdlArguments, { cwd: updateFolder || process.cwd(), stdio: 'pipe' }); + handleProgramInfo(line) { + if (!this.preparingDownload) { + this.preparingDownload = true; + this.ui.stdout.write('Preparing to download files...'); + } + const match = line.match(/sectors_total=(\d+)/); + if (match) { + this.totalSectorsInAllFiles += parseInt(match[1], 10); + } + } - const handleStream = (stream) => { - stream.on('data', chunk => { - chunk.toString().split('\n').map(line => line.trim()).filter(Boolean).forEach(line => { - processLogLine(line, qdlProcess); + handleStartFlashingModule(line) { + const moduleNameMatch = line.match(/module=(.*?),/); + const sectorsTotalMatch = line.match(/sectors_total=(\d+)/); + if (moduleNameMatch && sectorsTotalMatch) { + this.currentModuleName = moduleNameMatch[1]; + this.currentModuleSectors = parseInt(sectorsTotalMatch[1], 10); + + if (!this.progressBarInitialized) { + this.progressBarInitialized = true; + this.progressBar.start(this.totalSectorsInAllFiles, this.totalSectorsFlashed, { + description: `Flashing ${this.currentModuleName}` + }); + } else { + this.progressBar.update(this.totalSectorsFlashed, { + description: `Flashing ${this.currentModuleName}` }); + } + } + } + + handleFlashingModule(line) { + const sectorsFlashedMatch = line.match(/sectors_done=(\d+)/); + if (sectorsFlashedMatch) { + const sectorsFlashed = parseInt(sectorsFlashedMatch[1], 10); + this.progressBar.update(this.totalSectorsFlashed + sectorsFlashed, { + description: `Flashing module: ${this.currentModuleName} (${sectorsFlashed}/${this.currentModuleSectors} sectors)` }); - }; - handleStream(qdlProcess.stdout, 'stdout'); - handleStream(qdlProcess.stderr, 'stderr'); + if (sectorsFlashed === this.currentModuleSectors) { + this.totalSectorsFlashed += this.currentModuleSectors; + this.progressBar.update({ description: `Flashed ${this.currentModuleName}` }); + } - await qdlProcess; - return; - } finally { - progressBar.stop(); + if (this.totalSectorsFlashed === this.totalSectorsInAllFiles) { + this.progressBar.update({ description: 'Flashing complete' }); + } + } } + } -module.exports = { - run -}; +module.exports = QdlFlasher;