Added Mail interface and POP client, pop connections still needs to be closes when program is finished

This commit is contained in:
Dennis Eckerskorn 2025-01-14 21:54:03 +01:00
parent 128ab84429
commit 36e1a225e6
11 changed files with 341 additions and 100 deletions

View File

@ -2,11 +2,7 @@
<module type="PYTHON_MODULE" version="4">
<component name="NewModuleRootManager">
<content url="file://$MODULE_DIR$" />
<orderEntry type="jdk" jdkName="Python 3.12" jdkType="Python SDK" />
<orderEntry type="jdk" jdkName="Python 3.13" jdkType="Python SDK" />
<orderEntry type="sourceFolder" forTests="false" />
</component>
<component name="PyDocumentationSettings">
<option name="format" value="PLAIN" />
<option name="myDocStringFormat" value="Plain" />
</component>
</module>

View File

@ -1,7 +1,7 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="Black">
<option name="sdkName" value="Python 3.12" />
<option name="sdkName" value="Python 3.13" />
</component>
<component name="ProjectRootManager" version="2" project-jdk-name="Python 3.12" project-jdk-type="Python SDK" />
<component name="ProjectRootManager" version="2" project-jdk-name="Python 3.13" project-jdk-type="Python SDK" />
</project>

Binary file not shown.

View File

@ -8,10 +8,11 @@ from email.mime.multipart import MIMEMultipart
import sqlite3
from datetime import datetime
class EmailClientPOP:
def __init__(self, pop_server, smtp_server, email, password, pop_port = 110, smtp_port=25):
def __init__(self, pop_server, smtp_server, email, password, pop_port=110, smtp_port=25):
self.pop_server = pop_server
self.smtp = smtp_server
self.smtp_server = smtp_server
self.email = email
self.password = password
self.pop_port = pop_port
@ -19,7 +20,7 @@ class EmailClientPOP:
self.pop_conn = None
self.smtp_conn = None
#Ruta del archivo SQLite:
# Ruta del archivo SQLite:
self.db_file = os.path.join("resources/db_email", "emails.db")
self.init_database()
@ -40,8 +41,169 @@ class EmailClientPOP:
received_at DATETIME
)
""")
cursor.execute("""
CREATE TABLE IF NOT EXISTS sent_emails (
id INTEGER PRIMARY KEY AUTOINCREMENT,
recipient TEXT NOT NULL,
subject TEXT,
body TEXT,
sent_at DATETIME
)
""")
conn.commit()
conn.close()
print(f"Base de datos inicializada: {self.db_file}")
except sqlite3.Error as e:
print(f"Error al inicializar la base de datos: {e}")
print(f"Error al inicializar la base de datos: {e}")
def connect_pop(self):
"""Conexión al servidor POP"""
try:
self.pop_conn = poplib.POP3(self.pop_server, self.pop_port)
self.pop_conn.user(self.email)
self.pop_conn.pass_(self.password)
print("Conexión POP exitosa")
except Exception as e:
print(f"Error al conectar al servidor POP: {e}")
self.pop_conn = None
def connect_smtp(self):
"""Conexión al servidor SMTP"""
try:
self.smtp_conn = smtplib.SMTP(self.smtp_server, self.smtp_port)
self.smtp_conn.login(self.email, self.password)
print("Conexión SMTP exitosa")
except Exception as e:
print(f"Error al conectar al servidor SMTP: {e}")
self.smtp_conn = None
def is_connected(self):
"""Verifica si hay una conexión válida tanto de POP como de SMTP"""
return self.pop_conn is not None and self.smtp_conn is not None
def reconnect(self):
"""Intenta reconectar a los servidores POP y SMTP"""
print("Intentando reconectar al servidor de correo...")
self.connect_pop()
self.connect_smtp()
def fetch_unread_count(self):
"""Obtener el número de correos (POP3 no distingue entre leídos y no leídos)"""
try:
if not self.pop_conn:
self.connect_pop()
num_messages = len(self.pop_conn.list()[1])
return num_messages # Total de mensajes
except Exception as e:
print(f"Error al obtener el conteo de correos: {e}")
return 0
def fetch_emails(self, save_to_db=True):
"""Obtiene correos desde el servidor POP"""
try:
if not self.pop_conn:
self.connect_pop()
emails = []
num_messages = len(self.pop_conn.list()[1])
for i in range(1, num_messages + 1):
raw_messages = b"\n".join(self.pop_conn.retr(i)[1])
msg = email.message_from_bytes(raw_messages)
subject, encoding = decode_header(msg["Subject"])[0]
if isinstance(subject, bytes):
subject = subject.decode(encoding or "utf-8")
sender = msg.get("From")
body = ""
if msg.is_multipart():
for part in msg.walk():
if part.get_content_type() == "text/plain":
body = part.get_payload(decode=True).decode("utf-8", errors="ignore")
else:
body = msg.get_payload(decode=True).decode("utf-8", errors="ignore")
email_data = {
"sender": sender,
"subject": subject,
"body": body,
"received_at": datetime.now().isoformat()
}
emails.append(email_data)
if save_to_db:
self.save_email_to_db(email_data)
return emails
except Exception as e:
print(f"Error al obtener los correos: {e}")
return []
def fetch_folders(self):
"""POP3 no tiene carpetas, devuelve un mensaje indicando esto"""
print("El protocolo POP3 no soporta carpetas. Devuelve solo la bandeja de entrada.")
return ["INBOX"]
def save_email_to_db(self, email_data):
"""Guarda un correo recibido en la base de datos."""
try:
conn = sqlite3.connect(self.db_file)
cursor = conn.cursor()
cursor.execute("""
INSERT INTO emails (sender, subject, body, received_at)
VALUES (?, ?, ?, ?)
""", (email_data["sender"], email_data["subject"], email_data["body"], email_data["received_at"]))
conn.commit()
conn.close()
print("Correo recibido guardado en la base de datos.")
except sqlite3.Error as e:
print(f"Error al guardar correo recibido: {e}")
def send_mail(self, recipient, subject, body, save_to_db=True):
"""Envía un correo usando SMTP"""
try:
if not self.smtp_conn:
self.connect_smtp()
msg = MIMEMultipart()
msg["From"] = self.email
msg["To"] = recipient
msg["Subject"] = subject
msg.attach(MIMEText(body, "plain"))
self.smtp_conn.sendmail(self.email, recipient, msg.as_string())
print(f"Correo ha sido enviado a {recipient}")
if save_to_db:
self.save_sent_mail_to_db({
"recipient": recipient,
"subject": subject,
"body": body,
"sent_at": datetime.now().isoformat()
})
except Exception as e:
print(f"Error al enviar correo: {e}")
def save_sent_mail_to_db(self, email_data):
"""Guarda un correo enviado en la base de datos."""
try:
conn = sqlite3.connect(self.db_file)
cursor = conn.cursor()
cursor.execute("""
INSERT INTO sent_emails (recipient, subject, body, sent_at)
VALUES (?, ?, ?, ?)
""", (email_data["recipient"], email_data["subject"], email_data["body"], email_data["sent_at"]))
conn.commit()
conn.close()
print("Correo enviado guardado en la base de datos.")
except sqlite3.Error as e:
print(f"Error al guardar correo enviado: {e}")
def list_emails(self, limit=10):
"""Lista correos recientes (máximo `limit`)"""
emails = self.fetch_emails()
return emails[:limit]
def close_connections(self):
"""Cierra las conexiones POP y SMTP"""
if self.pop_conn:
self.pop_conn.quit()
if self.smtp_conn:
self.smtp_conn.quit()

View File

@ -6,7 +6,7 @@ from src.services.processes_manager import ProcessManager
from src.services.system_monitor import SystemMonitor
from src.services.tetris_game import TetrisGame
from src.services.threads_manager import ThreadsManager
from src.services.email_client_imap import EmailClientImap
from src.services.email_client_pop import EmailClientPOP
class CenteredWindow(ctk.CTk):
@ -16,9 +16,9 @@ class CenteredWindow(ctk.CTk):
self.title(title)
self.after_tasks = []
#Configurar Email Client
self.email_client = EmailClientImap(
imap_server="192.168.120.103",
# Configurar Email Client IMAP
self.email_client = EmailClientPOP(
pop_server="192.168.120.103",
smtp_server="192.168.120.103",
email="dennis@psp.ieslamar.org",
password="1234"
@ -28,21 +28,21 @@ class CenteredWindow(ctk.CTk):
self.process_manager = ProcessManager()
self.system_monitor = None
# Obtener la resolucion de la pantalla:
# Obtener la resolución de la pantalla:
screen_width = self.winfo_screenwidth()
screen_height = self.winfo_screenheight()
# Calcula el tamaño de la ventana según procentaje de la pantalla:
# Calcula el tamaño de la ventana según porcentaje de la pantalla:
window_width = int(screen_width * width_percentage)
window_height = int(screen_height * height_percentage)
# Calcular la posicion para centrar la ventana:
# Calcular la posición para centrar la ventana:
position_x = (screen_width - window_width) // 2
position_y = (screen_height - window_height) // 2
self.geometry(f"{window_width}x{window_height}+{position_x}+{position_y}")
#Configura la ventana
# Configura la ventana
self.configure_window()
self.protocol("WM_DELETE_WINDOW", self.on_close)
@ -62,7 +62,6 @@ class CenteredWindow(ctk.CTk):
self.thread_manager.start_threads()
def on_close(self):
"""Maneja el cierre de la ventana"""
self.thread_manager.stop_threads()
@ -70,7 +69,7 @@ class CenteredWindow(ctk.CTk):
if hasattr(self, "tetris_game") and self.tetris_game.running:
self.tetris_game.stop_game()
if "tetris_game" in self.thread_manager.tasks:
if "tetris_game" in self.thread_manager.tasks:
self.thread_manager.tasks["tetris_game"].stop()
if hasattr(self.thread_manager, "scrapper"):
@ -91,10 +90,16 @@ class CenteredWindow(ctk.CTk):
# Secciones y botones
sections = {
"Aplicaciones": [
("Abrir Chrome", lambda: self.process_manager.open_resource("browser", "https://google.com", "Cannot open browser")),
("Visual Studio Code", lambda: self.process_manager.open_resource("program", r"C:\Program Files\Microsoft VS Code\Code.exe", "Can't find VSCode")),
("Explorador de Windows", lambda: self.process_manager.open_resource("program", "explorer.exe", "Can't open Windows Explorer")),
("Notepad++", lambda: self.process_manager.open_resource("program", r"C:\Program Files\Notepad++\notepad++.exe", "Can't open Notepad++"))
("Abrir Chrome",
lambda: self.process_manager.open_resource("browser", "https://google.com", "Cannot open browser")),
("Visual Studio Code",
lambda: self.process_manager.open_resource("program", r"C:\Program Files\Microsoft VS Code\Code.exe",
"Can't find VSCode")),
("Explorador de Windows",
lambda: self.process_manager.open_resource("program", "explorer.exe", "Can't open Windows Explorer")),
("Notepad++",
lambda: self.process_manager.open_resource("program", r"C:\Program Files\Notepad++\notepad++.exe",
"Can't open Notepad++"))
]
}
@ -104,13 +109,13 @@ class CenteredWindow(ctk.CTk):
url_entry_chrome.pack(pady=5, padx=10)
# Botón para abrir la URL ingresada
internet_access_button = ctk.CTkButton(
left_panel,
text="Buscar URL",
command=lambda: self.process_manager.open_resource("browser", url_entry_chrome.get(), "Cannot open browser")
)
internet_access_button = ctk.CTkButton(
left_panel,
text="Buscar URL",
command=lambda: self.process_manager.open_resource("browser", url_entry_chrome.get(), "Cannot open browser")
)
internet_access_button.pack(pady=5, padx=10)
for section, buttons in sections.items():
if section:
section_label = ctk.CTkLabel(left_panel, text=section, font=("Arial", 12, "bold"))
@ -122,20 +127,20 @@ class CenteredWindow(ctk.CTk):
scrapping_label = ctk.CTkLabel(left_panel, text="Scrapping", font=("Arial", 12, "bold"))
scrapping_label.pack(anchor=ctk.W, pady=5, padx=10)
url_entry = ctk.CTkEntry(left_panel, placeholder_text="Introduce la URL para scrapear")
url_entry.pack(pady=5, padx=10)
url_entry = ctk.CTkEntry(left_panel, placeholder_text="Introduce la URL para scrapear")
url_entry.pack(pady=5, padx=10)
self.left_panel = left_panel
self.left_panel.url_entry = url_entry
self.left_panel.url_entry_chrome = url_entry_chrome
start_button = ctk.CTkButton(left_panel, text="Iniciar Scrapping", command=self.thread_manager.scrapper.start_scraping)
start_button = ctk.CTkButton(left_panel, text="Iniciar Scrapping",
command=self.thread_manager.scrapper.start_scraping)
start_button.pack(pady=5, padx=10)
stop_button = ctk.CTkButton(left_panel, text="Detener Scrapping", command=self.thread_manager.scrapper.stop_scraping)
stop_button = ctk.CTkButton(left_panel, text="Detener Scrapping",
command=self.thread_manager.scrapper.stop_scraping)
stop_button.pack(pady=5, padx=10)
def create_center_panel(self):
# Panel central con pestañas
center_panel = ctk.CTkFrame(self)
@ -147,9 +152,12 @@ class CenteredWindow(ctk.CTk):
# Crear pestañas y manejar contenido por separado
for tab_name in ["Scrapping", "Radio", "Correos", "Juego", "Sistema"]:
tab = tab_view.add(tab_name)
if tab_name == "Radio":
self.create_radio_tab(tab)
if tab_name == "Radio":
self.create_radio_tab(tab)
if tab_name == "Correos":
self.create_email_tab(tab)
if tab_name == "Scrapping":
text_widget = ctk.CTkTextbox(tab, width=500, height=400)
@ -161,11 +169,11 @@ class CenteredWindow(ctk.CTk):
if tab_name == "Sistema":
# Crear un frame para los gráficos del sistema
system_frame = ctk.CTkFrame(tab)
system_frame.pack(fill=ctk.BOTH, expand=True, padx=5, pady=5)
system_frame = ctk.CTkFrame(tab)
system_frame.pack(fill=ctk.BOTH, expand=True, padx=5, pady=5)
# Inicializar SystemMonitor con el frame de la pestaña
self.system_monitor = SystemMonitor(system_frame)
self.system_monitor = SystemMonitor(system_frame)
# Asignar el system_monitor al thread_manager
self.thread_manager.set_system_monitor(self.system_monitor)
@ -192,33 +200,25 @@ class CenteredWindow(ctk.CTk):
self.tetris_game = TetrisGame(game_frame)
self.tetris_game.pack()
# else:
# Agregar contenido genérico a otras pestañas
#label = ctk.CTkLabel(tab, text=f"Contenido de {tab_name}", font=("Arial", 12))
#label.pack(pady=10)
# else:
# Agregar contenido genérico a otras pestañas
# label = ctk.CTkLabel(tab, text=f"Contenido de {tab_name}", font=("Arial", 12))
# label.pack(pady=10)
def start_tetris_game(self):
"""Método para iniciar el juego."""
if not self.tetris_game.running:
self.tetris_game.running = True
#self.tetris_game.update_game()
# self.tetris_game.update_game()
def pause_tetris_game(self):
"""Método para pausar el juego."""
self.tetris_game.running = False
def restart_tetris_game(self):
"""Método para reiniciar el juego."""
self.tetris_game.reset_game()
def create_right_panel(self):
# Panel derecho
right_panel = ctk.CTkFrame(self, width=250)
@ -236,7 +236,8 @@ class CenteredWindow(ctk.CTk):
# Lista de alumnos
for i in range(1, 4):
student_label = ctk.CTkLabel(right_panel, text=f"Alumno {i}", font=("Arial", 12, "bold"), text_color="black")
student_label = ctk.CTkLabel(right_panel, text=f"Alumno {i}", font=("Arial", 12, "bold"),
text_color="black")
student_label.pack(anchor=ctk.W, pady=5, padx=10)
student_info = ctk.CTkLabel(
@ -247,8 +248,6 @@ class CenteredWindow(ctk.CTk):
)
student_info.pack(anchor=ctk.W, padx=10)
def create_bottom_bar(self):
# Crear la barra inferior
self.bottom_bar = ctk.CTkFrame(self, fg_color="lightblue", height=40)
@ -258,7 +257,8 @@ class CenteredWindow(ctk.CTk):
self.info_labels = {
"hora": ctk.CTkLabel(self.bottom_bar, text="Hora: --:--:--", font=("Arial", 12), text_color="black"),
"fecha": ctk.CTkLabel(self.bottom_bar, text="Fecha: --/--/----", font=("Arial", 12), text_color="black"),
"temperatura": ctk.CTkLabel(self.bottom_bar, text="Temperatura local: --°C", font=("Arial", 12), text_color="black"),
"temperatura": ctk.CTkLabel(self.bottom_bar, text="Temperatura local: --°C", font=("Arial", 12),
text_color="black"),
"emails": ctk.CTkLabel(self.bottom_bar, text="Correos sin leer: 0", font=("Arial", 12), text_color="black"),
}
@ -266,58 +266,141 @@ class CenteredWindow(ctk.CTk):
for label in self.info_labels.values():
label.pack(side=ctk.LEFT, padx=10, pady=5)
def dummy_action(self):
print("Acción no implementada")
def create_radio_tab(self, tab):
"""Crea la interfaz para la funcionalidad de emisoras de radio."""
self.radio_player = self.thread_manager.radio_player
def create_radio_tab(self, tab):
"""Crea la interfaz para la funcionalidad de emisoras de radio."""
self.radio_player = self.thread_manager.radio_player
# Lista de emisoras
radio_stations = {
"Box Radio UK": "http://uk2.internet-radio.com:8024/",
"Jazz Radio": "http://us2.internet-radio.com:8443/",
"Deep House Radio": "http://uk7.internet-radio.com:8000/",
}
radio_stations = {
"Box Radio UK": "http://uk2.internet-radio.com:8024/",
"Jazz Radio": "http://us2.internet-radio.com:8443/",
"Deep House Radio": "http://uk7.internet-radio.com:8000/",
}
# Dropdown para seleccionar emisora
self.selected_station = ctk.StringVar(value="Selecciona una emisora")
station_menu = ctk.CTkOptionMenu(tab, variable=self.selected_station, values=list(radio_stations.keys()))
station_menu.pack(pady=10)
self.selected_station = ctk.StringVar(value="Selecciona una emisora")
station_menu = ctk.CTkOptionMenu(tab, variable=self.selected_station, values=list(radio_stations.keys()))
station_menu.pack(pady=10)
# Botón para reproducir
play_button = ctk.CTkButton(
tab,
text="Reproducir",
command=lambda: self.start_radio(radio_stations[self.selected_station.get()])
)
play_button.pack(pady=5)
play_button = ctk.CTkButton(
tab,
text="Reproducir",
command=lambda: self.start_radio(radio_stations[self.selected_station.get()])
)
play_button.pack(pady=5)
# Botón para detener
stop_button = ctk.CTkButton(
tab,
text="Detener",
command=self.stop_radio,
stop_button = ctk.CTkButton(
tab,
text="Detener",
command=self.stop_radio,
state=tk.DISABLED # Deshabilitado inicialmente
)
stop_button.pack(pady=5)
)
stop_button.pack(pady=5)
# Guardar referencias para habilitar/deshabilitar botones
self.radio_controls = {"play_button": play_button, "stop_button": stop_button}
self.radio_controls = {"play_button": play_button, "stop_button": stop_button}
def start_radio(self, url):
"""Inicia la reproducción de radio y actualiza los botones."""
if url == "Selecciona una emisora":
tk.messagebox.showwarning("Advertencia", "Por favor, selecciona una emisora válida.")
return
self.radio_player.play(url)
self.radio_controls["play_button"].configure(state=tk.DISABLED)
self.radio_controls["stop_button"].configure(state=tk.NORMAL)
def start_radio(self, url):
"""Inicia la reproducción de radio y actualiza los botones."""
if url == "Selecciona una emisora":
tk.messagebox.showwarning("Advertencia", "Por favor, selecciona una emisora válida.")
return
self.radio_player.play(url)
self.radio_controls["play_button"].configure(state=tk.DISABLED)
self.radio_controls["stop_button"].configure(state=tk.NORMAL)
def stop_radio(self):
"""Detiene la reproducción de radio y actualiza los botones."""
self.radio_player.stop()
self.radio_controls["play_button"].configure(state=tk.NORMAL)
self.radio_controls["stop_button"].configure(state=tk.DISABLED)
def stop_radio(self):
"""Detiene la reproducción de radio y actualiza los botones."""
self.radio_player.stop()
self.radio_controls["play_button"].configure(state=tk.NORMAL)
self.radio_controls["stop_button"].configure(state=tk.DISABLED)
def create_email_tab(self, tab):
"""Crea una interfaz moderna para gestionar los correos con customtkinter."""
# Configurar el grid para permitir que los elementos se expandan
tab.grid_columnconfigure(0, weight=1)
tab.grid_rowconfigure(0, weight=1)
# Crear un marco principal para la pestaña
main_frame = ctk.CTkFrame(tab)
main_frame.grid(row=0, column=0, sticky="nsew", padx=10, pady=10)
# Lista de correos en un marco
listbox_frame = ctk.CTkFrame(main_frame)
listbox_frame.grid(row=0, column=0, sticky="nsew", padx=10, pady=10)
# Crear una lista de correos con un scrollbar
self.email_listbox = ctk.CTkTextbox(listbox_frame, width=800, height=800)
self.email_listbox.grid(row=0, column=0, sticky="nsew", padx=10, pady=10)
self.email_listbox.configure(state="disabled") # Inicialmente deshabilitada
scrollbar = ctk.CTkScrollbar(listbox_frame, command=self.email_listbox.yview)
scrollbar.grid(row=0, column=1, sticky="ns")
self.email_listbox.configure(yscrollcommand=scrollbar.set)
# Frame para botones de acción
button_frame = ctk.CTkFrame(main_frame)
button_frame.grid(row=1, column=0, sticky="ew", padx=10, pady=10)
# Crear botones para acciones: Marcar como leído, Eliminar, Actualizar
mark_read_button = ctk.CTkButton(
button_frame, text="Marcar como leído", command=self.mark_email_as_read, fg_color="green"
)
mark_read_button.grid(row=0, column=0, padx=5, pady=5, sticky="ew")
delete_button = ctk.CTkButton(
button_frame, text="Eliminar correo", command=self.delete_email, fg_color="red"
)
delete_button.grid(row=0, column=1, padx=5, pady=5, sticky="ew")
refresh_button = ctk.CTkButton(
button_frame, text="Actualizar", command=self.refresh_email_list, fg_color="blue"
)
refresh_button.grid(row=0, column=2, padx=5, pady=5, sticky="ew")
# Expandir las filas y columnas del marco principal
main_frame.grid_rowconfigure(0, weight=1)
main_frame.grid_columnconfigure(0, weight=1)
def refresh_email_list(self):
"""Actualiza la lista de correos en la interfaz."""
try:
if not self.email_client.is_connected():
self.email_client.reconnect()
if self.email_client.is_connected():
emails = self.email_client.fetch_emails()
self.email_listbox.delete(0, tk.END)
for email in emails:
self.email_listbox.insert(tk.END, f"{email['subject']} - {email['from']}")
else:
print("No hay conexión al servidor de correo.")
except Exception as e:
print(f"Error al actualizar la lista de correos: {e}")
def mark_email_as_read(self):
"""Marca el correo seleccionado como leído."""
selected_index = self.email_listbox.curselection()
if not selected_index:
print("No se ha seleccionado ningún correo.")
return
selected_email = self.email_listbox.get(selected_index)
# Aquí puedes agregar la lógica para marcar el correo como leído
print(f"Correo marcado como leído: {selected_email}")
def delete_email(self):
"""Elimina el correo seleccionado."""
selected_index = self.email_listbox.curselection()
if not selected_index:
print("No se ha seleccionado ningún correo.")
return
selected_email = self.email_listbox.get(selected_index)
# Aquí puedes agregar la lógica para eliminar el correo
print(f"Correo eliminado: {selected_email}")