Skip to content

Commit b0e2c25

Browse files
Use /fs/ JSON 'writable' flag, cache it, drop DELETE-method check
The previous readOnly() heuristic checked whether DELETE was advertised in the OPTIONS Access-Control-Allow-Methods response. On CircuitPython 10.0.3 (observed on a LilyGo T-Display S3) DELETE was advertised even when USB had claimed the filesystem and PUT requests were rejected with a free-form 'USB ACTIVE, try resetting board' body that the editor never surfaced -- so saves silently failed instead of being skipped up front. Switch to the same source of truth circup uses: the 'writable' field in the /fs/ JSON response. Cache the value on the FileTransferClient so readOnly() doesn't fire an extra GET on every call, and let listDir() populate the cache when it runs (which already happens at connect time via web.js, so the typical hot path costs no extra round-trip). The cache resets naturally because FileTransferClient is reconstructed on every (re)connect. Co-authored-by: tyeth <tyethgundry@googlemail.com>
1 parent c6d118c commit b0e2c25

1 file changed

Lines changed: 21 additions & 7 deletions

File tree

js/common/web-file-transfer.js

Lines changed: 21 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -3,23 +3,32 @@ class FileTransferClient {
33
this.hostname = hostname;
44
this.connectionStatus = connectionStatusCB;
55
this._allowedMethods = null;
6+
// Cached `writable` flag from the /fs/ JSON response. Populated by
7+
// listDir() and by readOnly() when listDir hasn't been called yet.
8+
// A new FileTransferClient is created on every (re)connect, so this
9+
// cache resets naturally with the connection lifecycle.
10+
this._writable = null;
611
}
712

813
async readOnly() {
914
await this._checkConnection();
10-
console.log("Checking read only");
11-
const response = await this._fetch("/fs/", {method: "GET", headers: {"Accept": "application/json"}})
12-
const result = await response.json();
13-
//TODO: Tyeth: cache this value until reconnection, as listdir / connect already fetch it
14-
return result.writable === undefined || result.writable === false || !this._allowedMethods.includes("DELETE");
15+
// Older CircuitPython releases advertised DELETE in OPTIONS even when
16+
// the filesystem was actually read-only (USB had it), so we use the
17+
// `writable` field from the /fs/ JSON response instead -- same source
18+
// of truth as circup. If we already pulled it via listDir, reuse it.
19+
if (this._writable === null) {
20+
const response = await this._fetch("/fs/", {method: "GET", headers: {"Accept": "application/json"}});
21+
const result = await response.json();
22+
this._writable = result.writable === true;
23+
}
24+
return !this._writable;
1525
}
1626

1727
async _checkConnection() {
1828
if (!this.connectionStatus() && this._allowedMethods !== null) {
1929
throw new Error("Unable to perform file operation. Not Connected.");
2030
}
2131

22-
//TODO: Tyeth: reset this on reconnection
2332
if (this._allowedMethods === null) {
2433
const status = await this._fetch("/fs/", {method: "OPTIONS"});
2534
this._allowedMethods = status.headers.get("Access-Control-Allow-Methods").split(/,/).map(method => {return method.trim().toUpperCase();});
@@ -136,7 +145,12 @@ class FileTransferClient {
136145

137146
const response = await this._fetch(`/fs${path}`, {headers: {"Accept": "application/json"}});
138147
const results = await response.json();
139-
let listings = results
148+
// Cache the writable flag whenever the FS root response carries it,
149+
// so readOnly() doesn't need a separate round-trip.
150+
if (results.writable !== undefined) {
151+
this._writable = results.writable === true;
152+
}
153+
let listings = results;
140154
if (results.files !== undefined) {
141155
listings = results.files;
142156
}

0 commit comments

Comments
 (0)