import win32com.client import os import hashlib from datetime import datetime, timedelta import time from nicegui import ui, run # --- LOGICA DI BACKEND --- def get_file_hash(file_path): """Calcola l'MD5 per identificare file identici ed evitare duplicati.""" hasher = hashlib.md5() with open(file_path, 'rb') as f: for chunk in iter(lambda: f.read(4096), b""): hasher.update(chunk) return hasher.hexdigest() class ArchiverGUI: def __init__(self): self.running = False self.progress = 0.0 self.current_mail = "In attesa di avvio..." self.mesi_it = { 1: "Gennaio", 2: "Febbraio", 3: "Marzo", 4: "Aprile", 5: "Maggio", 6: "Giugno", 7: "Luglio", 8: "Agosto", 9: "Settembre", 10: "Ottobre", 11: "Novembre", 12: "Dicembre" } async def start_archiving(self, archive_name, onedrive_path, months_limit): self.running = True self.progress = 0.0 ui.notify('Connessione a Outlook in corso...', color='info') # Avvia la logica in un thread dedicato per non bloccare l'interfaccia await run.io_bound(self.run_logic, archive_name, onedrive_path, months_limit) self.running = False if self.progress >= 1.0: self.current_mail = "Archiviazione completata con successo!" ui.notify('Processo terminato!', type='positive') def run_logic(self, archive_name, onedrive_path, months_limit): try: if not os.path.exists(onedrive_path): os.makedirs(onedrive_path) outlook = win32com.client.Dispatch("Outlook.Application") namespace = outlook.GetNamespace("MAPI") inbox = namespace.GetDefaultFolder(6) # 6 = OlFolderInbox try: archive_root = namespace.Folders.Item(archive_name) except Exception: self.current_mail = f"ERRORE: Archivio '{archive_name}' non trovato." self.running = False return # Calcolo data limite per il filtro cutoff_date = datetime.now() - timedelta(days=int(months_limit) * 30) filter_str = f"[ReceivedTime] < '{cutoff_date.strftime('%d/%m/%Y %H:%M')}'" items = inbox.Items.Restrict(filter_str) items.Sort("[ReceivedTime]", True) total = items.Count if total == 0: self.current_mail = "Nessuna mail trovata con i criteri selezionati." self.progress = 1.0 return processed_files = {} # Ciclo dal fondo verso l'inizio per non sballare gli indici di Outlook for i in range(total, 0, -1): if not self.running: self.current_mail = "Processo interrotto dall'utente." break try: item = items.Item(i) rt = item.ReceivedTime received_time = datetime(rt.year, rt.month, rt.day, rt.hour, rt.minute) self.current_mail = f"[{total-i+1}/{total}] {item.Subject[:40]}..." self.progress = float((total - i + 1) / total) # 1. ELIMINAZIONE ALLEGATI E AGGIUNTA LINK (Mentre รจ ancora in Inbox) # Questo garantisce i permessi di scrittura necessari per cancellare i file self.process_attachments(item, onedrive_path, received_time, processed_files) item.Save() # 2. PREPARAZIONE CARTELLA DESTINAZIONE anno_str = str(received_time.year) nome_mese = self.mesi_it[received_time.month] month_folder_name = f"{received_time.month:02d}-{nome_mese}" y_f = self.get_or_create_folder(archive_root, anno_str) target_folder = self.get_or_create_folder(y_f, month_folder_name) # 3. SPOSTAMENTO DEFINITIVO item.Move(target_folder) # Piccola pausa per dare respiro al server Exchange time.sleep(0.1) except Exception as e: print(f"Errore su singola mail: {e}") continue except Exception as e: self.current_mail = f"Errore critico: {str(e)}" self.running = False def get_or_create_folder(self, parent, name): try: return parent.Folders.Item(name) except: return parent.Folders.Add(name) def process_attachments(self, mail_item, onedrive_path, received_time, processed_files): """Rimuove gli allegati reali, li salva su OneDrive e inserisce il link nella mail.""" if mail_item.Class != 43: return # 43 = OlMail date_prefix = received_time.strftime("%Y-%m-%d") # Usiamo un ciclo reverse sugli allegati per la rimozione sicura count = mail_item.Attachments.Count for j in range(count, 0, -1): try: att = mail_item.Attachments.Item(j) # Filtro: Solo file reali (Type 1), escludendo immagini nelle firme (Inline) if att.Type != 1: continue is_inline = False try: # Controlla il tag MAPI per il Content-ID delle immagini incorporate if att.PropertyAccessor.GetProperty("http://schemas.microsoft.com/mapi/proptag/0x3712001E"): is_inline = True except: pass if is_inline: continue print("da qui gestisco gli allegati") # Salvataggio fisico del file temp_path = os.path.join(os.environ['TEMP'], att.FileName) att.SaveAsFile(temp_path) f_hash = get_file_hash(temp_path) # Nome file univoco con Data e Hash corto unique_name = f"{date_prefix}_{f_hash[:6]}_{att.FileName}" dest_path = os.path.join(onedrive_path, unique_name) if f_hash not in processed_files: if not os.path.exists(dest_path): os.replace(temp_path, dest_path) processed_files[f_hash] = dest_path else: if os.path.exists(temp_path): os.remove(temp_path) # Inserimento link HTML nel corpo della mail link_html = ( f"