From b6e24230cf716f5ccd0f0d38f24a05719f2c2b80 Mon Sep 17 00:00:00 2001 From: Scott Shawcroft Date: Tue, 16 Aug 2022 14:40:32 -0700 Subject: [PATCH 1/4] Add uploading a directory and its contents This only works for one top level directory at a time. --- .../shared/web_workflow/static/directory.html | 4 ++- .../shared/web_workflow/static/directory.js | 27 ++++++++++++++++--- 2 files changed, 26 insertions(+), 5 deletions(-) diff --git a/supervisor/shared/web_workflow/static/directory.html b/supervisor/shared/web_workflow/static/directory.html index 5551410d73..8ac3353bcc 100644 --- a/supervisor/shared/web_workflow/static/directory.html +++ b/supervisor/shared/web_workflow/static/directory.html @@ -16,7 +16,9 @@
- + + +
+📁  diff --git a/supervisor/shared/web_workflow/static/directory.js b/supervisor/shared/web_workflow/static/directory.js index 981d95f68c..7ea4841280 100644 --- a/supervisor/shared/web_workflow/static/directory.js +++ b/supervisor/shared/web_workflow/static/directory.js @@ -1,5 +1,6 @@ let new_directory_name = document.getElementById("name"); let files = document.getElementById("files"); +let dirs = document.getElementById("dirs"); var url_base = window.location; var current_path; @@ -148,8 +149,21 @@ async function mkdir(e) { } async function upload(e) { - for (const file of files.files) { - let file_path = new URL("/fs" + current_path + file.name, url_base); + let made_dirs = new Set(); + for (const file of [...files.files, ...dirs.files]) { + let file_name = file.name; + if (file.webkitRelativePath) { + file_name = file.webkitRelativePath; + let components = file_name.split("/"); + components.pop(); + let parent_dir = components.join("/"); + if (!made_dirs.has(parent_dir)) { + new_directory_name.value = parent_dir; + await mkdir(null); + made_dirs.add(parent_dir); + } + } + let file_path = new URL("/fs" + current_path + file_name, url_base); const response = await fetch(file_path, { method: "PUT", @@ -165,6 +179,7 @@ async function upload(e) { } } files.value = ""; + dirs.value = ""; upload_button.disabled = true; } @@ -196,10 +211,14 @@ mkdir_button.onclick = mkdir; let upload_button = document.getElementById("upload"); upload_button.onclick = upload; -upload_button.disabled = files.files.length == 0; +upload_button.disabled = files.files.length == 0 && dirs.files.length == 0; files.onchange = () => { - upload_button.disabled = files.files.length == 0; + upload_button.disabled = files.files.length == 0 && dirs.files.length == 0; +} + +dirs.onchange = () => { + upload_button.disabled = files.files.length == 0 && dirs.files.length == 0; } mkdir_button.disabled = new_directory_name.value.length == 0; From 3493be7757d0468d2972c172c09d069b0ef60ae4 Mon Sep 17 00:00:00 2001 From: Scott Shawcroft Date: Tue, 16 Aug 2022 17:03:09 -0700 Subject: [PATCH 2/4] Fix recursive delete, add upload labels and progress --- supervisor/shared/web_workflow/static/directory.html | 5 +++-- supervisor/shared/web_workflow/static/directory.js | 5 +++++ supervisor/shared/web_workflow/web_workflow.c | 9 ++++++++- 3 files changed, 16 insertions(+), 3 deletions(-) diff --git a/supervisor/shared/web_workflow/static/directory.html b/supervisor/shared/web_workflow/static/directory.html index 8ac3353bcc..011d56fa61 100644 --- a/supervisor/shared/web_workflow/static/directory.html +++ b/supervisor/shared/web_workflow/static/directory.html @@ -16,9 +16,10 @@
- - + + +
+📁  diff --git a/supervisor/shared/web_workflow/static/directory.js b/supervisor/shared/web_workflow/static/directory.js index 7ea4841280..0047435016 100644 --- a/supervisor/shared/web_workflow/static/directory.js +++ b/supervisor/shared/web_workflow/static/directory.js @@ -149,7 +149,10 @@ async function mkdir(e) { } async function upload(e) { + let progress = document.querySelector("progress"); let made_dirs = new Set(); + progress.max = files.files.length + dirs.files.length; + progress.value = 0; for (const file of [...files.files, ...dirs.files]) { let file_name = file.name; if (file.webkitRelativePath) { @@ -177,9 +180,11 @@ async function upload(e) { if (response.ok) { refresh_list(); } + progress.value += 1; } files.value = ""; dirs.value = ""; + progress.value = 0; upload_button.disabled = true; } diff --git a/supervisor/shared/web_workflow/web_workflow.c b/supervisor/shared/web_workflow/web_workflow.c index 1144d41bdc..7dbe6dbf92 100644 --- a/supervisor/shared/web_workflow/web_workflow.c +++ b/supervisor/shared/web_workflow/web_workflow.c @@ -766,14 +766,21 @@ static void _reply_with_version_json(socketpool_socket_obj_t *socket, _request * // Copied from ble file_transfer.c. We should share it. STATIC FRESULT _delete_directory_contents(FATFS *fs, const TCHAR *path) { FF_DIR dir; - FRESULT res = f_opendir(fs, &dir, path); FILINFO file_info; // Check the stack since we're putting paths on it. if (mp_stack_usage() >= MP_STATE_THREAD(stack_limit)) { return FR_INT_ERR; } + FRESULT res = FR_OK; while (res == FR_OK) { + res = f_opendir(fs, &dir, path); + if (res != FR_OK) { + break; + } res = f_readdir(&dir, &file_info); + // We close and reopen the directory every time since we're deleting + // entries and it may invalidate the directory handle. + f_closedir(&dir); if (res != FR_OK || file_info.fname[0] == '\0') { break; } From 9a5f00a093bf7741b42fe02006fd979ceebd1b42 Mon Sep 17 00:00:00 2001 From: Scott Shawcroft Date: Wed, 17 Aug 2022 11:31:11 -0700 Subject: [PATCH 3/4] Remove upload button and fix mkdir parents --- supervisor/shared/bluetooth/file_transfer.c | 39 +------------ .../shared/web_workflow/static/directory.html | 1 - .../shared/web_workflow/static/directory.js | 24 ++++---- supervisor/shared/web_workflow/web_workflow.c | 46 +-------------- supervisor/shared/workflow.c | 58 +++++++++++++++++++ supervisor/shared/workflow.h | 6 ++ 6 files changed, 80 insertions(+), 94 deletions(-) diff --git a/supervisor/shared/bluetooth/file_transfer.c b/supervisor/shared/bluetooth/file_transfer.c index 216cf3d567..159c639e4d 100644 --- a/supervisor/shared/bluetooth/file_transfer.c +++ b/supervisor/shared/bluetooth/file_transfer.c @@ -47,11 +47,11 @@ #include "supervisor/shared/reload.h" #include "supervisor/shared/bluetooth/file_transfer.h" #include "supervisor/shared/bluetooth/file_transfer_protocol.h" +#include "supervisor/shared/workflow.h" #include "supervisor/shared/tick.h" #include "supervisor/usb.h" #include "py/mpstate.h" -#include "py/stackctrl.h" STATIC bleio_service_obj_t supervisor_ble_service; 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) { return WRITE_DATA; } -STATIC FRESULT _delete_directory_contents(FATFS *fs, const TCHAR *path) { - FF_DIR dir; - FRESULT res = f_opendir(fs, &dir, path); - FILINFO file_info; - // Check the stack since we're putting paths on it. - if (mp_stack_usage() >= MP_STATE_THREAD(stack_limit)) { - return FR_INT_ERR; - } - while (res == FR_OK) { - res = f_readdir(&dir, &file_info); - if (res != FR_OK || file_info.fname[0] == '\0') { - break; - } - size_t pathlen = strlen(path); - size_t fnlen = strlen(file_info.fname); - TCHAR full_path[pathlen + 1 + fnlen]; - memcpy(full_path, path, pathlen); - full_path[pathlen] = '/'; - size_t full_pathlen = pathlen + 1 + fnlen; - memcpy(full_path + pathlen + 1, file_info.fname, fnlen); - full_path[full_pathlen] = '\0'; - if ((file_info.fattrib & AM_DIR) != 0) { - res = _delete_directory_contents(fs, full_path); - } - if (res != FR_OK) { - break; - } - res = f_unlink(fs, full_path); - } - f_closedir(&dir); - return res; -} - STATIC uint8_t _process_delete(const uint8_t *raw_buf, size_t command_len) { const struct delete_command *command = (struct delete_command *)raw_buf; 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) { FRESULT result = f_stat(fs, path, &file); if (result == FR_OK) { if ((file.fattrib & AM_DIR) != 0) { - result = _delete_directory_contents(fs, path); + result = supervisor_workflow_delete_directory_contents(fs, path); } if (result == FR_OK) { result = f_unlink(fs, path); @@ -503,7 +470,7 @@ STATIC uint8_t _process_mkdir(const uint8_t *raw_buf, size_t command_len) { DWORD fattime; response.truncated_time = truncate_time(command->modification_time, &fattime); override_fattime(fattime); - FRESULT result = f_mkdir(fs, path); + FRESULT result = supervisor_workflow_mkdir_parents(fs, path); override_fattime(0); #if CIRCUITPY_USB_MSC usb_msc_unlock(); diff --git a/supervisor/shared/web_workflow/static/directory.html b/supervisor/shared/web_workflow/static/directory.html index 011d56fa61..ebe4b03010 100644 --- a/supervisor/shared/web_workflow/static/directory.html +++ b/supervisor/shared/web_workflow/static/directory.html @@ -18,7 +18,6 @@
-
+📁  diff --git a/supervisor/shared/web_workflow/static/directory.js b/supervisor/shared/web_workflow/static/directory.js index 0047435016..06ce7274b3 100644 --- a/supervisor/shared/web_workflow/static/directory.js +++ b/supervisor/shared/web_workflow/static/directory.js @@ -6,6 +6,11 @@ var url_base = window.location; var current_path; var editable = undefined; +function set_upload_enabled(enabled) { + files.disabled = !enabled; + dirs.disabled = !enabled; +} + async function refresh_list() { function compareValues(a, b) { @@ -46,7 +51,7 @@ async function refresh_list() { ); editable = status.headers.get("Access-Control-Allow-Methods").includes("DELETE"); new_directory_name.disabled = !editable; - files.disabled = !editable; + set_upload_enabled(editable); if (!editable) { let usbwarning = document.querySelector("#usbwarning"); usbwarning.style.display = "block"; @@ -149,6 +154,7 @@ async function mkdir(e) { } async function upload(e) { + set_upload_enabled(false); let progress = document.querySelector("progress"); let made_dirs = new Set(); progress.max = files.files.length + dirs.files.length; @@ -185,7 +191,7 @@ async function upload(e) { files.value = ""; dirs.value = ""; progress.value = 0; - upload_button.disabled = true; + set_upload_enabled(true); } async function del(e) { @@ -213,18 +219,8 @@ find_devices(); let mkdir_button = document.getElementById("mkdir"); mkdir_button.onclick = mkdir; -let upload_button = document.getElementById("upload"); -upload_button.onclick = upload; - -upload_button.disabled = files.files.length == 0 && dirs.files.length == 0; - -files.onchange = () => { - upload_button.disabled = files.files.length == 0 && dirs.files.length == 0; -} - -dirs.onchange = () => { - upload_button.disabled = files.files.length == 0 && dirs.files.length == 0; -} +files.onchange = upload; +dirs.onchange = upload; mkdir_button.disabled = new_directory_name.value.length == 0; diff --git a/supervisor/shared/web_workflow/web_workflow.c b/supervisor/shared/web_workflow/web_workflow.c index 7dbe6dbf92..bfc899a3fd 100644 --- a/supervisor/shared/web_workflow/web_workflow.c +++ b/supervisor/shared/web_workflow/web_workflow.c @@ -42,6 +42,7 @@ #include "supervisor/shared/translate/translate.h" #include "supervisor/shared/web_workflow/web_workflow.h" #include "supervisor/shared/web_workflow/websocket.h" +#include "supervisor/shared/workflow.h" #include "supervisor/usb.h" #include "shared-bindings/hashlib/__init__.h" @@ -763,47 +764,6 @@ static void _reply_with_version_json(socketpool_socket_obj_t *socket, _request * _send_chunk(socket, ""); } -// Copied from ble file_transfer.c. We should share it. -STATIC FRESULT _delete_directory_contents(FATFS *fs, const TCHAR *path) { - FF_DIR dir; - FILINFO file_info; - // Check the stack since we're putting paths on it. - if (mp_stack_usage() >= MP_STATE_THREAD(stack_limit)) { - return FR_INT_ERR; - } - FRESULT res = FR_OK; - while (res == FR_OK) { - res = f_opendir(fs, &dir, path); - if (res != FR_OK) { - break; - } - res = f_readdir(&dir, &file_info); - // We close and reopen the directory every time since we're deleting - // entries and it may invalidate the directory handle. - f_closedir(&dir); - if (res != FR_OK || file_info.fname[0] == '\0') { - break; - } - size_t pathlen = strlen(path); - size_t fnlen = strlen(file_info.fname); - TCHAR full_path[pathlen + 1 + fnlen]; - memcpy(full_path, path, pathlen); - full_path[pathlen] = '/'; - size_t full_pathlen = pathlen + 1 + fnlen; - memcpy(full_path + pathlen + 1, file_info.fname, fnlen); - full_path[full_pathlen] = '\0'; - if ((file_info.fattrib & AM_DIR) != 0) { - res = _delete_directory_contents(fs, full_path); - } - if (res != FR_OK) { - break; - } - res = f_unlink(fs, full_path); - } - f_closedir(&dir); - return res; -} - // FATFS has a two second timestamp resolution but the BLE API allows for nanosecond resolution. // This function truncates the time the time to a resolution storable by FATFS and fills in the // FATFS encoded version into fattime. @@ -1054,7 +1014,7 @@ static bool _reply(socketpool_socket_obj_t *socket, _request *request) { FRESULT result = f_stat(fs, path, &file); if (result == FR_OK) { if ((file.fattrib & AM_DIR) != 0) { - result = _delete_directory_contents(fs, path); + result = supervisor_workflow_delete_directory_contents(fs, path); } if (result == FR_OK) { result = f_unlink(fs, path); @@ -1105,7 +1065,7 @@ static bool _reply(socketpool_socket_obj_t *socket, _request *request) { truncate_time(request->timestamp_ms * 1000000, &fattime); override_fattime(fattime); } - FRESULT result = f_mkdir(fs, path); + FRESULT result = supervisor_workflow_mkdir_parents(fs, path); override_fattime(0); #if CIRCUITPY_USB_MSC usb_msc_unlock(); diff --git a/supervisor/shared/workflow.c b/supervisor/shared/workflow.c index 7b14c663c4..40a095720c 100644 --- a/supervisor/shared/workflow.c +++ b/supervisor/shared/workflow.c @@ -26,6 +26,7 @@ #include #include "py/mpconfig.h" +#include "py/stackctrl.h" #include "supervisor/background_callback.h" #include "supervisor/workflow.h" #include "supervisor/serial.h" @@ -115,3 +116,60 @@ void supervisor_workflow_start(void) { supervisor_start_web_workflow(); #endif } + +FRESULT supervisor_workflow_mkdir_parents(FATFS *fs, char *path) { + FRESULT result = FR_OK; + // Make parent directories. + for (size_t j = 1; j < strlen(path); j++) { + if (path[j] == '/') { + path[j] = '\0'; + result = f_mkdir(fs, path); + path[j] = '/'; + if (result != FR_OK && result != FR_EXIST) { + return result; + } + } + } + // Make the target directory. + return f_mkdir(fs, path); +} + +FRESULT supervisor_workflow_delete_directory_contents(FATFS *fs, const TCHAR *path) { + FF_DIR dir; + FILINFO file_info; + // Check the stack since we're putting paths on it. + if (mp_stack_usage() >= MP_STATE_THREAD(stack_limit)) { + return FR_INT_ERR; + } + FRESULT res = FR_OK; + while (res == FR_OK) { + res = f_opendir(fs, &dir, path); + if (res != FR_OK) { + break; + } + res = f_readdir(&dir, &file_info); + // We close and reopen the directory every time since we're deleting + // entries and it may invalidate the directory handle. + f_closedir(&dir); + if (res != FR_OK || file_info.fname[0] == '\0') { + break; + } + size_t pathlen = strlen(path); + size_t fnlen = strlen(file_info.fname); + TCHAR full_path[pathlen + 1 + fnlen]; + memcpy(full_path, path, pathlen); + full_path[pathlen] = '/'; + size_t full_pathlen = pathlen + 1 + fnlen; + memcpy(full_path + pathlen + 1, file_info.fname, fnlen); + full_path[full_pathlen] = '\0'; + if ((file_info.fattrib & AM_DIR) != 0) { + res = supervisor_workflow_delete_directory_contents(fs, full_path); + } + if (res != FR_OK) { + break; + } + res = f_unlink(fs, full_path); + } + f_closedir(&dir); + return res; +} diff --git a/supervisor/shared/workflow.h b/supervisor/shared/workflow.h index b3c817fb8b..df483e0ebc 100644 --- a/supervisor/shared/workflow.h +++ b/supervisor/shared/workflow.h @@ -26,4 +26,10 @@ #pragma once +#include "lib/oofatfs/ff.h" + extern bool supervisor_workflow_connecting(void); + +// File system helpers for workflow code. +FRESULT supervisor_workflow_mkdir_parents(FATFS *fs, char *path); +FRESULT supervisor_workflow_delete_directory_contents(FATFS *fs, const TCHAR *path); From b7a2a3d72499f122599861c8077867a5e74c8512 Mon Sep 17 00:00:00 2001 From: Scott Shawcroft Date: Wed, 17 Aug 2022 13:52:13 -0700 Subject: [PATCH 4/4] Fix arm builds --- supervisor/shared/workflow.c | 1 + 1 file changed, 1 insertion(+) diff --git a/supervisor/shared/workflow.c b/supervisor/shared/workflow.c index 40a095720c..a3fd287bb0 100644 --- a/supervisor/shared/workflow.c +++ b/supervisor/shared/workflow.c @@ -26,6 +26,7 @@ #include #include "py/mpconfig.h" +#include "py/mpstate.h" #include "py/stackctrl.h" #include "supervisor/background_callback.h" #include "supervisor/workflow.h"