diff --git a/main.py b/main.py index 416ceee..8bfd406 100644 --- a/main.py +++ b/main.py @@ -1,9 +1,558 @@ import time - import flet as ft - from components.functions import * from components.widgets import * +import os +import shutil +import socket +from typing import Dict +from http.server import SimpleHTTPRequestHandler, ThreadingHTTPServer +import threading +import webbrowser +from functools import partial + +os.environ["FLET_SECRET_KEY"] = "zxczxczxcSS" +os.environ["FLET_UPLOAD_DIR"] = "assets/uploads" +os.environ["FLET_ASSETS_DIR"] = "assets" +assets_dir = "assets" +upload_dir = os.path.join(assets_dir, "uploads") + +# Ensure upload directory exists +os.makedirs(upload_dir, exist_ok=True) + +def get_local_ip(): + try: + # Crear un socket UDP para obtener la IP local + s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) + s.connect(("8.8.8.8", 80)) + ip = s.getsockname()[0] + s.close() + return ip + except Exception: + return "0.0.0.0" # Fallback a todas las interfaces si no se puede determinar la IP + +class CustomHandler(SimpleHTTPRequestHandler): + def __init__(self, *args, directory=None, **kwargs): + super().__init__(*args, directory=directory, **kwargs) + + def do_GET(self): + if self.path == '/': + self.send_response(200) + self.send_header("Content-type", "text/html") + self.end_headers() + self.wfile.write(self.generate_html().encode()) + else: + # Manejar el error de favicon.ico + if self.path == '/favicon.ico': + self.send_response(404) + self.end_headers() + return + super().do_GET() + + def handle_one_request(self): + try: + super().handle_one_request() + except BrokenPipeError: + # Ignorar errores de pipe roto + pass + + def generate_html(self): + files = os.listdir(upload_dir) + html_content = f""" + + + + + + Servidor July + + + + +
+
+

Servidor Archivos (july 🐎)

+ {"

No hay archivos disponibles para descargar.

" if not files else f''' + + + + + + {"".join([self.generate_file_row(file) for file in files])} +
Nombre del archivoAcciones
+ '''} +
+ + + + + """ + return html_content + + def generate_file_row(self, filename): + return f""" + + {filename} + Descargar + + """ + +class FileUploader(ft.Container): + def __init__( + self, + ring_size:int = 20, + server_port:int = 8000 + ): + super().__init__() + + self.server_port = server_port + self.server = None + self.server_thread = None + self.is_server_running = False + self.local_ip = get_local_ip() + + self.ring_size = ring_size + self.icon_size = self.ring_size * 0.75 + self.progs_ring: Dict[str, ft.ProgressBar] = {} + self.anim_switchers: Dict[str, ft.AnimatedSwitcher] = {} + self.files_column = ft.Column() + + self.fp = ft.FilePicker( + on_result=self.handle_file_picked + ) + + self.upload_button = ft.ElevatedButton("Subir", on_click=self.handle_upload, visible=False) + + # Server controls con IP local + self.server_status = ft.Text("Apagado", color="red") + self.ip_text = ft.Text(f"IP: {self.local_ip}", visible=False) + self.server_button = ft.ElevatedButton( + "Encender", + icon=ft.icons.PLAY_ARROW, + on_click=self.toggle_server, + bgcolor=ft.colors.GREEN + ) + + self.server_url = ft.TextButton( + "Abrir en el navegador", + icon=ft.icons.OPEN_IN_BROWSER, + on_click=lambda _: webbrowser.open(f"http://{self.local_ip}:{self.server_port}"), + visible=False + ) + + self.content = ft.Column( + [ + ft.ElevatedButton( + "Seleccionar archivos", + icon=ft.icons.UPLOAD_FILE, + on_click=lambda _: self.fp.pick_files( + allow_multiple=True, + allowed_extensions=["jpg", "png", "jpeg", "pdf", "mp4", "mp3", "doc", "docx", "xls", "xlsx", "ppt", "pptx", "txt", "bat", "sh", "py", "c", "cpp", "java", "css", "js", "html", "php", "rb", "go", "js", "py", "cs", "json", "xml", "svg", "psd", "ai", "eps", "indd", "ps", "pdf"] + ), + ), + self.server_button, + ft.Row( + [ + self.server_status, + self.ip_text, + ], + alignment=ft.MainAxisAlignment.START + ), + self.server_url, + self.upload_button, + self.files_column + ] + ) + + def handle_file_picked(self, e: ft.FilePickerResultEvent): + if not e.files: + return + + # first of all we remove evry file from assets/uploads + for f in os.listdir(upload_dir): + os.remove(os.path.join(upload_dir, f)) + + + self.upload_button.visible = True + self.upload_button.update() + self.progs_ring = {} + self.anim_switchers = {} + self.files_column.controls.clear() + + for f in e.files: + progress_ring = ft.ProgressRing( + value=0, + width=self.ring_size, + height=self.ring_size, + color=ft.colors.GREEN + ) + + switcher = ft.AnimatedSwitcher( + content=ft.Container( + width=self.ring_size, + height=self.ring_size, + content=ft.Icon( + ft.icons.UPLOAD_FILE, + expand=True, + size=self.icon_size + ), + ), + duration=300, + reverse_duration=100, + transition=ft.AnimatedSwitcherTransition.SCALE + ) + + self.progs_ring[f.name] = progress_ring + self.anim_switchers[f.name] = switcher + + self.files_column.controls.append( + ft.Row( + [ + ft.Stack([progress_ring, switcher]), + ft.Text(f.name), + ] + ) + ) + self.update() + + def handle_upload(self, e): + if self.fp.result and self.fp.result.files: + for f in self.fp.result.files: + try: + dest_path = os.path.join(upload_dir, f.name) + shutil.copy2(f.path, dest_path) + + self.progs_ring[f.name].value = 100 + self.anim_switchers[f.name].content = ft.Container( + width=self.ring_size, + height=self.ring_size, + content=ft.Icon( + ft.icons.DONE, + expand=True, + size=self.icon_size + ), + ) + except Exception as ex: + print(f"Error subiendo {f.name}: {str(ex)}") + self.progs_ring[f.name].color = ft.colors.RED + self.anim_switchers[f.name].content = ft.Container( + width=self.ring_size, + height=self.ring_size, + content=ft.Icon( + ft.icons.ERROR, + expand=True, + size=self.icon_size + ), + ) + finally: + self.progs_ring[f.name].update() + self.anim_switchers[f.name].update() + + def start_server(self): + if not os.path.exists(upload_dir): + os.makedirs(upload_dir) + + handler = partial(CustomHandler, directory=assets_dir) + # Cambiar 'localhost' por '0.0.0.0' para escuchar en todas las interfaces + self.server = ThreadingHTTPServer(('0.0.0.0', self.server_port), handler) + self.server_thread = threading.Thread(target=self.server.serve_forever) + self.server_thread.daemon = True + self.server_thread.start() + self.is_server_running = True + + self.server_status.value = f"Server: Running on port {self.server_port}" + self.server_status.color = "green" + self.server_button.text = "Stop Server" + self.server_button.icon = ft.icons.STOP + self.server_button.bgcolor = ft.colors.RED + self.server_url.visible = True + self.ip_text.visible = True + self.update() + + def stop_server(self): + if self.server: + self.server.shutdown() + self.server.server_close() + self.is_server_running = False + + self.server_status.value = "Apagado" + self.server_status.color = "red" + self.server_button.text = "Encender" + self.server_button.icon = ft.icons.PLAY_ARROW + self.server_button.bgcolor = ft.colors.GREEN + self.server_url.visible = False + self.ip_text.visible = False + + self.update() + + def toggle_server(self, e): + if self.is_server_running: + self.stop_server() + else: + self.start_server() + + def did_mount(self): + self.page.overlay.append(self.fp) + self.page.update() + return super().did_mount() + + def will_unmount(self): + self.stop_server() + return super().did_mount() class RusContactos(ft.Tabs): @@ -458,14 +1007,29 @@ def on_resize(e): content=RusContactos(), ), ), + ft.Tab( + text="FED", + content=ft.Container( + ft.Tabs( + [ + TabFederacionFranquicias(), + + ], + tab_alignment=ft.TabAlignment.CENTER, + expand=True, + ), + expand=True, + ), + ), ft.Tab( text="GENERAL", content=ft.Container( ft.Tabs( [ TabGeneralPatentes(), - TabFederacionFranquicias(), ft.Tab(text="Contador Billetes", content=Billetes()), + ft.Tab(text="Server", content=FileUploader()), + ], tab_alignment=ft.TabAlignment.CENTER, expand=True,