download and remove folders in browser

frdel committed Nov 28, 2024 at 23:05 UTC 857f8b6d82ec6707f45c67fa7e2a360e535071b0
4 files changed +47 -37
python/api/download_work_dir_file.py
+13 -10
@@ -6,22 +6,25 @@ from python.helpers import files
6 import os
7
8
9 -
10 -
9 class DownloadWorkDirFile(ApiHandler):
10 async def process(self, input: dict, request: Request) -> dict | Response:
13 - file_path = request.args.get('path', '')
11 + file_path = request.args.get("path", "")
12 if not file_path:
13 raise ValueError("No file path provided")
16 -
14 +
15 work_dir = files.get_abs_path("work_dir")
16 browser = FileBrowser(work_dir)
19 -
20 - full_path = browser.get_file_path(file_path)
21 - if full_path:
17 +
18 + full_path = browser.get_full_path(file_path, True)
19 + if os.path.isdir(full_path):
20 + zip_file = browser.zip_dir(full_path)
21 return send_file(
23 - full_path,
22 + zip_file,
23 as_attachment=True,
25 - download_name=os.path.basename(file_path)
24 + download_name=f"{os.path.basename(file_path)}.zip",
25 + )
26 + if full_path:
27 + return send_file(
28 + full_path, as_attachment=True, download_name=os.path.basename(file_path)
29 )
27 - raise Exception("File not found")
\ No newline at end of file
30 + raise Exception("File not found")
python/helpers/file_browser.py
+32 -23
@@ -1,9 +1,14 @@
1 import os
2 from pathlib import Path
3 +import shutil
4 +import tempfile
5 from typing import Dict, List, Tuple, Optional, Any
6 +import zipfile
7 from werkzeug.utils import secure_filename
8 from datetime import datetime
9
10 +from python.helpers import files
11 +
12 class FileBrowser:
13 ALLOWED_EXTENSIONS = {
14 'image': {'jpg', 'jpeg', 'png', 'bmp'},
@@ -69,10 +74,8 @@ class FileBrowser:
74 if os.path.exists(full_path):
75 if os.path.isfile(full_path):
76 os.remove(full_path)
72 - elif os.path.isdir(full_path) and not os.listdir(full_path):
73 - os.rmdir(full_path)
74 - else:
75 - raise ValueError("Can only delete files or empty directories")
77 + elif os.path.isdir(full_path):
78 + shutil.rmtree(full_path)
79 return True
80
81 return False
@@ -82,12 +85,14 @@ class FileBrowser:
85 return False
86
87 def _is_allowed_file(self, filename: str, file) -> bool:
85 - if not filename:
86 - return False
87 - ext = self._get_file_extension(filename)
88 - all_allowed = set().union(*self.ALLOWED_EXTENSIONS.values())
89 - if ext not in all_allowed:
90 - return False
88 + # allow any file to be uploaded in file browser
89 +
90 + # if not filename:
91 + # return False
92 + # ext = self._get_file_extension(filename)
93 + # all_allowed = set().union(*self.ALLOWED_EXTENSIONS.values())
94 + # if ext not in all_allowed:
95 + # return False
96
97 return True # Allow the file if it passes the checks
98
@@ -154,20 +159,12 @@ class FileBrowser:
159 print(f"Error reading directory: {e}")
160 return {"entries": [], "current_path": "", "parent_path": ""}
161
157 - def get_file_path(self, file_path: str) -> Optional[Path]:
162 + def get_full_path(self, file_path: str, allow_dir: bool = False) -> str:
163 """Get full file path if it exists and is within base_dir"""
159 - try:
160 - full_path = (self.base_dir / file_path).resolve()
161 - if not str(full_path).startswith(str(self.base_dir)):
162 - raise ValueError("Invalid path")
163 -
164 - if os.path.isfile(full_path):
165 - return full_path
166 - return None
167 -
168 - except Exception as e:
169 - print(f"Error accessing file {file_path}: {e}")
170 - return None
164 + full_path = files.get_abs_path(self.base_dir,file_path)
165 + if not files.exists(full_path):
166 + raise ValueError(f"File {file_path} not found")
167 + return full_path
168
169 def _get_file_type(self, filename: str) -> str:
170 ext = self._get_file_extension(filename)
@@ -175,4 +172,16 @@ class FileBrowser:
172 if ext in extensions:
173 return file_type
174 return 'unknown'
175 +
176 + def zip_dir(self, dir_path: str):
177 + full_path = self.get_full_path(dir_path, allow_dir=True)
178 + zip_file_path = tempfile.NamedTemporaryFile(suffix='.zip', delete=False).name
179 + base_name = os.path.basename(full_path)
180 + with zipfile.ZipFile(zip_file_path, "w", compression=zipfile.ZIP_DEFLATED) as zip:
181 + for root, _, files in os.walk(full_path):
182 + for file in files:
183 + file_path = os.path.join(root, file)
184 + rel_path = os.path.relpath(file_path, full_path)
185 + zip.write(file_path, os.path.join(base_name, rel_path))
186 + return zip_file_path
187
\ No newline at end of file
webui/index.html
+2 -3
@@ -581,7 +581,7 @@
581
582 <div class="file-actions">
583 <button class="action-button download-button"
584 - @click.stop="downloadFile(file)" x-bind:class="{ 'invisible': file.is_dir }">
584 + @click.stop="downloadFile(file)">
585 <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 19.5 19.5">
586 <path
587 d="m.75,14.25v2.25c0,1.24,1.01,2.25,2.25,2.25h13.5c1.24,0,2.25-1.01,2.25-2.25v-2.25m-4.5-4.5l-4.5,4.5m0,0l-4.5-4.5m4.5,4.5V.75"
@@ -589,8 +589,7 @@
589 stroke-linejoin="round" stroke-width="1.5"></path>
590 </svg>
591 </button>
592 - <button class="delete-button" @click.stop="deleteFile(file)"
593 - x-bind:class="{ 'invisible': file.is_dir && file.size !== 0 }">
592 + <button class="delete-button" @click.stop="deleteFile(file)">
593 <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 15.03 22.53"
594 fill="currentColor">
595 <path
webui/js/file_browser.js
-1
@@ -186,7 +186,6 @@ const fileBrowserModalProxy = {
186 },
187
188 async downloadFile(file) {
189 - if (file.is_dir) return;
189
190 try {
191