Skip to content

Commit 7cc4f1f

Browse files
authored
Merge pull request #200 from makermelissa/serial-file-transfer
Revamped USB file transfer using REPL
2 parents a0f4b7c + ac90abe commit 7cc4f1f

13 files changed

Lines changed: 832 additions & 212 deletions

index.html

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -179,7 +179,7 @@
179179
<button class="purple-button ok-button">Ok</button>
180180
</div>
181181
</div>
182-
<div class="popup-modal shadow prompt" data-popup-modal="progress">
182+
<div class="popup-modal shadow prompt" data-popup-modal="progress" data-tabbable="false">
183183
<div class="label centered" id="status"></div>
184184
<div class="label centered" id="percentage"></div>
185185
<progress value="0"></progress>

js/common/dialogs.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -101,7 +101,7 @@ class GenericModal {
101101
this._modalLayerId = modalLayers.length;
102102
modal.style.zIndex = BLACKOUT_ZINDEX + 1 + (this._modalLayerId * 2);
103103

104-
if (!this._trap){
104+
if (!this._trap && modal.dataset.tabbable !== "false"){
105105
this._trap = focusTrap.createFocusTrap(modal, {
106106
initialFocus: () => modal,
107107
allowOutsideClick: true,

js/common/file_dialog.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -183,7 +183,7 @@ class FileDialog extends GenericModal {
183183
this._addFile({path: "..", isDir: true}, "fa-folder-open");
184184
}
185185
if (!this._fileHelper) {
186-
console.log("no client");
186+
console.error("no client");
187187
return;
188188
}
189189

@@ -196,7 +196,7 @@ class FileDialog extends GenericModal {
196196
this._addFile(fileObj);
197197
}
198198
} catch (e) {
199-
console.log(e);
199+
console.error(e);
200200
}
201201
this._setElementValue('fileNameField', "");
202202
this._setElementEnabled('okButton', this._validSelectableFolder());
Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -84,7 +84,7 @@ class FileTransferClient {
8484

8585
async loadDirHandle(preferSaved = true) {
8686
if (preferSaved) {
87-
const result = await loadSavedDirHandle();
87+
const result = await this.loadSavedDirHandle();
8888
if (!result) {
8989
return true;
9090
}

js/common/repl-file-transfer.js

Lines changed: 119 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,119 @@
1+
import {FileOps} from '@adafruit/circuitpython-repl-js';
2+
3+
class FileTransferClient {
4+
constructor(connectionStatusCB, repl) {
5+
this.connectionStatus = connectionStatusCB;
6+
this._dirHandle = null;
7+
this._fileops = new FileOps(repl, false);
8+
this._isReadOnly = null;
9+
}
10+
11+
async readOnly() {
12+
await this._checkConnection();
13+
return this._isReadOnly;
14+
}
15+
16+
async _checkConnection() {
17+
if (!this.connectionStatus(true)) {
18+
throw new Error("Unable to perform file operation. Not Connected.");
19+
}
20+
21+
if (this._isReadOnly === null) {
22+
this._isReadOnly = await this._fileops.isReadOnly();
23+
}
24+
}
25+
26+
async _checkWritable() {
27+
if (await this.readOnly()) {
28+
throw new Error("File System is Read Only.");
29+
}
30+
}
31+
32+
async readFile(path, raw = false) {
33+
await this._checkConnection();
34+
let contents = await this._fileops.readFile(path, raw);
35+
if (contents === null) {
36+
return raw ? null : "";
37+
}
38+
return contents;
39+
}
40+
41+
async writeFile(path, offset, contents, modificationTime, raw = false) {
42+
await this._checkConnection();
43+
await this._checkWritable();
44+
45+
if (!raw) {
46+
let encoder = new TextEncoder();
47+
let same = contents.slice(0, offset);
48+
let different = contents.slice(offset);
49+
offset = encoder.encode(same).byteLength;
50+
contents = encoder.encode(different);
51+
} else if (offset > 0) {
52+
contents = contents.slice(offset);
53+
}
54+
55+
return await this._fileops.writeFile(path, contents, offset, modificationTime, raw);
56+
}
57+
58+
async makeDir(path, modificationTime = Date.now()) {
59+
await this._checkConnection();
60+
await this._checkWritable();
61+
62+
return await this._fileops.makeDir(path, modificationTime);
63+
}
64+
65+
// Returns an array of objects, one object for each file or directory in the given path
66+
async listDir(path) {
67+
await this._checkConnection();
68+
return await this._fileops.listDir(path);
69+
}
70+
71+
// Deletes the file or directory at the given path. Directories must be empty.
72+
async delete(path) {
73+
await this._checkConnection();
74+
await this._checkWritable();
75+
76+
return await this._fileops.delete(path);
77+
}
78+
79+
// Moves the file or directory from oldPath to newPath.
80+
async move(oldPath, newPath) {
81+
await this._checkConnection();
82+
await this._checkWritable();
83+
84+
return await this._fileops.move(oldPath, newPath);
85+
}
86+
87+
async versionInfo() {
88+
// Possibly open /boot_out.txt and read the version info
89+
let versionInfo = {};
90+
console.log("Reading version info");
91+
let bootout = await this.readFile('/boot_out.txt', false);
92+
console.log(bootout);
93+
if (!bootout) {
94+
return null;
95+
}
96+
97+
// Add these items as they are found
98+
const searchItems = {
99+
version: /Adafruit CircuitPython (.*?) on/,
100+
build_date: /on ([0-9]{4}-[0-9]{2}-[0-9]{2});/,
101+
board_name: /; (.*?) with/,
102+
mcu_name: /with (.*?)\r?\n/,
103+
board_id: /Board ID:(.*?)\r?\n/,
104+
uid: /UID:([0-9A-F]{12})\r?\n/,
105+
}
106+
107+
for (const [key, regex] of Object.entries(searchItems)) {
108+
const match = bootout.match(regex);
109+
110+
if (match) {
111+
versionInfo[key] = match[1];
112+
}
113+
}
114+
115+
return versionInfo;
116+
}
117+
}
118+
119+
export {FileTransferClient};

js/script.js

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -6,9 +6,9 @@ import { python } from "@codemirror/lang-python";
66
import { syntaxHighlighting, indentUnit } from "@codemirror/language";
77
import { classHighlighter } from "@lezer/highlight";
88

9-
import { Terminal } from 'xterm';
10-
import { FitAddon } from 'xterm-addon-fit';
11-
import { WebLinksAddon } from 'xterm-addon-web-links';
9+
import { Terminal } from '@xterm/xterm';
10+
import { FitAddon } from '@xterm/addon-fit';
11+
import { WebLinksAddon } from '@xterm/addon-web-links';
1212

1313
import state from './state.js'
1414
import { BLEWorkflow } from './workflows/ble.js';
@@ -443,15 +443,14 @@ async function saveFileContents(path) {
443443
let oldUnchanged = unchanged;
444444
unchanged = doc.length;
445445
try {
446-
console.log("write");
447446
if (await workflow.writeFile(path, contents, offset)) {
448447
setFilename(workflow.currentFilename);
449448
setSaved(true);
450449
} else {
451450
await showMessage(`Saving file '${workflow.currentFilename} failed.`);
452451
}
453452
} catch (e) {
454-
console.log("write failed", e, e.stack);
453+
console.error("write failed", e, e.stack);
455454
unchanged = Math.min(oldUnchanged, unchanged);
456455
if (currentTimeout != null) {
457456
clearTimeout(currentTimeout);

js/workflows/ble.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
* This class will encapsulate all of the workflow functions specific to BLE
33
*/
44

5-
import {FileTransferClient} from 'ble-file-transfer-js';
5+
import {FileTransferClient} from '@adafruit/ble-file-transfer-js';
66

77
import {CONNTYPE, CONNSTATE} from '../constants.js';
88
import {Workflow} from './workflow.js';

0 commit comments

Comments
 (0)