pyzbrowser 1.0.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- pyzbrowser/__init__.py +47 -0
- pyzbrowser/__main__.py +9 -0
- pyzbrowser/routes.py +636 -0
- pyzbrowser/templates/index.html +567 -0
- pyzbrowser/utils.py +120 -0
- pyzbrowser-1.0.0.dist-info/METADATA +313 -0
- pyzbrowser-1.0.0.dist-info/RECORD +10 -0
- pyzbrowser-1.0.0.dist-info/WHEEL +5 -0
- pyzbrowser-1.0.0.dist-info/entry_points.txt +2 -0
- pyzbrowser-1.0.0.dist-info/top_level.txt +1 -0
pyzbrowser/__init__.py
ADDED
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
from flask import Flask
|
|
2
|
+
import os
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
app = Flask(__name__)
|
|
6
|
+
|
|
7
|
+
from pyzbrowser.routes import (
|
|
8
|
+
browse,
|
|
9
|
+
create_folder,
|
|
10
|
+
upload_files,
|
|
11
|
+
delete_item,
|
|
12
|
+
view_file,
|
|
13
|
+
download_file,
|
|
14
|
+
api_search,
|
|
15
|
+
download_selected,
|
|
16
|
+
download_zip,
|
|
17
|
+
ROOT_DIRS,
|
|
18
|
+
)
|
|
19
|
+
|
|
20
|
+
__all__ = [
|
|
21
|
+
"browse",
|
|
22
|
+
"create_folder",
|
|
23
|
+
"upload_files",
|
|
24
|
+
"delete_item",
|
|
25
|
+
"view_file",
|
|
26
|
+
"download_file",
|
|
27
|
+
"api_search",
|
|
28
|
+
"download_selected",
|
|
29
|
+
"download_zip",
|
|
30
|
+
]
|
|
31
|
+
|
|
32
|
+
def main():
|
|
33
|
+
# Verifica se pelo menos um diretório existe antes de iniciar
|
|
34
|
+
valid_dirs = []
|
|
35
|
+
for name, path in ROOT_DIRS:
|
|
36
|
+
if os.path.exists(path):
|
|
37
|
+
valid_dirs.append(name)
|
|
38
|
+
else:
|
|
39
|
+
print(f"Aviso: O diretório '{name}' ({path}) não foi encontrado.")
|
|
40
|
+
|
|
41
|
+
if not valid_dirs:
|
|
42
|
+
print("Erro: Nenhum diretório raiz válido foi encontrado.")
|
|
43
|
+
else:
|
|
44
|
+
print(
|
|
45
|
+
f"PyBrowser iniciado com {len(valid_dirs)} pasta(s) raiz: {', '.join(valid_dirs)}"
|
|
46
|
+
)
|
|
47
|
+
app.run(host="0.0.0.0", debug=True, port=5000)
|
pyzbrowser/__main__.py
ADDED
pyzbrowser/routes.py
ADDED
|
@@ -0,0 +1,636 @@
|
|
|
1
|
+
import os
|
|
2
|
+
import socket
|
|
3
|
+
from datetime import datetime
|
|
4
|
+
|
|
5
|
+
import zipstream
|
|
6
|
+
from flask import (
|
|
7
|
+
Response,
|
|
8
|
+
abort,
|
|
9
|
+
redirect,
|
|
10
|
+
render_template,
|
|
11
|
+
request,
|
|
12
|
+
send_from_directory,
|
|
13
|
+
url_for,
|
|
14
|
+
)
|
|
15
|
+
from werkzeug.utils import secure_filename
|
|
16
|
+
|
|
17
|
+
from pyzbrowser import app
|
|
18
|
+
from pyzbrowser.utils import (
|
|
19
|
+
get_file_icon,
|
|
20
|
+
get_root_dir,
|
|
21
|
+
get_root_name_from_path,
|
|
22
|
+
load_root_dirs,
|
|
23
|
+
remove_root_from_path,
|
|
24
|
+
)
|
|
25
|
+
|
|
26
|
+
ROOT_DIRS = load_root_dirs()
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
@app.route("/")
|
|
30
|
+
@app.route("/browse/<path:subpath>")
|
|
31
|
+
def browse(subpath=""):
|
|
32
|
+
hostname = socket.gethostname()
|
|
33
|
+
|
|
34
|
+
# Se não há subpath, mostrar as pastas raiz disponíveis
|
|
35
|
+
if not subpath:
|
|
36
|
+
items = []
|
|
37
|
+
for root_name, root_path in ROOT_DIRS:
|
|
38
|
+
if os.path.exists(root_path):
|
|
39
|
+
try:
|
|
40
|
+
created_time = os.path.getctime(root_path)
|
|
41
|
+
created_date = datetime.fromtimestamp(created_time).strftime(
|
|
42
|
+
"%d/%m/%Y %H:%M"
|
|
43
|
+
)
|
|
44
|
+
except Exception:
|
|
45
|
+
created_time = 0
|
|
46
|
+
created_date = "Desconhecida"
|
|
47
|
+
|
|
48
|
+
items.append(
|
|
49
|
+
{
|
|
50
|
+
"name": root_name,
|
|
51
|
+
"path": root_name,
|
|
52
|
+
"is_dir": True,
|
|
53
|
+
"icon": "📁",
|
|
54
|
+
"created": created_time,
|
|
55
|
+
"created_date": created_date,
|
|
56
|
+
"ext": "",
|
|
57
|
+
"is_viewable": False,
|
|
58
|
+
}
|
|
59
|
+
)
|
|
60
|
+
|
|
61
|
+
return render_template(
|
|
62
|
+
"index.html",
|
|
63
|
+
items=items,
|
|
64
|
+
current_path="",
|
|
65
|
+
parent_path="",
|
|
66
|
+
hostname=hostname,
|
|
67
|
+
search_query="",
|
|
68
|
+
sort_by="name",
|
|
69
|
+
)
|
|
70
|
+
|
|
71
|
+
# Extrair o nome da raiz do path
|
|
72
|
+
root_name = get_root_name_from_path(subpath)
|
|
73
|
+
if not root_name:
|
|
74
|
+
abort(404)
|
|
75
|
+
|
|
76
|
+
ROOT_DIR = get_root_dir(root_name, ROOT_DIRS)
|
|
77
|
+
if not ROOT_DIR:
|
|
78
|
+
abort(404)
|
|
79
|
+
|
|
80
|
+
# Remover o nome da raiz do subpath para obter o caminho relativo
|
|
81
|
+
relative_path = remove_root_from_path(subpath)
|
|
82
|
+
|
|
83
|
+
# Segurança: Evita que o usuário suba níveis com ".."
|
|
84
|
+
full_path = os.path.normpath(os.path.join(ROOT_DIR, relative_path))
|
|
85
|
+
if not full_path.startswith(os.path.abspath(ROOT_DIR)):
|
|
86
|
+
abort(403)
|
|
87
|
+
|
|
88
|
+
if not os.path.exists(full_path):
|
|
89
|
+
abort(404)
|
|
90
|
+
|
|
91
|
+
# Se for arquivo, faz o download direto
|
|
92
|
+
if os.path.isfile(full_path):
|
|
93
|
+
return send_from_directory(
|
|
94
|
+
os.path.dirname(full_path), os.path.basename(full_path)
|
|
95
|
+
)
|
|
96
|
+
|
|
97
|
+
# Obter parâmetros de busca e ordenação
|
|
98
|
+
search_query = request.args.get("search", "").lower().strip()
|
|
99
|
+
sort_by = request.args.get("sort", "name") # name, date, type
|
|
100
|
+
|
|
101
|
+
# Listagem de diretório
|
|
102
|
+
items = []
|
|
103
|
+
try:
|
|
104
|
+
for name in os.listdir(full_path):
|
|
105
|
+
# Filtrar por busca
|
|
106
|
+
if search_query and search_query not in name.lower():
|
|
107
|
+
continue
|
|
108
|
+
|
|
109
|
+
item_full_path = os.path.join(full_path, name)
|
|
110
|
+
# Incluir o nome da raiz no path
|
|
111
|
+
if relative_path:
|
|
112
|
+
rel_path = f"{root_name}/{relative_path}/{name}".replace("\\", "/")
|
|
113
|
+
else:
|
|
114
|
+
rel_path = f"{root_name}/{name}".replace("\\", "/")
|
|
115
|
+
is_dir = os.path.isdir(item_full_path)
|
|
116
|
+
|
|
117
|
+
# Obter data de criação
|
|
118
|
+
try:
|
|
119
|
+
created_time = os.path.getctime(item_full_path)
|
|
120
|
+
created_date = datetime.fromtimestamp(created_time).strftime(
|
|
121
|
+
"%d/%m/%Y %H:%M"
|
|
122
|
+
)
|
|
123
|
+
except Exception:
|
|
124
|
+
created_time = 0
|
|
125
|
+
created_date = "Desconhecida"
|
|
126
|
+
|
|
127
|
+
ext = os.path.splitext(name)[1].lower() if not is_dir else ""
|
|
128
|
+
|
|
129
|
+
# Verificar se é um arquivo visualizável
|
|
130
|
+
viewable_extensions = [
|
|
131
|
+
# Imagens
|
|
132
|
+
".jpg",
|
|
133
|
+
".jpeg",
|
|
134
|
+
".png",
|
|
135
|
+
".gif",
|
|
136
|
+
".bmp",
|
|
137
|
+
".svg",
|
|
138
|
+
".webp",
|
|
139
|
+
".ico",
|
|
140
|
+
# Vídeos
|
|
141
|
+
".mp4",
|
|
142
|
+
".webm",
|
|
143
|
+
".ogg",
|
|
144
|
+
# Áudio
|
|
145
|
+
".mp3",
|
|
146
|
+
".wav",
|
|
147
|
+
".ogg",
|
|
148
|
+
".m4a",
|
|
149
|
+
# Documentos
|
|
150
|
+
".pdf",
|
|
151
|
+
# Texto/Código
|
|
152
|
+
".txt",
|
|
153
|
+
".md",
|
|
154
|
+
".log",
|
|
155
|
+
".py",
|
|
156
|
+
".js",
|
|
157
|
+
".jsx",
|
|
158
|
+
".ts",
|
|
159
|
+
".tsx",
|
|
160
|
+
".ini",
|
|
161
|
+
".html",
|
|
162
|
+
".htm",
|
|
163
|
+
".css",
|
|
164
|
+
".scss",
|
|
165
|
+
".sass",
|
|
166
|
+
".json",
|
|
167
|
+
".xml",
|
|
168
|
+
".yaml",
|
|
169
|
+
".yml",
|
|
170
|
+
".c",
|
|
171
|
+
".cpp",
|
|
172
|
+
".h",
|
|
173
|
+
".hpp",
|
|
174
|
+
".java",
|
|
175
|
+
".php",
|
|
176
|
+
".rb",
|
|
177
|
+
".go",
|
|
178
|
+
".rs",
|
|
179
|
+
".sh",
|
|
180
|
+
".bat",
|
|
181
|
+
]
|
|
182
|
+
is_viewable = ext in viewable_extensions
|
|
183
|
+
|
|
184
|
+
items.append(
|
|
185
|
+
{
|
|
186
|
+
"name": name,
|
|
187
|
+
"path": rel_path,
|
|
188
|
+
"is_dir": is_dir,
|
|
189
|
+
"icon": get_file_icon(name, is_dir),
|
|
190
|
+
"created": created_time,
|
|
191
|
+
"created_date": created_date,
|
|
192
|
+
"ext": ext,
|
|
193
|
+
"is_viewable": is_viewable,
|
|
194
|
+
}
|
|
195
|
+
)
|
|
196
|
+
except PermissionError:
|
|
197
|
+
abort(403)
|
|
198
|
+
|
|
199
|
+
# Ordenar itens
|
|
200
|
+
if sort_by == "name":
|
|
201
|
+
items.sort(key=lambda x: (not x["is_dir"], x["name"].lower()))
|
|
202
|
+
elif sort_by == "date":
|
|
203
|
+
items.sort(key=lambda x: (not x["is_dir"], -x["created"]))
|
|
204
|
+
elif sort_by == "type":
|
|
205
|
+
items.sort(key=lambda x: (not x["is_dir"], x["ext"], x["name"].lower()))
|
|
206
|
+
|
|
207
|
+
# Calcular o caminho pai para o botão "Voltar"
|
|
208
|
+
parent_path = ""
|
|
209
|
+
if subpath:
|
|
210
|
+
parts = subpath.split("/")
|
|
211
|
+
if len(parts) > 1:
|
|
212
|
+
parent_path = "/".join(parts[:-1])
|
|
213
|
+
# Se só tem o nome da raiz, parent_path fica vazio (volta para home)
|
|
214
|
+
|
|
215
|
+
return render_template(
|
|
216
|
+
"index.html",
|
|
217
|
+
items=items,
|
|
218
|
+
current_path=subpath,
|
|
219
|
+
parent_path=parent_path,
|
|
220
|
+
hostname=hostname,
|
|
221
|
+
search_query=search_query,
|
|
222
|
+
sort_by=sort_by,
|
|
223
|
+
)
|
|
224
|
+
|
|
225
|
+
|
|
226
|
+
@app.route("/create_folder", methods=["POST"])
|
|
227
|
+
def create_folder():
|
|
228
|
+
current_path = request.form.get("path", "")
|
|
229
|
+
folder_name = request.form.get("folder_name", "").strip()
|
|
230
|
+
|
|
231
|
+
if not folder_name or not current_path:
|
|
232
|
+
abort(400)
|
|
233
|
+
|
|
234
|
+
# Segurança: limpar o nome da pasta
|
|
235
|
+
folder_name = secure_filename(folder_name)
|
|
236
|
+
|
|
237
|
+
# Extrair o nome da raiz
|
|
238
|
+
root_name = get_root_name_from_path(current_path)
|
|
239
|
+
if not root_name:
|
|
240
|
+
abort(404)
|
|
241
|
+
|
|
242
|
+
ROOT_DIR = get_root_dir(root_name, ROOT_DIRS)
|
|
243
|
+
if not ROOT_DIR:
|
|
244
|
+
abort(404)
|
|
245
|
+
|
|
246
|
+
relative_path = remove_root_from_path(current_path)
|
|
247
|
+
full_path = os.path.normpath(os.path.join(ROOT_DIR, relative_path))
|
|
248
|
+
if not full_path.startswith(os.path.abspath(ROOT_DIR)):
|
|
249
|
+
abort(403)
|
|
250
|
+
|
|
251
|
+
new_folder_path = os.path.join(full_path, folder_name)
|
|
252
|
+
|
|
253
|
+
try:
|
|
254
|
+
os.makedirs(new_folder_path, exist_ok=True)
|
|
255
|
+
except Exception:
|
|
256
|
+
abort(500)
|
|
257
|
+
|
|
258
|
+
# Redirecionar de volta para o diretório atual
|
|
259
|
+
if current_path:
|
|
260
|
+
return redirect(url_for("browse", subpath=current_path))
|
|
261
|
+
else:
|
|
262
|
+
return redirect(url_for("browse"))
|
|
263
|
+
|
|
264
|
+
|
|
265
|
+
@app.route("/upload", methods=["POST"])
|
|
266
|
+
def upload_files():
|
|
267
|
+
current_path = request.form.get("path", "")
|
|
268
|
+
|
|
269
|
+
if not current_path:
|
|
270
|
+
abort(400)
|
|
271
|
+
|
|
272
|
+
# Extrair o nome da raiz
|
|
273
|
+
root_name = get_root_name_from_path(current_path)
|
|
274
|
+
if not root_name:
|
|
275
|
+
abort(404)
|
|
276
|
+
|
|
277
|
+
ROOT_DIR = get_root_dir(root_name, ROOT_DIRS)
|
|
278
|
+
if not ROOT_DIR:
|
|
279
|
+
abort(404)
|
|
280
|
+
|
|
281
|
+
relative_path = remove_root_from_path(current_path)
|
|
282
|
+
|
|
283
|
+
# Validar diretório de destino
|
|
284
|
+
full_path = os.path.normpath(os.path.join(ROOT_DIR, relative_path))
|
|
285
|
+
if not full_path.startswith(os.path.abspath(ROOT_DIR)):
|
|
286
|
+
abort(403)
|
|
287
|
+
|
|
288
|
+
if not os.path.exists(full_path) or not os.path.isdir(full_path):
|
|
289
|
+
abort(404)
|
|
290
|
+
|
|
291
|
+
# Verificar se há arquivos no request
|
|
292
|
+
if "files" not in request.files:
|
|
293
|
+
abort(400)
|
|
294
|
+
|
|
295
|
+
files = request.files.getlist("files")
|
|
296
|
+
|
|
297
|
+
if not files:
|
|
298
|
+
abort(400)
|
|
299
|
+
|
|
300
|
+
# Salvar cada arquivo
|
|
301
|
+
for file in files:
|
|
302
|
+
if file and file.filename:
|
|
303
|
+
# Para uploads de pastas, preservar a estrutura de diretórios
|
|
304
|
+
filename = file.filename
|
|
305
|
+
|
|
306
|
+
# Normalizar o caminho (trocar barras e remover ../.. )
|
|
307
|
+
filename = filename.replace("\\", "/")
|
|
308
|
+
|
|
309
|
+
# Separar diretório e nome do arquivo
|
|
310
|
+
if "/" in filename:
|
|
311
|
+
# É um arquivo dentro de uma estrutura de pastas
|
|
312
|
+
parts = filename.split("/")
|
|
313
|
+
# Criar diretórios necessários
|
|
314
|
+
dir_path = os.path.join(full_path, *parts[:-1])
|
|
315
|
+
try:
|
|
316
|
+
os.makedirs(dir_path, exist_ok=True)
|
|
317
|
+
except Exception:
|
|
318
|
+
continue
|
|
319
|
+
|
|
320
|
+
# Nome do arquivo final
|
|
321
|
+
final_filename = secure_filename(parts[-1])
|
|
322
|
+
file_path = os.path.join(dir_path, final_filename)
|
|
323
|
+
else:
|
|
324
|
+
# Arquivo único
|
|
325
|
+
final_filename = secure_filename(filename)
|
|
326
|
+
file_path = os.path.join(full_path, final_filename)
|
|
327
|
+
|
|
328
|
+
try:
|
|
329
|
+
file.save(file_path)
|
|
330
|
+
except Exception:
|
|
331
|
+
# Continuar com os próximos arquivos mesmo se um falhar
|
|
332
|
+
continue
|
|
333
|
+
|
|
334
|
+
# Redirecionar de volta para o diretório atual
|
|
335
|
+
if current_path:
|
|
336
|
+
return redirect(url_for("browse", subpath=current_path))
|
|
337
|
+
else:
|
|
338
|
+
return redirect(url_for("browse"))
|
|
339
|
+
|
|
340
|
+
|
|
341
|
+
@app.route("/delete/<path:subpath>", methods=["POST"])
|
|
342
|
+
def delete_item(subpath):
|
|
343
|
+
import shutil
|
|
344
|
+
|
|
345
|
+
# Extrair o nome da raiz
|
|
346
|
+
root_name = get_root_name_from_path(subpath)
|
|
347
|
+
if not root_name:
|
|
348
|
+
abort(404)
|
|
349
|
+
|
|
350
|
+
ROOT_DIR = get_root_dir(root_name, ROOT_DIRS)
|
|
351
|
+
if not ROOT_DIR:
|
|
352
|
+
abort(404)
|
|
353
|
+
|
|
354
|
+
relative_path = remove_root_from_path(subpath)
|
|
355
|
+
full_path = os.path.normpath(os.path.join(ROOT_DIR, relative_path))
|
|
356
|
+
if not full_path.startswith(os.path.abspath(ROOT_DIR)):
|
|
357
|
+
abort(403)
|
|
358
|
+
|
|
359
|
+
if not os.path.exists(full_path):
|
|
360
|
+
abort(404)
|
|
361
|
+
|
|
362
|
+
try:
|
|
363
|
+
if os.path.isdir(full_path):
|
|
364
|
+
shutil.rmtree(full_path)
|
|
365
|
+
else:
|
|
366
|
+
os.remove(full_path)
|
|
367
|
+
except Exception:
|
|
368
|
+
abort(500)
|
|
369
|
+
|
|
370
|
+
# Redirecionar de volta para o diretório pai
|
|
371
|
+
parent_path = "/".join(subpath.split("/")[:-1])
|
|
372
|
+
if parent_path:
|
|
373
|
+
return redirect(url_for("browse", subpath=parent_path))
|
|
374
|
+
else:
|
|
375
|
+
return redirect(url_for("browse"))
|
|
376
|
+
|
|
377
|
+
|
|
378
|
+
@app.route("/view/<path:subpath>")
|
|
379
|
+
def view_file(subpath):
|
|
380
|
+
# Extrair o nome da raiz
|
|
381
|
+
root_name = get_root_name_from_path(subpath)
|
|
382
|
+
if not root_name:
|
|
383
|
+
abort(404)
|
|
384
|
+
|
|
385
|
+
ROOT_DIR = get_root_dir(root_name, ROOT_DIRS)
|
|
386
|
+
if not ROOT_DIR:
|
|
387
|
+
abort(404)
|
|
388
|
+
|
|
389
|
+
relative_path = remove_root_from_path(subpath)
|
|
390
|
+
full_path = os.path.normpath(os.path.join(ROOT_DIR, relative_path))
|
|
391
|
+
if not full_path.startswith(os.path.abspath(ROOT_DIR)):
|
|
392
|
+
abort(403)
|
|
393
|
+
|
|
394
|
+
if not os.path.exists(full_path) or os.path.isdir(full_path):
|
|
395
|
+
abort(404)
|
|
396
|
+
|
|
397
|
+
# Servir arquivo sem forçar download (inline)
|
|
398
|
+
return send_from_directory(os.path.dirname(full_path), os.path.basename(full_path))
|
|
399
|
+
|
|
400
|
+
|
|
401
|
+
@app.route("/download/<path:subpath>")
|
|
402
|
+
def download_file(subpath):
|
|
403
|
+
# Extrair o nome da raiz
|
|
404
|
+
root_name = get_root_name_from_path(subpath)
|
|
405
|
+
if not root_name:
|
|
406
|
+
abort(404)
|
|
407
|
+
|
|
408
|
+
ROOT_DIR = get_root_dir(root_name, ROOT_DIRS)
|
|
409
|
+
if not ROOT_DIR:
|
|
410
|
+
abort(404)
|
|
411
|
+
|
|
412
|
+
relative_path = remove_root_from_path(subpath)
|
|
413
|
+
full_path = os.path.normpath(os.path.join(ROOT_DIR, relative_path))
|
|
414
|
+
if not full_path.startswith(os.path.abspath(ROOT_DIR)):
|
|
415
|
+
abort(403)
|
|
416
|
+
|
|
417
|
+
if not os.path.exists(full_path):
|
|
418
|
+
abort(404)
|
|
419
|
+
|
|
420
|
+
# Se for pasta, redirecionar para zipar
|
|
421
|
+
if os.path.isdir(full_path):
|
|
422
|
+
return redirect(url_for("download_zip", subpath=subpath))
|
|
423
|
+
|
|
424
|
+
# Forçar download do arquivo
|
|
425
|
+
return send_from_directory(
|
|
426
|
+
os.path.dirname(full_path), os.path.basename(full_path), as_attachment=True
|
|
427
|
+
)
|
|
428
|
+
|
|
429
|
+
|
|
430
|
+
@app.route("/api/search")
|
|
431
|
+
def api_search():
|
|
432
|
+
from flask import jsonify
|
|
433
|
+
|
|
434
|
+
query = request.args.get("q", "").lower().strip()
|
|
435
|
+
|
|
436
|
+
if not query:
|
|
437
|
+
return jsonify({"results": []})
|
|
438
|
+
|
|
439
|
+
results = []
|
|
440
|
+
max_results = 100 # Limitar resultados
|
|
441
|
+
|
|
442
|
+
try:
|
|
443
|
+
# Buscar em todas as pastas raiz
|
|
444
|
+
for root_name, ROOT_DIR in ROOT_DIRS:
|
|
445
|
+
if not os.path.exists(ROOT_DIR):
|
|
446
|
+
continue
|
|
447
|
+
|
|
448
|
+
for root, dirs, files in os.walk(ROOT_DIR):
|
|
449
|
+
# Verificar se ainda está dentro do ROOT_DIR
|
|
450
|
+
if not os.path.abspath(root).startswith(os.path.abspath(ROOT_DIR)):
|
|
451
|
+
continue
|
|
452
|
+
|
|
453
|
+
# Buscar em pastas
|
|
454
|
+
for dir_name in dirs:
|
|
455
|
+
if len(results) >= max_results:
|
|
456
|
+
break
|
|
457
|
+
if query in dir_name.lower():
|
|
458
|
+
full_path = os.path.join(root, dir_name)
|
|
459
|
+
rel_path = os.path.relpath(full_path, ROOT_DIR).replace(
|
|
460
|
+
"\\", "/"
|
|
461
|
+
)
|
|
462
|
+
parent_path = os.path.relpath(root, ROOT_DIR).replace("\\", "/")
|
|
463
|
+
if parent_path == ".":
|
|
464
|
+
parent_path = ""
|
|
465
|
+
|
|
466
|
+
# Adicionar o nome da raiz ao path
|
|
467
|
+
if parent_path:
|
|
468
|
+
full_rel_path = f"{root_name}/{rel_path}"
|
|
469
|
+
full_parent_path = f"{root_name}/{parent_path}"
|
|
470
|
+
else:
|
|
471
|
+
full_rel_path = f"{root_name}/{rel_path}"
|
|
472
|
+
full_parent_path = root_name
|
|
473
|
+
|
|
474
|
+
results.append(
|
|
475
|
+
{
|
|
476
|
+
"name": dir_name,
|
|
477
|
+
"path": full_rel_path,
|
|
478
|
+
"parent_path": full_parent_path,
|
|
479
|
+
"icon": get_file_icon(dir_name, True),
|
|
480
|
+
"is_dir": True,
|
|
481
|
+
}
|
|
482
|
+
)
|
|
483
|
+
|
|
484
|
+
# Buscar em arquivos
|
|
485
|
+
for file_name in files:
|
|
486
|
+
if len(results) >= max_results:
|
|
487
|
+
break
|
|
488
|
+
if query in file_name.lower():
|
|
489
|
+
full_path = os.path.join(root, file_name)
|
|
490
|
+
rel_path = os.path.relpath(full_path, ROOT_DIR).replace(
|
|
491
|
+
"\\", "/"
|
|
492
|
+
)
|
|
493
|
+
parent_path = os.path.relpath(root, ROOT_DIR).replace("\\", "/")
|
|
494
|
+
if parent_path == ".":
|
|
495
|
+
parent_path = ""
|
|
496
|
+
|
|
497
|
+
# Adicionar o nome da raiz ao path
|
|
498
|
+
if parent_path:
|
|
499
|
+
full_rel_path = f"{root_name}/{rel_path}"
|
|
500
|
+
full_parent_path = f"{root_name}/{parent_path}"
|
|
501
|
+
else:
|
|
502
|
+
full_rel_path = f"{root_name}/{rel_path}"
|
|
503
|
+
full_parent_path = root_name
|
|
504
|
+
|
|
505
|
+
results.append(
|
|
506
|
+
{
|
|
507
|
+
"name": file_name,
|
|
508
|
+
"path": full_rel_path,
|
|
509
|
+
"parent_path": full_parent_path,
|
|
510
|
+
"icon": get_file_icon(file_name, False),
|
|
511
|
+
"is_dir": False,
|
|
512
|
+
}
|
|
513
|
+
)
|
|
514
|
+
|
|
515
|
+
if len(results) >= max_results:
|
|
516
|
+
break
|
|
517
|
+
|
|
518
|
+
if len(results) >= max_results:
|
|
519
|
+
break
|
|
520
|
+
|
|
521
|
+
except Exception as e:
|
|
522
|
+
return jsonify({"results": [], "error": str(e)})
|
|
523
|
+
|
|
524
|
+
return jsonify({"results": results})
|
|
525
|
+
|
|
526
|
+
|
|
527
|
+
@app.route("/download_selected", methods=["POST"])
|
|
528
|
+
def download_selected():
|
|
529
|
+
|
|
530
|
+
selected_items = request.form.getlist("items[]")
|
|
531
|
+
|
|
532
|
+
if not selected_items:
|
|
533
|
+
abort(400)
|
|
534
|
+
|
|
535
|
+
# Se apenas 1 item e é arquivo, baixar direto
|
|
536
|
+
if len(selected_items) == 1:
|
|
537
|
+
subpath = selected_items[0]
|
|
538
|
+
|
|
539
|
+
# Extrair o nome da raiz
|
|
540
|
+
root_name = get_root_name_from_path(subpath)
|
|
541
|
+
if not root_name:
|
|
542
|
+
abort(404)
|
|
543
|
+
|
|
544
|
+
ROOT_DIR = get_root_dir(root_name, ROOT_DIRS)
|
|
545
|
+
if not ROOT_DIR:
|
|
546
|
+
abort(404)
|
|
547
|
+
|
|
548
|
+
relative_path = remove_root_from_path(subpath)
|
|
549
|
+
full_path = os.path.normpath(os.path.join(ROOT_DIR, relative_path))
|
|
550
|
+
if not full_path.startswith(os.path.abspath(ROOT_DIR)):
|
|
551
|
+
abort(403)
|
|
552
|
+
|
|
553
|
+
if os.path.isfile(full_path):
|
|
554
|
+
return send_from_directory(
|
|
555
|
+
os.path.dirname(full_path),
|
|
556
|
+
os.path.basename(full_path),
|
|
557
|
+
as_attachment=True,
|
|
558
|
+
)
|
|
559
|
+
|
|
560
|
+
# Múltiplos items ou pasta: zipar
|
|
561
|
+
z = zipstream.ZipFile(mode="w", compression=zipstream.ZIP_DEFLATED)
|
|
562
|
+
|
|
563
|
+
# Determinar nome do arquivo ZIP
|
|
564
|
+
if len(selected_items) == 1:
|
|
565
|
+
zip_filename = os.path.basename(selected_items[0]) + ".zip"
|
|
566
|
+
else:
|
|
567
|
+
zip_filename = "download.zip"
|
|
568
|
+
|
|
569
|
+
for subpath in selected_items:
|
|
570
|
+
# Extrair o nome da raiz
|
|
571
|
+
root_name = get_root_name_from_path(subpath)
|
|
572
|
+
if not root_name:
|
|
573
|
+
continue
|
|
574
|
+
|
|
575
|
+
ROOT_DIR = get_root_dir(root_name, ROOT_DIRS)
|
|
576
|
+
if not ROOT_DIR:
|
|
577
|
+
continue
|
|
578
|
+
|
|
579
|
+
relative_path = remove_root_from_path(subpath)
|
|
580
|
+
full_path = os.path.normpath(os.path.join(ROOT_DIR, relative_path))
|
|
581
|
+
if not full_path.startswith(os.path.abspath(ROOT_DIR)):
|
|
582
|
+
continue
|
|
583
|
+
|
|
584
|
+
if not os.path.exists(full_path):
|
|
585
|
+
continue
|
|
586
|
+
|
|
587
|
+
if os.path.isfile(full_path):
|
|
588
|
+
arcname = os.path.basename(full_path)
|
|
589
|
+
z.write(full_path, arcname)
|
|
590
|
+
elif os.path.isdir(full_path):
|
|
591
|
+
for root, dirs, files in os.walk(full_path):
|
|
592
|
+
for file in files:
|
|
593
|
+
file_path = os.path.join(root, file)
|
|
594
|
+
arcname = os.path.join(
|
|
595
|
+
os.path.basename(full_path),
|
|
596
|
+
os.path.relpath(file_path, full_path),
|
|
597
|
+
)
|
|
598
|
+
z.write(file_path, arcname)
|
|
599
|
+
|
|
600
|
+
response = Response(z, mimetype="application/zip")
|
|
601
|
+
response.headers["Content-Disposition"] = f"attachment; filename={zip_filename}"
|
|
602
|
+
return response
|
|
603
|
+
|
|
604
|
+
|
|
605
|
+
@app.route("/zip/<path:subpath>")
|
|
606
|
+
def download_zip(subpath):
|
|
607
|
+
# Extrair o nome da raiz
|
|
608
|
+
root_name = get_root_name_from_path(subpath)
|
|
609
|
+
if not root_name:
|
|
610
|
+
abort(404)
|
|
611
|
+
|
|
612
|
+
ROOT_DIR = get_root_dir(root_name, ROOT_DIRS)
|
|
613
|
+
if not ROOT_DIR:
|
|
614
|
+
abort(404)
|
|
615
|
+
|
|
616
|
+
relative_path = remove_root_from_path(subpath)
|
|
617
|
+
full_path = os.path.normpath(os.path.join(ROOT_DIR, relative_path))
|
|
618
|
+
if not full_path.startswith(os.path.abspath(ROOT_DIR)):
|
|
619
|
+
abort(403)
|
|
620
|
+
|
|
621
|
+
if not os.path.isdir(full_path):
|
|
622
|
+
abort(400)
|
|
623
|
+
|
|
624
|
+
# Criando o Stream de ZIP
|
|
625
|
+
z = zipstream.ZipFile(mode="w", compression=zipstream.ZIP_DEFLATED)
|
|
626
|
+
|
|
627
|
+
for root, dirs, files in os.walk(full_path):
|
|
628
|
+
for file in files:
|
|
629
|
+
file_path = os.path.join(root, file)
|
|
630
|
+
arcname = os.path.relpath(file_path, full_path)
|
|
631
|
+
z.write(file_path, arcname)
|
|
632
|
+
|
|
633
|
+
filename = os.path.basename(full_path) or "download"
|
|
634
|
+
response = Response(z, mimetype="application/zip")
|
|
635
|
+
response.headers["Content-Disposition"] = f"attachment; filename={filename}.zip"
|
|
636
|
+
return response
|