Merge remote-tracking branch 'adafruit/main' into upload_folder
This commit is contained in:
commit
3aa5f60cb9
@ -202,6 +202,24 @@ Example:
|
||||
curl -v -u :passw0rd -X PUT -L --location-trusted http://circuitpython.local/fs/lib/hello/world/
|
||||
```
|
||||
|
||||
##### Move
|
||||
Moves the directory at the given path to ``X-Destination``. Also known as rename.
|
||||
|
||||
The custom `X-Destination` header stores the destination path of the directory.
|
||||
|
||||
* `201 Created` - Directory renamed
|
||||
* `401 Unauthorized` - Incorrect password
|
||||
* `403 Forbidden` - No `CIRCUITPY_WEB_API_PASSWORD` set
|
||||
* `404 Not Found` - Source directory not found or destination path is missing
|
||||
* `409 Conflict` - USB is active and preventing file system modification
|
||||
* `412 Precondition Failed` - The destination path is already in use
|
||||
|
||||
Example:
|
||||
|
||||
```sh
|
||||
curl -v -u :passw0rd -X MOVE -H "X-Destination: /fs/lib/hello2/" -L --location-trusted http://circuitpython.local/fs/lib/hello/
|
||||
```
|
||||
|
||||
##### DELETE
|
||||
Deletes the directory and all of its contents.
|
||||
|
||||
@ -214,7 +232,7 @@ Deletes the directory and all of its contents.
|
||||
Example:
|
||||
|
||||
```sh
|
||||
curl -v -u :passw0rd -X DELETE -L --location-trusted http://circuitpython.local/fs/lib/hello/world/
|
||||
curl -v -u :passw0rd -X DELETE -L --location-trusted http://circuitpython.local/fs/lib/hello2/world/
|
||||
```
|
||||
|
||||
|
||||
@ -270,6 +288,25 @@ curl -v -u :passw0rd -L --location-trusted http://circuitpython.local/fs/lib/hel
|
||||
```
|
||||
|
||||
|
||||
##### Move
|
||||
Moves the file at the given path to the ``X-Destination``. Also known as rename.
|
||||
|
||||
The custom `X-Destination` header stores the destination path of the file.
|
||||
|
||||
* `201 Created` - File renamed
|
||||
* `401 Unauthorized` - Incorrect password
|
||||
* `403 Forbidden` - No `CIRCUITPY_WEB_API_PASSWORD` set
|
||||
* `404 Not Found` - Source file not found or destination path is missing
|
||||
* `409 Conflict` - USB is active and preventing file system modification
|
||||
* `412 Precondition Failed` - The destination path is already in use
|
||||
|
||||
Example:
|
||||
|
||||
```sh
|
||||
curl -v -u :passw0rd -X MOVE -H "X-Destination: /fs/lib/hello/world2.txt" -L --location-trusted http://circuitpython.local/fs/lib/hello/world.txt
|
||||
```
|
||||
|
||||
|
||||
##### DELETE
|
||||
Deletes the file.
|
||||
|
||||
@ -283,7 +320,7 @@ Deletes the file.
|
||||
Example:
|
||||
|
||||
```sh
|
||||
curl -v -u :passw0rd -X DELETE -L --location-trusted http://circuitpython.local/fs/lib/hello/world.txt
|
||||
curl -v -u :passw0rd -X DELETE -L --location-trusted http://circuitpython.local/fs/lib/hello/world2.txt
|
||||
```
|
||||
|
||||
### `/cp/`
|
||||
|
@ -30,22 +30,9 @@
|
||||
#include "peripherals/touch.h"
|
||||
#include "shared-bindings/microcontroller/Pin.h"
|
||||
|
||||
static uint16_t get_raw_reading(touchio_touchin_obj_t *self) {
|
||||
#if defined(CONFIG_IDF_TARGET_ESP32)
|
||||
uint16_t touch_value;
|
||||
#else
|
||||
uint32_t touch_value;
|
||||
#endif;
|
||||
touch_pad_read_raw_data(self->pin->touch_channel, &touch_value);
|
||||
if (touch_value > UINT16_MAX) {
|
||||
return UINT16_MAX;
|
||||
}
|
||||
return (uint16_t)touch_value;
|
||||
}
|
||||
|
||||
void common_hal_touchio_touchin_construct(touchio_touchin_obj_t *self,
|
||||
const mcu_pin_obj_t *pin) {
|
||||
if (pin->touch_channel == TOUCH_PAD_MAX) {
|
||||
if (pin->touch_channel == NO_TOUCH_CHANNEL) {
|
||||
raise_ValueError_invalid_pin();
|
||||
}
|
||||
claim_pin(pin);
|
||||
@ -53,15 +40,12 @@ void common_hal_touchio_touchin_construct(touchio_touchin_obj_t *self,
|
||||
// initialize touchpad
|
||||
peripherals_touch_init(pin->touch_channel);
|
||||
|
||||
// wait for touch data to reset
|
||||
mp_hal_delay_ms(10);
|
||||
|
||||
// Set a "touched" threshold not too far above the initial value.
|
||||
// For simple finger touch, the values may vary as much as a factor of two,
|
||||
// but for touches using fruit or other objects, the difference is much less.
|
||||
|
||||
self->pin = pin;
|
||||
self->threshold = get_raw_reading(self) + 100;
|
||||
self->threshold = common_hal_touchio_touchin_get_raw_value(self) + 100;
|
||||
}
|
||||
|
||||
bool common_hal_touchio_touchin_deinited(touchio_touchin_obj_t *self) {
|
||||
@ -77,11 +61,11 @@ void common_hal_touchio_touchin_deinit(touchio_touchin_obj_t *self) {
|
||||
}
|
||||
|
||||
bool common_hal_touchio_touchin_get_value(touchio_touchin_obj_t *self) {
|
||||
return get_raw_reading(self) > self->threshold;
|
||||
return common_hal_touchio_touchin_get_raw_value(self) > self->threshold;
|
||||
}
|
||||
|
||||
uint16_t common_hal_touchio_touchin_get_raw_value(touchio_touchin_obj_t *self) {
|
||||
return get_raw_reading(self);
|
||||
return peripherals_touch_read(self->pin->touch_channel);
|
||||
}
|
||||
|
||||
uint16_t common_hal_touchio_touchin_get_threshold(touchio_touchin_obj_t *self) {
|
||||
|
@ -43,7 +43,7 @@ void peripherals_touch_never_reset(const bool enable) {
|
||||
void peripherals_touch_init(const touch_pad_t touchpad) {
|
||||
if (!touch_inited) {
|
||||
touch_pad_init();
|
||||
touch_pad_set_fsm_mode(TOUCH_FSM_MODE_TIMER);
|
||||
touch_pad_set_fsm_mode(TOUCH_FSM_MODE_SW);
|
||||
}
|
||||
// touch_pad_config() must be done before touch_pad_fsm_start() the first time.
|
||||
// Otherwise the calibration is wrong and we get maximum raw values if there is
|
||||
@ -52,13 +52,27 @@ void peripherals_touch_init(const touch_pad_t touchpad) {
|
||||
touch_pad_config(touchpad, 0);
|
||||
#else
|
||||
touch_pad_config(touchpad);
|
||||
touch_pad_fsm_start();
|
||||
#endif
|
||||
touch_inited = true;
|
||||
}
|
||||
|
||||
uint16_t peripherals_touch_read(touch_pad_t touchpad) {
|
||||
#if defined(CONFIG_IDF_TARGET_ESP32)
|
||||
uint16_t touch_value;
|
||||
touch_pad_read(touchpad, &touch_value);
|
||||
// ESP32 touch_pad_read() returns a lower value when a pin is touched instead of a higher value.
|
||||
// Flip the values around to be consistent with TouchIn assumptions.
|
||||
return UINT16_MAX - touch_value;
|
||||
#else
|
||||
uint32_t touch_value;
|
||||
touch_pad_sw_start();
|
||||
while (!touch_pad_meas_is_done()) {
|
||||
}
|
||||
touch_pad_read_raw_data(touchpad, &touch_value);
|
||||
if (touch_value > UINT16_MAX) {
|
||||
return UINT16_MAX;
|
||||
}
|
||||
return (uint16_t)touch_value;
|
||||
#endif
|
||||
if (!touch_inited) {
|
||||
#if defined(CONFIG_IDF_TARGET_ESP32)
|
||||
touch_pad_sw_start();
|
||||
#else
|
||||
touch_pad_fsm_start();
|
||||
#endif
|
||||
touch_inited = true;
|
||||
}
|
||||
}
|
||||
|
@ -29,6 +29,7 @@
|
||||
|
||||
#include "driver/touch_pad.h"
|
||||
|
||||
extern uint16_t peripherals_touch_read(touch_pad_t touchpad);
|
||||
extern void peripherals_touch_reset(void);
|
||||
extern void peripherals_touch_never_reset(const bool enable);
|
||||
extern void peripherals_touch_init(const touch_pad_t touchpad);
|
||||
|
12
supervisor/shared/web_workflow/static/code.html
Normal file
12
supervisor/shared/web_workflow/static/code.html
Normal file
@ -0,0 +1,12 @@
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<title>Online Code Editor</title>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<script type="module" src="https://code.circuitpython.org/assets/js/device.js"></script>
|
||||
</body>
|
||||
</html>
|
@ -10,9 +10,9 @@
|
||||
<body>
|
||||
<h1><a href="/"><img src="/favicon.ico"/></a> <span id="path"></span></h1>
|
||||
<div id="usbwarning" style="display: none;">ℹ️ USB is using the storage. Only allowing reads. See <a href="https://learn.adafruit.com/circuitpython-essentials/circuitpython-storage">the CircuitPython Essentials: Storage guide</a> for details.</div>
|
||||
<template id="row"><tr><td></td><td></td><td><a></a></td><td></td><td><button class="delete">🗑️</button></td><td><a class="edit_link" href="">Edit</a></td></tr></template>
|
||||
<template id="row"><tr><td></td><td></td><td><a class="path"></a></td><td class="modtime"></td><td><button class="rename">✏️ Rename</button></td><td><button class="delete">🗑️ Delete</button></td><td><a class="edit_link" href=""><button>📝 Edit</button></a></td></tr></template>
|
||||
<table>
|
||||
<thead><tr><th>Type</th><th>Size</th><th>Path</th><th>Modified</th><th></th></tr></thead>
|
||||
<thead><tr><th>Type</th><th>Size</th><th>Path</th><th>Modified</th><th colspan="3"></th></tr></thead>
|
||||
<tbody></tbody>
|
||||
</table>
|
||||
<hr>
|
||||
|
@ -6,21 +6,22 @@ var url_base = window.location;
|
||||
var current_path;
|
||||
var editable = undefined;
|
||||
|
||||
function compareValues(a, b) {
|
||||
if (a.directory == b.directory && a.name.toLowerCase() === b.name.toLowerCase()) {
|
||||
return 0;
|
||||
} else if (a.directory != b.directory) {
|
||||
return a.directory < b.directory ? 1 : -1;
|
||||
} else {
|
||||
return a.name.toLowerCase() < b.name.toLowerCase() ? -1 : 1;
|
||||
}
|
||||
}
|
||||
|
||||
function set_upload_enabled(enabled) {
|
||||
files.disabled = !enabled;
|
||||
dirs.disabled = !enabled;
|
||||
}
|
||||
|
||||
async function refresh_list() {
|
||||
|
||||
function compareValues(a, b) {
|
||||
if (a.directory == b.directory && a.name.toLowerCase() === b.name.toLowerCase()) {
|
||||
return 0;
|
||||
} else {
|
||||
return a.directory.toString().substring(3,4)+a.name.toLowerCase() < b.directory.toString().substring(3,4)+b.name.toLowerCase() ? -1 : 1;
|
||||
}
|
||||
}
|
||||
|
||||
current_path = window.location.hash.substr(1);
|
||||
if (current_path == "") {
|
||||
current_path = "/";
|
||||
@ -58,7 +59,7 @@ async function refresh_list() {
|
||||
}
|
||||
}
|
||||
|
||||
if (window.location.path != "/fs/") {
|
||||
if (current_path != "/") {
|
||||
var clone = template.content.cloneNode(true);
|
||||
var td = clone.querySelectorAll("td");
|
||||
td[0].textContent = "📁";
|
||||
@ -68,6 +69,8 @@ async function refresh_list() {
|
||||
path.textContent = "..";
|
||||
// Remove the delete button
|
||||
td[4].replaceChildren();
|
||||
td[5].replaceChildren();
|
||||
td[6].replaceChildren();
|
||||
new_children.push(clone);
|
||||
}
|
||||
|
||||
@ -88,31 +91,46 @@ async function refresh_list() {
|
||||
file_path = api_url;
|
||||
}
|
||||
|
||||
var text_file = false;
|
||||
if (f.directory) {
|
||||
icon = "📁";
|
||||
} else if(f.name.endsWith(".txt") ||
|
||||
f.name.endsWith(".env") ||
|
||||
f.name.endsWith(".py") ||
|
||||
f.name.endsWith(".js") ||
|
||||
f.name.endsWith(".json")) {
|
||||
icon = "📄";
|
||||
text_file = true;
|
||||
} else if (f.name.endsWith(".html")) {
|
||||
icon = "🌐";
|
||||
text_file = true;
|
||||
}
|
||||
td[0].textContent = icon;
|
||||
td[1].textContent = f.file_size;
|
||||
var path = clone.querySelector("a");
|
||||
var path = clone.querySelector("a.path");
|
||||
path.href = file_path;
|
||||
path.textContent = f.name;
|
||||
td[3].textContent = (new Date(f.modified_ns / 1000000)).toLocaleString();
|
||||
let modtime = clone.querySelector("td.modtime");
|
||||
modtime.textContent = (new Date(f.modified_ns / 1000000)).toLocaleString();
|
||||
var delete_button = clone.querySelector("button.delete");
|
||||
delete_button.value = api_url;
|
||||
delete_button.disabled = !editable;
|
||||
delete_button.onclick = del;
|
||||
|
||||
if (editable && !f.directory) {
|
||||
|
||||
var rename_button = clone.querySelector("button.rename");
|
||||
rename_button.value = api_url;
|
||||
rename_button.disabled = !editable;
|
||||
rename_button.onclick = rename;
|
||||
|
||||
let edit_link = clone.querySelector(".edit_link");
|
||||
if (text_file && editable && !f.directory) {
|
||||
edit_url = new URL(edit_url, url_base);
|
||||
let edit_link = clone.querySelector(".edit_link");
|
||||
edit_link.href = edit_url
|
||||
} else if (f.directory) {
|
||||
edit_link.style = "display: none;";
|
||||
} else {
|
||||
edit_link.querySelector("button").disabled = true;
|
||||
}
|
||||
|
||||
new_children.push(clone);
|
||||
@ -214,6 +232,26 @@ async function del(e) {
|
||||
}
|
||||
}
|
||||
|
||||
async function rename(e) {
|
||||
let fn = new URL(e.target.value);
|
||||
var new_fn = prompt("Rename to ", fn.pathname.substr(3));
|
||||
if (new_fn === null) {
|
||||
return;
|
||||
}
|
||||
let new_uri = new URL("/fs" + new_fn, fn);
|
||||
const response = await fetch(e.target.value,
|
||||
{
|
||||
method: "MOVE",
|
||||
headers: {
|
||||
'X-Destination': new_uri.pathname,
|
||||
},
|
||||
}
|
||||
)
|
||||
if (response.ok) {
|
||||
refresh_list();
|
||||
}
|
||||
}
|
||||
|
||||
find_devices();
|
||||
|
||||
let mkdir_button = document.getElementById("mkdir");
|
||||
|
@ -2,7 +2,7 @@
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title>Code Edit</title>
|
||||
<title>Offline Code Edit</title>
|
||||
<link rel="stylesheet" href="http://circuitpython.org/assets/css/webworkflow-8.css">
|
||||
<link rel="stylesheet" href="/style.css">
|
||||
</head>
|
||||
|
@ -17,3 +17,7 @@ body {
|
||||
margin: 0;
|
||||
font-size: 0.7em;
|
||||
}
|
||||
|
||||
:disabled {
|
||||
filter: saturate(0%);
|
||||
}
|
||||
|
@ -1,7 +1,7 @@
|
||||
var url_base = window.location;
|
||||
var current_path;
|
||||
|
||||
var mdns_works = window.location.hostname.endsWith(".local");
|
||||
var mdns_works = url_base.hostname.endsWith(".local");
|
||||
|
||||
async function find_devices() {
|
||||
var version_response = await fetch("/cp/version.json");
|
||||
|
@ -79,8 +79,9 @@ typedef struct {
|
||||
enum request_state state;
|
||||
char method[8];
|
||||
char path[256];
|
||||
char destination[256];
|
||||
char header_key[64];
|
||||
char header_value[64];
|
||||
char header_value[256];
|
||||
// We store the origin so we can reply back with it.
|
||||
char origin[64];
|
||||
size_t content_length;
|
||||
@ -554,6 +555,15 @@ static void _reply_conflict(socketpool_socket_obj_t *socket, _request *request)
|
||||
_send_str(socket, "\r\nUSB storage active.");
|
||||
}
|
||||
|
||||
|
||||
static void _reply_precondition_failed(socketpool_socket_obj_t *socket, _request *request) {
|
||||
_send_strs(socket,
|
||||
"HTTP/1.1 412 Precondition Failed\r\n",
|
||||
"Content-Length: 0\r\n", NULL);
|
||||
_cors_header(socket, request);
|
||||
_send_str(socket, "\r\n");
|
||||
}
|
||||
|
||||
static void _reply_payload_too_large(socketpool_socket_obj_t *socket, _request *request) {
|
||||
_send_strs(socket,
|
||||
"HTTP/1.1 413 Payload Too Large\r\n",
|
||||
@ -893,6 +903,7 @@ static void _write_file_and_reply(socketpool_socket_obj_t *socket, _request *req
|
||||
|
||||
#define STATIC_FILE(filename) extern uint32_t filename##_length; extern uint8_t filename[]; extern const char *filename##_content_type;
|
||||
|
||||
STATIC_FILE(code_html);
|
||||
STATIC_FILE(directory_html);
|
||||
STATIC_FILE(directory_js);
|
||||
STATIC_FILE(welcome_html);
|
||||
@ -953,6 +964,28 @@ static uint8_t _hex2nibble(char h) {
|
||||
return h - 'a' + 0xa;
|
||||
}
|
||||
|
||||
// Decode percent encoding in place. Only do this once on a string!
|
||||
static void _decode_percents(char *str) {
|
||||
size_t o = 0;
|
||||
size_t i = 0;
|
||||
size_t startlen = strlen(str);
|
||||
while (i < startlen) {
|
||||
if (str[i] == '%') {
|
||||
str[o] = _hex2nibble(str[i + 1]) << 4 | _hex2nibble(str[i + 2]);
|
||||
i += 3;
|
||||
} else {
|
||||
if (i != o) {
|
||||
str[o] = str[i];
|
||||
}
|
||||
i += 1;
|
||||
}
|
||||
o += 1;
|
||||
}
|
||||
if (o < i) {
|
||||
str[o] = '\0';
|
||||
}
|
||||
}
|
||||
|
||||
static bool _reply(socketpool_socket_obj_t *socket, _request *request) {
|
||||
if (request->redirect) {
|
||||
_reply_redirect(socket, request, request->path);
|
||||
@ -973,23 +1006,8 @@ static bool _reply(socketpool_socket_obj_t *socket, _request *request) {
|
||||
// Decode any percent encoded bytes so that we're left with UTF-8.
|
||||
// We only do this on /fs/ paths and after redirect so that any
|
||||
// path echoing we do stays encoded.
|
||||
size_t o = 0;
|
||||
size_t i = 0;
|
||||
while (i < strlen(request->path)) {
|
||||
if (request->path[i] == '%') {
|
||||
request->path[o] = _hex2nibble(request->path[i + 1]) << 4 | _hex2nibble(request->path[i + 2]);
|
||||
i += 3;
|
||||
} else {
|
||||
if (i != o) {
|
||||
request->path[o] = request->path[i];
|
||||
}
|
||||
i += 1;
|
||||
}
|
||||
o += 1;
|
||||
}
|
||||
if (o < i) {
|
||||
request->path[o] = '\0';
|
||||
}
|
||||
_decode_percents(request->path);
|
||||
|
||||
char *path = request->path + 3;
|
||||
size_t pathlen = strlen(path);
|
||||
FATFS *fs = filesystem_circuitpy();
|
||||
@ -1033,6 +1051,34 @@ static bool _reply(socketpool_socket_obj_t *socket, _request *request) {
|
||||
_reply_no_content(socket, request);
|
||||
return true;
|
||||
}
|
||||
} else if (strcasecmp(request->method, "MOVE") == 0) {
|
||||
if (_usb_active()) {
|
||||
_reply_conflict(socket, request);
|
||||
return false;
|
||||
}
|
||||
|
||||
_decode_percents(request->destination);
|
||||
char *destination = request->destination + 3;
|
||||
size_t destinationlen = strlen(destination);
|
||||
if (destination[destinationlen - 1] == '/' && destinationlen > 1) {
|
||||
destination[destinationlen - 1] = '\0';
|
||||
}
|
||||
|
||||
FRESULT result = f_rename(fs, path, destination);
|
||||
#if CIRCUITPY_USB_MSC
|
||||
usb_msc_unlock();
|
||||
#endif
|
||||
if (result == FR_EXIST) { // File exists and won't be overwritten.
|
||||
_reply_precondition_failed(socket, request);
|
||||
} else if (result == FR_NO_PATH || result == FR_NO_FILE) { // Missing higher directories or target file.
|
||||
_reply_missing(socket, request);
|
||||
} else if (result != FR_OK) {
|
||||
ESP_LOGE(TAG, "move error %d %s", result, path);
|
||||
_reply_server_error(socket, request);
|
||||
} else {
|
||||
_reply_created(socket, request);
|
||||
return true;
|
||||
}
|
||||
} else if (directory) {
|
||||
if (strcasecmp(request->method, "GET") == 0) {
|
||||
FF_DIR dir;
|
||||
@ -1110,6 +1156,8 @@ static bool _reply(socketpool_socket_obj_t *socket, _request *request) {
|
||||
} else {
|
||||
_REPLY_STATIC(socket, request, edit_html);
|
||||
}
|
||||
} else if (strcmp(request->path, "/code/") == 0) {
|
||||
_REPLY_STATIC(socket, request, code_html);
|
||||
} else if (strncmp(request->path, "/cp/", 4) == 0) {
|
||||
const char *path = request->path + 3;
|
||||
if (strcasecmp(request->method, "OPTIONS") == 0) {
|
||||
@ -1285,6 +1333,8 @@ static void _process_request(socketpool_socket_obj_t *socket, _request *request)
|
||||
} else if (strcasecmp(request->header_key, "Sec-WebSocket-Key") == 0 &&
|
||||
strlen(request->header_value) == 24) {
|
||||
strcpy(request->websocket_key, request->header_value);
|
||||
} else if (strcasecmp(request->header_key, "X-Destination") == 0) {
|
||||
strcpy(request->destination, request->header_value);
|
||||
}
|
||||
ESP_LOGI(TAG, "Header %s %s", request->header_key, request->header_value);
|
||||
} else if (request->offset > sizeof(request->header_value) - 1) {
|
||||
|
Loading…
Reference in New Issue
Block a user