Kleine Überprüfung meines Programms

Wenn du dir nicht sicher bist, in welchem der anderen Foren du die Frage stellen sollst, dann bist du hier im Forum für allgemeine Fragen sicher richtig.
Antworten
Hellstorm
User
Beiträge: 231
Registriert: Samstag 22. Juni 2013, 15:01

Hallo zusammen :)

Ich wollte fragen, ob jemand eventuell mein kleines Programm überprüfen kann, so wie ich es bis jetzt gemacht habe (Damit ich keine großen Fehler eingebaut habe)? Es lädt eine Datei über SFTP hoch. Ist noch etwas grob und einige Funktionen fehlen noch, aber zumindest funktioniert es schon einmal halbwegs.

Zum Aufbau:

uploader.py mit der Klasse Uploader, die das Hochladen übernimmt.
uploader_cmd.py: Ist die Kommandozeilenversion, eine GUI-Version soll noch irgendwann folgenden. Diese Datei wird ausgeführt.
hilfsfunktionen.py mit einigen Funktionen, die sowohl in der Kommandozeilenversion als auch in der GUI-Version genutzt werden sollen, weswegen ich sie in eine seperate Datei ausgelagert habe.

Also zuerst dann die uploader.py:

Code: Alles auswählen

from os.path import getsize, split
import sys
from time import time

try:
    import paramiko
except ImportError:
    print("Bitte das Modul paramiko installieren.")
    print("Muss Python 3-kompatibel sein,\
    z.B. https://github.com/nischu7/paramiko/")
    sys.exit(1)

class Uploader(object):
    def __init__(self, einstellungen):
        """Konstruktor, der das Objekt initialisiert, zum SSH-Server verbindet 
        und anfängliche Einstellungen macht"""
        
        print("Verbinde mit Server {0}...".format(einstellungen["hostname"]))
        paramiko.util.log_to_file("paramiko.log")
        self.sshclient = paramiko.SSHClient()
        self.sshclient.set_missing_host_key_policy(paramiko.WarningPolicy())
        self.sshclient.load_system_host_keys()
        self.sshclient.connect(einstellungen["hostname"], 
                                port = int(einstellungen["port"]), 
                                username = einstellungen["benutzername"], 
                                password = einstellungen["passwort"], 
                                allow_agent = False)
                                
        self.remoteverzeichnis = einstellungen["remoteverzeichnis"]
        self.datenbank = einstellungen["datenbank"]

        #Initialer Eintrag für die Prozentanzeige
        self.letzter_aufruf = time()
        self.letzte_bytes = 0
        
        #TODO: Initiale Konfiguration auf dem Server:
        #Schauen, ob uploadverzeichnis angelegt ist,
        #ob loeschen.py da ist,
        #usw.
        

    def hochladen(self, datei, ablaufzeit):
        """Lädt die Datei über SFTP hoch und führt die hinzufuegen.py
        auf dem Server aus, um den Eintrag in die Datenbank zu speichern

        Nimmt den Dateipfad und die Ablaufzeit (+ wieviel Sekunden)
        in Sekunden entgegen."""

        dateiname = split(datei)[1]

        #FIXME: Wenn Datei 0 Byte groß ist, gibt es einen Fehler.
        
        print("Lade Datei „{0}“ ({1}) hoch...".format(dateiname, 
                        groesse_zusammenfuegen(groesse_lesbar_machen(getsize(datei)))))
        zeit_vorher = time()
        self.letzter_aufruf = time()
        sftpverbindung = self.sshclient.open_sftp()
        sftpverbindung.put(datei, 
                            "{0}{1}".format(self.remoteverzeichnis, dateiname), 
                            callback=self.fortschritt_anzeigen)
        #FIXME: Wenn das remoteverzeichnis nicht auf / endet, geht es nicht.
        sys.stdout.write("\n")  # Um die nächste Zeile anzufangen.

        zeitdauer = time() - zeit_vorher
        if zeitdauer == 0:
            zeitdauer = 1  # Damit keine Division durch Null passiert.
        geschwindigkeit = getsize(datei) / zeitdauer
        geschwindigkeit_lesbar = groesse_lesbar_machen(geschwindigkeit)
        formatierte_geschwindigkeit = "{0:.2f} {1}/s".format(
                                        geschwindigkeit_lesbar[0],
                                        geschwindigkeit_lesbar[1])

        print("Datei in {0:.2f} Sekunden hochgeladen ({1})".format(
                                        zeitdauer,
                                        formatierte_geschwindigkeit))

        #In Datenbank eintragen.
        try:
            self.sshclient.exec_command("echo \"{0}{1},{2}\" >> {3}".format(
                                        self.remoteverzeichnis,
                                        dateiname,
                                        ablaufzeit, 
                                        self.datenbank))
        except SSHException as e:
            print("Fehler beim Hochladen: {}".format(e))
        #TODO: Auf Fehler überwachen und gucken, ob das richtig geklappt hat.
        #TODO: Kommata in Dateinamen escapen

    def fortschritt_anzeigen(self, bytes_uebertragen, bytes_gesamt):
        """Callback-Funktion, die die Geschwindigkeit auf der Konsole anzeigt."""
        bytes_differenz = bytes_uebertragen - self.letzte_bytes
        zeit_differenz = time() - self.letzter_aufruf
        if zeit_differenz == 0:
            zeit_differenz = 1
        geschwindigkeit = (bytes_differenz / zeit_differenz) / 1024  #in KiB
        prozent = (bytes_uebertragen / bytes_gesamt) * 100

        #Konsolenausgabe
        sys.stdout.write("\r{0:.1f} % übertragen. Geschwindigkeit: {1:.0f} KiB/s".format(prozent, geschwindigkeit))
        sys.stdout.flush()
        #TODO: Bei Python3.3 kann hier print() mit flush benutzt werden.
        #TODO: Geschwindigkeit soll nur jede Sekunde o.ä. verändert werden.

        #Für Geschwindigkeitsdifferenz Zeit und Bytes
        #des aktuellen Durchlaufs speichern.
        self.letzter_aufruf = time()
        self.letzte_bytes = bytes_uebertragen
        
    def anfangseinstellungen_erstellen(self):
        #TODO: Gucken, ob alles angelegt ist, ansonsten herüberkopieren
        pass

def groesse_lesbar_machen(dateigroesse):
    """Konvertiert eine Zahl in Bytes in KiB/MiB/... und gibt eine Liste mit
    der Dateigröße und dem Praefix zurück, z.B. (100, 'KiB')"""

    einheiten = (
                    (1024 ** 5, "PiB"),
                    (1024 ** 4, "TiB"),
                    (1024 ** 3, "GiB"),
                    (1024 ** 2, "MiB"),
                    (1024 ** 1, "KiB"),
                    (1024 ** 0, "B")
                )

    for faktor, praefix in einheiten:
        if dateigroesse >= faktor:
            return (dateigroesse / faktor,  praefix)

def groesse_zusammenfuegen(groesse_getrennt):
    """Fügt die getrennte Größe aus groesse_lesbar_machen 
    zu einem String zusammen"""
        
    return ("{0:.2f} {1}".format(groesse_getrennt[0], groesse_getrennt[1]))

Dann hilfsfunktionen.py:

Code: Alles auswählen

from os.path import split
import configparser
import zipfile
import pyperclip
#http://coffeeghost.net/src/pyperclip.py"
import urllib.parse

def finde_einstellung(liste, wert):
    """Gibt ein Listenelement (ein Wörterbuch) aus einer Liste zurück, welches 
    den Eintrag wert hat. 
    
    Die Liste besteht aus mehreren Wörterbüchern."""
    
    #TODO: Nur zurückgeben, wenn auch der Schlüssel passt.
    #TODO: Noch Fehlerbehandlung einbauen, wenn nichts gefunden wird.
    
    for eintrag in liste:
        if wert in eintrag.values():
            return eintrag
    
def zippen(dateiname, dateien):
    """Zippt mehrere Dateien und speichert sie unter dem Namen dateiname"""
    
    with zipfile.ZipFile(dateiname, "w") as zipdatei:
        for datei in dateien:
            zipdatei.write(datei, split(datei)[1])
            #TODO: Unicode-Dateien

def kopiere_in_zwischenablage(eintraege, trenner="\n"):
    """Kopiert die Einträge in die Zwischenablage. Mehrere Einträge werden durch
    standardmäßig durch eine neue Zeile getrennt 
    (kann mit trenner angepasst werden)"""
    
    zusammengefuegt = trenner.join(eintraege)
    pyperclip.copy(zusammengefuegt)
    
def webadresse_erstellen(adresse, dateiname):
    """Fügt den Domain- und Ordnerteil zusammen mit dem Dateinamen zu einer
    URL zusammen. Dabei werden Sonderzeichen ersetzt."""
   
    return adresse + urllib.parse.quote(dateiname)

def neue_konfiguration_speichern(konfigurationsset, einstellungsdatei):
    """Speichert die Konfiguration eines neu eingelesenen Servers in eine 
    configparser-Datei.
    konfigurationsset ist ein Dictionary mit den Einstellungen,
    einstellungsdatei die Datei in die gespeichert werden soll"""
    
    #TODO: Nur hinzufügen, nicht überschreiben.
    konfiguration = configparser.ConfigParser(interpolation=None)
    konfiguration[konfigurationsset["name"]] = konfigurationsset
    
    with open(einstellungsdatei, "w") as konfigurationsdatei:
       konfiguration.write(konfigurationsdatei)
       
def gespeicherte_konfiguration_lesen(einstellungsdatei):
    """Liest gespeicherte Konfigurationen aus der Einstellungsdatei 
    und gibt sie zurück"""
    
    serverliste = []
    
    konfiguration = configparser.ConfigParser(interpolation=None)
    konfiguration.read_file(open(einstellungsdatei))
    konfigurationsliste = list(konfiguration.sections())
    
    for eintrag in konfigurationsliste:
        einstellungen = konfiguration.items(eintrag)
        einstellungen.append(("name", eintrag))
        serverliste.append(dict(einstellungen))
    
    return serverliste
Und schließlich uploader_cmd.py:

Code: Alles auswählen

#! /usr/bin/env python3


import uploader
from hilfsfunktionen import *

from os.path import splitext, split
from getpass import getpass
from time import time
from collections import OrderedDict
import argparse

def zeit_trennen(zeit):
    """Trennt einen String im Format „15min“ oder „15 min“ in
    ein die Zahl und die Einheit."""
    
    zeiteinheit = zeit.lstrip("0123456789 ")
    zeitwert = int(zeit[:-len(zeiteinheit)])
    return zeitwert, zeiteinheit

def zeit_umwandeln(zeitwert, zeiteinheit):
    """Multipliziert den Wert mit der Zeiteinheit.
    
    Erwartet einen Integer bei zeitwert und (s | min | h | d | w | m | a) 
    bei zeiteinheit. Ein Monat wird mit 30 Tagen berechnet, ein Jahr mit 365 Tagen"""
    
    einheiten = {
                "s": 1, 
                "min": 60, 
                "h": 3600, 
                "d": 3600 * 24, 
                "w": 3600 * 24 * 7,
                "m": 3600 * 24 * 7 * 30, 
                "a": 3600 * 24 * 7 * 30 * 365}
        
    return zeitwert * einheiten[zeiteinheit]

def neue_konfiguration_einlesen():
    """Liest eine neue Serverkonfiguration von der Konsole ein und gibt ein
    Konfigurationsset in Form eines Dictionarys zurück."""
    
    servername = input("Servername: ")
    hostname = input("Host-IP-Adresse: ")
    port = input("Port: ")
    benutzername = input("Benutzername: ")
    passwort = getpass("Passwort: ")
    remoteverzeichnis = input("Wo sollen die Dateien auf dem Server gespeichert werden? ")
    webadresse = input("Wie ist die Webadresse vom Server? ")
    datenbank = input("Wo soll die Datenbank gespeichert werden? (eine csv-Datei angeben)")
    
    konfiguration = OrderedDict()
    konfiguration["name"] = servername
    konfiguration["hostname"] = hostname
    konfiguration["port"] = port
    konfiguration["benutzername"] = benutzername
    konfiguration["passwort"] = passwort
    konfiguration["remoteverzeichnis"] = remoteverzeichnis
    konfiguration["webadresse"] = webadresse
    konfiguration["datenbank"] = datenbank

    return konfiguration

def main():
    parser = argparse.ArgumentParser("Ein Uploader, der zu einem SSH-Server hochlädt.")
    parser.add_argument("-s", "--server", type=str, 
                        help="Wählt einen Server mit Namen aus", required=True)
    parser.add_argument("-a", "--ablaufzeit", help="Ablaufzeit", default="1d", 
                        required=True)
    parser.add_argument("--einstellungsdatei", help="Pfad zur Einstellungsdatei", 
                        default="test.ini")
    parser.add_argument("-z", "--zippen", help="Zippt die Dateien.")
    parser.add_argument("dateien", nargs="+", help="Hochzuladenen Dateien")
    args = parser.parse_args()
    
    einstellungsdatei = args.einstellungsdatei

    print("Willkommen zum Uploader!")
    
    if args.server != None:
        serverliste = gespeicherte_konfiguration_lesen(einstellungsdatei)
        server = finde_einstellung(serverliste, args.server)
    
    print("Lade auf den Server {0} hoch.".format(server["name"]))
    
    hochgeladene_dateien = []  #Liste mit allen hochgeladenen Dateien
    
    if len(args.dateien) > 1:
        if args.zippen != None:
            zipdateiname = args.zippen
            if splitext(args.zippen)[1] != ".zip":
                zipdateiname = args.zippen + ".zip"
            #TODO: Ein temporäres Verzeichnis bestimmen, damit es nicht in den Programmordner speichert.
            zippen(zipdateiname, args.dateien)
            hochladeliste = [zipdateiname]
        else:
            hochladeliste = [eintrag for eintrag in args.dateien]
    else:
        hochladeliste = args.dateien
        
    zeitwert, zeiteinheit = zeit_trennen(args.ablaufzeit)
    ablaufzeit = int(time()) + zeit_umwandeln(zeitwert, zeiteinheit)
    
    hochlader = uploader.Uploader(server)

    #Dateien einzeln hochladen
    for eintrag in hochladeliste:
        hochlader.hochladen(eintrag, ablaufzeit)
        hochgeladene_dateien.append(webadresse_erstellen(
                                server["webadresse"], split(eintrag)[1]))

    kopiere_in_zwischenablage(hochgeladene_dateien)
    
    #TODO: Wenn erfolgreich, dann Datei löschen.
    #os.remove(zipdateiname)
    
    print("Erfolgreich folgende Dateien hochgeladen:")
    for eintrag in hochladeliste:
        print(split(eintrag)[1])
        
if __name__ == "__main__":
    main()
Schon einmal danke an jeden, der sich durch den unübersichtlichen und langen Quelltext wälzt :)
karolus
User
Beiträge: 141
Registriert: Samstag 22. August 2009, 22:34

Hallo

Ich zitiere mal PeterMaffay:
Über sieben Brücken musst du gehn, sieben dunkle .... überstehen.
( Bei den Monaten siehts auch nicht besser aus )

Karolus
Antworten