diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..505a3b1
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,10 @@
+# Python-generated files
+__pycache__/
+*.py[oc]
+build/
+dist/
+wheels/
+*.egg-info
+
+# Virtual environments
+.venv
diff --git a/ArchiviaMailGui.py b/ArchiviaMailGui.py
index ca5d09a..e69de29 100644
--- a/ArchiviaMailGui.py
+++ b/ArchiviaMailGui.py
@@ -1,231 +0,0 @@
-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"
"
- f"
📎 Allegato spostato su OneDrive ({date_prefix}):"
- f"
{att.FileName} "
- )
- mail_item.HTMLBody = link_html + mail_item.HTMLBody
-
- # RIMOZIONE FISICA DALLA MAIL
- mail_item.Attachments.Remove(j)
-
- except Exception as e:
- print(f"Errore su allegato {j}: {e}")
- continue
-
-# --- INTERFACCIA GRAFICA (NICEGUI) ---
-
-archiver = ArchiverGUI()
-
-@ui.page('/')
-def main_page():
- ui.query('body').style('background-color: #f0f2f5; font-family: "Segoe UI", Tahoma, Geneva, Verdana, sans-serif;')
-
- with ui.header().classes('items-center justify-between bg-blue-9 shadow-4 q-pa-md'):
- ui.label('Outlook Smart Archiver').classes('text-h5 text-white font-bold')
- ui.icon('inventory_2', size='lg', color='white')
-
- with ui.column().classes('w-full max-w-3xl mx-auto q-pa-lg gap-6'):
-
- # CARD CONFIGURAZIONE
- with ui.card().classes('w-full q-pa-md shadow-2 border-l-4 border-blue-9'):
- ui.label('Configurazione').classes('text-h6 text-blue-9 mb-2')
-
- arc_name = ui.input('Nome dell\'Archivio Online',
- value='Archivio Online - Nome.Cognome@intesasanpaolo.com').classes('w-full')
-
- od_path = ui.input('Percorso locale OneDrive per Allegati',
- value=r'C:\Users\--UTENZA--\OneDrive - Intesa SanPaolo\Allegati_Outlook').classes('w-full')
-
- with ui.row().classes('w-full items-center mt-4 bg-grey-2 q-pa-sm rounded'):
- 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 bg-blue-9')
-
- # CARD PROGRESSO
- with ui.card().classes('w-full q-pa-md shadow-2'):
- ui.label('Avanzamento').classes('text-h6 mb-2')
-
- with ui.column().classes('w-full items-center'):
- ui.label().bind_text_from(archiver, 'current_mail').classes('text-grey-8 text-italic mb-2 text-center')
-
- # Contenitore della barra
- with ui.linear_progress().bind_value_from(archiver, 'progress').props('stripe size=40px color=blue-9').classes('rounded-borders relative shadow-1') as p:
- # Etichetta percentuale centrata e troncata
- ui.label().bind_text_from(archiver, 'progress', backward=lambda x: f'{x * 100:.2f}%') \
- .classes('absolute-center text-blue text-weight-bold') \
- .style('text-shadow: 1px 1px 3px rgba(0,0,0,0.4); font-size: 1.2rem;')
-
- with ui.row().classes('w-full justify-center mt-8 gap-4'):
- ui.button('AVVIA ARCHIVIAZIONE', icon='rocket_launch', color='green-8',
- on_click=lambda: archiver.start_archiving(arc_name.value, od_path.value, slider.value)) \
- .classes('q-px-lg').bind_enabled_from(archiver, 'running', backward=lambda x: not x)
-
- ui.button('STOP', icon='block', color='red-8',
- on_click=lambda: setattr(archiver, 'running', False)) \
- .classes('q-px-lg').bind_enabled_from(archiver, 'running')
-
- ui.markdown('--- \n *Ricorda: Chiudi Outlook prima di avviare il processo per evitare blocchi.*').classes('text-center text-grey-6 text-xs')
-
-# Avvio dell'applicazione
-ui.run(native=True, window_size=(800, 600), title='Outlook Archiver Pro')
\ No newline at end of file
diff --git a/ArchiviazioneMail.py b/ArchiviazioneMail.py
index 02ae6e8..e69de29 100644
--- a/ArchiviazioneMail.py
+++ b/ArchiviazioneMail.py
@@ -1,122 +0,0 @@
-import win32com.client
-from datetime import datetime, timedelta
-from tqdm import tqdm
-import time
-import locale
-
-# --- CONFIGURAZIONE ---
-ARCHIVE_NAME = "Archivio online - alessandro.battilani@intesasanpaolo.com"
-# MONTHS_LIMIT = 3
-# ----------------------
-
-# Imposta la localizzazione in italiano
-try:
- locale.setlocale(locale.LC_TIME, "it_IT.UTF-8")
-except: # noqa: E722
- try:
- locale.setlocale(locale.LC_TIME, "ita_ita")
- except: # noqa: E722
- print("Locale italiano non impostato, uso nomi manuali.")
-
-def main():
- print("Connessione a Outlook in corso...")
- try:
- outlook = win32com.client.Dispatch("Outlook.Application")
- namespace = outlook.GetNamespace("MAPI")
- archive_root = namespace.Folders.Item(ARCHIVE_NAME)
-
- # 6 = Inbox (ReceivedTime), 5 = Sent Items (SentOn)
- folders_to_process = [
- (namespace.GetDefaultFolder(6), "[ReceivedTime]", "ReceivedTime"),
- (namespace.GetDefaultFolder(5), "[SentOn]", "SentOn")
- ]
- except Exception as e:
- print(f"Errore connessione: {e}")
- return
-
- stringa_mounths_limit = input("\n Quanti mesi vuoi tenere in linea? (default 3)") or '3'
-
- # Convertiamo la stringa in un numero intero
- MONTHS_LIMIT = int(stringa_mounths_limit)
-
- cutoff_date = datetime.now() - timedelta(days=MONTHS_LIMIT * 30)
- filter_date_str = cutoff_date.strftime("%d/%m/%Y %H:%M")
-
- 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"}
-
- for source_folder, filter_attr, time_attr in folders_to_process:
- print(f"\n--- Analisi cartella: {source_folder.Name} ---")
-
- filter_str = f"{filter_attr} < '{filter_date_str}'"
- items = source_folder.Items.Restrict(filter_str)
- items.Sort(filter_attr, True)
-
- total_items = items.Count
- if total_items == 0:
- print(f"Nessuna mail più vecchia di {MONTHS_LIMIT} mesi in {source_folder.Name}.")
- continue
-
- archived_count = 0
- # tqdm posizionato esternamente per monitorare il progresso reale
- with tqdm(total=total_items, desc=f"Archiviazione {source_folder.Name}", unit="mail", colour='green') as pbar:
- for i in range(total_items, 0, -1):
- try:
- item = items.Item(i)
-
- if not hasattr(item, time_attr):
- pbar.update(1)
- continue
-
- # Recupero data dinamico
- rt = getattr(item, time_attr)
- received_time = datetime(rt.year, rt.month, rt.day, rt.hour, rt.minute)
- anno_str = str(received_time.year)
- nome_mese = mesi_it[received_time.month]
- pbar.set_description(f"Archiviazione {source_folder.Name} - Mese: {nome_mese} {anno_str}")
-
- # Gestione struttura cartelle (Source -> Anno -> Mese)
- try:
- arch_type = archive_root.Folders.Item(source_folder.Name)
- except: # noqa: E722
- arch_type = archive_root.Folders.Add(source_folder.Name)
-
- try:
- y_f = arch_type.Folders.Item(anno_str)
- except: # noqa: E722
- y_f = arch_type.Folders.Add(anno_str)
-
- month_folder_name = f"{received_time.month:02d}-{nome_mese}"
- try:
- target_folder = y_f.Folders.Item(month_folder_name)
- except: # noqa: E722
- target_folder = y_f.Folders.Add(month_folder_name)
-
- # --- TENTA LO SPOSTAMENTO CON RETRY (Reintegrato) ---
- archived_item = None
- for tentativo in range(3):
- try:
- archived_item = item.Move(target_folder)
- if archived_item:
- archived_item.Save()
- time.sleep(0.4) # Pausa vitale
- break
- except: # noqa: E722
- time.sleep(1) # Attesa per server busy
-
- if archived_item:
- archived_count += 1
- pbar.set_postfix(successo=archived_count)
- else:
- pbar.set_postfix(error="Move fallito")
-
- except Exception as e:
- pbar.set_postfix(err=str(e)[:15])
-
- # Update della barra sempre alla fine del ciclo i
- pbar.update(1)
-
- print("\nOperazione conclusa con successo.")
-
-if __name__ == "__main__":
- main()
\ No newline at end of file
diff --git a/main.py b/main.py
new file mode 100644
index 0000000..7f948e2
--- /dev/null
+++ b/main.py
@@ -0,0 +1,6 @@
+def main():
+ print("Hello from archivemail!")
+
+
+if __name__ == "__main__":
+ main()
diff --git a/pyproject.toml b/pyproject.toml
index eba2f34..6aa8d53 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -1,5 +1,5 @@
[project]
-name = "macro-outlook"
+name = "archivemail"
version = "0.1.0"
description = "Add your description here"
readme = "README.md"