diff --git a/assets/qdl/darwin/arm64/qdl b/assets/qdl/darwin/arm64/qdl index 20a5b3bd2..59828b7fe 100755 Binary files a/assets/qdl/darwin/arm64/qdl and b/assets/qdl/darwin/arm64/qdl differ diff --git a/assets/qdl/darwin/x64/qdl b/assets/qdl/darwin/x64/qdl index d7d3de0d5..a98b99728 100755 Binary files a/assets/qdl/darwin/x64/qdl and b/assets/qdl/darwin/x64/qdl differ diff --git a/assets/qdl/linux/x64/qdl b/assets/qdl/linux/x64/qdl index 3842d1481..3945b0090 100755 Binary files a/assets/qdl/linux/x64/qdl and b/assets/qdl/linux/x64/qdl differ diff --git a/assets/qdl/win32/x64/qdl.exe b/assets/qdl/win32/x64/qdl.exe index 8bd2a257d..43b41891d 100644 Binary files a/assets/qdl/win32/x64/qdl.exe and b/assets/qdl/win32/x64/qdl.exe differ 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 8b8ad7cda..a64717c38 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,14 +64,14 @@ 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 }) { - this.ui.write(`${os.EOL}Ensure only one device is connected to the computer${os.EOL}`); + 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; let includeDir = ''; @@ -101,27 +101,27 @@ module.exports = class FlashCommand extends CLICommandBase { filesToProgram = files; } - this.ui.write(`Starting download. The download may take several minutes...${os.EOL}`); - - const res = await qdl.run({ - files: filesToProgram, - includeDir, - updateFolder, - zip: zipFile, - verbose, - ui: this.ui - }); - // put the output in a log file if not verbose - if (!verbose) { - const outputLog = path.join(process.cwd(), `qdl-output-${Date.now()}.log`); - if (res?.stdout) { - await fs.writeFile(outputLog, res.stdout); - } - this.ui.write(`Download complete. Output log available at ${outputLog}${os.EOL}`); - } else { - this.ui.write(`Download complete${os.EOL}`); + this.ui.write(`Starting download. This may take several minutes...${os.EOL}`); + if (output && !fs.existsSync(output)) { + fs.mkdirSync(output); + } + const outputLog = path.join(output ? output : process.cwd(), `tachyon_flash_${Date.now()}.log`); + try { + this.ui.write(`Logs are being written to: ${outputLog}${os.EOL}`); + const qdl = new QdlFlasher({ + files: filesToProgram, + includeDir, + updateFolder, + zip: zipFile, + ui: this.ui, + outputLogFile: outputLog + }); + await qdl.run(); + fs.appendFileSync(outputLog, 'Download complete.'); + } catch (error) { + this.ui.write('Download failed'); + fs.appendFileSync(outputLog, 'Download failed with error: ' + error.message); } - // TODO: Handle errors } async _extractFlashFilesFromDir(dirPath) { diff --git a/src/lib/qdl.js b/src/lib/qdl.js index 079e23ed5..b9fa38358 100644 --- a/src/lib/qdl.js +++ b/src/lib/qdl.js @@ -9,46 +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`); - return path.join(tmpDir, 'qdl' + (archName === 'win32' ? '.exe' : '')); -} + const qdlProcess = execa(qdlPath, qdlArguments, { + cwd: this.updateFolder || process.cwd(), + stdio: 'pipe' + }); + + const handleStream = (stream) => { + stream.on('data', chunk => { + chunk.toString().split('\n').map(line => line.trim()).filter(Boolean).forEach(line => { + this.processLogLine(line, qdlProcess); + }); + }); + }; + + handleStream(qdlProcess.stdout); + handleStream(qdlProcess.stderr); + + await qdlProcess; + } finally { + if (this.progressBarInitialized) { + this.progressBar.stop(); + } + } + } + + 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'); + } -/** - */ -async function run({ files, includeDir, updateFolder, zip, verbose, ui }) { - const qdl = await getExecutable(); + // 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 qdlArgs = [ - '--storage', - TACHYON_STORAGE_TYPE, - ...(zip ? ['--zip', zip] : []), - ...(includeDir ? ['--include', includeDir] : []), - ...files - ]; + return path.join(tmpDir, 'qdl' + (archName === 'win32' ? '.exe' : '')); + } + + buildArgs({ files, includeDir, zip }) { + return [ + '--storage', TACHYON_STORAGE_TYPE, + ...(zip ? ['--zip', zip] : []), + ...(includeDir ? ['--include', includeDir] : []), + ...files + ]; + } - if (verbose) { - ui.write(`Command: ${qdl} ${qdlArgs.join(' ')}${os.EOL}`); + processLogLine(line, process) { + fs.appendFileSync(this.outputLogFile, `${line}\n`); + + if (line.includes('Waiting for EDL device')) { + this.handleError(process, `Ensure your device is connected and in EDL mode${os.EOL}`); + } else if (line.includes('[ERROR]')) { + this.handleError(process, `${os.EOL}Error detected: ${line}${os.EOL}`); + } else { + this.processFlashingLogs(line); + } } - const res = await execa(qdl, qdlArgs, { - cwd: updateFolder || process.cwd(), - stdio: verbose ? 'inherit' : 'pipe' - }); + 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')) { + this.handleModuleStart(line); + } else if (line.includes('status=Flashing module')) { + this.handleModuleProgress(line); + } + } + + 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); + } + } + + handleModuleStart(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}` + }); + } + } + } + + handleModuleProgress(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)` + }); + + if (sectorsFlashed === this.currentModuleSectors) { + this.totalSectorsFlashed += this.currentModuleSectors; + this.progressBar.update({ description: `Flashed ${this.currentModuleName}` }); + } + + if (this.totalSectorsFlashed === this.totalSectorsInAllFiles) { + this.progressBar.update({ description: 'Flashing complete' }); + } + } + } - return res; } -module.exports = { - run -}; + +module.exports = QdlFlasher;