fix(ampy): 添加 dispose 方法 null 检查防止重复调用报错

This commit is contained in:
yczpf2019
2026-01-24 20:28:38 +08:00
parent c232332d69
commit 0c6199d8e4

View File

@@ -1,429 +1,433 @@
goog.loadJs('web', () => { goog.loadJs('web', () => {
goog.require('path'); goog.require('path');
goog.require('Mustache'); goog.require('Mustache');
goog.require('Mixly.Env'); goog.require('Mixly.Env');
goog.require('Mixly.Events'); goog.require('Mixly.Events');
goog.require('Mixly.Msg'); goog.require('Mixly.Msg');
goog.require('Mixly.Ampy'); goog.require('Mixly.Ampy');
goog.require('Mixly.Web'); goog.require('Mixly.Web');
goog.provide('Mixly.Web.Ampy'); goog.provide('Mixly.Web.Ampy');
const { const {
Env, Env,
Events, Events,
Msg, Msg,
Ampy, Ampy,
Web Web
} = Mixly; } = Mixly;
class AmpyExt extends Ampy { class AmpyExt extends Ampy {
static { static {
this.LS = goog.readFileSync(path.join(Env.templatePath, 'python/ls.py')); this.LS = goog.readFileSync(path.join(Env.templatePath, 'python/ls.py'));
this.LS_RECURSIVE = goog.readFileSync(path.join(Env.templatePath, 'python/ls-recursive.py')); this.LS_RECURSIVE = goog.readFileSync(path.join(Env.templatePath, 'python/ls-recursive.py'));
this.LS_LONG_FORMAT = goog.readFileSync(path.join(Env.templatePath, 'python/ls-long-format.py')); this.LS_LONG_FORMAT = goog.readFileSync(path.join(Env.templatePath, 'python/ls-long-format.py'));
this.MKDIR = goog.readFileSync(path.join(Env.templatePath, 'python/mkdir.py')); this.MKDIR = goog.readFileSync(path.join(Env.templatePath, 'python/mkdir.py'));
this.MKFILE = goog.readFileSync(path.join(Env.templatePath, 'python/mkfile.py')); this.MKFILE = goog.readFileSync(path.join(Env.templatePath, 'python/mkfile.py'));
this.RENAME = goog.readFileSync(path.join(Env.templatePath, 'python/rename.py')); this.RENAME = goog.readFileSync(path.join(Env.templatePath, 'python/rename.py'));
this.RM = goog.readFileSync(path.join(Env.templatePath, 'python/rm.py')); this.RM = goog.readFileSync(path.join(Env.templatePath, 'python/rm.py'));
this.RMDIR = goog.readFileSync(path.join(Env.templatePath, 'python/rmdir.py')); this.RMDIR = goog.readFileSync(path.join(Env.templatePath, 'python/rmdir.py'));
this.GET = goog.readFileSync(path.join(Env.templatePath, 'python/get.py')); this.GET = goog.readFileSync(path.join(Env.templatePath, 'python/get.py'));
this.CWD = goog.readFileSync(path.join(Env.templatePath, 'python/cwd.py')); this.CWD = goog.readFileSync(path.join(Env.templatePath, 'python/cwd.py'));
this.CPDIR = goog.readFileSync(path.join(Env.templatePath, 'python/cpdir.py')); this.CPDIR = goog.readFileSync(path.join(Env.templatePath, 'python/cpdir.py'));
this.CPFILE = goog.readFileSync(path.join(Env.templatePath, 'python/cpfile.py')); this.CPFILE = goog.readFileSync(path.join(Env.templatePath, 'python/cpfile.py'));
} }
#device_ = null; #device_ = null;
#receiveTemp_ = []; #receiveTemp_ = [];
#writeBuffer_ = true; #writeBuffer_ = true;
#active_ = false; #active_ = false;
#dataLength_ = 256; #dataLength_ = 256;
#events_ = new Events(['message', 'replaceMessage']) #events_ = new Events(['message', 'replaceMessage'])
constructor(device, writeBuffer = true, dataLength = 256) { constructor(device, writeBuffer = true, dataLength = 256) {
super(); super();
this.#device_ = device; this.#device_ = device;
this.#writeBuffer_ = writeBuffer; this.#writeBuffer_ = writeBuffer;
this.#dataLength_ = dataLength; this.#dataLength_ = dataLength;
this.#addEventsListener_(); this.#addEventsListener_();
} }
#addEventsListener_() { #addEventsListener_() {
this.#device_.bind('onChar', (char) => { this.#device_.bind('onChar', (char) => {
if (['\r', '\n'].includes(char)) { if (['\r', '\n'].includes(char)) {
this.#receiveTemp_.push(''); this.#receiveTemp_.push('');
} else {
let line = this.#receiveTemp_.pop() ?? '';
this.#receiveTemp_.push(line + char);
}
});
}
bind(...args) {
return this.#events_.bind(...args);
}
message(message) {
this.#events_.run('message', message);
}
replaceMessage(lineNumber, message) {
this.#events_.run('replaceMessage', lineNumber, message);
}
getProgressMessage(name, percent) {
const sended = parseInt(percent * 45);
const left = percent === 0 ? '' : Array(sended).fill('=').join('');
const right = percent === 100 ? '' : Array(45 - sended).fill('-').join('');
return `${name} → |${left}${right}| ${(percent * 100).toFixed(1)}%`;
}
isActive() {
return this.#active_;
}
async readUntil(ending, withEnding = true, timeout = 5000) {
const startTime = Number(new Date());
let nowTime = startTime;
let readStr = '';
while (nowTime - startTime < timeout) {
const nowTime = Number(new Date());
let len = this.#receiveTemp_.length;
for (let i = 0; i < len; i++) {
const data = this.#receiveTemp_.shift();
let index = data.toLowerCase().indexOf(ending);
if (index !== -1) {
if (withEnding) {
index += ending.length;
}
this.#receiveTemp_.unshift(data.substring(index));
readStr += data.substring(0, index);
return readStr;
} else { } else {
readStr += data; let line = this.#receiveTemp_.pop() ?? '';
if (i !== len - 1) { this.#receiveTemp_.push(line + char);
readStr += '\n'; }
});
}
bind(...args) {
return this.#events_.bind(...args);
}
message(message) {
this.#events_.run('message', message);
}
replaceMessage(lineNumber, message) {
this.#events_.run('replaceMessage', lineNumber, message);
}
getProgressMessage(name, percent) {
const sended = parseInt(percent * 45);
const left = percent === 0 ? '' : Array(sended).fill('=').join('');
const right = percent === 100 ? '' : Array(45 - sended).fill('-').join('');
return `${name} → |${left}${right}| ${(percent * 100).toFixed(1)}%`;
}
isActive() {
return this.#active_;
}
async readUntil(ending, withEnding = true, timeout = 5000) {
const startTime = Number(new Date());
let nowTime = startTime;
let readStr = '';
while (nowTime - startTime < timeout) {
const nowTime = Number(new Date());
let len = this.#receiveTemp_.length;
for (let i = 0; i < len; i++) {
const data = this.#receiveTemp_.shift();
let index = data.toLowerCase().indexOf(ending);
if (index !== -1) {
if (withEnding) {
index += ending.length;
}
this.#receiveTemp_.unshift(data.substring(index));
readStr += data.substring(0, index);
return readStr;
} else {
readStr += data;
if (i !== len - 1) {
readStr += '\n';
}
} }
} }
if (nowTime - startTime >= timeout) {
return '';
}
if (!this.isActive()) {
throw new Error(Msg.Lang['ampy.dataReadInterrupt']);
}
await this.#device_.sleep(100);
} }
if (nowTime - startTime >= timeout) { }
async interrupt(timeout = 1000) {
for (let i = 0; i < 5; i++) {
// 中断两次
await this.#device_.sendBuffer([0x0D, 0x03]);
await this.#device_.sleep(100);
await this.#device_.sendBuffer([0x03]);
await this.#device_.sleep(100);
if (await this.readUntil('>>>', true, timeout)) {
return true;
}
}
return false;
}
async enterRawREPL(timeout = 1000) {
for (let i = 0; i < 5; i++) {
await this.#device_.sendBuffer([0x01]);
await this.#device_.sleep(100);
if (await this.readUntil('raw repl; ctrl-b to exit', true, timeout)) {
return true;
}
}
return false;
}
async exitRawREPL(timeout = 5000) {
await this.#device_.sendBuffer([0x02]);
await this.#device_.sleep(100);
let succeed = false;
if (await this.readUntil('>>>', true, timeout)) {
succeed = true;
}
return succeed;
}
async exitREPL(timeout = 5000) {
await this.#device_.sendBuffer([0x04]);
await this.#device_.sleep(100);
let succeed = false;
if (await this.readUntil('soft reboot', false, timeout)) {
succeed = true;
}
return succeed;
}
async exec(str, timeout = 5000) {
if (this.#writeBuffer_) {
const buffer = this.#device_.encode(str);
const len = Math.ceil(buffer.length / this.#dataLength_);
for (let i = 0; i < len; i++) {
const start = i * this.#dataLength_;
const end = Math.min((i + 1) * this.#dataLength_, buffer.length);
const writeBuffer = buffer.slice(start, end);
await this.#device_.sendBuffer(writeBuffer);
await this.#device_.sleep(10);
}
} else {
for (let i = 0; i < str.length / this.#dataLength_; i++) {
const start = i * this.#dataLength_;
const end = Math.min((i + 1) * this.#dataLength_, str.length);
let data = str.substring(start, end);
await this.#device_.sendString(data);
await this.#device_.sleep(10);
}
}
await this.#device_.sendBuffer([0x04]);
return await this.follow(timeout);
}
async follow(timeout = 1000) {
let data = await this.readUntil('\x04', true, timeout);
if (data.length < 1) {
throw new Error(Msg.Lang['ampy.waitingFirstEOFTimeout']);
}
let start = data.toLowerCase().lastIndexOf('ok');
if (start === -1) {
start = 0;
} else {
start += 2;
}
data = data.substring(start, data.length - 1);
let dataError = await this.readUntil('\x04', true, timeout);
if (dataError.length < 1) {
throw new Error(Msg.Lang['ampy.secondEOFTimeout']);
}
dataError = dataError.substring(0, dataError.length - 1);
return { data, dataError };
}
async enter() {
if (this.isActive()) {
return;
}
this.#active_ = true;
await this.#device_.open(115200);
await this.#device_.sleep(500);
await this.#device_.sendBuffer([0x02]);
if (!await this.interrupt()) {
throw new Error(Msg.Lang['ampy.interruptFailed']);
}
if (!await this.enterRawREPL()) {
throw new Error(Msg.Lang['ampy.enterRawREPLFailed']);
}
}
async exit() {
if (!this.isActive()) {
return;
}
if (!await this.exitRawREPL()) {
throw new Error(Msg.Lang['ampy.exitRawREPLFailed']);
}
/*if (!await this.exitREPL()) {
throw new Error(Msg.Lang['ampy.exitREPLFailed']);
}*/
await this.#device_.close();
this.#active_ = false;
}
async get(filename, encoding = 'utf8', timeout = 5000) {
if (!this.isActive()) {
throw new Error(Msg.Lang['ampy.portIsNotOpen']);
}
const code = Mustache.render(AmpyExt.GET, {
path: filename
});
const { data, dataError } = await this.exec(code, timeout);
if (dataError) {
return ''; return '';
} }
if (encoding === 'utf8') {
return this.#device_.decode(this.unhexlify(data));
} else {
return this.unhexlify(data);
}
}
async put(filename, data, timeout = 5000) {
if (!this.isActive()) { if (!this.isActive()) {
throw new Error(Msg.Lang['ampy.dataReadInterrupt']); throw new Error(Msg.Lang['ampy.portIsNotOpen']);
} }
await this.#device_.sleep(100); this.message(`Writing ${filename}...\n`);
} this.message(this.getProgressMessage('', 0));
} await this.exec(`file = open('${filename}', 'wb')`, timeout);
let buffer = null;
async interrupt(timeout = 1000) { if (data.constructor === String) {
for (let i = 0; i < 5; i++) { buffer = this.#device_.encode(data);
// 中断两次 } else if (data.constructor === ArrayBuffer) {
await this.#device_.sendBuffer([0x0D, 0x03]); buffer = new Uint8Array(data);
await this.#device_.sleep(100); } else {
await this.#device_.sendBuffer([0x03]); buffer = data;
await this.#device_.sleep(100);
if (await this.readUntil('>>>', true, timeout)) {
return true;
} }
} const len = Math.ceil(buffer.length / 64);
return false; if (!len) {
} this.replaceMessage(-1, this.getProgressMessage('', 1));
async enterRawREPL(timeout = 1000) {
for (let i = 0; i < 5; i++) {
await this.#device_.sendBuffer([0x01]);
await this.#device_.sleep(100);
if (await this.readUntil('raw repl; ctrl-b to exit', true, timeout)) {
return true;
} }
} let sendedLength = 0;
return false;
}
async exitRawREPL(timeout = 5000) {
await this.#device_.sendBuffer([0x02]);
await this.#device_.sleep(100);
let succeed = false;
if (await this.readUntil('>>>', true, timeout)) {
succeed = true;
}
return succeed;
}
async exitREPL(timeout = 5000) {
await this.#device_.sendBuffer([0x04]);
await this.#device_.sleep(100);
let succeed = false;
if (await this.readUntil('soft reboot', false, timeout)) {
succeed = true;
}
return succeed;
}
async exec(str, timeout = 5000) {
if (this.#writeBuffer_) {
const buffer = this.#device_.encode(str);
const len = Math.ceil(buffer.length / this.#dataLength_);
for (let i = 0; i < len; i++) { for (let i = 0; i < len; i++) {
const start = i * this.#dataLength_; const writeBuffer = buffer.slice(i * 64, Math.min((i + 1) * 64, buffer.length));
const end = Math.min((i + 1) * this.#dataLength_, buffer.length); sendedLength += writeBuffer.length;
const writeBuffer = buffer.slice(start, end); const percent = sendedLength / buffer.length;
await this.#device_.sendBuffer(writeBuffer); this.replaceMessage(-1, this.getProgressMessage('', percent));
await this.#device_.sleep(10); let writeStr = '';
} for (let num of writeBuffer) {
} else { let numStr = num.toString(16);
for (let i = 0; i < str.length / this.#dataLength_; i++) { if (numStr.length === 1) {
const start = i * this.#dataLength_; numStr = '0' + numStr;
const end = Math.min((i + 1) * this.#dataLength_, str.length); }
let data = str.substring(start, end); writeStr += '\\x' + numStr;
await this.#device_.sendString(data);
await this.#device_.sleep(10);
}
}
await this.#device_.sendBuffer([0x04]);
return await this.follow(timeout);
}
async follow(timeout = 1000) {
let data = await this.readUntil('\x04', true, timeout);
if (data.length < 1) {
throw new Error(Msg.Lang['ampy.waitingFirstEOFTimeout']);
}
let start = data.toLowerCase().lastIndexOf('ok');
if (start === -1) {
start = 0;
} else {
start += 2;
}
data = data.substring(start, data.length - 1);
let dataError = await this.readUntil('\x04', true, timeout);
if (dataError.length < 1) {
throw new Error(Msg.Lang['ampy.secondEOFTimeout']);
}
dataError = dataError.substring(0, dataError.length - 1);
return { data, dataError };
}
async enter() {
if (this.isActive()) {
return;
}
this.#active_ = true;
await this.#device_.open(115200);
await this.#device_.sleep(500);
await this.#device_.sendBuffer([0x02]);
if (!await this.interrupt()) {
throw new Error(Msg.Lang['ampy.interruptFailed']);
}
if (!await this.enterRawREPL()) {
throw new Error(Msg.Lang['ampy.enterRawREPLFailed']);
}
}
async exit() {
if (!this.isActive()) {
return;
}
if (!await this.exitRawREPL()) {
throw new Error(Msg.Lang['ampy.exitRawREPLFailed']);
}
/*if (!await this.exitREPL()) {
throw new Error(Msg.Lang['ampy.exitREPLFailed']);
}*/
await this.#device_.close();
this.#active_ = false;
}
async get(filename, encoding = 'utf8', timeout = 5000) {
if (!this.isActive()) {
throw new Error(Msg.Lang['ampy.portIsNotOpen']);
}
const code = Mustache.render(AmpyExt.GET, {
path: filename
});
const { data, dataError } = await this.exec(code, timeout);
if (dataError) {
return '';
}
if (encoding === 'utf8') {
return this.#device_.decode(this.unhexlify(data));
} else {
return this.unhexlify(data);
}
}
async put(filename, data, timeout = 5000) {
if (!this.isActive()) {
throw new Error(Msg.Lang['ampy.portIsNotOpen']);
}
this.message(`Writing ${filename}...\n`);
this.message(this.getProgressMessage('', 0));
await this.exec(`file = open('${filename}', 'wb')`, timeout);
let buffer = null;
if (data.constructor === String) {
buffer = this.#device_.encode(data);
} else if (data.constructor === ArrayBuffer) {
buffer = new Uint8Array(data);
} else {
buffer = data;
}
const len = Math.ceil(buffer.length / 64);
if (!len) {
this.replaceMessage(-1, this.getProgressMessage('', 1));
}
let sendedLength = 0;
for (let i = 0; i < len; i++) {
const writeBuffer = buffer.slice(i * 64, Math.min((i + 1) * 64, buffer.length));
sendedLength += writeBuffer.length;
const percent = sendedLength / buffer.length;
this.replaceMessage(-1, this.getProgressMessage('', percent));
let writeStr = '';
for (let num of writeBuffer) {
let numStr = num.toString(16);
if (numStr.length === 1) {
numStr = '0' + numStr;
} }
writeStr += '\\x' + numStr; await this.exec(`file.write(b'${writeStr}')`, timeout);
} }
await this.exec(`file.write(b'${writeStr}')`, timeout); await this.exec('file.close()', timeout);
this.message('\n');
} }
await this.exec('file.close()', timeout);
this.message('\n');
}
async ls(directory = '/', longFormat = true, recursive = false, timeout = 5000) { async ls(directory = '/', longFormat = true, recursive = false, timeout = 5000) {
if (!this.isActive()) { if (!this.isActive()) {
throw new Error(Msg.Lang['ampy.portIsNotOpen']); throw new Error(Msg.Lang['ampy.portIsNotOpen']);
}
let code = '';
if (longFormat) {
code = Mustache.render(AmpyExt.LS_LONG_FORMAT, {
path: directory
});
} else if (recursive) {
code = Mustache.render(AmpyExt.LS_RECURSIVE, {
path: directory
});
} else {
code = Mustache.render(AmpyExt.LS, {
path: directory
});
}
const { data, dataError } = await this.exec(code, timeout);
if (dataError) {
return [];
}
return JSON.parse(data.replaceAll('\'', '\"'));
} }
let code = '';
if (longFormat) { async mkdir(directory, timeout = 5000) {
code = Mustache.render(AmpyExt.LS_LONG_FORMAT, { if (!this.isActive()) {
throw new Error(Msg.Lang['ampy.portIsNotOpen']);
}
const code = Mustache.render(AmpyExt.MKDIR, {
path: directory path: directory
}); });
} else if (recursive) { const { dataError } = await this.exec(code, timeout);
code = Mustache.render(AmpyExt.LS_RECURSIVE, { return !dataError;
}
async mkfile(file, timeout = 5000) {
if (!this.isActive()) {
throw new Error(Msg.Lang['ampy.portIsNotOpen']);
}
const code = Mustache.render(AmpyExt.MKFILE, {
path: file
});
const { dataError } = await this.exec(code, timeout);
return !dataError;
}
async rename(oldname, newname, timeout = 5000) {
if (!this.isActive()) {
throw new Error(Msg.Lang['ampy.portIsNotOpen']);
}
const code = Mustache.render(AmpyExt.RENAME, {
oldPath: oldname,
newPath: newname
});
const { dataError } = await this.exec(code, timeout);
return !dataError;
}
async rm(filename, timeout = 5000) {
if (!this.isActive()) {
throw new Error(Msg.Lang['ampy.portIsNotOpen']);
}
const code = Mustache.render(AmpyExt.RM, {
path: filename
});
await this.exec(code);
const { dataError } = await this.exec(code, timeout);
return !dataError;
}
async rmdir(directory, timeout = 5000) {
if (!this.isActive()) {
throw new Error(Msg.Lang['ampy.portIsNotOpen']);
}
const code = Mustache.render(AmpyExt.RMDIR, {
path: directory path: directory
}); });
} else { const { dataError } = await this.exec(code, timeout);
code = Mustache.render(AmpyExt.LS, { return !dataError;
path: directory }
async cpdir(oldname, newname, timeout = 5000) {
if (!this.isActive()) {
throw new Error(Msg.Lang['ampy.portIsNotOpen']);
}
const code = Mustache.render(AmpyExt.CPDIR, {
oldPath: oldname,
newPath: newname
}); });
const { data, dataError } = await this.exec(code, timeout);
console.log(data, dataError)
return !dataError;
} }
const { data, dataError } = await this.exec(code, timeout);
if (dataError) { async cpfile(oldname, newname, timeout = 5000) {
return []; if (!this.isActive()) {
throw new Error(Msg.Lang['ampy.portIsNotOpen']);
}
const code = Mustache.render(AmpyExt.CPFILE, {
oldPath: oldname,
newPath: newname
});
const { dataError } = await this.exec(code, timeout);
return !dataError;
}
async cwd(timeout = 5000) {
if (!this.isActive()) {
throw new Error(Msg.Lang['ampy.portIsNotOpen']);
}
const code = Mustache.render(AmpyExt.CWD, {});
const { data, dataError } = await this.exec(code, timeout);
if (dataError) {
return '/';
}
return data;
}
getDevice() {
return this.#device_;
}
async dispose() {
this.#active_ = false;
if (this.#device_) {
await this.#device_.dispose();
this.#device_ = null;
}
if (this.#events_) {
this.#events_.reset();
this.#events_ = null;
}
} }
return JSON.parse(data.replaceAll('\'', '\"'));
} }
async mkdir(directory, timeout = 5000) { Web.Ampy = AmpyExt;
if (!this.isActive()) {
throw new Error(Msg.Lang['ampy.portIsNotOpen']);
}
const code = Mustache.render(AmpyExt.MKDIR, {
path: directory
});
const { dataError } = await this.exec(code, timeout);
return !dataError;
}
async mkfile(file, timeout = 5000) {
if (!this.isActive()) {
throw new Error(Msg.Lang['ampy.portIsNotOpen']);
}
const code = Mustache.render(AmpyExt.MKFILE, {
path: file
});
const { dataError } = await this.exec(code, timeout);
return !dataError;
}
async rename(oldname, newname, timeout = 5000) {
if (!this.isActive()) {
throw new Error(Msg.Lang['ampy.portIsNotOpen']);
}
const code = Mustache.render(AmpyExt.RENAME, {
oldPath: oldname,
newPath: newname
});
const { dataError } = await this.exec(code, timeout);
return !dataError;
}
async rm(filename, timeout = 5000) {
if (!this.isActive()) {
throw new Error(Msg.Lang['ampy.portIsNotOpen']);
}
const code = Mustache.render(AmpyExt.RM, {
path: filename
});
await this.exec(code);
const { dataError } = await this.exec(code, timeout);
return !dataError;
}
async rmdir(directory, timeout = 5000) {
if (!this.isActive()) {
throw new Error(Msg.Lang['ampy.portIsNotOpen']);
}
const code = Mustache.render(AmpyExt.RMDIR, {
path: directory
});
const { dataError } = await this.exec(code, timeout);
return !dataError;
}
async cpdir(oldname, newname, timeout = 5000) {
if (!this.isActive()) {
throw new Error(Msg.Lang['ampy.portIsNotOpen']);
}
const code = Mustache.render(AmpyExt.CPDIR, {
oldPath: oldname,
newPath: newname
});
const { data, dataError } = await this.exec(code, timeout);
console.log(data, dataError)
return !dataError;
}
async cpfile(oldname, newname, timeout = 5000) {
if (!this.isActive()) {
throw new Error(Msg.Lang['ampy.portIsNotOpen']);
}
const code = Mustache.render(AmpyExt.CPFILE, {
oldPath: oldname,
newPath: newname
});
const { dataError } = await this.exec(code, timeout);
return !dataError;
}
async cwd(timeout = 5000) {
if (!this.isActive()) {
throw new Error(Msg.Lang['ampy.portIsNotOpen']);
}
const code = Mustache.render(AmpyExt.CWD, {});
const { data, dataError } = await this.exec(code, timeout);
if (dataError) {
return '/';
}
return data;
}
getDevice() {
return this.#device_;
}
async dispose() {
this.#active_ = false;
await this.#device_.dispose();
this.#device_ = null;
this.#events_.reset();
this.#events_ = null;
}
}
Web.Ampy = AmpyExt;
}); });