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