gui v1
This commit is contained in:
232
ArchiviaMailGui.py
Normal file
232
ArchiviaMailGui.py
Normal file
@@ -0,0 +1,232 @@
|
|||||||
|
import win32com.client
|
||||||
|
import os
|
||||||
|
import hashlib
|
||||||
|
from datetime import datetime, timedelta
|
||||||
|
import time
|
||||||
|
import asyncio
|
||||||
|
from nicegui import ui, run
|
||||||
|
|
||||||
|
# --- LOGICA DI BACKEND ---
|
||||||
|
|
||||||
|
def get_file_hash(file_path):
|
||||||
|
"""Calcola l'hash per identificare file identici."""
|
||||||
|
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..."
|
||||||
|
# Dizionario per la localizzazione italiana
|
||||||
|
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')
|
||||||
|
|
||||||
|
# Esegue la logica pesante in un thread separato
|
||||||
|
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)
|
||||||
|
|
||||||
|
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
|
||||||
|
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."
|
||||||
|
return
|
||||||
|
|
||||||
|
processed_files = {}
|
||||||
|
|
||||||
|
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
|
||||||
|
# Convertiamo il tempo di Outlook in oggetto datetime Python
|
||||||
|
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 = (total - i + 1) / total
|
||||||
|
|
||||||
|
# Gestione Cartelle con Localizzazione Italiana
|
||||||
|
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)
|
||||||
|
|
||||||
|
# Spostamento
|
||||||
|
archived_item = item.Move(target_folder)
|
||||||
|
if archived_item:
|
||||||
|
archived_item.Save()
|
||||||
|
# Passiamo processed_files per evitare duplicati nella stessa sessione
|
||||||
|
self.process_attachments(archived_item, onedrive_path, received_time, processed_files)
|
||||||
|
time.sleep(0.3) # Pausa per sincronizzazione Exchange
|
||||||
|
except Exception:
|
||||||
|
continue
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
self.current_mail = f"Errore: {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, archived_item, onedrive_path, received_time, processed_files):
|
||||||
|
if archived_item.Class != 43:
|
||||||
|
return
|
||||||
|
|
||||||
|
# Controlliamo il conteggio iniziale
|
||||||
|
count = archived_item.Attachments.Count
|
||||||
|
if count == 0:
|
||||||
|
return
|
||||||
|
|
||||||
|
has_changed = False
|
||||||
|
date_prefix = received_time.strftime("%Y-%m-%d")
|
||||||
|
|
||||||
|
# IMPORTANTE: Cicliamo al contrario (da count a 1)
|
||||||
|
# Quando si eliminano oggetti da una collezione, bisogna sempre andare dall'ultimo al primo
|
||||||
|
for j in range(count, 0, -1):
|
||||||
|
try:
|
||||||
|
att = archived_item.Attachments.Item(j)
|
||||||
|
|
||||||
|
# Filtri (Tipo e Immagini Inline)
|
||||||
|
if att.Type != 1: continue
|
||||||
|
try:
|
||||||
|
if att.PropertyAccessor.GetProperty("http://schemas.microsoft.com/mapi/proptag/0x3712001E"):
|
||||||
|
continue
|
||||||
|
except: pass
|
||||||
|
|
||||||
|
# Salvataggio su OneDrive
|
||||||
|
temp_path = os.path.join(os.environ['TEMP'], att.FileName)
|
||||||
|
att.SaveAsFile(temp_path)
|
||||||
|
f_hash = get_file_hash(temp_path)
|
||||||
|
|
||||||
|
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)
|
||||||
|
|
||||||
|
# Prepariamo il link HTML
|
||||||
|
link_html = (
|
||||||
|
f"<div style='background:#f3f3f3; padding:10px; border:1px solid #ccc; margin:10px 0; font-family:Arial;'>"
|
||||||
|
f"<b>📎 Allegato archiviato ({date_prefix}):</b><br>"
|
||||||
|
f"<a href='file:///{dest_path}'>{att.FileName}</a></div>"
|
||||||
|
)
|
||||||
|
|
||||||
|
# Applichiamo le modifiche
|
||||||
|
archived_item.HTMLBody = link_html + archived_item.HTMLBody
|
||||||
|
|
||||||
|
# ELIMINAZIONE FORZATA
|
||||||
|
att.Delete()
|
||||||
|
has_changed = True
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
print(f"Errore allegato: {e}")
|
||||||
|
continue
|
||||||
|
|
||||||
|
# SALVATAGGIO DEFINITIVO: Senza questo, il Delete() non viene sincronizzato
|
||||||
|
if has_changed:
|
||||||
|
archived_item.Save()
|
||||||
|
|
||||||
|
# --- INTERFACCIA GRAFICA ---
|
||||||
|
|
||||||
|
archiver = ArchiverGUI()
|
||||||
|
|
||||||
|
@ui.page('/')
|
||||||
|
def main_page():
|
||||||
|
ui.query('body').style('background-color: #f0f2f5')
|
||||||
|
|
||||||
|
with ui.header().classes('items-center justify-between bg-blue-9'):
|
||||||
|
ui.label('Outlook Smart Archiver').classes('text-h5 q-ml-md')
|
||||||
|
ui.icon('archive', size='lg').classes('q-mr-md')
|
||||||
|
|
||||||
|
with ui.column().classes('w-full max-w-3xl mx-auto q-pa-lg gap-6'):
|
||||||
|
with ui.card().classes('w-full q-pa-md shadow-3'):
|
||||||
|
ui.label('Parametri di Configurazione').classes('text-h6 mb-2')
|
||||||
|
|
||||||
|
arc_name = ui.input('Nome Archivio Online (esatto)',
|
||||||
|
value='Archivio Online - nome.cognome@intesasanpaolo.com').classes('w-full')
|
||||||
|
|
||||||
|
od_path = ui.input('Cartella Destinazione OneDrive',
|
||||||
|
value=r'C:\Users\utente\OneDrive - Intesa SanPaolo\Allegati_Outlook').classes('w-full')
|
||||||
|
|
||||||
|
with ui.row().classes('w-full items-center mt-4'):
|
||||||
|
ui.label('Mesi da mantenere nella Inbox:')
|
||||||
|
slider = ui.slider(min=0, max=24, value=6).classes('col px-4')
|
||||||
|
ui.badge().bind_text_from(slider, 'value').classes('text-lg')
|
||||||
|
|
||||||
|
with ui.card().classes('w-full q-pa-md shadow-3'):
|
||||||
|
ui.label('Stato Avanzamento').classes('text-h6 mb-2')
|
||||||
|
|
||||||
|
with ui.column().classes('w-full items-center'):
|
||||||
|
# Titolo della mail
|
||||||
|
ui.label().bind_text_from(archiver, 'current_mail').classes('text-blue-9 text-weight-medium mb-1')
|
||||||
|
|
||||||
|
# Barra di avanzamento
|
||||||
|
# Nota: rimosso bind_content_from che causava l'errore
|
||||||
|
prog = ui.linear_progress().bind_value_from(archiver, 'progress') \
|
||||||
|
.props('stripe size=35px') \
|
||||||
|
.classes('rounded-borders relative')
|
||||||
|
|
||||||
|
# Usiamo un'etichetta separata che "si appoggia" alla barra
|
||||||
|
# o iniettiamo il valore direttamente così:
|
||||||
|
with prog:
|
||||||
|
ui.label().bind_text_from(archiver, 'progress', backward=lambda x: f'{x * 100:.2f}%') \
|
||||||
|
.classes('absolute-center text-blue text-weight-bold')
|
||||||
|
|
||||||
|
with ui.row().classes('w-full justify-center mt-6 gap-4'):
|
||||||
|
ui.button('AVVIA PROCESSO', icon='play_arrow', color='green-7',
|
||||||
|
on_click=lambda: archiver.start_archiving(arc_name.value, od_path.value, slider.value)) \
|
||||||
|
.bind_enabled_from(archiver, 'running', backward=lambda x: not x)
|
||||||
|
|
||||||
|
ui.button('INTERROMPI', icon='stop', color='red-7',
|
||||||
|
on_click=lambda: setattr(archiver, 'running', False)) \
|
||||||
|
.bind_enabled_from(archiver, 'running')
|
||||||
|
|
||||||
|
ui.run(title='Outlook Archiver GUI', port=8080, reload=False, dark=False)
|
||||||
Reference in New Issue
Block a user