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]))
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
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()