@Sero: Diese Werkzeuge um aus Python Installer/EXEn zu machen, versuchen zu ermitteln was sie alles mit verpacken müssen. Das klappt bei reinem Python-Code ganz gut, aber bei in C geschriebenen Abhängigkeiten können die ja nicht in das Modul schauen um zu sehen was da noch so mit muss. Das muss man dann manuell angeben.
Anmerkungen zum Quelltext: `tqdm` wird importiert aber nirgends verwendet.
Auf Modulebene sollte nur Code stehen der Konstanten, Funktionen, und Klassen definiert. Das Hauptprogramm steht üblicherweise in einer Funktion die `main()` heisst. Besonders unübersichtlich wird es wenn man das Hauptprogramm auch noch mit Funktionsdefinitionen vermischt.
Kommentare sollen dem Leser einen Mehrwert über den Code geben. Faustregel: Kommentare beschreiben nicht *was* der Code macht, denn das steht da bereits als Code, sondern warum er das macht. Sofern das nicht offensichtlich ist. Offensichtlich ist in aller Regel auch was in der Dokumentation von Python und den verwendeten Bibliotheken steht.
Namen sollten nicht weit von ihrem Verwendungsort definiert werden. `email_gefunden` wird ja erst mehrere Zeilen später in einer Schleife benötigt. Wenn man den Code mal umschreibt, beispielsweise weil man Teile in eigene Funktionen/Methoden auslagern möchte, dann sollte zusammengehörender Code nahe beieinander stehen, damit man sich das nicht umständlicher zusammensuchen muss als es notwendig ist.
Der Code berücksichtig nicht wenn der Benutzer gar keinen Speicherpfad auswählt, sondern diesen Auswahldialog einfach abbricht.
Es darf nur *ein* `Tk`-Objekt geben. Das ist *das* Hauptfenster. Zusätzliche Fenster macht man mit `tkinter.Toplevel`. Allerdings gibt es ja ein unbenutzes `Tk`-Objekt. Das kann man ja einfach wieder anzeigen.
`variable` bei `Progressbar` wird nicht verwendet.
`enumerate()` kann man die Startzahl mitgeben, dann braucht man später nicht 1 auf `i` drauf addieren. `i` könnte auch besser heissen. Ebenso `item`, was ja offensichtlich eine `email` ist.
Statt `os.path` & Co verwendet man besser `pathlib`.
Warum wird der Dateiname in Name und Erweiterung aufgeteilt wenn diese beiden Dinge grundsätzlich wieder zusammengefügt werden‽
Die Tk-Hauptschleife wird nirgends aufgerufen, stattdessen wird `update()` verwendet. Das macht man nicht. Da können auch unschöne Sachen passieren, denn für das manuelle Aufrufen von `update()` gelten Regeln für die man sich mit Tk schon ganz gut auskennen muss. Und das skaliert auch überhaupt nicht wenn man anfängt die Hauptschleife selbst zu machen.
Da man hier mit einer Rückruffunktion arbeiten muss, die sich so einiges über die Aufrufe hinweg merken muss, kommt man um eine eigene Klasse nicht herum.
Ungetestet:
Code: Alles auswählen
#!/usr/bin/env python3
import datetime
from pathlib import Path
from tkinter import Tk, filedialog, messagebox, ttk
import win32com.client
class App(Tk):
def __init__(self):
Tk.__init__(self)
self.title("Emails werden durchsucht")
progress_bar = ttk.Progressbar(self, length=300, mode="determinate")
progress_bar.pack(padx=20, pady=20)
progress_percent = ttk.Label(self)
progress_percent.pack()
self.heutiges_datum = None
self.email_count = None
self.save_path = None
self.email_gefunden = None
def update_progress_bar(self, value):
self.progress_bar["value"] = value
self.progress_percent["text"] = f"{int(value)}%"
def _process_mails(self, emails, email_number=1):
try:
email = next(emails)
except StopIteration:
if self.email_gefunden:
messagebox.showinfo("Erledigt", "Anhänge heruntergeladen")
else:
messagebox.showinfo(
"NOT FOUND", "Keine Email von heute gefunden"
)
self.quit()
else:
#
# Nur E-Mails mit Anhängen (`Class`).
#
if (
email.Class == 43
and email.ReceivedTime.date() == self.heutiges_datum
):
for attachment in email.Attachments:
for code in ["prd", "abn"]:
if code in email.Subject:
prefix = code + "-"
break
else:
prefix = ""
#
# TODO Schauen ob die API bei `FileName` *garantiert*, dass
# da kein Pfad dabei ist, und diese *Sicherheitslücke*
# entsprechend behandeln.
#
# TODO Ist da überhaupt ein Dateiname garantiert? Und was
# ist wenn mehrere Mails dort den gleichen Dateinamen
# haben, sollen dann einfach vorhandene Dateien
# überschrieben werden? Generell vorhandene Dateien
# einfach überschreiben?
#
attachment.SaveAsFile(
str(Path(self.save_path, prefix + attachment.FileName))
)
self.email_gefunden = True
self.update_progress_bar(email_number / self.email_count * 100)
self.after(10, self.process_mails, emails, email_number + 1)
def process_mails(self, emails, save_path):
self.save_path = save_path
self.heutiges_datum = datetime.date.today()
self.email_count = len(emails)
self.email_gefunden = False
self._process_mails(iter(emails))
def main():
mail_folder = (
win32com.client.Dispatch("Outlook.Application")
.GetNamespace("MAPI")
.Folders.Item("ServiceDesk-DBVertrieb")
.Folders.Item("02 - Verfahren")
.Folders.Item("QMS/EMS")
)
app = App()
app.withdraw()
save_path = filedialog.askdirectory(
title="Speicherort für die heruntergeladenen Dateien auswählen"
)
if save_path:
app.deiconify()
app.after_idle(app.process_mails, mail_folder.Items, save_path)
app.mainloop()
if __name__ == "__main__":
main()
Bezüglich des `Filename` sind da noch zwei TODOs drin die man (IMHO dringend) angehen sollte.