[WSGI] uploader.py

Stellt hier eure Projekte vor.
Internetseiten, Skripte, und alles andere bzgl. Python.
Antworten
Benutzeravatar
jens
Moderator
Beiträge: 8482
Registriert: Dienstag 10. August 2004, 09:40
Wohnort: duisburg
Kontaktdaten:

Montag 21. November 2005, 15:44

EDIT: Die neuste Version pack ich in mein SVN: http://pylucid.python-hosting.com/brows ... _uploader/

Hier ist die erste Version meines Uploaders...

Code: Alles auswählen

#!/usr/bin/env python
# -*- coding: utf-8 -*-

""" uploader
used WSGI: http://wsgiarea.pocoo.org by Armin Ronacher <armin.ronacher@active-4.com>

Konfiguration:
==============
 only_https
    Erlaubt nur HTTPs zugriffe, ermittelt mit os.environ["HTTPS"] == "on"
    
 only_auth_users
    Erlaubt nur "eingeloggten" Usern den Zugriff. Dabei ist die Apache-Basic-Auth. mit
    .htaccess und .htpasswd gemeint s. http://httpd.apache.org/docs/2.0/howto/auth.html
    ermittelt mit: os.environ.has_key("AUTH_TYPE") and os.environ.has_key("REMOTE_USER")
    
 send_email_notify = True
    Sendet bei jedem gemachten Upload eine eMail mit Informationen.
    
Ablauf:
=======
 Zu jeder hoch geladenen Datei wird eine *.nfo-Textdatei erstellt. Diese ist im
 ConfigParser-ini-Format. Es werden Informationen zur Datei gespeichert. u.a.:
    * fileinfo: Ergebniss des Linux 'file'-Befehls
    * client_info: Username (nur bei only_auth_users=True) und socket.getfqdn()-Info
    
 Beim generieren der Dateiliste werden die *.nfo-Dateien wieder ausgelesen.
"""

__author__      = "Jens Diemer"
__url__         = "http://www.jensdiemer.de"
__license__     = "GNU General Public License (GPL)"
__description__ = "a small upload-form used WSGI"
__version__     = "0.1"

__info__        = 'uploader v%s' % __version__

__history__ = """
v0.1
    - erste Version
"""

from __future__ import generators

import sys, os, time, socket, ConfigParser

from wsgi.middlewares import debug; debug.cgi_debug()
from wsgi.wrappers.basecgi import WSGIServer

from wsgi import tools, http

only_https = True
only_auth_users = True
send_email_notify = True
notifer_email_from_adress = "auto_mailer@foobar.de"
notifer_email_to_adress = "uploader@foobar.de"
bufsize = 8192
upload_dir = "uploads"



html_head = """
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<title>File Upload</title>
<style type="text/css">
html, body {
    padding: 10px;
    background-color: #EEF8FF;
}
body {
    font-family: tahoma, arial, sans-serif;
    color: #000000;
    font-size: 0.9em;
    background-color: #DBF1FF;
    margin: 10px;
    padding: 20px;
    border: 3px solid #3CBFFF;
}
table.filelist {
    border: 1px solid #3CBFFF;
}
.filelist td {
	padding-left: 5px;
	padding-right: 5px;
}
#footer {
  font-size: 0.6em;
}
#footer, #footer a {
  margin-top: 1em;
  border-top: 1px solid #3CBFFF;
  color: #3CBFFF;
  text-decoration:none;
  text-align: right;
}
</style>
</head>
<body>"""


html_form = """
    <h1>File Upload</h1>
    <form action="" method="post" enctype="multipart/form-data">
        <p><input type="file" name="upload" size="40" />
        <input type="submit" value="upload" /></p>
    </form>"""


html_footer = """<div id="footer">%s by <a href="http://www.jensdiemer.de">Jens Diemer</a></div>
</body></html>""" % __info__


email_notify_text = """
A person '%(client_info)s' used the uploader.

File information:
%(fileinfo)s

--
This is a automaticly constructed notify message.
created with %(info)s
"""




class Application:

    def __init__(self, environ, start_response):
        self.environ = environ
        self.start_response = start_response

    def incoming(self, formdata):
        filename = os.path.join(upload_dir, formdata['upload']['filename'])

        try:
            client_info = socket.getfqdn(os.environ["REMOTE_ADDR"])
        except Exception, e:
            client_info = "Error: %s" % e

        if only_auth_users == True:
            # Usernamen hinzufügen
            client_info = "%s - %s" % (os.environ["REMOTE_USER"], client_info)

        file_obj = formdata['upload']['descriptor']
        bytesreaded = 0
        try:
            f = file(filename, 'wb')
            time_threshold = start_time = int(time.time())
            while 1:
                data = file_obj.read(bufsize)
                if not data:
                    break
                bytesreaded += len(data)
                f.write(data)

                current_time = int(time.time())
                if current_time > time_threshold:
                    yield "<p>read: %sBytes (%s)</p>" % (
                        bytesreaded, time.strftime("%H:%M:%S", time.localtime())
                    )
                    time_threshold = current_time

            end_time = time.time()
            f.close()

            performance = bytesreaded / (end_time-start_time) / 1024
            yield "<h2>File saved (%.2fKByes/sec.)</h2>" % performance
        except Exception, e:
            yield "<h2>ERROR: Can't write file: %s</h2>" % e
            return
        else:
            try:
                self.write_info_file(client_info, filename, bytesreaded)
                f = file(filename+".nfo", "rU")
                fileinfo = f.read()
                f.close()
            except Exception, e:
                yield "<h2>ERROR: Can't write info file: %s</h2>" % e
                fileinfo = "ERROR: %s" % e

        if send_email_notify == True:
            email().send(
                from_adress = notifer_email_from_adress,
                to_adress   = notifer_email_to_adress,
                subject     = "uploaded: '%s' from '%s'" % (filename, client_info),
                text        = email_notify_text % {
                    "client_info"   : client_info,
                    "fileinfo"      : fileinfo,
                    "info"          : "%s (Python v%s)" % (__info__, sys.version),
                }
            )

        yield '<a href="?">continue</a>'

    def view_uploaded_files(self):
        yield "<h2>Filellist:</h2>"

        yield '<table class="filelist">\n'
        for filename in os.listdir(upload_dir):
            if filename.endswith(".nfo"):
                continue
            yield "<tr>\n"
            yield "\t<td>%s</td>\n" % filename

            try:
                client_info, size, fileinfo, upload_time = self.read_info_file(filename)
            except Exception, e:
                yield '\t<td colspan="4"><small>Error, get fileinfo: %s</small></td>\n' % e
            else:
                yield "\t<td>%s</td>\n" % client_info
                yield '\t<td align="right">%0.2fKB</td>\n' % (size/1024.0)
                yield "\t<td>%s</td>\n" % upload_time
                yield "\t<td>%s</td>\n" % fileinfo

            yield "</tr>\n"
        yield "</table>\n"

    def get_file_info(self, filename):
        """ Datei Information mit Linux 'file' Befehl zusammentragen """
        file_cmd_out = os.popen("file '%s'" % filename).readlines()[0]
        file_cmd_out = file_cmd_out.split(":",1)[1]
        return file_cmd_out

        if file_cmd_out.find("ERROR") != -1:
            # Ersatz für >"ERROR" in fileinfo< ;)
            return ""
        else:
            return file_cmd_out

        return mtime, size, file_cmd_out

    def write_info_file(self, client_info, filename, bytesreaded):
        f = file(filename+".nfo", "wU")
        config = ConfigParser.ConfigParser()
        config.add_section("info")

        config.set("info", "client_info", client_info)
        config.set("info", "filename", filename)
        config.set("info", "bytes", bytesreaded)
        config.set("info", "fileinfo", self.get_file_info(filename))
        config.set("info", "upload_time", time.strftime("%d %b %Y %H:%M:%S", time.localtime()))

        config.write(f)
        f.close()

    def read_info_file(self, filename):
        filename = os.path.join(upload_dir, filename)
        f = file(filename+".nfo", "rU")
        config = ConfigParser.ConfigParser()
        config.readfp(f)
        f.close()

        client_info = config.get("info", "client_info")
        size        = int(config.get("info", "bytes"))
        fileinfo    = config.get("info", "fileinfo")
        upload_time = config.get("info", "upload_time")

        return client_info, size, fileinfo, upload_time

    def __iter__(self):
        self.start_response(http.get_status(200), [('Content-Type', 'text/html')])
        yield html_head

        if only_https == True:
            # Nur https Verbindungen "erlauben"
            if os.environ.get("HTTPS", False) != "on":
                yield "<h2>Error: Only HTTPs connections allow!</h2>"
                link = "https://%s%s" % (os.environ["HTTP_HOST"], os.environ["SCRIPT_NAME"])
                yield '<h4>try: <a href="%s">%s</a></h4>' % (link, link)
                yield html_footer
                return

        if only_auth_users == True:
            # Der User muß über Apache's basic auth eingeloggt sein
            if not (os.environ.has_key("AUTH_TYPE") and os.environ.has_key("REMOTE_USER")):
                yield "<h2>Error: Only identified users allow!</h2>"
                yield html_footer
                return

        params = tools.get_parameters(self.environ)
        if not 'do' in params or params['do'] == 'upload':
            formdata = tools.get_files(self.environ)
            if formdata:
                for line in self.incoming(formdata): yield line
            else:
                yield html_form
                for line in self.view_uploaded_files(): yield line

        else:
            raise RuntimeError, 'Invalid Request'

        yield html_footer



class email:
    def send(self, from_adress, to_adress, subject, text):
        import time, smtplib
        from email.MIMEText import MIMEText

        msg = MIMEText(
            _text = text,
            _subtype = "plain",
            _charset = "UTF-8"
        )
        msg['From'] = from_adress
        msg['To'] = to_adress
        msg['Subject'] = subject
        # Datum nach RFC 2822 Internet email standard.
        msg['Date'] = time.strftime("%a, %d %b %Y %H:%M:%S +0000", time.gmtime())
        msg['User-Agent'] = "%s (Python v%s)" % (__info__, sys.version)

        s = smtplib.SMTP()
        s.connect()
        s.sendmail(msg['From'], [msg['To']], msg.as_string())
        s.close()


#___________________________________________________________________________

if __name__ == '__main__':
    app = Application
    WSGIServer(app).run()
Was ich doof finde... Bei großen Dateien sieht man erstmal nix... Aber ich glaube das läßt sich nicht ändern... Ich hatte zwar diese gepufferte while-Schleife extra dafür eingebaut, die Statusinformationen abgiebt, aber ich glaube das alles kommt erst zum tragen, wenn der Upload schon geschehen ist... Die Preformanze anzeige spiegelt auch IHMO nur die Platten-Schreibgeschwindigkeit mit und nicht die Übertragungszeit :(

Wie kann man das besser machen???

Schön wäre ja ein AJAX Upload. Aber JS dürfen keine lokalen Dateien öffnen, was ja auch gut ist ;)
Zuletzt geändert von jens am Donnerstag 15. Dezember 2005, 16:12, insgesamt 1-mal geändert.

CMS in Python: http://www.pylucid.org
GitHub | Open HUB | Xing | Linked in
Bitcoins to: 1JEgSQepxGjdprNedC9tXQWLpS424AL8cd
mitsuhiko
User
Beiträge: 1790
Registriert: Donnerstag 28. Oktober 2004, 16:33
Wohnort: Graz, Steiermark - Österreich
Kontaktdaten:

Montag 21. November 2005, 19:59

jens hat geschrieben:Was ich doof finde... Bei großen Dateien sieht man erstmal nix... Aber ich glaube das läßt sich nicht ändern... Ich hatte zwar diese gepufferte while-Schleife extra dafür eingebaut, die Statusinformationen abgiebt, aber ich glaube das alles kommt erst zum tragen, wenn der Upload schon geschehen ist... Die Preformanze anzeige spiegelt auch IHMO nur die Platten-Schreibgeschwindigkeit mit und nicht die Übertragungszeit :(
Also da kann man leider nicht sonderlich viel beeinflussen, wenn jemand hier eine bessere Idee hätte, wäre ich sehr dankbar :)
jens hat geschrieben:Schön wäre ja ein AJAX Upload. Aber JS dürfen keine lokalen Dateien öffnen, was ja auch gut ist ;)
Jep. Das wäre doch etwas gefährlich :-)

Aber nette Arbeit, schön, dass da noch jemand WSGIarea verwendet ^^
TUFKAB – the user formerly known as blackbird
Benutzeravatar
jens
Moderator
Beiträge: 8482
Registriert: Dienstag 10. August 2004, 09:40
Wohnort: duisburg
Kontaktdaten:

Montag 21. November 2005, 20:26

Ich hatte irgendwie die Idee, das die HTML-Form, also der Browser erstmal die Daten der Datei an das JS schickt... Aber was wird wohl auch nicht gehen...

Eine andere Lösung wäre ein Python-Client zu schreiben, der die Übermittlung übernimmt ;)
Das Einloggen per Apache's basic auth über https sollte an für sich kein Problem sein, oder? Vielleicht doch ein kleines TK Programm. Es fragt nach Username, Passwort ab und eine Dateiauswahldialog kommt mit dem man die Datei auswählen kann und ab geht's...

CMS in Python: http://www.pylucid.org
GitHub | Open HUB | Xing | Linked in
Bitcoins to: 1JEgSQepxGjdprNedC9tXQWLpS424AL8cd
Leonidas
Administrator
Beiträge: 16024
Registriert: Freitag 20. Juni 2003, 16:30
Kontaktdaten:

Montag 21. November 2005, 21:36

jens hat geschrieben:Vielleicht doch ein kleines TK Programm. Es fragt nach Username, Passwort ab und eine Dateiauswahldialog kommt mit dem man die Datei auswählen kann und ab geht's...
Klar, um etwas hochzuladen lade ich mir erstmal wieder Python runter um die Tk-Libs wiederzubekommen und dann lade ich mir ein Tkinter-Programm runter bei dem mir erstmal wegen des Looks schlecht wird und bei dem ich mich später auch wegen des Feels ärgern werde. Ne, bleib lieber beim guten, (im Vergleich mit Tk) neuen HTML.

Und was wäre mit den Usern von Lynx, Links und ELinks?
My god, it's full of CARs! | Leonidasvoice vs Modvoice
Benutzeravatar
jens
Moderator
Beiträge: 8482
Registriert: Dienstag 10. August 2004, 09:40
Wohnort: duisburg
Kontaktdaten:

Montag 21. November 2005, 21:40

Es geht mir nicht um eine allgemeine Lösung, da kann man besser FTP benutzten. Leider ist das nicht möglich :(
Außerdem ist es für große Dateien, das klappt zwar auch jetzt schon mit dem Skript von oben. Aber mich stört etwas, das man vom Browser keine Statusinformation bekommt...
Nun könnte man auch eine Extension für Firefox schreiben, aber das kann ich nicht :(

CMS in Python: http://www.pylucid.org
GitHub | Open HUB | Xing | Linked in
Bitcoins to: 1JEgSQepxGjdprNedC9tXQWLpS424AL8cd
dejot
User
Beiträge: 16
Registriert: Mittwoch 2. November 2005, 17:36
Wohnort: Werne
Kontaktdaten:

Samstag 26. November 2005, 15:04

ist doch bei php-uploads nicht anders.

Imho reicht es, einen Infotext zu schreiben, der den User darauf hinweist, dass der Upload bei größeren Dateien etwas dauern kann und er sich keine Sorgen machen muss oder so :)
Benutzeravatar
jens
Moderator
Beiträge: 8482
Registriert: Dienstag 10. August 2004, 09:40
Wohnort: duisburg
Kontaktdaten:

Donnerstag 1. Dezember 2005, 11:32

Jep... Gerade hab wurde mit meinem Uploader ein 780MB Datei hochgeschoben... Somit ist es auch für große Dateien brauchbar *freu* :D

CMS in Python: http://www.pylucid.org
GitHub | Open HUB | Xing | Linked in
Bitcoins to: 1JEgSQepxGjdprNedC9tXQWLpS424AL8cd
Benutzeravatar
jens
Moderator
Beiträge: 8482
Registriert: Dienstag 10. August 2004, 09:40
Wohnort: duisburg
Kontaktdaten:

Donnerstag 1. Dezember 2005, 15:16

Mit fällt da gerade ein, wie man evtl. doch ein Status entlocken kann:

Nur das gerade laufende CGI-Skript weiß ja, wieviel Bytes schon übertragen wurden. Das könnte das Upload-Skript, noch wärend der upload läuft, immer mal wieder in eine Textdatei oder DB schreiben.

Beim Uploaden öffnet sich beim Client ein zweites Fenster, um dort den Status anzugeben. Der Client ruft das Status-CGI-Skript auf. Welches eine einfache HTML-Seite ist, mit den Informationen aus der Text-Datei/DB...

Das einzige was fehlt, ist die gesammt größe in Bytes... Oder wird die evtl. vom Browser im content-length übertragen? Dann hätte man auch die!

Man könnte evtl. auch eine AJAX Lösung bauen, oder einfach nur ein iFrame mit einem auto-reload... Dann könnte man alles in einem einzigen Fenster ausgeben...

Ich frage mich nur, warum da bisher niemand drauf gekommen ist... z.B. beim GMX-MediaCenter...

Was haltet ihr davon?

Leider fehlt mir gerade die Zeit das mal eben zu testen... Aber vielleicht später...

EDIT: Eine Umsetzung gibt es bereits: http://sean.treadway.info/demo/upload (Thx. blackbird für den Link via IRC) Allerdings ist diese Lösung mit RubyOnRails und benutzt wohl fastCGI, welches ich nicht hab...

CMS in Python: http://www.pylucid.org
GitHub | Open HUB | Xing | Linked in
Bitcoins to: 1JEgSQepxGjdprNedC9tXQWLpS424AL8cd
Benutzeravatar
jens
Moderator
Beiträge: 8482
Registriert: Dienstag 10. August 2004, 09:40
Wohnort: duisburg
Kontaktdaten:

Freitag 31. März 2006, 19:30

jens hat geschrieben:Das gerade laufende CGI-Skript weiß ja, wieviel Bytes schon übertragen wurden. Das könnte das Upload-Skript, noch wärend der upload läuft, immer mal wieder in eine Textdatei oder DB schreiben.
Ich hab es gerade mal mit PyDown implementiert, nur das SQLite genommen wird...

Allerdings klappt das leider nicht. Nicht als CGI und nicht als fastCGI... Kann es sein, das Apache erst das Skript startet, wenn der Upload vom Client fertig ist???

Wobei... Vielleicht funkt mir auch colubrid dazwischen?!?!

EDIT: Jep es liegt an colubrid. Es liest die POST Daten zuerst und in einem Rutsch ein. deswegen sieht meine WebApp davon nix. Es kann zwar in Blöcken die Daten einlesen, aber die WebApp startet ja quasi erst dann, wenn der Upload schon fertig ist :(
Aber das kann man wohl mit einer middleware regeln...

CMS in Python: http://www.pylucid.org
GitHub | Open HUB | Xing | Linked in
Bitcoins to: 1JEgSQepxGjdprNedC9tXQWLpS424AL8cd
Antworten