Merge remote-tracking branch 'adafruit/main' into upload_folder

This commit is contained in:
Scott Shawcroft 2022-08-17 13:15:37 -07:00
commit 3aa5f60cb9
No known key found for this signature in database
GPG Key ID: 0DFD512649C052DA
11 changed files with 207 additions and 67 deletions

View File

@ -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/`

View File

@ -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) {

View File

@ -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;
}
}

View File

@ -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);

View 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>

View File

@ -10,9 +10,9 @@
<body>
<h1><a href="/"><img src="/favicon.ico"/></a>&nbsp;<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>

View File

@ -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");

View File

@ -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>

View File

@ -17,3 +17,7 @@ body {
margin: 0;
font-size: 0.7em;
}
:disabled {
filter: saturate(0%);
}

View File

@ -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");

View File

@ -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) {