1
0
Fork 0
mirror of https://github.com/betaflight/betaflight-configurator.git synced 2025-07-25 09:15:49 +03:00

Fix intermittent backup corruption (#4392)

* Fix intermittent backup corruption

* Fix sonar

* Fix persistence issue and add debugging
This commit is contained in:
Mark Haslinghuis 2025-03-20 20:38:41 +01:00 committed by GitHub
parent 616b21446f
commit 1857d80a34
No known key found for this signature in database
GPG key ID: B5690EEEBB952194

View file

@ -15,13 +15,22 @@ class AutoBackup {
constructor() {
this.outputHistory = "";
this.callback = null;
// Store bound handler references to ensure proper removal
this.boundReadSerialAdapter = null;
this.boundHandleConnect = null;
this.boundHandleDisconnect = null;
}
handleConnect(openInfo) {
console.log("Connected to serial port:", openInfo);
if (openInfo) {
serial.removeEventListener("receive", this.readSerialAdapter);
serial.addEventListener("receive", this.readSerialAdapter.bind(this));
// Ensure we have a fresh start
this.cleanupListeners();
this.outputHistory = "";
// Store bound reference for later cleanup
this.boundReadSerialAdapter = this.readSerialAdapter.bind(this);
serial.addEventListener("receive", this.boundReadSerialAdapter);
this.run();
} else {
@ -31,10 +40,25 @@ class AutoBackup {
handleDisconnect(event) {
gui_log(i18n.getMessage(event.detail ? "serialPortClosedOk" : "serialPortClosedFail"));
this.cleanupListeners();
}
serial.removeEventListener("receive", this.readSerialAdapter);
serial.removeEventListener("connect", this.handleConnect);
serial.removeEventListener("disconnect", this.handleDisconnect);
// New method to ensure all listeners are properly removed
cleanupListeners() {
if (this.boundReadSerialAdapter) {
serial.removeEventListener("receive", this.boundReadSerialAdapter);
this.boundReadSerialAdapter = null;
}
if (this.boundHandleConnect) {
serial.removeEventListener("connect", this.boundHandleConnect);
this.boundHandleConnect = null;
}
if (this.boundHandleDisconnect) {
serial.removeEventListener("disconnect", this.boundHandleDisconnect);
this.boundHandleDisconnect = null;
}
}
readSerialAdapter(info) {
@ -47,7 +71,8 @@ class AutoBackup {
}
onClose() {
serial.addEventListener("disconnect", this.handleDisconnect.bind(this), { once: true });
this.boundHandleDisconnect = this.handleDisconnect.bind(this);
serial.addEventListener("disconnect", this.boundHandleDisconnect, { once: true });
serial.disconnect();
}
@ -83,14 +108,98 @@ class AutoBackup {
console.log("Running backup");
await this.activateCliMode();
await this.sendCommand("diff all");
this.waitForCommandCompletion("diff all");
}
setTimeout(async () => {
this.sendCommand("exit", this.onClose);
// remove the command from the output
const data = this.outputHistory.split("\n").slice(1).join("\n");
await this.save(data);
}, 1500);
waitForCommandCompletion(command) {
// Clear previous output
this.outputHistory = "";
// Add debug mode for troubleshooting
const DEBUG = true;
// Send the command
this.sendCommand(command);
if (DEBUG) console.log(`AutoBackup: Command sent: "${command}"`);
// Set up a check interval
const checkInterval = 100; // Check every 100ms
const maxWaitTime = 30000; // Increase to 30 seconds max wait - some configs are large
let elapsedTime = 0;
const intervalId = setInterval(() => {
elapsedTime += checkInterval;
if (DEBUG && elapsedTime % 1000 === 0) {
console.log(
`AutoBackup: Waiting for ${elapsedTime / 1000}s, buffer length: ${this.outputHistory.length} chars`,
);
if (this.outputHistory.length > 0) {
// Show last 30 chars for debugging
const lastChars = this.outputHistory.slice(-30).replace(/\r/g, "\\r").replace(/\n/g, "\\n");
console.log(`AutoBackup: Last chars: "${lastChars}"`);
}
}
// More robust prompt detection with multiple patterns
const hasPrompt =
this.outputHistory.endsWith("# ") ||
this.outputHistory.endsWith("#\r") ||
this.outputHistory.endsWith("#\n") ||
this.outputHistory.endsWith("#\r\n") ||
this.outputHistory.match(/\r?\n# ?$/);
if (hasPrompt) {
clearInterval(intervalId);
if (DEBUG) console.log("AutoBackup: Prompt detected, processing output");
// Process and save the output - more robust parsing
let lines = this.outputHistory.split(/\r?\n/);
// Log line count for debugging
if (DEBUG) console.log(`AutoBackup: Received ${lines.length} lines of output`);
// Check if first line contains the command
if (lines[0].includes(command)) {
lines = lines.slice(1);
if (DEBUG) console.log("AutoBackup: Removed command line from output");
}
// Check if last line is a prompt
if (
lines.length > 0 &&
(lines[lines.length - 1].trim() === "#" || lines[lines.length - 1].trim() === "")
) {
lines = lines.slice(0, -1);
if (DEBUG) console.log("AutoBackup: Removed prompt line from output");
}
const data = lines.join("\n");
if (DEBUG) console.log(`AutoBackup: Final data length: ${data.length} chars`);
this.sendCommand("exit", this.onClose.bind(this));
this.save(data);
}
// Check if we've waited too long
else if (elapsedTime >= maxWaitTime) {
clearInterval(intervalId);
console.error(`AutoBackup: Timeout waiting for command completion after ${maxWaitTime / 1000}s`);
// Try to save what we have (partial data is better than none)
if (DEBUG) console.log(`AutoBackup: Saving partial data, buffer length: ${this.outputHistory.length}`);
const lines = this.outputHistory.split(/\r?\n/);
// Remove first line if it contains the command
const filteredLines = lines[0].includes(command) ? lines.slice(1) : lines;
const data = filteredLines.join("\n");
this.sendCommand("exit", this.onClose.bind(this));
this.save(data);
}
}, checkInterval);
}
async activateCliMode() {
@ -125,13 +234,17 @@ class AutoBackup {
}
execute(callback) {
// Reset state at the beginning of a new run
this.outputHistory = "";
this.callback = callback;
this.cleanupListeners();
const port = PortHandler.portPicker.selectedPort;
const baud = PortHandler.portPicker.selectedBauds;
if (port.startsWith("serial")) {
serial.addEventListener("connect", this.handleConnect.bind(this), { once: true });
this.boundHandleConnect = this.handleConnect.bind(this);
serial.addEventListener("connect", this.boundHandleConnect, { once: true });
serial.connect(port, { baudRate: baud });
} else {
gui_log(i18n.getMessage("firmwareFlasherNoPortSelected"));