Skip to content

Commit

Permalink
Refactor qdl flasher. Improve output logging
Browse files Browse the repository at this point in the history
  • Loading branch information
keeramis committed Jan 16, 2025
1 parent 4f13ca0 commit 646ee1c
Show file tree
Hide file tree
Showing 3 changed files with 145 additions and 89 deletions.
6 changes: 5 additions & 1 deletion src/cli/flash.js
Original file line number Diff line number Diff line change
Expand Up @@ -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');
Expand All @@ -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.
Expand Down
17 changes: 10 additions & 7 deletions src/cmd/flash.js
Original file line number Diff line number Diff line change
Expand Up @@ -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';

Expand All @@ -44,8 +44,8 @@ module.exports = class FlashCommand extends CLICommandBase {
target,
port,
yes,
verbose,
tachyon,
output,
'application-only': applicationOnly
}) {
if (!tachyon && !device && !binary && !local) {
Expand All @@ -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;
Expand Down Expand Up @@ -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');
Expand Down
211 changes: 130 additions & 81 deletions src/lib/qdl.js
Original file line number Diff line number Diff line change
Expand Up @@ -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;

0 comments on commit 646ee1c

Please sign in to comment.