esp32tool 1.1.4 → 1.1.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/js/script.js +78 -11
- package/package.json +1 -1
- package/src/wasm/fatfs/fatfs.wasm +0 -0
- package/src/wasm/fatfs/index.d.ts +11 -7
- package/src/wasm/fatfs/index.js +184 -114
package/js/script.js
CHANGED
|
@@ -2219,8 +2219,48 @@ function refreshLittleFS() {
|
|
|
2219
2219
|
littlefsBreadcrumb.textContent = currentLittleFSPath || '/';
|
|
2220
2220
|
butLittlefsUp.disabled = currentLittleFSPath === '/' || !currentLittleFSPath;
|
|
2221
2221
|
|
|
2222
|
-
// List files
|
|
2223
|
-
|
|
2222
|
+
// List files - the list() function behavior differs between filesystems
|
|
2223
|
+
let entries;
|
|
2224
|
+
|
|
2225
|
+
if (currentFilesystemType === 'fatfs') {
|
|
2226
|
+
// FatFS returns ALL files recursively from root, so we always list from root and filter
|
|
2227
|
+
const allEntries = currentLittleFS.list('/');
|
|
2228
|
+
const isRoot = currentLittleFSPath === '/';
|
|
2229
|
+
|
|
2230
|
+
// Filter to show only direct children
|
|
2231
|
+
entries = allEntries.filter(entry => {
|
|
2232
|
+
// Remove /fatfs prefix from entry path for comparison
|
|
2233
|
+
let entryPath = entry.path;
|
|
2234
|
+
if (entryPath.startsWith('/fatfs/')) {
|
|
2235
|
+
entryPath = entryPath.slice(6);
|
|
2236
|
+
} else if (entryPath === '/fatfs') {
|
|
2237
|
+
entryPath = '/';
|
|
2238
|
+
}
|
|
2239
|
+
|
|
2240
|
+
if (isRoot) {
|
|
2241
|
+
// In root: only show top-level entries
|
|
2242
|
+
const withoutLeadingSlash = entryPath.slice(1);
|
|
2243
|
+
return withoutLeadingSlash && !withoutLeadingSlash.includes('/');
|
|
2244
|
+
} else {
|
|
2245
|
+
// In subdirectory: entry must be direct child of current path
|
|
2246
|
+
const expectedPrefix = currentLittleFSPath + '/';
|
|
2247
|
+
if (!entryPath.startsWith(expectedPrefix)) {
|
|
2248
|
+
return false;
|
|
2249
|
+
}
|
|
2250
|
+
const relativePath = entryPath.slice(expectedPrefix.length);
|
|
2251
|
+
return relativePath && !relativePath.includes('/');
|
|
2252
|
+
}
|
|
2253
|
+
});
|
|
2254
|
+
|
|
2255
|
+
// Add name attribute for FatFS entries
|
|
2256
|
+
entries = entries.map(entry => ({
|
|
2257
|
+
...entry,
|
|
2258
|
+
name: entry.path.split('/').pop() || entry.path
|
|
2259
|
+
}));
|
|
2260
|
+
} else {
|
|
2261
|
+
// LittleFS and SPIFFS return only direct children
|
|
2262
|
+
entries = currentLittleFS.list(currentLittleFSPath);
|
|
2263
|
+
}
|
|
2224
2264
|
|
|
2225
2265
|
// Clear table
|
|
2226
2266
|
littlefsFileList.innerHTML = '';
|
|
@@ -2252,7 +2292,8 @@ function refreshLittleFS() {
|
|
|
2252
2292
|
icon.className = 'file-icon';
|
|
2253
2293
|
icon.textContent = entry.type === 'dir' ? '📁' : '📄';
|
|
2254
2294
|
|
|
2255
|
-
|
|
2295
|
+
// Use entry.name instead of parsing the path
|
|
2296
|
+
const name = entry.name || entry.path.split('/').filter(Boolean).pop() || '/';
|
|
2256
2297
|
const nameText = document.createElement('span');
|
|
2257
2298
|
nameText.textContent = name;
|
|
2258
2299
|
|
|
@@ -2313,7 +2354,20 @@ function refreshLittleFS() {
|
|
|
2313
2354
|
* Navigate to a directory in LittleFS
|
|
2314
2355
|
*/
|
|
2315
2356
|
function navigateLittleFS(path) {
|
|
2316
|
-
|
|
2357
|
+
// Remove /fatfs prefix if present (for FatFS compatibility)
|
|
2358
|
+
let normalizedPath = path;
|
|
2359
|
+
if (normalizedPath.startsWith('/fatfs/')) {
|
|
2360
|
+
normalizedPath = normalizedPath.slice(6); // Remove '/fatfs' keeping the /
|
|
2361
|
+
} else if (normalizedPath === '/fatfs') {
|
|
2362
|
+
normalizedPath = '/';
|
|
2363
|
+
}
|
|
2364
|
+
|
|
2365
|
+
// Remove trailing slash except for root
|
|
2366
|
+
if (normalizedPath !== '/' && normalizedPath.endsWith('/')) {
|
|
2367
|
+
normalizedPath = normalizedPath.slice(0, -1);
|
|
2368
|
+
}
|
|
2369
|
+
|
|
2370
|
+
currentLittleFSPath = normalizedPath;
|
|
2317
2371
|
refreshLittleFS();
|
|
2318
2372
|
}
|
|
2319
2373
|
|
|
@@ -2323,12 +2377,12 @@ function navigateLittleFS(path) {
|
|
|
2323
2377
|
function clickLittlefsUp() {
|
|
2324
2378
|
if (currentLittleFSPath === '/' || !currentLittleFSPath) return;
|
|
2325
2379
|
|
|
2380
|
+
// Split path and remove last segment
|
|
2326
2381
|
const parts = currentLittleFSPath.split('/').filter(Boolean);
|
|
2327
2382
|
parts.pop();
|
|
2328
|
-
|
|
2329
|
-
|
|
2330
|
-
|
|
2331
|
-
}
|
|
2383
|
+
|
|
2384
|
+
// Reconstruct path
|
|
2385
|
+
currentLittleFSPath = parts.length ? '/' + parts.join('/') : '/';
|
|
2332
2386
|
refreshLittleFS();
|
|
2333
2387
|
}
|
|
2334
2388
|
|
|
@@ -2433,7 +2487,8 @@ async function clickLittlefsWrite() {
|
|
|
2433
2487
|
butLittlefsWrite.disabled = false;
|
|
2434
2488
|
butLittlefsClose.disabled = false;
|
|
2435
2489
|
butLittlefsUpload.disabled = !littlefsFileInput.files.length;
|
|
2436
|
-
|
|
2490
|
+
// Re-enable mkdir only if not SPIFFS
|
|
2491
|
+
butLittlefsMkdir.disabled = (currentFilesystemType === 'spiffs');
|
|
2437
2492
|
}
|
|
2438
2493
|
}
|
|
2439
2494
|
|
|
@@ -2522,10 +2577,22 @@ async function clickLittlefsUpload() {
|
|
|
2522
2577
|
/**
|
|
2523
2578
|
* Create new directory
|
|
2524
2579
|
*/
|
|
2525
|
-
function clickLittlefsMkdir() {
|
|
2580
|
+
async function clickLittlefsMkdir() {
|
|
2526
2581
|
if (!currentLittleFS) return;
|
|
2527
2582
|
|
|
2528
|
-
|
|
2583
|
+
// Check if mkdir is supported (SPIFFS doesn't support directories)
|
|
2584
|
+
if (currentFilesystemType === 'spiffs') {
|
|
2585
|
+
errorMsg('SPIFFS does not support directories. Files are stored in a flat structure.');
|
|
2586
|
+
return;
|
|
2587
|
+
}
|
|
2588
|
+
|
|
2589
|
+
let dirName;
|
|
2590
|
+
if (isElectron) {
|
|
2591
|
+
dirName = await window.electronAPI.showPrompt('Enter directory name:');
|
|
2592
|
+
} else {
|
|
2593
|
+
dirName = prompt('Enter directory name:');
|
|
2594
|
+
}
|
|
2595
|
+
|
|
2529
2596
|
if (!dirName || !dirName.trim()) return;
|
|
2530
2597
|
|
|
2531
2598
|
try {
|
package/package.json
CHANGED
|
Binary file
|
|
@@ -1,26 +1,30 @@
|
|
|
1
|
-
import type { FileSource,
|
|
1
|
+
import type { BinarySource, FileSource, FileSystemUsage } from "../shared/types";
|
|
2
|
+
export declare const FAT_MOUNT = "/fatfs";
|
|
2
3
|
export interface FatFSEntry {
|
|
3
4
|
path: string;
|
|
4
5
|
size: number;
|
|
5
|
-
type:
|
|
6
|
+
type: "file" | "dir";
|
|
6
7
|
}
|
|
7
8
|
export interface FatFSOptions {
|
|
8
9
|
blockSize?: number;
|
|
9
10
|
blockCount?: number;
|
|
10
|
-
wasmURL?: string | URL;
|
|
11
11
|
formatOnInit?: boolean;
|
|
12
|
+
wasmURL?: string | URL;
|
|
12
13
|
}
|
|
13
14
|
export interface FatFS {
|
|
14
|
-
|
|
15
|
-
list(): FatFSEntry[];
|
|
15
|
+
list(path?: string): FatFSEntry[];
|
|
16
16
|
readFile(path: string): Uint8Array;
|
|
17
|
+
toImage(): Uint8Array;
|
|
18
|
+
getUsage(): FileSystemUsage;
|
|
19
|
+
format(): void;
|
|
17
20
|
writeFile(path: string, data: FileSource): void;
|
|
18
21
|
deleteFile(path: string): void;
|
|
19
|
-
|
|
22
|
+
mkdir(path: string): void;
|
|
23
|
+
rename(oldPath: string, newPath: string): void;
|
|
20
24
|
}
|
|
21
25
|
export declare class FatFSError extends Error {
|
|
22
26
|
readonly code: number;
|
|
23
27
|
constructor(message: string, code: number);
|
|
24
28
|
}
|
|
25
|
-
export declare function createFatFS(options?: FatFSOptions): Promise<FatFS>;
|
|
26
29
|
export declare function createFatFSFromImage(image: BinarySource, options?: FatFSOptions): Promise<FatFS>;
|
|
30
|
+
export declare function createFatFS(options?: FatFSOptions): Promise<FatFS>;
|
package/src/wasm/fatfs/index.js
CHANGED
|
@@ -1,7 +1,9 @@
|
|
|
1
|
-
const
|
|
2
|
-
const DEFAULT_BLOCK_COUNT = 1024;
|
|
1
|
+
export const FAT_MOUNT = "/fatfs";
|
|
3
2
|
const INITIAL_LIST_BUFFER = 4096;
|
|
4
|
-
const
|
|
3
|
+
const DEFAULT_BLOCK_SIZE = 4096;
|
|
4
|
+
const DEFAULT_BLOCK_COUNT = 128;
|
|
5
|
+
const FATFS_ERR_INVAL = -1;
|
|
6
|
+
const FATFS_ERR_NOSPC = -3;
|
|
5
7
|
export class FatFSError extends Error {
|
|
6
8
|
constructor(message, code) {
|
|
7
9
|
super(message);
|
|
@@ -9,61 +11,60 @@ export class FatFSError extends Error {
|
|
|
9
11
|
this.name = "FatFSError";
|
|
10
12
|
}
|
|
11
13
|
}
|
|
12
|
-
export async function createFatFS(options = {}) {
|
|
13
|
-
console.info("[fatfs-wasm] createFatFS() starting", options);
|
|
14
|
-
const wasmURL = options.wasmURL ?? new URL("./fatfs.wasm", import.meta.url);
|
|
15
|
-
const exports = await instantiateFatFSModule(wasmURL);
|
|
16
|
-
const blockSize = options.blockSize ?? DEFAULT_BLOCK_SIZE;
|
|
17
|
-
const blockCount = options.blockCount ?? DEFAULT_BLOCK_COUNT;
|
|
18
|
-
const initResult = exports.fatfsjs_init(blockSize, blockCount);
|
|
19
|
-
console.info("[fatfs-wasm] fatfsjs_init returned", initResult);
|
|
20
|
-
if (initResult < 0) {
|
|
21
|
-
throw new FatFSError("Failed to initialize FatFS", initResult);
|
|
22
|
-
}
|
|
23
|
-
if (options.formatOnInit) {
|
|
24
|
-
const formatResult = exports.fatfsjs_format();
|
|
25
|
-
console.info("[fatfs-wasm] fatfsjs_format returned", formatResult);
|
|
26
|
-
if (formatResult < 0) {
|
|
27
|
-
throw new FatFSError("Failed to format FatFS volume", formatResult);
|
|
28
|
-
}
|
|
29
|
-
}
|
|
30
|
-
const client = new FatFSClient(exports);
|
|
31
|
-
console.info("[fatfs-wasm] Filesystem initialized");
|
|
32
|
-
return client;
|
|
33
|
-
}
|
|
34
14
|
export async function createFatFSFromImage(image, options = {}) {
|
|
35
15
|
console.info("[fatfs-wasm] createFatFSFromImage() starting");
|
|
36
16
|
const wasmURL = options.wasmURL ?? new URL("./fatfs.wasm", import.meta.url);
|
|
37
17
|
const exports = await instantiateFatFSModule(wasmURL);
|
|
38
18
|
const bytes = asBinaryUint8Array(image);
|
|
39
19
|
const blockSize = options.blockSize ?? DEFAULT_BLOCK_SIZE;
|
|
40
|
-
if (blockSize
|
|
41
|
-
throw new Error(
|
|
20
|
+
if (blockSize !== DEFAULT_BLOCK_SIZE) {
|
|
21
|
+
throw new Error(`blockSize must be ${DEFAULT_BLOCK_SIZE}`);
|
|
42
22
|
}
|
|
43
|
-
|
|
44
|
-
const blockCount = options.blockCount ?? inferredBlocks;
|
|
45
|
-
if (blockCount * blockSize !== bytes.length) {
|
|
23
|
+
if (options.blockCount && options.blockCount * blockSize !== bytes.length) {
|
|
46
24
|
throw new Error("Image size must equal blockSize * blockCount");
|
|
47
25
|
}
|
|
48
|
-
const
|
|
49
|
-
|
|
50
|
-
|
|
26
|
+
const heap = new Uint8Array(exports.memory.buffer);
|
|
27
|
+
const imagePtr = exports.malloc(bytes.length || 1);
|
|
28
|
+
if (!imagePtr) {
|
|
29
|
+
throw new FatFSError("Failed to allocate WebAssembly memory", FATFS_ERR_NOSPC);
|
|
51
30
|
}
|
|
52
31
|
try {
|
|
53
|
-
|
|
54
|
-
const
|
|
55
|
-
heap.set(bytes, ptr);
|
|
56
|
-
const initResult = exports.fatfsjs_init_from_image(blockSize, blockCount, ptr, bytes.length);
|
|
32
|
+
heap.set(bytes, imagePtr);
|
|
33
|
+
const initResult = exports.fatfsjs_init_from_image(imagePtr, bytes.length);
|
|
57
34
|
if (initResult < 0) {
|
|
58
|
-
throw new FatFSError("Failed to initialize
|
|
35
|
+
throw new FatFSError("Failed to initialize FAT16 image", initResult);
|
|
59
36
|
}
|
|
60
37
|
}
|
|
61
38
|
finally {
|
|
62
|
-
exports.free(
|
|
39
|
+
exports.free(imagePtr);
|
|
63
40
|
}
|
|
64
|
-
const client = new FatFSClient(exports);
|
|
65
41
|
console.info("[fatfs-wasm] Filesystem initialized from image");
|
|
66
|
-
return
|
|
42
|
+
return new FatFSClient(exports);
|
|
43
|
+
}
|
|
44
|
+
export async function createFatFS(options = {}) {
|
|
45
|
+
console.info("[fatfs-wasm] createFatFS() starting", options);
|
|
46
|
+
const wasmURL = options.wasmURL ?? new URL("./fatfs.wasm", import.meta.url);
|
|
47
|
+
const exports = await instantiateFatFSModule(wasmURL);
|
|
48
|
+
const blockSize = options.blockSize ?? DEFAULT_BLOCK_SIZE;
|
|
49
|
+
const blockCount = options.blockCount ?? DEFAULT_BLOCK_COUNT;
|
|
50
|
+
if (blockSize !== DEFAULT_BLOCK_SIZE) {
|
|
51
|
+
throw new Error(`blockSize must be ${DEFAULT_BLOCK_SIZE}`);
|
|
52
|
+
}
|
|
53
|
+
if (!Number.isFinite(blockCount) || blockCount <= 0) {
|
|
54
|
+
throw new Error("blockCount must be a positive integer");
|
|
55
|
+
}
|
|
56
|
+
const initResult = exports.fatfsjs_init(blockSize, blockCount);
|
|
57
|
+
if (initResult < 0) {
|
|
58
|
+
throw new FatFSError("Failed to initialize FatFS", initResult);
|
|
59
|
+
}
|
|
60
|
+
if (options.formatOnInit) {
|
|
61
|
+
const formatResult = exports.fatfsjs_format();
|
|
62
|
+
if (formatResult < 0) {
|
|
63
|
+
throw new FatFSError("Failed to format filesystem", formatResult);
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
console.info("[fatfs-wasm] Filesystem initialized");
|
|
67
|
+
return new FatFSClient(exports);
|
|
67
68
|
}
|
|
68
69
|
class FatFSClient {
|
|
69
70
|
constructor(exports) {
|
|
@@ -73,46 +74,56 @@ class FatFSClient {
|
|
|
73
74
|
this.exports = exports;
|
|
74
75
|
this.heapU8 = new Uint8Array(this.exports.memory.buffer);
|
|
75
76
|
}
|
|
76
|
-
|
|
77
|
-
const
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
list() {
|
|
77
|
+
list(path = FAT_MOUNT) {
|
|
78
|
+
const normalized = normalizeMountPath(path);
|
|
79
|
+
const basePath = normalized;
|
|
80
|
+
const pathPtr = this.allocString(normalized);
|
|
81
81
|
let capacity = this.listBufferSize;
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
82
|
+
try {
|
|
83
|
+
while (true) {
|
|
84
|
+
const ptr = this.alloc(capacity);
|
|
85
|
+
try {
|
|
86
|
+
const used = this.exports.fatfsjs_list(pathPtr, ptr, capacity);
|
|
87
|
+
if (used === FATFS_ERR_NOSPC) {
|
|
88
|
+
this.listBufferSize = capacity * 2;
|
|
89
|
+
capacity = this.listBufferSize;
|
|
90
|
+
continue;
|
|
91
|
+
}
|
|
92
|
+
this.assertOk(used, "list files");
|
|
93
|
+
if (used === 0) {
|
|
94
|
+
return [];
|
|
95
|
+
}
|
|
96
|
+
const payload = this.decoder.decode(this.heapU8.subarray(ptr, ptr + used));
|
|
97
|
+
return parseListPayload(payload).map((entry) => ({
|
|
98
|
+
...entry,
|
|
99
|
+
path: joinListPath(basePath, entry.path),
|
|
100
|
+
}));
|
|
90
101
|
}
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
return [];
|
|
102
|
+
finally {
|
|
103
|
+
this.exports.free(ptr);
|
|
94
104
|
}
|
|
95
|
-
const payload = this.decoder.decode(this.heapU8.subarray(ptr, ptr + used));
|
|
96
|
-
return parseListPayload(payload);
|
|
97
|
-
}
|
|
98
|
-
finally {
|
|
99
|
-
this.exports.free(ptr);
|
|
100
105
|
}
|
|
101
106
|
}
|
|
107
|
+
finally {
|
|
108
|
+
this.exports.free(pathPtr);
|
|
109
|
+
}
|
|
102
110
|
}
|
|
103
111
|
readFile(path) {
|
|
104
|
-
const
|
|
105
|
-
|
|
112
|
+
const normalized = normalizeMountPath(path);
|
|
113
|
+
if (normalized === FAT_MOUNT) {
|
|
114
|
+
throw new FatFSError("Path must point to a file", FATFS_ERR_INVAL);
|
|
115
|
+
}
|
|
116
|
+
const pathPtr = this.allocString(normalized);
|
|
106
117
|
try {
|
|
107
118
|
const size = this.exports.fatfsjs_file_size(pathPtr);
|
|
108
|
-
this.assertOk(size, `stat file "${
|
|
119
|
+
this.assertOk(size, `stat file "${normalized}"`);
|
|
109
120
|
if (size === 0) {
|
|
110
121
|
return new Uint8Array();
|
|
111
122
|
}
|
|
112
123
|
const dataPtr = this.alloc(size);
|
|
113
124
|
try {
|
|
114
125
|
const read = this.exports.fatfsjs_read_file(pathPtr, dataPtr, size);
|
|
115
|
-
this.assertOk(read, `read file "${
|
|
126
|
+
this.assertOk(read, `read file "${normalized}"`);
|
|
116
127
|
return this.heapU8.slice(dataPtr, dataPtr + size);
|
|
117
128
|
}
|
|
118
129
|
finally {
|
|
@@ -123,17 +134,50 @@ class FatFSClient {
|
|
|
123
134
|
this.exports.free(pathPtr);
|
|
124
135
|
}
|
|
125
136
|
}
|
|
137
|
+
toImage() {
|
|
138
|
+
const size = this.exports.fatfsjs_storage_size();
|
|
139
|
+
if (size === 0) {
|
|
140
|
+
return new Uint8Array();
|
|
141
|
+
}
|
|
142
|
+
const ptr = this.alloc(size);
|
|
143
|
+
try {
|
|
144
|
+
const copied = this.exports.fatfsjs_export_image(ptr, size);
|
|
145
|
+
this.assertOk(copied, "export filesystem image");
|
|
146
|
+
return this.heapU8.slice(ptr, ptr + size);
|
|
147
|
+
}
|
|
148
|
+
finally {
|
|
149
|
+
this.exports.free(ptr);
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
getUsage() {
|
|
153
|
+
const capacityBytes = this.exports.fatfsjs_storage_size();
|
|
154
|
+
const entries = this.list(FAT_MOUNT);
|
|
155
|
+
const usedBytes = entries.reduce((acc, entry) => (entry.type === "file" ? acc + entry.size : acc), 0);
|
|
156
|
+
const freeBytes = capacityBytes > usedBytes ? capacityBytes - usedBytes : 0;
|
|
157
|
+
return {
|
|
158
|
+
capacityBytes,
|
|
159
|
+
usedBytes,
|
|
160
|
+
freeBytes,
|
|
161
|
+
};
|
|
162
|
+
}
|
|
163
|
+
format() {
|
|
164
|
+
const result = this.exports.fatfsjs_format();
|
|
165
|
+
this.assertOk(result, "format filesystem");
|
|
166
|
+
}
|
|
126
167
|
writeFile(path, data) {
|
|
127
|
-
const
|
|
168
|
+
const normalized = normalizeMountPath(path);
|
|
169
|
+
if (normalized === FAT_MOUNT) {
|
|
170
|
+
throw new FatFSError("Path must point to a file", FATFS_ERR_INVAL);
|
|
171
|
+
}
|
|
128
172
|
const payload = asUint8Array(data, this.encoder);
|
|
129
|
-
const pathPtr = this.allocString(
|
|
130
|
-
const dataPtr = this.alloc(payload.length);
|
|
173
|
+
const pathPtr = this.allocString(normalized);
|
|
174
|
+
const dataPtr = payload.length ? this.alloc(payload.length) : 0;
|
|
131
175
|
try {
|
|
132
|
-
if (payload.length > 0
|
|
176
|
+
if (payload.length > 0) {
|
|
133
177
|
this.heapU8.set(payload, dataPtr);
|
|
134
178
|
}
|
|
135
|
-
const result = this.exports.fatfsjs_write_file(pathPtr,
|
|
136
|
-
this.assertOk(result, `write file "${
|
|
179
|
+
const result = this.exports.fatfsjs_write_file(pathPtr, dataPtr, payload.length);
|
|
180
|
+
this.assertOk(result, `write file "${normalized}"`);
|
|
137
181
|
}
|
|
138
182
|
finally {
|
|
139
183
|
if (dataPtr) {
|
|
@@ -143,29 +187,45 @@ class FatFSClient {
|
|
|
143
187
|
}
|
|
144
188
|
}
|
|
145
189
|
deleteFile(path) {
|
|
146
|
-
const
|
|
147
|
-
|
|
190
|
+
const normalized = normalizeMountPath(path);
|
|
191
|
+
if (normalized === FAT_MOUNT) {
|
|
192
|
+
throw new FatFSError("Path must point to a file", FATFS_ERR_INVAL);
|
|
193
|
+
}
|
|
194
|
+
const pathPtr = this.allocString(normalized);
|
|
148
195
|
try {
|
|
149
196
|
const result = this.exports.fatfsjs_delete_file(pathPtr);
|
|
150
|
-
this.assertOk(result, `delete file "${
|
|
197
|
+
this.assertOk(result, `delete file "${normalized}"`);
|
|
151
198
|
}
|
|
152
199
|
finally {
|
|
153
200
|
this.exports.free(pathPtr);
|
|
154
201
|
}
|
|
155
202
|
}
|
|
156
|
-
|
|
157
|
-
const
|
|
158
|
-
if (
|
|
159
|
-
return
|
|
203
|
+
mkdir(path) {
|
|
204
|
+
const normalized = normalizeMountPath(path);
|
|
205
|
+
if (normalized === FAT_MOUNT) {
|
|
206
|
+
return;
|
|
160
207
|
}
|
|
161
|
-
const
|
|
208
|
+
const pathPtr = this.allocString(normalized);
|
|
162
209
|
try {
|
|
163
|
-
const
|
|
164
|
-
this.assertOk(
|
|
165
|
-
return this.heapU8.slice(ptr, ptr + size);
|
|
210
|
+
const result = this.exports.fatfsjs_mkdir(pathPtr);
|
|
211
|
+
this.assertOk(result, `mkdir "${normalized}"`);
|
|
166
212
|
}
|
|
167
213
|
finally {
|
|
168
|
-
this.exports.free(
|
|
214
|
+
this.exports.free(pathPtr);
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
rename(oldPath, newPath) {
|
|
218
|
+
const from = normalizeMountPath(oldPath);
|
|
219
|
+
const to = normalizeMountPath(newPath);
|
|
220
|
+
const fromPtr = this.allocString(from);
|
|
221
|
+
const toPtr = this.allocString(to);
|
|
222
|
+
try {
|
|
223
|
+
const result = this.exports.fatfsjs_rename(fromPtr, toPtr);
|
|
224
|
+
this.assertOk(result, `rename "${from}" -> "${to}"`);
|
|
225
|
+
}
|
|
226
|
+
finally {
|
|
227
|
+
this.exports.free(fromPtr);
|
|
228
|
+
this.exports.free(toPtr);
|
|
169
229
|
}
|
|
170
230
|
}
|
|
171
231
|
refreshHeap() {
|
|
@@ -179,7 +239,7 @@ class FatFSClient {
|
|
|
179
239
|
}
|
|
180
240
|
const ptr = this.exports.malloc(size);
|
|
181
241
|
if (!ptr) {
|
|
182
|
-
throw new FatFSError("Failed to allocate WebAssembly memory",
|
|
242
|
+
throw new FatFSError("Failed to allocate WebAssembly memory", FATFS_ERR_NOSPC);
|
|
183
243
|
}
|
|
184
244
|
this.refreshHeap();
|
|
185
245
|
return ptr;
|
|
@@ -204,7 +264,7 @@ async function instantiateFatFSModule(input) {
|
|
|
204
264
|
const imports = createDefaultImports(wasmContext);
|
|
205
265
|
let response = await fetch(source);
|
|
206
266
|
if (!response.ok) {
|
|
207
|
-
throw new Error(`Unable to fetch
|
|
267
|
+
throw new Error(`Unable to fetch FATFS wasm from ${response.url}`);
|
|
208
268
|
}
|
|
209
269
|
console.info("[fatfs-wasm] Fetch complete, status", response.status);
|
|
210
270
|
if ("instantiateStreaming" in WebAssembly && typeof WebAssembly.instantiateStreaming === "function") {
|
|
@@ -216,10 +276,10 @@ async function instantiateFatFSModule(input) {
|
|
|
216
276
|
return streaming.instance.exports;
|
|
217
277
|
}
|
|
218
278
|
catch (error) {
|
|
219
|
-
console.warn("Unable to instantiate
|
|
279
|
+
console.warn("Unable to instantiate FATFS wasm via streaming, retrying with arrayBuffer()", error);
|
|
220
280
|
response = await fetch(source);
|
|
221
281
|
if (!response.ok) {
|
|
222
|
-
throw new Error(`Unable to fetch
|
|
282
|
+
throw new Error(`Unable to fetch FATFS wasm from ${response.url}`);
|
|
223
283
|
}
|
|
224
284
|
console.info("[fatfs-wasm] Fallback fetch complete, status", response.status);
|
|
225
285
|
}
|
|
@@ -239,26 +299,38 @@ function parseListPayload(payload) {
|
|
|
239
299
|
.split("\n")
|
|
240
300
|
.filter((line) => line.length > 0)
|
|
241
301
|
.map((line) => {
|
|
242
|
-
const [rawPath, rawSize] = line.split("\t");
|
|
243
|
-
const path = rawPath ?? "";
|
|
244
|
-
const size = Number(rawSize ?? "0") || 0;
|
|
245
|
-
// Determine type: directories end with '/', files don't
|
|
246
|
-
const type = path.endsWith('/') ? 'dir' : 'file';
|
|
302
|
+
const [rawPath, rawSize, rawType] = line.split("\t");
|
|
247
303
|
return {
|
|
248
|
-
path:
|
|
249
|
-
size:
|
|
250
|
-
type:
|
|
304
|
+
path: rawPath ?? "",
|
|
305
|
+
size: Number(rawSize ?? "0") || 0,
|
|
306
|
+
type: rawType === "d" ? "dir" : "file",
|
|
251
307
|
};
|
|
252
308
|
});
|
|
253
309
|
}
|
|
254
|
-
function
|
|
255
|
-
const
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
}
|
|
260
|
-
const collapsed =
|
|
261
|
-
|
|
310
|
+
function normalizeMountPath(input) {
|
|
311
|
+
const raw = (input ?? "").trim();
|
|
312
|
+
if (!raw || raw === "/") {
|
|
313
|
+
return FAT_MOUNT;
|
|
314
|
+
}
|
|
315
|
+
const normalized = raw.replace(/\\/g, "/").replace(/\/{2,}/g, "/");
|
|
316
|
+
const collapsed = normalized.endsWith("/") && normalized !== "/" ? normalized.slice(0, -1) : normalized;
|
|
317
|
+
const lower = collapsed.toLowerCase();
|
|
318
|
+
if (lower.startsWith(FAT_MOUNT)) {
|
|
319
|
+
const rest = collapsed.slice(FAT_MOUNT.length);
|
|
320
|
+
return rest ? `${FAT_MOUNT}${rest}` : FAT_MOUNT;
|
|
321
|
+
}
|
|
322
|
+
if (collapsed.startsWith("/")) {
|
|
323
|
+
return `${FAT_MOUNT}${collapsed}`;
|
|
324
|
+
}
|
|
325
|
+
return `${FAT_MOUNT}/${collapsed}`;
|
|
326
|
+
}
|
|
327
|
+
function joinListPath(basePath, entryPath) {
|
|
328
|
+
const base = basePath.replace(/\/+$/, "");
|
|
329
|
+
if (!entryPath || entryPath === "/") {
|
|
330
|
+
return base || FAT_MOUNT;
|
|
331
|
+
}
|
|
332
|
+
const trimmed = entryPath.replace(/^\/+/, "");
|
|
333
|
+
return base === FAT_MOUNT ? `${FAT_MOUNT}/${trimmed}` : `${base}/${trimmed}`;
|
|
262
334
|
}
|
|
263
335
|
function asUint8Array(source, encoder) {
|
|
264
336
|
if (typeof source === "string") {
|
|
@@ -301,13 +373,13 @@ function createDefaultImports(context) {
|
|
|
301
373
|
const ok = () => 0;
|
|
302
374
|
return {
|
|
303
375
|
env: {
|
|
304
|
-
emscripten_notify_memory_growth: noop
|
|
376
|
+
emscripten_notify_memory_growth: noop,
|
|
305
377
|
},
|
|
306
378
|
wasi_snapshot_preview1: {
|
|
307
379
|
fd_close: ok,
|
|
308
380
|
fd_seek: ok,
|
|
309
|
-
fd_write: (fd, iov, iovcnt, pnum) => handleFdWrite(context, fd, iov, iovcnt, pnum)
|
|
310
|
-
}
|
|
381
|
+
fd_write: (fd, iov, iovcnt, pnum) => handleFdWrite(context, fd, iov, iovcnt, pnum),
|
|
382
|
+
},
|
|
311
383
|
};
|
|
312
384
|
}
|
|
313
385
|
function handleFdWrite(context, fd, iov, iovcnt, pnum) {
|
|
@@ -322,13 +394,11 @@ function handleFdWrite(context, fd, iov, iovcnt, pnum) {
|
|
|
322
394
|
const ptr = view.getUint32(base, true);
|
|
323
395
|
const len = view.getUint32(base + 4, true);
|
|
324
396
|
total += len;
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
// console.info(`[fatfs-wasm::fd_write fd=${fd}] ${text}`);
|
|
331
|
-
// }
|
|
397
|
+
if (fd === 1 || fd === 2) {
|
|
398
|
+
const bytes = new Uint8Array(memory.buffer, ptr, len);
|
|
399
|
+
const text = new TextDecoder().decode(bytes);
|
|
400
|
+
console.info(`[fatfs-wasm::fd_write fd=${fd}] ${text}`);
|
|
401
|
+
}
|
|
332
402
|
}
|
|
333
403
|
view.setUint32(pnum, total, true);
|
|
334
404
|
return 0;
|