feat: Allow deleting files and directories
All checks were successful
Build RPi Pico firmware image / Build-Firmware (push) Successful in 4m40s
Check code formatting / Check-C-Format (push) Successful in 7s
Check code formatting / Check-Python-Flake8 (push) Successful in 10s
Check code formatting / Check-Bash-Shellcheck (push) Successful in 4s
Run unit tests on host / Run-Unit-Tests (push) Successful in 8s
Run pytests / Check-Pytest (push) Successful in 10s

Signed-off-by: Matthias Blankertz <matthias@blankertz.org>
This commit is contained in:
2025-12-21 15:12:31 +01:00
parent d96350c1a7
commit 7be038d0d1
3 changed files with 57 additions and 35 deletions

View File

@@ -235,51 +235,22 @@
<div class="scroll-container"> <div class="scroll-container">
<div class="tree" id="playlist-filebrowser-tree"> <div class="tree" id="playlist-filebrowser-tree">
<ul><li>Loading...</li></ul> <ul><li>Loading...</li></ul>
<!--<ul>
<li>
<span class="caret"></span>
<span class="node">Fruits</span>
<ul>
<li>
<span class="caret"></span>
<span class="node">Apple</span>
</li>
<li>
<span class="caret"></span>
<span class="node">Citrus</span>
<ul>
<li>
<span class="caret"></span>
<span class="node">Orange</span>
</li>
<li>
<span class="caret"></span>
<span class="node">Lemon</span>
</li>
</ul>
</li>
<li>
<span class="caret"></span>
<span class="node">Strawberry</span>
</li>
</ul>
</li>
</ul> -->
</div> </div>
</div> </div>
<div class="flex-horizontal"> <div class="flex-horizontal">
<button id="playlist-filebrowser-cancel">Cancel</button> <button id="playlist-filebrowser-cancel">Cancel</button>
<button id="playlist-filebrowser-delete" style="background-color: orangered">Delete selected</button>
<button id="playlist-filebrowser-addtrack">Add track(s)</button> <button id="playlist-filebrowser-addtrack">Add track(s)</button>
</div> </div>
<label>Upload files</label> <hr>
<div class="flex-horizontal"> <div class="flex-horizontal">
<input type="file" id="playlist-filebrowser-upload-files" multiple accept="audio/mpeg" /> <input type="file" id="playlist-filebrowser-upload-files" multiple accept="audio/mpeg" />
<progress id="playlist-filebrowser-upload-progress" max="100" value="0" style:"flex-grow: 1">0%</progress>
<button id="playlist-filebrowser-upload">Upload</button> <button id="playlist-filebrowser-upload">Upload</button>
<progress id="playlist-filebrowser-upload-progress" max="100" value="0">0%</progress>
</div> </div>
<div class="flex-horizontal"> <div class="flex-horizontal">
<input type="text" id="playlist-filebrowser-mkdir-name" /> <input type="text" id="playlist-filebrowser-mkdir-name" placeholder="Directory Name" />
<button id="playlist-filebrowser-mkdir">Create directory</button> <button id="playlist-filebrowser-mkdir" style="width: 20%">Create directory</button>
</div> </div>
</div> </div>
</div> </div>
@@ -752,6 +723,9 @@
document.getElementById('playlist-filebrowser-mkdir').addEventListener("click", (e) => { document.getElementById('playlist-filebrowser-mkdir').addEventListener("click", (e) => {
createDirectory(); createDirectory();
}); });
document.getElementById('playlist-filebrowser-delete').addEventListener("click", (e) => {
deleteItems();
});
tree.init(); tree.init();
} }
@@ -908,6 +882,27 @@
onShow(); onShow();
} }
async function deleteItems() {
const tree = document.getElementById("playlist-filebrowser-tree");
const selectedNodes = [...tree.querySelectorAll(".selected")];
if (selectedNodes.length === 0) {
alert("Please select something to delete");
return;
}
const items = selectedNodes.map(n => n.getAttribute('data-path'));
items.sort();
items.reverse();
for (const item of items) {
const saveRes = await fetch(`/api/v1/audiofiles?location=${item}`,
{method: 'DELETE'});
if (!saveRes.ok) {
alert(`Failed to delete item ${item}: ${await saveRes.text()}`);
}
}
// Reload file list from device
onShow();
}
let tree = (() => { let tree = (() => {
let tree = null; let tree = null;
function init() { function init() {

View File

@@ -161,7 +161,11 @@ class PlayerApp:
self._onIdle() self._onIdle()
if filename is not None: if filename is not None:
print(f'Playing {filename!r}') print(f'Playing {filename!r}')
self.mp3file = open(filename, 'rb') try:
self.mp3file = open(filename, 'rb')
except OSError as ex:
print(f"Could not play file {filename}: {ex}")
return
self.player.play(self.mp3file, offset) self.player.play(self.mp3file, offset)
self.paused = False self.paused = False
self._onActive() self._onActive()

View File

@@ -223,3 +223,26 @@ async def audiofile_upload(request):
return '', 204 return '', 204
else: else:
return 'size mismatch', 500 return 'size mismatch', 500
def recursive_delete(path):
stat = os.stat(path)
if stat[0] == 0x8000:
os.remove(path)
elif stat[0] == 0x4000:
for entry in os.ilistdir(path):
entry_path = path + '/' + entry[0]
recursive_delete(entry_path)
os.rmdir(path)
@webapp.route('/api/v1/audiofiles', methods=['DELETE'])
async def audiofile_delete(request):
if 'location' not in request.args:
return 'missing location', 400
location = request.args['location']
if '..' in location or len(location) == 0:
return 'bad location', 400
path = fsroot + '/' + request.args['location']
recursive_delete(path)
return '', 204