Skip to content

Commit 3e60ec3

Browse files
authored
Merge pull request #6770 from tannewt/upload_folder
Add uploading a directory and its contents
2 parents da5c0cc + b7a2a3d commit 3e60ec3

File tree

6 files changed

+106
-88
lines changed

6 files changed

+106
-88
lines changed

supervisor/shared/bluetooth/file_transfer.c

Lines changed: 3 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -47,11 +47,11 @@
4747
#include "supervisor/shared/reload.h"
4848
#include "supervisor/shared/bluetooth/file_transfer.h"
4949
#include "supervisor/shared/bluetooth/file_transfer_protocol.h"
50+
#include "supervisor/shared/workflow.h"
5051
#include "supervisor/shared/tick.h"
5152
#include "supervisor/usb.h"
5253

5354
#include "py/mpstate.h"
54-
#include "py/stackctrl.h"
5555

5656
STATIC bleio_service_obj_t supervisor_ble_service;
5757
STATIC bleio_uuid_obj_t supervisor_ble_service_uuid;
@@ -387,39 +387,6 @@ STATIC uint8_t _process_write_data(const uint8_t *raw_buf, size_t command_len) {
387387
return WRITE_DATA;
388388
}
389389

390-
STATIC FRESULT _delete_directory_contents(FATFS *fs, const TCHAR *path) {
391-
FF_DIR dir;
392-
FRESULT res = f_opendir(fs, &dir, path);
393-
FILINFO file_info;
394-
// Check the stack since we're putting paths on it.
395-
if (mp_stack_usage() >= MP_STATE_THREAD(stack_limit)) {
396-
return FR_INT_ERR;
397-
}
398-
while (res == FR_OK) {
399-
res = f_readdir(&dir, &file_info);
400-
if (res != FR_OK || file_info.fname[0] == '\0') {
401-
break;
402-
}
403-
size_t pathlen = strlen(path);
404-
size_t fnlen = strlen(file_info.fname);
405-
TCHAR full_path[pathlen + 1 + fnlen];
406-
memcpy(full_path, path, pathlen);
407-
full_path[pathlen] = '/';
408-
size_t full_pathlen = pathlen + 1 + fnlen;
409-
memcpy(full_path + pathlen + 1, file_info.fname, fnlen);
410-
full_path[full_pathlen] = '\0';
411-
if ((file_info.fattrib & AM_DIR) != 0) {
412-
res = _delete_directory_contents(fs, full_path);
413-
}
414-
if (res != FR_OK) {
415-
break;
416-
}
417-
res = f_unlink(fs, full_path);
418-
}
419-
f_closedir(&dir);
420-
return res;
421-
}
422-
423390
STATIC uint8_t _process_delete(const uint8_t *raw_buf, size_t command_len) {
424391
const struct delete_command *command = (struct delete_command *)raw_buf;
425392
size_t header_size = sizeof(struct delete_command);
@@ -446,7 +413,7 @@ STATIC uint8_t _process_delete(const uint8_t *raw_buf, size_t command_len) {
446413
FRESULT result = f_stat(fs, path, &file);
447414
if (result == FR_OK) {
448415
if ((file.fattrib & AM_DIR) != 0) {
449-
result = _delete_directory_contents(fs, path);
416+
result = supervisor_workflow_delete_directory_contents(fs, path);
450417
}
451418
if (result == FR_OK) {
452419
result = f_unlink(fs, path);
@@ -503,7 +470,7 @@ STATIC uint8_t _process_mkdir(const uint8_t *raw_buf, size_t command_len) {
503470
DWORD fattime;
504471
response.truncated_time = truncate_time(command->modification_time, &fattime);
505472
override_fattime(fattime);
506-
FRESULT result = f_mkdir(fs, path);
473+
FRESULT result = supervisor_workflow_mkdir_parents(fs, path);
507474
override_fattime(0);
508475
#if CIRCUITPY_USB_MSC
509476
usb_msc_unlock();

supervisor/shared/web_workflow/static/directory.html

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,9 @@ <h1><a href="/"><img src="/favicon.ico"/></a>&nbsp;<span id="path"></span></h1>
1616
<tbody></tbody>
1717
</table>
1818
<hr>
19-
<input type="file" id="files" multiple><button type="submit" id="upload">Upload</button>
19+
<label>📄 <input type="file" id="files" multiple></label>
20+
<label for="dirs">📁 <input type="file" id="dirs" multiple webkitdirectory></label>
21+
<label>Upload progress:<progress value="0"></progress></label>
2022
<hr>
2123
+📁&nbsp;<input type="text" id="name"><button type="submit" id="mkdir">Create Directory</button>
2224
</body></html>

supervisor/shared/web_workflow/static/directory.js

Lines changed: 32 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
let new_directory_name = document.getElementById("name");
22
let files = document.getElementById("files");
3+
let dirs = document.getElementById("dirs");
34

45
var url_base = window.location;
56
var current_path;
@@ -15,10 +16,12 @@ function compareValues(a, b) {
1516
}
1617
}
1718

18-
async function refresh_list() {
19-
20-
19+
function set_upload_enabled(enabled) {
20+
files.disabled = !enabled;
21+
dirs.disabled = !enabled;
22+
}
2123

24+
async function refresh_list() {
2225
current_path = window.location.hash.substr(1);
2326
if (current_path == "") {
2427
current_path = "/";
@@ -49,7 +52,7 @@ async function refresh_list() {
4952
);
5053
editable = status.headers.get("Access-Control-Allow-Methods").includes("DELETE");
5154
new_directory_name.disabled = !editable;
52-
files.disabled = !editable;
55+
set_upload_enabled(editable);
5356
if (!editable) {
5457
let usbwarning = document.querySelector("#usbwarning");
5558
usbwarning.style.display = "block";
@@ -169,8 +172,25 @@ async function mkdir(e) {
169172
}
170173

171174
async function upload(e) {
172-
for (const file of files.files) {
173-
let file_path = new URL("/fs" + current_path + file.name, url_base);
175+
set_upload_enabled(false);
176+
let progress = document.querySelector("progress");
177+
let made_dirs = new Set();
178+
progress.max = files.files.length + dirs.files.length;
179+
progress.value = 0;
180+
for (const file of [...files.files, ...dirs.files]) {
181+
let file_name = file.name;
182+
if (file.webkitRelativePath) {
183+
file_name = file.webkitRelativePath;
184+
let components = file_name.split("/");
185+
components.pop();
186+
let parent_dir = components.join("/");
187+
if (!made_dirs.has(parent_dir)) {
188+
new_directory_name.value = parent_dir;
189+
await mkdir(null);
190+
made_dirs.add(parent_dir);
191+
}
192+
}
193+
let file_path = new URL("/fs" + current_path + file_name, url_base);
174194
const response = await fetch(file_path,
175195
{
176196
method: "PUT",
@@ -184,9 +204,12 @@ async function upload(e) {
184204
if (response.ok) {
185205
refresh_list();
186206
}
207+
progress.value += 1;
187208
}
188209
files.value = "";
189-
upload_button.disabled = true;
210+
dirs.value = "";
211+
progress.value = 0;
212+
set_upload_enabled(true);
190213
}
191214

192215
async function del(e) {
@@ -234,14 +257,8 @@ find_devices();
234257
let mkdir_button = document.getElementById("mkdir");
235258
mkdir_button.onclick = mkdir;
236259

237-
let upload_button = document.getElementById("upload");
238-
upload_button.onclick = upload;
239-
240-
upload_button.disabled = files.files.length == 0;
241-
242-
files.onchange = () => {
243-
upload_button.disabled = files.files.length == 0;
244-
}
260+
files.onchange = upload;
261+
dirs.onchange = upload;
245262

246263
mkdir_button.disabled = new_directory_name.value.length == 0;
247264

supervisor/shared/web_workflow/web_workflow.c

Lines changed: 3 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@
4242
#include "supervisor/shared/translate/translate.h"
4343
#include "supervisor/shared/web_workflow/web_workflow.h"
4444
#include "supervisor/shared/web_workflow/websocket.h"
45+
#include "supervisor/shared/workflow.h"
4546
#include "supervisor/usb.h"
4647

4748
#include "shared-bindings/hashlib/__init__.h"
@@ -773,40 +774,6 @@ static void _reply_with_version_json(socketpool_socket_obj_t *socket, _request *
773774
_send_chunk(socket, "");
774775
}
775776

776-
// Copied from ble file_transfer.c. We should share it.
777-
STATIC FRESULT _delete_directory_contents(FATFS *fs, const TCHAR *path) {
778-
FF_DIR dir;
779-
FRESULT res = f_opendir(fs, &dir, path);
780-
FILINFO file_info;
781-
// Check the stack since we're putting paths on it.
782-
if (mp_stack_usage() >= MP_STATE_THREAD(stack_limit)) {
783-
return FR_INT_ERR;
784-
}
785-
while (res == FR_OK) {
786-
res = f_readdir(&dir, &file_info);
787-
if (res != FR_OK || file_info.fname[0] == '\0') {
788-
break;
789-
}
790-
size_t pathlen = strlen(path);
791-
size_t fnlen = strlen(file_info.fname);
792-
TCHAR full_path[pathlen + 1 + fnlen];
793-
memcpy(full_path, path, pathlen);
794-
full_path[pathlen] = '/';
795-
size_t full_pathlen = pathlen + 1 + fnlen;
796-
memcpy(full_path + pathlen + 1, file_info.fname, fnlen);
797-
full_path[full_pathlen] = '\0';
798-
if ((file_info.fattrib & AM_DIR) != 0) {
799-
res = _delete_directory_contents(fs, full_path);
800-
}
801-
if (res != FR_OK) {
802-
break;
803-
}
804-
res = f_unlink(fs, full_path);
805-
}
806-
f_closedir(&dir);
807-
return res;
808-
}
809-
810777
// FATFS has a two second timestamp resolution but the BLE API allows for nanosecond resolution.
811778
// This function truncates the time the time to a resolution storable by FATFS and fills in the
812779
// FATFS encoded version into fattime.
@@ -1065,7 +1032,7 @@ static bool _reply(socketpool_socket_obj_t *socket, _request *request) {
10651032
FRESULT result = f_stat(fs, path, &file);
10661033
if (result == FR_OK) {
10671034
if ((file.fattrib & AM_DIR) != 0) {
1068-
result = _delete_directory_contents(fs, path);
1035+
result = supervisor_workflow_delete_directory_contents(fs, path);
10691036
}
10701037
if (result == FR_OK) {
10711038
result = f_unlink(fs, path);
@@ -1144,7 +1111,7 @@ static bool _reply(socketpool_socket_obj_t *socket, _request *request) {
11441111
truncate_time(request->timestamp_ms * 1000000, &fattime);
11451112
override_fattime(fattime);
11461113
}
1147-
FRESULT result = f_mkdir(fs, path);
1114+
FRESULT result = supervisor_workflow_mkdir_parents(fs, path);
11481115
override_fattime(0);
11491116
#if CIRCUITPY_USB_MSC
11501117
usb_msc_unlock();

supervisor/shared/workflow.c

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,8 @@
2626

2727
#include <stdbool.h>
2828
#include "py/mpconfig.h"
29+
#include "py/mpstate.h"
30+
#include "py/stackctrl.h"
2931
#include "supervisor/background_callback.h"
3032
#include "supervisor/workflow.h"
3133
#include "supervisor/serial.h"
@@ -115,3 +117,60 @@ void supervisor_workflow_start(void) {
115117
supervisor_start_web_workflow();
116118
#endif
117119
}
120+
121+
FRESULT supervisor_workflow_mkdir_parents(FATFS *fs, char *path) {
122+
FRESULT result = FR_OK;
123+
// Make parent directories.
124+
for (size_t j = 1; j < strlen(path); j++) {
125+
if (path[j] == '/') {
126+
path[j] = '\0';
127+
result = f_mkdir(fs, path);
128+
path[j] = '/';
129+
if (result != FR_OK && result != FR_EXIST) {
130+
return result;
131+
}
132+
}
133+
}
134+
// Make the target directory.
135+
return f_mkdir(fs, path);
136+
}
137+
138+
FRESULT supervisor_workflow_delete_directory_contents(FATFS *fs, const TCHAR *path) {
139+
FF_DIR dir;
140+
FILINFO file_info;
141+
// Check the stack since we're putting paths on it.
142+
if (mp_stack_usage() >= MP_STATE_THREAD(stack_limit)) {
143+
return FR_INT_ERR;
144+
}
145+
FRESULT res = FR_OK;
146+
while (res == FR_OK) {
147+
res = f_opendir(fs, &dir, path);
148+
if (res != FR_OK) {
149+
break;
150+
}
151+
res = f_readdir(&dir, &file_info);
152+
// We close and reopen the directory every time since we're deleting
153+
// entries and it may invalidate the directory handle.
154+
f_closedir(&dir);
155+
if (res != FR_OK || file_info.fname[0] == '\0') {
156+
break;
157+
}
158+
size_t pathlen = strlen(path);
159+
size_t fnlen = strlen(file_info.fname);
160+
TCHAR full_path[pathlen + 1 + fnlen];
161+
memcpy(full_path, path, pathlen);
162+
full_path[pathlen] = '/';
163+
size_t full_pathlen = pathlen + 1 + fnlen;
164+
memcpy(full_path + pathlen + 1, file_info.fname, fnlen);
165+
full_path[full_pathlen] = '\0';
166+
if ((file_info.fattrib & AM_DIR) != 0) {
167+
res = supervisor_workflow_delete_directory_contents(fs, full_path);
168+
}
169+
if (res != FR_OK) {
170+
break;
171+
}
172+
res = f_unlink(fs, full_path);
173+
}
174+
f_closedir(&dir);
175+
return res;
176+
}

supervisor/shared/workflow.h

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,4 +26,10 @@
2626

2727
#pragma once
2828

29+
#include "lib/oofatfs/ff.h"
30+
2931
extern bool supervisor_workflow_connecting(void);
32+
33+
// File system helpers for workflow code.
34+
FRESULT supervisor_workflow_mkdir_parents(FATFS *fs, char *path);
35+
FRESULT supervisor_workflow_delete_directory_contents(FATFS *fs, const TCHAR *path);

0 commit comments

Comments
 (0)