mirror of
https://github.com/ALLFATHER-BV/wadamesh.git
synced 2026-09-27 15:48:07 +00:00
Merge pr453
This commit is contained in:
@@ -21,7 +21,7 @@ R"FILEPAGE(<!doctype html>
|
||||
:root{color-scheme:dark;--bg:#0b0d0e;--panel:#15191a;--line:#303738;--text:#edf2f1;--muted:#929b99;--accent:#15b6a6;--danger:#ef6a67}
|
||||
*{box-sizing:border-box}body{margin:0;min-height:100vh;background:linear-gradient(150deg,#0b0d0e,#111817);color:var(--text);font-family:ui-monospace,SFMono-Regular,Menlo,monospace}
|
||||
main{width:min(560px,100%);margin:0 auto;padding:24px 16px 40px}header{display:flex;align-items:center;gap:11px;margin-bottom:24px}header b{font-size:20px;letter-spacing:0}header i{display:block;width:9px;height:9px;border-radius:50%;background:var(--accent);box-shadow:0 0 12px var(--accent)}
|
||||
section{border:1px solid var(--line);background:var(--panel);border-radius:8px;padding:16px;margin:12px 0}label{display:block;color:var(--muted);font-size:12px;margin-bottom:7px}input,button{font:inherit;border-radius:6px;min-height:42px}input[type=password]{width:100%;padding:9px 11px;background:#0d1112;color:var(--text);border:1px solid var(--line);font-size:18px;letter-spacing:3px}button{border:1px solid var(--line);background:#202627;color:var(--text);padding:8px 14px;cursor:pointer}button.primary{background:var(--accent);border-color:var(--accent);color:#061412;font-weight:700}button.danger{color:var(--danger)}button:disabled{opacity:.4;cursor:not-allowed}.row{display:flex;gap:8px;margin-top:10px}.row>*{flex:1}.drop{display:block;border:1px dashed #53605e;border-radius:8px;padding:24px 12px;text-align:center;color:var(--muted);cursor:pointer}.drop.ready{border-color:var(--accent);color:var(--text)}#pick{position:absolute;opacity:0;pointer-events:none}#name{margin-top:10px;overflow-wrap:anywhere}progress{width:100%;height:10px;margin-top:14px;accent-color:var(--accent)}#status{min-height:20px;margin-top:10px;color:var(--muted);font-size:13px}#status.ok{color:var(--accent)}#status.err{color:var(--danger)}small{display:block;color:var(--muted);line-height:1.5;margin-top:12px}.file{display:flex;align-items:center;gap:10px;padding:9px 0;border-bottom:1px solid var(--line)}.file:last-child{border-bottom:0}.file span{flex:1;min-width:0;overflow-wrap:anywhere}.file small{margin:2px 0 0}.empty{color:var(--muted);padding:10px 0}
|
||||
section{border:1px solid var(--line);background:var(--panel);border-radius:8px;padding:16px;margin:12px 0}label{display:block;color:var(--muted);font-size:12px;margin-bottom:7px}input,button,select{font:inherit;border-radius:6px;min-height:42px}input[type=password]{width:100%;padding:9px 11px;background:#0d1112;color:var(--text);border:1px solid var(--line);font-size:18px;letter-spacing:3px}button,select{border:1px solid var(--line);background:#202627;color:var(--text);padding:8px 14px}button{cursor:pointer}button.primary{background:var(--accent);border-color:var(--accent);color:#061412;font-weight:700}button.danger{color:var(--danger)}button:disabled,select:disabled{opacity:.4;cursor:not-allowed}.row{display:flex;gap:8px;margin-top:10px}.row>*{flex:1}.file-tools{align-items:center}.file-tools b{flex:1.4}.file-tools select{min-width:0}.drop{display:block;border:1px dashed #53605e;border-radius:8px;padding:24px 12px;text-align:center;color:var(--muted);cursor:pointer}.drop.ready{border-color:var(--accent);color:var(--text)}#pick{position:absolute;opacity:0;pointer-events:none}#name{margin-top:10px;overflow-wrap:anywhere}progress{width:100%;height:10px;margin-top:14px;accent-color:var(--accent)}#status{min-height:20px;margin-top:10px;color:var(--muted);font-size:13px}#status.ok{color:var(--accent)}#status.err{color:var(--danger)}small{display:block;color:var(--muted);line-height:1.5;margin-top:12px}.file{display:flex;align-items:center;gap:10px;padding:9px 0;border-bottom:1px solid var(--line)}.file:last-child{border-bottom:0}.file span{flex:1;min-width:0;overflow-wrap:anywhere}.file small{margin:2px 0 0}.actions{display:flex;gap:6px}.actions button{padding:7px 10px}.empty{color:var(--muted);padding:10px 0}
|
||||
</style>
|
||||
</head>
|
||||
<body><main>
|
||||
@@ -41,8 +41,8 @@ section{border:1px solid var(--line);background:var(--panel);border-radius:8px;p
|
||||
<small>Files are uploaded to the device's SD card in <b>/transfer</b>. The transfer session ends when you leave the File Transfer app on the device.</small>
|
||||
</section>
|
||||
<section>
|
||||
<div class="row" style="margin-top:0"><b>Device files</b><button id="refresh" disabled>Refresh</button></div>
|
||||
<div id="files" class="empty">Connect to list screenshots and transferred files.</div>
|
||||
<div class="row file-tools" style="margin-top:0"><b>Device files</b><select id="filter" aria-label="File type" disabled><option value="all">ALL</option><option value="screenshots">Screenshots</option><option value="crashes">Crash reports</option><option value="backups">Backups</option><option value="transfers">Transfers</option></select><button id="refresh" disabled>Refresh</button></div>
|
||||
<div id="files" class="empty">Connect to list screenshots, crash reports, backups, and transferred files.</div>
|
||||
</section>
|
||||
</main>
|
||||
<script>
|
||||
@@ -52,22 +52,24 @@ let entries=[],downloadParts=[],downloadSize=0,downloadOffset=0,downloadName='';
|
||||
const table=new Uint32Array(256);for(let n=0;n<256;n++){let c=n;for(let k=0;k<8;k++)c=(c&1)?(0xedb88320^(c>>>1)):(c>>>1);table[n]=c>>>0}
|
||||
function crcAdd(c,a){for(let i=0;i<a.length;i++)c=table[(c^a[i])&255]^(c>>>8);return c>>>0}
|
||||
function status(text,kind=''){E('status').textContent=text;E('status').className=kind}
|
||||
function controls(){const ready=ws&&ws.readyState===1&&authed&&state==='ready';E('upload').disabled=!(ready&&file);E('refresh').disabled=!ready;E('cancel').disabled=state!=='uploading'&&state!=='downloading'}
|
||||
function controls(){const ready=ws&&ws.readyState===1&&authed&&state==='ready';E('upload').disabled=!(ready&&file);E('refresh').disabled=!ready;E('filter').disabled=!ready;E('cancel').disabled=state!=='uploading'&&state!=='downloading'}
|
||||
function resetUpload(){if(cancelTimer){clearTimeout(cancelTimer);cancelTimer=0}offset=0;crc=0xffffffff;pendingEnd=false;expectedAck=0;cancelText=cancelKind='';downloadParts=[];downloadSize=downloadOffset=0;E('progress').value=0;state=authed?'ready':'idle';controls()}
|
||||
function safeName(raw){return raw.replace(/[^A-Za-z0-9._-]/g,'_').replace(/^\.+/,'').slice(0,64)}
|
||||
function choose(f){if(!f)return;if(f.size<=0||f.size>MAX){status('File must be between 1 byte and 512 MB.','err');return}file=f;name=safeName(f.name)||'upload.bin';E('name').textContent=(name===f.name?name:(f.name+' → '+name))+' ('+f.size.toLocaleString()+' bytes)';E('drop').classList.add('ready');controls()}
|
||||
function connect(){const code=E('code').value.trim();if(!/^\d{6}$/.test(code)){status('Enter the six-digit code.','err');return}if(ws)ws.close();authed=false;state='connecting';controls();const socket=new WebSocket((location.protocol==='https:'?'wss://':'ws://')+location.host+'/files');ws=socket;socket.binaryType='arraybuffer';socket.onopen=()=>{if(ws===socket){status('Authenticating…');socket.send('AUTH '+code)}};socket.onerror=()=>{if(ws===socket)status('Connection error.','err')};socket.onclose=()=>{if(ws!==socket)return;ws=null;authed=false;resetUpload();status('Disconnected. Reopen File Transfer on the device if the session ended.','err')};socket.onmessage=e=>{if(ws===socket)onMessage(e)}}
|
||||
function onMessage(ev){const bytes=typeof ev.data==='string'?null:new Uint8Array(ev.data);if(bytes&&bytes[0]===1){if(state!=='cancelling')downloadChunk(bytes);return}const text=typeof ev.data==='string'?ev.data:dec.decode(bytes);if(state==='cancelling'&&text!=='CANCELLED')return;if(text==='AUTH OK'){authed=true;state='ready';status('Connected.','ok');controls();refreshList();return}if(text==='READY'){state='uploading';status('Uploading…');controls();sendNext();return}if(text.startsWith('ACK ')){const next=Number(text.slice(4));if(state!=='uploading'||!Number.isSafeInteger(next)||next!==expectedAck||next>file.size){fail('Invalid acknowledgement.');return}offset=next;expectedAck=0;E('progress').value=Math.floor(offset*100/file.size);sendNext();return}if(text.startsWith('DONE ')){const completed=Number(text.slice(5));if(state!=='uploading'||!pendingEnd||completed!==file.size||offset!==file.size){fail('Invalid completion response.');return}E('progress').value=100;state='ready';controls();status('Upload complete: '+name,'ok');refreshList();return}if(text.startsWith('ENTRY ')){const m=text.match(/^ENTRY (\d+) (.+)$/);if(!m){fail('Invalid file list entry.');return}entries.push({size:Number(m[1]),path:m[2]});ws.send('LIST NEXT');return}if(text==='LIST DONE'){state='ready';renderFiles();controls();status(entries.length?'File list updated.':'No screenshots or transferred files yet.','ok');return}if(text.startsWith('FILE ')){const m=text.match(/^FILE (\d+) (.+)$/);if(!m){fail('Invalid download metadata.');return}downloadSize=Number(m[1]);if(!Number.isSafeInteger(downloadSize)||downloadSize<=0||downloadSize>MAX_DOWNLOAD){fail('Downloads are limited to 64 MB in this browser tool.');return}downloadName=m[2];downloadOffset=0;downloadParts=[];state='downloading';E('progress').value=0;controls();status('Downloading '+downloadName+'…');ws.send('READ 0');return}if(text==='CANCELLED'){const message=cancelText||'Transfer cancelled.',kind=cancelKind;resetUpload();status(message,kind);return}if(text.startsWith('ERR ')){fail(text.slice(4));return}}
|
||||
function onMessage(ev){const bytes=typeof ev.data==='string'?null:new Uint8Array(ev.data);if(bytes&&bytes[0]===1){if(state!=='cancelling')downloadChunk(bytes);return}const text=typeof ev.data==='string'?ev.data:dec.decode(bytes);if(state==='cancelling'&&text!=='CANCELLED')return;if(text==='AUTH OK'){authed=true;state='ready';status('Connected.','ok');controls();refreshList();return}if(text==='READY'){state='uploading';status('Uploading…');controls();sendNext();return}if(text.startsWith('ACK ')){const next=Number(text.slice(4));if(state!=='uploading'||!Number.isSafeInteger(next)||next!==expectedAck||next>file.size){fail('Invalid acknowledgement.');return}offset=next;expectedAck=0;E('progress').value=Math.floor(offset*100/file.size);sendNext();return}if(text.startsWith('DONE ')){const completed=Number(text.slice(5));if(state!=='uploading'||!pendingEnd||completed!==file.size||offset!==file.size){fail('Invalid completion response.');return}E('progress').value=100;state='ready';controls();status('Upload complete: '+name,'ok');refreshList();return}if(text.startsWith('ENTRY ')){const m=text.match(/^ENTRY (\d+) (.+)$/);if(!m){fail('Invalid file list entry.');return}entries.push({size:Number(m[1]),path:m[2]});ws.send('LIST NEXT');return}if(text==='LIST DONE'){state='ready';renderFiles();controls();status(entries.length?'File list updated.':'No downloadable device files yet.','ok');return}if(text.startsWith('FILE ')){const m=text.match(/^FILE (\d+) (.+)$/);if(!m){fail('Invalid download metadata.');return}downloadSize=Number(m[1]);if(!Number.isSafeInteger(downloadSize)||downloadSize<=0||downloadSize>MAX_DOWNLOAD){fail('Downloads are limited to 64 MB in this browser tool.');return}downloadName=m[2];downloadOffset=0;downloadParts=[];state='downloading';E('progress').value=0;controls();status('Downloading '+downloadName+'…');ws.send('READ 0');return}if(text==='CANCELLED'){const message=cancelText||'Transfer cancelled.',kind=cancelKind;resetUpload();status(message,kind);return}if(text.startsWith('ERR ')){fail(text.slice(4));return}}
|
||||
async function sendNext(){if(state!=='uploading'||!file)return;if(offset>=file.size){if(!pendingEnd){pendingEnd=true;ws.send('END '+((crc^0xffffffff)>>>0).toString(16).padStart(8,'0'))}return}const start=offset;const bytes=new Uint8Array(await file.slice(start,Math.min(start+CHUNK,file.size)).arrayBuffer());if(state!=='uploading'||start!==offset)return;crc=crcAdd(crc,bytes);const frame=new Uint8Array(bytes.length+4);new DataView(frame.buffer).setUint32(0,start,true);frame.set(bytes,4);expectedAck=start+bytes.length;ws.send(frame)}
|
||||
function upload(){if(!file||state!=='ready')return;offset=0;crc=0xffffffff;pendingEnd=false;expectedAck=0;E('progress').value=0;state='starting';controls();status('Preparing '+name+'…');ws.send('BEGIN '+file.size+' '+name)}
|
||||
function requestCancel(text='Transfer cancelled.',kind=''){if(state==='cancelling')return;if(ws&&ws.readyState===1&&authed&&state!=='ready'&&state!=='idle'&&state!=='connecting'){cancelText=text;cancelKind=kind;state='cancelling';controls();status('Cancelling…');ws.send('CANCEL');cancelTimer=setTimeout(()=>{if(state!=='cancelling')return;const socket=ws;resetUpload();status(text,kind);if(socket&&socket.readyState<2)socket.close()},5000);return}resetUpload();status(text,kind)}
|
||||
function cancel(){requestCancel()}
|
||||
function fail(text){if(state==='starting'||state==='uploading'||state==='downloading'||state==='listing'){requestCancel(text,'err');return}resetUpload();status(text,'err')}
|
||||
function refreshList(){if(!authed||state!=='ready')return;entries=[];state='listing';controls();status('Reading file list…');ws.send('LIST')}
|
||||
function renderFiles(){const box=E('files');box.textContent='';box.className=entries.length?'':'empty';if(!entries.length){box.textContent='No files found.';return}entries.forEach(item=>{const row=document.createElement('div');row.className='file';const info=document.createElement('span');const title=document.createElement('b');title.textContent=item.path;const size=document.createElement('small');size.textContent=item.size.toLocaleString()+' bytes';info.append(title,size);const button=document.createElement('button');button.textContent='Download';button.onclick=()=>startDownload(item.path);row.append(info,button);box.append(row)})}
|
||||
function fileKind(path){if(path.startsWith('/screenshots/'))return'screenshots';if(path.startsWith('/transfer/'))return'transfers';if(path.startsWith('/wadamesh-crash.'))return'crashes';if(path.startsWith('/meshcore-')&&path.endsWith('.json'))return'backups';return'other'}
|
||||
function renderFiles(){const box=E('files'),filter=E('filter').value,shown=entries.filter(item=>filter==='all'||fileKind(item.path)===filter);box.textContent='';box.className=shown.length?'':'empty';if(!shown.length){box.textContent=entries.length?'No files match this filter.':'No files found.';return}shown.forEach(item=>{const row=document.createElement('div');row.className='file';const info=document.createElement('span');const title=document.createElement('b');title.textContent=item.path;const size=document.createElement('small');size.textContent=item.size.toLocaleString()+' bytes';info.append(title,size);const actions=document.createElement('div');actions.className='actions';const download=document.createElement('button');download.textContent='Download';download.onclick=()=>startDownload(item.path);const remove=document.createElement('button');remove.className='danger';remove.textContent='Delete';remove.onclick=()=>deleteDeviceFile(item.path);actions.append(download,remove);row.append(info,actions);box.append(row)})}
|
||||
function startDownload(path){if(!authed||state!=='ready')return;state='starting';controls();status('Opening '+path+'…');ws.send('GET '+path)}
|
||||
function deleteDeviceFile(path){if(!authed||state!=='ready'||!confirm('Delete '+path+' from the device?'))return;entries=[];state='listing';controls();status('Deleting '+path+'…');ws.send('DELETE '+path)}
|
||||
function downloadChunk(bytes){if(state!=='downloading'||bytes.length<6){fail('Unexpected download data.');return}const at=new DataView(bytes.buffer,bytes.byteOffset,bytes.byteLength).getUint32(1,true),part=bytes.slice(5);if(at!==downloadOffset||downloadOffset+part.length>downloadSize){fail('Invalid download offset.');return}downloadParts.push(part);downloadOffset+=part.length;E('progress').value=Math.floor(downloadOffset*100/downloadSize);if(downloadOffset<downloadSize){ws.send('READ '+downloadOffset);return}if(downloadOffset!==downloadSize){fail('Download size mismatch.');return}const href=URL.createObjectURL(new Blob(downloadParts)),a=document.createElement('a');a.href=href;a.download=downloadName;document.body.append(a);a.click();a.remove();setTimeout(()=>URL.revokeObjectURL(href),1000);state='ready';downloadParts=[];controls();status('Downloaded '+downloadName,'ok')}
|
||||
E('connect').onclick=connect;E('upload').onclick=upload;E('cancel').onclick=cancel;E('refresh').onclick=refreshList;E('pick').onchange=e=>choose(e.target.files[0]);
|
||||
E('connect').onclick=connect;E('upload').onclick=upload;E('cancel').onclick=cancel;E('refresh').onclick=refreshList;E('filter').onchange=renderFiles;E('pick').onchange=e=>choose(e.target.files[0]);
|
||||
const drop=E('drop');drop.ondragover=e=>{e.preventDefault();drop.classList.add('ready')};drop.ondragleave=()=>{if(!file)drop.classList.remove('ready')};drop.ondrop=e=>{e.preventDefault();choose(e.dataTransfer.files[0])};
|
||||
</script></body></html>)FILEPAGE";
|
||||
|
||||
|
||||
@@ -51,11 +51,22 @@ inline bool fileNameValid(const char* name) {
|
||||
return true;
|
||||
}
|
||||
|
||||
inline bool firmwareExportName(const char* name) {
|
||||
if (!fileNameValid(name)) return false;
|
||||
if (strcmp(name, "wadamesh-crash.elf") == 0 ||
|
||||
strcmp(name, "wadamesh-crash.elf.txt") == 0) return true;
|
||||
const size_t len = strlen(name);
|
||||
return len > 14 && strncmp(name, "meshcore-", 9) == 0 &&
|
||||
strcmp(name + len - 5, ".json") == 0;
|
||||
}
|
||||
|
||||
inline bool readablePath(const char* path) {
|
||||
if (!path) return false;
|
||||
const char* leaf = nullptr;
|
||||
if (strncmp(path, "/screenshots/", 13) == 0) leaf = path + 13;
|
||||
else if (strncmp(path, "/transfer/", 10) == 0) leaf = path + 10;
|
||||
else if (path[0] == '/' && strchr(path + 1, '/') == nullptr &&
|
||||
firmwareExportName(path + 1)) leaf = path + 1;
|
||||
else return false;
|
||||
return fileNameValid(leaf) && strchr(leaf, '/') == nullptr;
|
||||
}
|
||||
|
||||
+49
-6
@@ -25373,19 +25373,20 @@ static void fileTransferListNext() {
|
||||
fs::FS& storage = fileTransferStorage();
|
||||
while (s_file_transfer_listing) {
|
||||
if (!s_file_transfer_list_dir) {
|
||||
const char* dir_path = s_file_transfer_list_phase == 0 ? "/screenshots" : "/transfer";
|
||||
const char* dir_path = s_file_transfer_list_phase == 0 ? "/screenshots" :
|
||||
s_file_transfer_list_phase == 1 ? "/transfer" : "/";
|
||||
s_file_transfer_list_dir = storage.open(dir_path, FILE_READ);
|
||||
markSdIo();
|
||||
if (!s_file_transfer_list_dir || !s_file_transfer_list_dir.isDirectory()) {
|
||||
if (s_file_transfer_list_dir) s_file_transfer_list_dir.close();
|
||||
if (s_file_transfer_list_phase == 1) {
|
||||
if (s_file_transfer_list_phase == 2) {
|
||||
fileTransferStorageIoFailed();
|
||||
s_file_transfer_listing = false;
|
||||
fileTransferReply("ERR SD read failed");
|
||||
return;
|
||||
}
|
||||
s_file_transfer_list_phase++;
|
||||
if (s_file_transfer_list_phase >= 2) {
|
||||
if (s_file_transfer_list_phase >= 3) {
|
||||
s_file_transfer_listing = false;
|
||||
fileTransferReply("LIST DONE");
|
||||
}
|
||||
@@ -25397,7 +25398,7 @@ static void fileTransferListNext() {
|
||||
if (!entry) {
|
||||
s_file_transfer_list_dir.close();
|
||||
s_file_transfer_list_phase++;
|
||||
if (s_file_transfer_list_phase >= 2) {
|
||||
if (s_file_transfer_list_phase >= 3) {
|
||||
s_file_transfer_listing = false;
|
||||
fileTransferReply("LIST DONE");
|
||||
}
|
||||
@@ -25413,8 +25414,11 @@ static void fileTransferListNext() {
|
||||
if (name_fits) snprintf(safe_leaf, sizeof safe_leaf, "%s", leaf);
|
||||
entry.close();
|
||||
if (directory || !name_fits || !WebFileTransferProtocol::fileNameValid(safe_leaf)) continue;
|
||||
if (s_file_transfer_list_phase == 2 &&
|
||||
!WebFileTransferProtocol::firmwareExportName(safe_leaf)) continue;
|
||||
|
||||
const char* prefix = s_file_transfer_list_phase == 0 ? "/screenshots/" : "/transfer/";
|
||||
const char* prefix = s_file_transfer_list_phase == 0 ? "/screenshots/" :
|
||||
s_file_transfer_list_phase == 1 ? "/transfer/" : "/";
|
||||
char reply[WebFileTransfer::MAX_REPLY_BYTES];
|
||||
snprintf(reply, sizeof reply, "ENTRY %lu %s%s", static_cast<unsigned long>(size),
|
||||
prefix, safe_leaf);
|
||||
@@ -25480,6 +25484,44 @@ static void fileTransferDownloadBegin(const char* path) {
|
||||
fileTransferReply(reply);
|
||||
}
|
||||
|
||||
static void fileTransferDelete(const char* path) {
|
||||
if (s_file_transfer_uploading || s_file_transfer_downloading || s_file_transfer_listing) {
|
||||
fileTransferReply("ERR transfer in progress");
|
||||
return;
|
||||
}
|
||||
if (!WebFileTransferProtocol::readablePath(path)) {
|
||||
fileTransferReply("ERR invalid delete path");
|
||||
return;
|
||||
}
|
||||
if (!fileTransferStorageReady()) {
|
||||
fileTransferStorageIoFailed();
|
||||
fileTransferReply("ERR SD card unavailable");
|
||||
return;
|
||||
}
|
||||
|
||||
fs::FS& storage = fileTransferStorage();
|
||||
File target = storage.open(path, FILE_READ);
|
||||
markSdIo();
|
||||
if (!target || target.isDirectory()) {
|
||||
if (target) target.close();
|
||||
fileTransferReply("ERR file unavailable");
|
||||
return;
|
||||
}
|
||||
target.close();
|
||||
if (!storage.remove(path)) {
|
||||
fileTransferStorageIoFailed();
|
||||
fileTransferReply("ERR delete failed");
|
||||
return;
|
||||
}
|
||||
markSdIo();
|
||||
const char* leaf = strrchr(path, '/');
|
||||
leaf = leaf ? leaf + 1 : path;
|
||||
snprintf(s_file_transfer_result, sizeof s_file_transfer_result, "Deleted %s", leaf);
|
||||
fileTransferResetRead();
|
||||
s_file_transfer_listing = true;
|
||||
fileTransferListNext();
|
||||
}
|
||||
|
||||
static void fileTransferDownloadRead(const char* command) {
|
||||
unsigned long requested = 0;
|
||||
char extra = 0;
|
||||
@@ -25572,6 +25614,7 @@ static void webFileTransferTick() {
|
||||
else if (strcmp(command, "LIST") == 0) fileTransferListBegin();
|
||||
else if (strcmp(command, "LIST NEXT") == 0) fileTransferListNext();
|
||||
else if (strncmp(command, "GET ", 4) == 0) fileTransferDownloadBegin(command + 4);
|
||||
else if (strncmp(command, "DELETE ", 7) == 0) fileTransferDelete(command + 7);
|
||||
else if (strncmp(command, "READ ", 5) == 0) fileTransferDownloadRead(command);
|
||||
else if (strcmp(command, "CANCEL") == 0) {
|
||||
fileTransferResetUpload(true);
|
||||
@@ -25696,7 +25739,7 @@ static void openFileTransferPage() {
|
||||
|
||||
const lv_coord_t width = sw - 28;
|
||||
lv_obj_t* intro = lv_label_create(s_file_transfer_root);
|
||||
lv_label_set_text(intro, TR("Upload files or download screenshots from a browser on the same Wi-Fi."));
|
||||
lv_label_set_text(intro, TR("Upload, download, or delete files from a browser on the same Wi-Fi."));
|
||||
lv_obj_set_style_text_font(intro, &g_font_14, LV_PART_MAIN);
|
||||
lv_obj_set_style_text_color(intro, lv_color_hex(COLOR_TEXT), LV_PART_MAIN);
|
||||
lv_label_set_long_mode(intro, LV_LABEL_LONG_WRAP);
|
||||
|
||||
@@ -50,9 +50,18 @@ int main() {
|
||||
|
||||
assert(readablePath("/screenshots/capture.png"));
|
||||
assert(readablePath("/transfer/archive.bin"));
|
||||
assert(readablePath("/wadamesh-crash.elf"));
|
||||
assert(readablePath("/wadamesh-crash.elf.txt"));
|
||||
assert(readablePath("/meshcore-backup.json"));
|
||||
assert(readablePath("/meshcore-20260907-120000.json"));
|
||||
assert(readablePath("/meshcore-backup-123.json"));
|
||||
assert(!readablePath("/transfer/.upload.part"));
|
||||
assert(!readablePath("/transfer/../secret.txt"));
|
||||
assert(!readablePath("/transfer/nested/file.txt"));
|
||||
assert(!readablePath("/contacts3"));
|
||||
assert(!readablePath("/other.json"));
|
||||
assert(!readablePath("/meshcore-.json"));
|
||||
assert(!readablePath("/meshcore-backup.json/extra"));
|
||||
assert(!readablePath("/other/archive.bin"));
|
||||
return 0;
|
||||
}
|
||||
Reference in New Issue
Block a user