← Commit history

demo: stage large part files through write_file_chunk (ab#90) - a 3.7 MB .step now transfers in ~1 MB chunks instead of 413ing

John Lauer ·9ec714507e ·2mo ago ·parent fe2019c
1 file changed +52−7
web-control-demo.html+52−7
@@ -1354,7 +1354,7 @@ for (const w of mine) {     if (note) {       note.innerHTML = esc(PART.kind) + " - .step is <strong>" + fmtKB(PART.stepBytes) + "</strong>" +         (PART.stepBytes > 2000000-          ? ' <span style="color:#d29922">- over the ~2.5 MB /command body cap, so staging the 3D model is expected to 413 (ab#90); the symbol and footprint still work</span>'+          ? ' <span style="color:#3fb950">- staged in ~1 MB chunks via write_file_chunk (ab#90), so the 3D model transfers fine</span>'           : "");     }   }@@ -1396,13 +1396,58 @@ for (const w of mine) {     return btoa(out);   }   function stageFail(msg) { var e = new Error(msg); e.staged = true; return e; }-  // Write one part file onto the PC via ab's write_file; resolves the ABSOLUTE path.+  // Write one part file onto the PC; resolves the ABSOLUTE path.+  //+  // Small files go through write_file in a single request. Anything big enough to+  // threaten the /command body cap goes through write_file_chunk instead+  // (ab 2.0.72, ab#90): base64 inflates ~33%, and a body over ~2.5 MB is refused+  // with 413 - which is why a real .step 3D model could not be staged at all+  // before. The chunked path sends ~1 MB of RAW bytes per request, so the request+  // size stops depending on the file size.+  var CHUNK_RAW = 1024 * 1024;              // ~1.37 MB of base64 per request+  var SINGLE_SHOT_MAX = 1024 * 1024;        // above this, chunk it+   function stageFile(c, name, b64) {-    return postSelf("write_file", { path: PART.dir + name, content: b64, encoding: "base64" }).then(function (r) {-      if (!(r.s === 200 && r.j && r.j.success)) throw stageFail("write_file " + name + ": " + ((r.j && (r.j.error || r.j.errorCode)) || r.s));-      clog(c, "  staged " + name + " -> " + r.j.path);-      return r.j.path;-    });+    var path = PART.dir + name;+    // b64 length -> raw bytes (4 base64 chars encode 3 bytes)+    var rawBytes = Math.floor(b64.length * 3 / 4);+    if (rawBytes <= SINGLE_SHOT_MAX) {+      return postSelf("write_file", { path: path, content: b64, encoding: "base64" }).then(function (r) {+        if (!(r.s === 200 && r.j && r.j.success)) throw stageFail("write_file " + name + ": " + ((r.j && (r.j.error || r.j.errorCode)) || r.s));+        clog(c, "  staged " + name + " -> " + r.j.path);+        return r.j.path;+      });+    }+    return stageFileChunked(c, name, path, b64, rawBytes);+  }++  // Chunk on BASE64 boundaries: 4 base64 chars == 3 raw bytes, so slicing at a+  // multiple of 4 decodes to exactly the bytes we intend and every chunk's offset+  // is computable without decoding anything client-side.+  function stageFileChunked(c, name, path, b64, rawBytes) {+    var b64Step = Math.floor(CHUNK_RAW / 3) * 4;+    var total = Math.ceil(b64.length / b64Step);+    clog(c, "  " + name + " is " + fmtKB(rawBytes) + " - too big for one /command body, sending " + total + " chunks via write_file_chunk");+    var i = 0, offset = 0, finalPath = path;+    function next() {+      if (i >= total) return finalPath;+      var slice = b64.substr(i * b64Step, b64Step);+      var isLast = (i === total - 1);+      return postSelf("write_file_chunk", {+        path: path, dataB64: slice, offset: offset, "final": isLast+      }).then(function (r) {+        if (!(r.s === 200 && r.j && r.j.success)) {+          throw stageFail("write_file_chunk " + name + " @" + offset + ": " + ((r.j && (r.j.error || r.j.errorCode)) || r.s));+        }+        finalPath = r.j.path || finalPath;+        offset = r.j.nextOffset;+        i++;+        if (isLast) clog(c, "  staged " + name + " -> " + finalPath + "  (" + fmtKB(r.j.size) + ", sha " + String(r.j.sha256 || "").slice(0, 12) + ")");+        else if (i % 2 === 0 || total <= 4) clog(c, "    chunk " + i + "/" + total + " (" + fmtKB(offset) + " of " + fmtKB(rawBytes) + ")");+        return next();+      });+    }+    return Promise.resolve().then(next);   }    // KiCad component staging (the kicad-bridge's own recipe, adom-bridge-sdk#4