file manager concluido 99% redondo

This commit is contained in:
2026-01-31 00:00:42 +00:00
parent da65b8d99f
commit 91979b9fd4
2 changed files with 73 additions and 12 deletions

View File

@@ -85,27 +85,74 @@ class FileManager:
parent = os.path.dirname(self.path) parent = os.path.dirname(self.path)
if self.path != ROOT_DIR: await self.navigate(parent) if self.path != ROOT_DIR: await self.navigate(parent)
# --- UPLOAD --- # --- UPLOAD CORRIGIDO FINAL (NOME E CONTEÚDO) ---
def open_upload_dialog(self): def open_upload_dialog(self):
with ui.dialog() as dialog, ui.card(): with ui.dialog() as dialog, ui.card():
ui.label(f'Upload para: {os.path.basename(self.path)}').classes('font-bold') ui.label(f'Upload para: {os.path.basename(self.path)}').classes('font-bold')
async def handle(e): async def handle(e):
try: try:
target = os.path.join(self.path, e.name) # 1. Recuperação do Nome (Prioridade para .filename do Starlette)
await run.io_bound(self._save_file, target, e.content) name = None
ui.notify(f'Sucesso: {e.name}')
except Exception as ex: ui.notify(f'Erro: {ex}', type='negative') # Verifica se existe o objeto interno 'file' (detectado nos logs anteriores)
if hasattr(e, 'file'):
# .filename é onde fica o nome original do arquivo no upload web
name = getattr(e.file, 'filename', None)
# Se não achar, tenta .name
if not name:
name = getattr(e.file, 'name', None)
# Se ainda não achou, tenta direto no evento (fallback)
if not name:
name = getattr(e, 'name', 'arquivo_sem_nome')
# 2. Leitura do Conteúdo (Assíncrona)
content = b''
if hasattr(e, 'file'):
# Tenta resetar o ponteiro de leitura para o início
if hasattr(e.file, 'seek'):
await e.file.seek(0)
# Lê os bytes (await é necessário aqui)
if hasattr(e.file, 'read'):
content = await e.file.read()
if not content:
ui.notify('Erro: Arquivo vazio ou ilegível', type='warning')
return
# 3. Salva no disco
target = os.path.join(self.path, name)
# Executa a gravação em thread separada para não travar o servidor
await run.io_bound(self._save_file_bytes, target, content)
ui.notify(f'Sucesso: {name}', type='positive')
except Exception as ex:
# Imprime no log do container para diagnóstico se falhar
print(f"ERRO CRITICO UPLOAD: {ex}")
ui.notify(f'Erro: {ex}', type='negative')
# Componente UI
ui.upload(on_upload=handle, auto_upload=True, multiple=True).props('accept=*').classes('w-full') ui.upload(on_upload=handle, auto_upload=True, multiple=True).props('accept=*').classes('w-full')
async def close_and_refresh(): async def close_and_refresh():
dialog.close() dialog.close()
await self.refresh() await self.refresh()
ui.button('Fechar e Atualizar', on_click=close_and_refresh).props('flat w-full') ui.button('Fechar e Atualizar', on_click=close_and_refresh).props('flat w-full')
dialog.open() dialog.open()
def _save_file(self, target, content):
with open(target, 'wb') as f: f.write(content.read())
# Função auxiliar simples para salvar bytes
def _save_file_bytes(self, target, content_bytes):
with open(target, 'wb') as f:
f.write(content_bytes)
# --- LÓGICA DE SELEÇÃO --- # --- LÓGICA DE SELEÇÃO ---
def toggle_select_mode(self): def toggle_select_mode(self):
self.is_selecting = not self.is_selecting self.is_selecting = not self.is_selecting
@@ -375,24 +422,38 @@ class FileManager:
ui.icon(icon, color='amber' if entry.is_dir() else 'grey') ui.icon(icon, color='amber' if entry.is_dir() else 'grey')
ui.label(entry.name).classes('text-sm font-medium flex-grow') ui.label(entry.name).classes('text-sm font-medium flex-grow')
# --- MENU DE CONTEXTO (CORRIGIDO) ---
def bind_context_menu(self, element, entry): def bind_context_menu(self, element, entry):
with ui.menu() as m: with ui.menu() as m:
if not entry.is_dir and entry.name.lower().endswith(('.mkv', '.mp4')): # Opções de mídia apenas para vídeos
if not entry.is_dir() and entry.name.lower().endswith(('.mkv', '.mp4', '.avi')):
ui.menu_item('Media Info', on_click=lambda: self.open_inspector(entry.path)) ui.menu_item('Media Info', on_click=lambda: self.open_inspector(entry.path))
ui.menu_item('Renomear', on_click=lambda: self.open_rename_dialog(entry.path)) ui.menu_item('Renomear', on_click=lambda: self.open_rename_dialog(entry.path))
ui.menu_item('Mover Para...', on_click=lambda: self.open_move_dialog([entry.path])) ui.menu_item('Mover Para...', on_click=lambda: self.open_move_dialog([entry.path]))
async def delete_single(): async def delete_single():
try: try:
ui.notify(f'Excluindo {entry.name}...') ui.notify(f'Excluindo {entry.name}...')
if entry.is_dir: await run.io_bound(shutil.rmtree, entry.path)
else: await run.io_bound(os.remove, entry.path) # CORREÇÃO AQUI: Adicionados parênteses () em is_dir()
# Sem eles, o Python acha que sempre é True (porque o método existe)
if entry.is_dir():
await run.io_bound(shutil.rmtree, entry.path)
else:
await run.io_bound(os.remove, entry.path)
await self.refresh() await self.refresh()
except Exception as e: print(f"Erro delete: {e}") ui.notify('Item excluído.', type='positive')
except Exception as e:
ui.notify(f"Erro ao excluir: {e}", type='negative')
print(f"DEBUG DELETE ERROR: {e}")
ui.menu_item('Excluir', on_click=delete_single).props('text-color=red') ui.menu_item('Excluir', on_click=delete_single).props('text-color=red')
element.on('contextmenu.prevent', lambda: m.open()) element.on('contextmenu.prevent', lambda: m.open())
# --- INSPECTOR (RESTAURADO E RICO) --- # --- INSPECTOR (RESTAURADO E RICO) ---
async def open_inspector(self, path): async def open_inspector(self, path):
dialog = ui.dialog() dialog = ui.dialog()