ftp upload

Stellt hier eure Projekte vor.
Internetseiten, Skripte, und alles andere bzgl. Python.
Gremlin
User
Beiträge: 166
Registriert: Freitag 28. Mai 2010, 23:49

Ein Skript zum speichern von Dateien oder spiegeln ganzer Verzeichnisse auf einem FTP Server. (Kann auch TLS und SSH)
Es wird mindestens Python 2.7 benötigt und wer SSH nutzen möchte, braucht das Paramiko library.

ftpupload.py (Erste Version)
ftpupload.py (Zweite Version)
ftpupload.py (Dritte Version, 11. Oktober 2010)
ftpupload.py (Vierte Version, 13. März 2011)
ftpupload.py (Fünfte Version, 5. Juni 2011)
ftpupload.py (Letzte Version, 30. April 2012)

Code: Alles auswählen

usage: ftpupload.py [-h] [-q] [-a] [-s] [-t] [-k] [-f] [-tls | -ssh] [--ascii]
                    [--keepalive INT] [--keyfile PATH] [--certfile PATH]
                    [--skip PATTERN [PATTERN ...]] [--fileauth PERM]
                    [--dirauth PERM] [--rules PATH] [--types PATH]
                    [--destination PATH] [--timeout INT] [--user USER]
                    [--pass PASS] [--port PORT]
                    host data [data ...]

File and directory uploading/mirroring to a ftp server.

positional arguments:
  host                  The servers ip or hostname to connect to.
  data                  Files and directories to be uploaded.

optional arguments:
  -h, --help            show this help message and exit
  -q                    No output to the screen will be made.
  -a                    Uses active mode instead of passive. (Ignored if SSH!)
  -s                    Synchronizes the given directories.
                        ATTENTION: This will delete everything on the server
                                   that is not present on your local machine!
  -t                    Synchronizes based on the modification times,
                        instead of the filesize.
  -k                    Keep the modification times of stored files.
                        (If possible.)
  -f                    Every statement within data will be used as list file.
  -tls                  Use TLSv1 to secure the connection.
  -ssh                  Use SSH to secure the connection
  --ascii               Use ascii transfer instead of binary. (Ignored if SSH!)
  --keepalive INT       Send keepalive packets to an SSH protected server.
                        (Default: off)
  --keyfile PATH        The keyfile to use. (Only used if in TLS mode!)
  --certfile PATH       The certfile to use. (Only used if in TLS mode!)
  --skip PATTERN [PATTERN ...]
                        Skip files which matches pattern.
  --fileauth PERM       The permission to set for files.
  --dirauth PERM        The permission to set for directories.
  --rules PATH          The path to the rule file to use.
  --types PATH          The path to the type file to use.
  --destination PATH    The path where to upload the data. (Default: root)
  --timeout INT         Wait before canceling connection attempt. (Default: 5)
  --user USER           The user to login. (Default: anonymous)
  --pass PASS           The password for the given user.
  --port PORT           The servers port to use. (Default: 21)

**A list file works as follows**
If you do not want to specify the files or directories to upload within the
command line, you can use one or more text files instead. Every line will be
parsed and if a string refers to a valid file or directory, it will be stored.

**The rule file works as follows**
If you want more control over the permissions to set, you can create a rule
file. Within this file you can specify different permissions for different
filetypes. If the type of a file that has been uploaded is specified in this
rule file, this permission will be set, otherwise the global permission (If
given) will be set.
The syntax of the rule file is like a ini-file without sections:
    .txt=0700
    .zip=0755
    .php=0644

**The type file works as follows**
If you are uploading many files in a row, you can create a type file to control
which types of files are being transfered with ascii mode. Just enter every
type with its leading dot on a single line within this file.
Please note that this file will have no effect if you are using the --ascii
option.
Danke an Hyperion, lunar und Blackjack für die Hilfe und Tipps. ;)
Zuletzt geändert von Gremlin am Freitag 1. Juni 2012, 15:29, insgesamt 5-mal geändert.
Gremlin
User
Beiträge: 166
Registriert: Freitag 28. Mai 2010, 23:49

Siehe oben.
Zuletzt geändert von Gremlin am Dienstag 12. Oktober 2010, 18:56, insgesamt 1-mal geändert.
Benutzeravatar
Hyperion
Moderator
Beiträge: 7478
Registriert: Freitag 4. August 2006, 14:56
Wohnort: Hamburg
Kontaktdaten:

Wieso implementierst Du argparse bzw. optparse nach? Dadurch erhältst Du auch einen schicken Hilfe-String gratis dazu - und könntest Dir den manuell erstellten Docstring sparen.
encoding_kapiert = all(verstehen(lesen(info)) for info in (Leonidas Folien, Blog, Folien & Text inkl. Python3, utf-8 everywhere))
assert encoding_kapiert
Gremlin
User
Beiträge: 166
Registriert: Freitag 28. Mai 2010, 23:49

Hm, weil... ich hab ehrlich gesagt keinen "richtigen" Grund. Es gefällt mir einfach nicht, dass ich mit argparse nicht sehe was ich habe. Letztlich wars aber doch eher der Grund, dass ich argparse erst später entdeckt habe, diese ellen lange Doku gesehen habe und zufrieden war mit dem was ich habe.

Gibts irgendwas was argparse so viel besser macht als meine Lösung? (Mal abgesehen davon dass meine Lösung letztlich überflüssig ist^^)
Benutzeravatar
Hyperion
Moderator
Beiträge: 7478
Registriert: Freitag 4. August 2006, 14:56
Wohnort: Hamburg
Kontaktdaten:

Gremlin hat geschrieben: Gibts irgendwas was argparse so viel besser macht als meine Lösung? (Mal abgesehen davon dass meine Lösung letztlich überflüssig ist^^)
Naja, da ich mir Deinen Code nicht direkt angeguckt habe, kann ich das schwer sagen. Ich würde eben generell sagen, dass man NIH vermeiden sollte. Bei einem Modul aus der StandardLib (oder einem guten externen) kannst Du davon ausgehen, dass der Entwickler viel Expertise für das Problem mitbringt oder durch die Entwicklung erworben hat. Kommandozeilen-Parsing ist für Dich ja nicht Hauptzweck Deiner Applikation, da bietet sich einfach die Verwendung von spezialisierten Modulen an.
encoding_kapiert = all(verstehen(lesen(info)) for info in (Leonidas Folien, Blog, Folien & Text inkl. Python3, utf-8 everywhere))
assert encoding_kapiert
Gremlin
User
Beiträge: 166
Registriert: Freitag 28. Mai 2010, 23:49

Musste zwar erstmal nachgucken was NIH bedeutet ( :lol: ) aber ok, werds vielleicht beim nächsten Mal wenn mir wieder langweilig ist abändern. Angucken muss ichs mir sowieso irgendwann, da komm ich nich drumherum.
lunar

"upload()" ist viel zu lang, und enthält sicher einiges, was man in separate Funktion auslagern könnte. Zudem ist die Logik konfus implementiert und geht in den ganzen wilden Sprüngen mit break und continue unter. Das sollte aufgeräumt und in eine lesbare Form gebracht werden.

UploadFile ist eigentlich keine Klasse, sondern wird als "Funktionsersatz" missbraucht, da Exemplare dieser Klasse nie an Namen gebunden werden, sondern immer und ausschließlich nur zum sofortigen Aufruf von ".start()" dienen.
Gremlin
User
Beiträge: 166
Registriert: Freitag 28. Mai 2010, 23:49

hehe, upload() war auch mal kürzer, aber mir kams irgendwie unübersichtlicher vor, soviel auf eigene Funktionen zu verteilen und habs wieder zusammengefasst :roll:
Und was meinst du mit konfuser Implementierung? Für mich ist da nichts konfus :wink:, ein Beispiel wäre nett.
Ach und mal so ganz allgemein, was bedeutet eigentlich lesbar? Wann ist ein Pythonskript/modul lesbar und wann nicht?

Die Sache bei UploadFile ist die, dass ich eine Fortschrittsanzeige haben wollte. Nachdem man bei ftplib.storbinary() bzw. ftplib.storlines() keine Möglichkeit hat, eigene Parameter an die callback Funktion zu senden, hab ich mich für eine Klasse entschieden. Denn irgendwo her muss die callback Funktion doch wissen wozu sie gehört, oder?
Und was ist nun schöner? Eine Klasse wie ich sie implementiert habe oder jede Menge globale Variablen?
BlackJack

@Gremlin: Mit konfus ist gemeint, dass es ausser Dir *jetzt* niemand versteht und Du in einem halben Jahr sicher auch nicht mehr auf Anhieb.

Lesbar bedeutet wenn man die Funktionsweise einer Funktion verstehen kann. Wenn ich bei `upload()` die innersten bedingten Verzweigungen am unteren Ende der Funktion verstanden habe, sind mir ganz sicher die weiter oben schon wieder aus dem Gedächtnis. Das ist einfach zu viel und zu verschachtelt und mit ``continue`` sollte man sparsam umgehen. Das hat hier ähnliche Auswirkungen auf die Übersichtlichkeit wie GOTO in BASIC.

Zum "schöner", davon dass das im Auge des Betrachters liegt, würde ich sagen weder noch. Da die Rückruffunktion sowieso "privat" ist, könnte man sie auch lokal definieren.

Und wenn eine Klasse, dann würde ich nur das in eine Klasse packen was auch wirklich den Zustand braucht, nämlich die Fortschrittsanzeige. Die könnte man dann auch für andere Sachen wiederverwenden, wenn sie nicht so eng mit dem Hochladen verbandelt wäre.

Was die Komplexität auf jeden Fall schonmal verringern könnte. wäre ein `Logger`-Objekt, das weiss ob es "ruhig" sein soll oder nicht. Dann bräuchte es die ganzen ``if not silent``-Abfragen und das durchschleifen des Flags durch das ganze Programm nicht.
lunar

@Gremlin: Ein Beispiel ist das "synchronize"-Argument. Aus dem Quelltext geht der Zweck (dem Docstring nach Durchführung einer Synchronisierung statt einer einfachen Kopie) nicht unmittelbar hervor, da "synchronize" in "upload()" an verschiedensten Stellen ohne direkten logischen Zusammenhang verwendet wird. Zudem wird viel Quelltext dupliziert (vgl. z.B. Z. 300ff und Z 280ff.).

Zusammen mit den vielfältigen Sprüngen mittels "break" und "continue" führt das dazu, dass man viel hin und her springen muss (wie in bester BASIC-GOTO-Manier), um die Bedeutung einzelner Namen wie eben "synchronize" zu erfassen, und zudem viele lange, aber vollkommen identische Konstrukte an verschiedenen Stellen gedanklich identifizieren muss. upload() ist Spaghetti-Code in Reinform, eine undurchdachte ad-hoc-Entwicklung mit reichlich Copy&Paste-Programmierung (verzeih die deutlichen Worte), und daher für andere keinesfalls gut lesbar (und für Dich in sechs Monaten bestimmt auch nicht mehr).

"upload()" sollte folglich in verschiedene Funktionen mit klarer Verantwortlichkeit getrennt werden, z.B. eine Funktion zur Kopie einer Datei, eine Funktion zur Kopie eines gesamten Verzeichnisses und eine Funktion zur Synchronisierung, inklusiver zugehöriger Hilfsfunktionen wie beispielsweise eine für das Anpassen der Berechtigungen.

Die Callback-Methode von "UploadFile" könnte man genauso gut als lokale oder partielle Funktion implementieren (ganz ohne Klasse oder globale Variablen). Wenn als Klasse (falls Dir diese Konzepte nichts sagen), dann aber zumindest als eigenen Funktor nur für die Fortschrittsanzeige. "UploadFile" an sich ist dagegen keinesfalls eine eigene Klasse wert.
Gremlin
User
Beiträge: 166
Registriert: Freitag 28. Mai 2010, 23:49

BlackJack hat geschrieben:mit ``continue`` sollte man sparsam umgehen
Das kann ich nur schwer nachvollziehen. Wozu gibt es denn sonst dieses Statement? Grade wenn so viel Wert auf die 79 Zeichen pro Zeile gelegt wird, ist es überaus nützlich, denn sonst wäre die andere Alternative doch nur noch mehr einzurücken, oder etwa nicht?
lunar hat geschrieben:Zusammen mit den vielfältigen Sprüngen mittels "break" und "continue"
Das find ich jetzt unfair, break benutze ich genau einmal :roll:


Zum Rest: Da werd ich wohl demnächst viel haben um mir meine Zeit zu vertreiben, danke :lol:
Und es ist nur upload() und UploadFile(), abgesehen von getoptions() :roll:, was überarbeitet werden sollte? (Sofern ihr euch das Skript so weit angesehen habt.)
BlackJack

@Gremlin: Gute Frage wozu es ``continue`` gibt. Auf jeden Fall nicht zur Vermeidung von tief verschachtelten Programmstrukturen, die sollte man IMHO nämlich *grundsätzlich* vermeiden.

Das Problem mit ``continue`` ist, dass man immer tief in die Verschachtelungen hinein schauen muss um die Wirkung von dieser Anweisung auf höher gelegene Ebenen zu sehen. Wenn man sich folgenden Quelltext anschaut:

Code: Alles auswählen

if x == y:
    for ...:
        if ...:
            ...
if a != b:
    ...
foobar(x, a)
Könnte man denken der Programmfluss wäre ein ``if``, gefolgt von einem ``if``, gefolgt von einem Funktionsaufruf. Das stimmt aber nur solange kein ``continue`` irgendwo in den Verzweigungen vorkommt. Dann muss man plötzlich den *ganzen* Quelltext bis ins kleinste Detail verstehen bevor man wissen kann unter welchen Bedingungen der scheinbar bedingungslose Aufruf von `foobar()` denn nun eigentlich wirklich stattfindet.
lunar

@Gremlin: Die Alternative ist nicht, weitere Einrückungsebenen einzuführen (deren hat Dein Programm eh bereits zu viele), sondern das Programm mittels Funktionen so zu strukturieren, dass tiefe Einrückungsebenen überhaupt gar nicht notwendig sind.

Die konkrete Anzahl der Vorkommen von "break" und "continue" habe ich nicht gezählt, und insofern einfach verallgemeinert. Ob Du das unfair findest, ist mir egal. Dein Programm verwendet unabhängig von der konkreten Anweisung schlicht zu viele derartige Sprünge.

Neben "upload()" und "UploadFile()" kann man sicherlich noch einiges verbessern, z.B.

Code: Alles auswählen

dirs = [d for d in dirs if not d.startswith('.')]
statt

Code: Alles auswählen

for d in dirs[:len(dirs)]:
    if d.startswith('.'):
        dirs.remove(d)
Das aber sind Details, das eigentliche Problem des Quelltexts ist die chaotische Struktur.
Gremlin
User
Beiträge: 166
Registriert: Freitag 28. Mai 2010, 23:49

BlackJack hat geschrieben:Das Problem mit ``continue`` ist, ...
Danke. Jetzt kams rüber. :)
lunar hat geschrieben:...dass tiefe Einrückungsebenen überhaupt gar nicht notwendig sind.
Wann ist eine Einrückung eigentlich "tief?"
lunar hat geschrieben:Neben "upload()" und "UploadFile()" kann man sicherlich noch einiges verbessern, z.B.
Hach ja, da sieht man mal wieder: Es zu kennen reicht nicht, man sollte auch wissen wann man es anwendet...
lunar

@Gremlin: Es gibt auch für „tiefe“ Einrückungsebenen keine Definition, doch sieben Ebenen sind zu viel. Das Problem ist dann, das man sich beim Lesen der innersten Ebene dann alle höheren Ebenen vergegenwärtigen muss, um festzustellen, ob und wann der gerade betrachtete Quelltext überhaupt erreichbar ist, was umso mehr erschwert wird, wenn die inneren Ebenen noch so lang sind, dass man die Bedingungen der höheren Ebenen gar nicht mehr in ein Fenster bekommt. Dann muss man zum Verständnis des Quelltexts wieder viel hin und her springen, was der Lesbarkeit sehr abträglich ist.
Benutzeravatar
snafu
User
Beiträge: 6738
Registriert: Donnerstag 21. Februar 2008, 17:31
Wohnort: Gelsenkirchen

Und abgesehen vom Hin- und Herscrollen kann man auch besser in Teilstücken denken, wenn diese klar benannt sind. Zum einen muss man bei guten sprechenden Namen häufig gar nicht erst in die Funktion reingucken bzw kann dies später machen, weil die Aufgabe der Funktion grob erraten werden kann und zum anderen dient so ein Name prima als Gedächtnisstütze, um den Funktionsinhalt gedanklich wieder schnell abrufen zu können.
Gremlin
User
Beiträge: 166
Registriert: Freitag 28. Mai 2010, 23:49

So, hab nun versucht möglichst alles zu berücksichtigen und glaube das auch geschafft zu haben. argparse ist jetzt auch drin. ;) (Auch wenn ich gefühlt genauso lang gebraucht habe die Doku durchzuarbeiten, wie meine eigene Funktion zu schreiben. :roll: )

ftpupload.py
lunar hat geschrieben:

Code: Alles auswählen

dirs = [d for d in dirs if not d.startswith('.')]
Das allerdings geht nicht. os.walk verwendet die Referenz und nicht den Namen. (Ist mir auch erst nach einiger Zeit klar geworden :roll: )

Zusätzlich hab ich auch noch TLS/SSL und SSH Funktionalität eingebaut und ein paar Bugs gefixt. (Auch wenn der Funktionsumfang von SSH sicher (noch?) nicht vollständig implementiert ist.)


Ist es eigentlich möglich, Einträge im PasteBin zu löschen?
lunar

@Gremlin: Aha, ich habe nicht bemerkt, dass diese Liste von "os.walk()" kam, bzw. dass Du diese Liste verändern möchtest, um "os.walk()" daran zu hindern, versteckte Verzeichnisse zu durchsuchen. In diesem Fall aber musst Du über eine Kopie der Liste iterieren, man kann nicht gleichzeitig iterieren und aus derselben Liste Elemente entfernen.

Den "Namen" kann "os.walk()" im Übrigen gar nicht "verwenden", da der Funktionskörper und der Aufrufer getrennte, vollkommen isolierte Namensräume haben. Das ist der Unterschied zwischen dem Objekt und seinem Namen.
Gremlin
User
Beiträge: 166
Registriert: Freitag 28. Mai 2010, 23:49

lunar hat geschrieben:Aha, ich habe nicht bemerkt, dass diese Liste von "os.walk()" kam, bzw. dass Du diese Liste verändern möchtest, um "os.walk()" daran zu hindern, versteckte Verzeichnisse zu durchsuchen.
Natürlich, war falsch formuliert :)
lunar hat geschrieben:In diesem Fall aber musst Du über eine Kopie der Liste iterieren, man kann nicht gleichzeitig iterieren und aus derselben Liste Elemente entfernen.
Grrr... Ja, danke. Wär mir wohl auch erst sonst irgendwann aufgefallen :roll:
Gremlin
User
Beiträge: 166
Registriert: Freitag 28. Mai 2010, 23:49

Da mir letzte Woche aufgefallen ist, dass hier die Möglichkeit fehlt den ASCII Übertragungsmodus zu aktivieren, hab ich das doch glatt mal nachimplementiert. Außerdem ist mir aufgefallen, dass das hier sowieso noch eine veraltete Version ist. Hab nach dem letzten Mal total vergessen das neue Skript ins Pastebin zu packen..

Folgende Änderungen:
  • Neue --ascii Option.
  • Neue --type Option.
  • Neue --keepalive Option.
  • Und endlich den Bug den lunar zuletzt bemängelt hatte gefixt. :oops:
Ach, ich hab auch noch die alten Pastebin-links im ersten Post wieder eingetragen.
Antworten