root/CodeSnippets/PyFileCenter/FileBrowser.py

Revision 175, 35.1 KB (checked in by pylucid, 4 years ago)

v0.6.1

  • Neu: cfg.pre_title und cfg.post_title
  • Bugfix: cfg.base_path kann nun auch das aktuelle Verzeichnis sein, also =""
  • alle os.path in posixpath getausch
  • SQL Fehler beim connect wird abgefangen
  • Bugfix: dbconfig
  • Property svn:eol-style set to LF
Line 
1#!/usr/bin/python
2# -*- coding: UTF-8 -*-
3
4__author__      = "Jens Diemer"
5__url__         = "http://www.jensdiemer.de/Programmieren/Python/PyFileCenter"
6#SVN: http://pylucid.python-hosting.com/file/CodeSnippets/PyFileCenter/FileBrowser.py
7__license__     = "GNU General Public License (GPL)"
8__description__ = "a pure Python-CGI-FileBrowser (Download-Center)"
9
10"""
11Beispiel "index.py":
12-------------------------------------------------------------------------------
13# Verzeichnis in dem sich "FileBrowser.py" befindet in den Suchpfad aufnehmen
14sys.path.insert( 0, os.environ["DOCUMENT_ROOT"] + "cgi-bin/PyFileCenter/" )
15
16import FileBrowser
17
18# db-config Überschreiben mit den richtigen Daten
19FileBrowser.cfg.dbHost         = 'localhost'
20FileBrowser.cfg.dbDatabaseName = 'DatabaseName'
21FileBrowser.cfg.dbUserName     = 'UserName'
22FileBrowser.cfg.dbPassword     = 'Password'
23
24# Nur Endungen anzeigen, die in der Liste vorkommen
25FileBrowser.cfg.ext_whitelist   = [ ".7z", ".zip", ".py" ]
26
27# Hauptverz.: Navigation nur innerhalb diese Verz. mit Unterverz. erlauben
28FileBrowser.cfg.base_path       = "Programmieren"
29
30# Verz. in der Liste auslassen:
31FileBrowser.cfg.dir_filter      = ["OLD"]
32
33# Dateien die nicht angezeigt werden sollen
34FileBrowser.cfg.file_filter     = ["index.py"]
35
36# =False -> Nur Dateien im aktuellen Verz. anzeigen
37FileBrowser.cfg.allow_subdirs   = True
38
39# =True -> User kann in ZIP Archiven reinsehen
40FileBrowser.cfg.allow_zipview   = True
41
42# zurück-Link der auf jeder Seite eingeblendes werden soll
43FileBrowser.cfg.backlink = {
44        "txt"   : "< zurück zur Homepage",
45        "url"   : "http://www.jensdiemer.de/"
46    }
47
48# Verzeichnis bezogene zurück-Links
49FileBrowser.cfg.dir_backlinks = {
50    "PyAdmin"       : ["< PyAdmin Projektseite",        "http://www.jensdiemer.de?PyAdmin"],
51    "PyDiskEraser"  : ["< PyDiskEraser Projektseite",   "http://www.jensdiemer.de?PyDiskEraser"],
52    "PyLucid"       : ["< PyLucid Projektseite",        "http://www.jensdiemer.de?PyLucid"]
53}
54
55# HTML-Head
56FileBrowser.cfg.html_head["robots"] = "index,follow"
57
58
59# Starten...
60FileBrowser.FileBrowser()
61-------------------------------------------------------------------------------
62
63Es gibt noch einige andere Parameter in der cfg-Klasse. Einfach mal unten in
64den Sourcen schauen!
65
66
67
68____________________________________________________________________________________
69
70Benötigte SQL-Tabellen
71
72CREATE TABLE `FileCenter_Stat_Path` (
73    `id` INT NOT NULL AUTO_INCREMENT ,
74    `name` VARCHAR( 255 ) NOT NULL ,
75    PRIMARY KEY ( `id` )
76);
77
78CREATE TABLE `FileCenter_Stat_Item` (
79    `id` INT NOT NULL AUTO_INCREMENT ,
80    `path_id` INT NOT NULL ,
81    `name` VARCHAR( 255 ) NOT NULL ,
82    `count` int(11) NOT NULL default '0',
83    PRIMARY KEY ( `id` )
84);
85
86
87"""
88
89__version__ = "v0.6.1"
90
91__history__ = """
92v0.6.1
93    - Neu: cfg.pre_title und cfg.post_title
94    - Bugfix: cfg.base_path kann nun auch das aktuelle Verzeichnis sein, also =""
95    - alle os.path in posixpath getausch
96    - SQL Fehler beim connect wird abgefangen
97    - Bugfix: dbconfig
98v0.6
99    - dbconfig als Klasse zum überscheiben geändert
100v0.5.2
101    - Bugfix: print_back_link() nutzt nun urllib.quote_plus() damit auch Sonderzeichen in
102        den Verzeichnisnamen erlaubt sind
103v0.5.1
104    - NEU: cfg.use_sql_statistic, damit es auch ohne SQL eingesetzt werden kann!
105    - NEU: cfg.debug
106v0.5.0
107    - NEU: MySQL Statistik
108v0.4.2
109    - Bilder-Gallerie: Ein Bild wird nun auch mit Thumbnail dargestellt, wenn es nicht
110        in cfg.thumb_pic_filter vorkommt, aber ein passendes Thumbnail gefunden wurde.
111v0.4.1
112    - Bilder-Gallerie: Beim anschauen eines Bilder kann man drauf klicken und es
113        es wird das nächste angezeigt
114v0.4.0
115    - NEU: Thumb-Gallerie-Einstellung
116    - NEU: CSS läßt sich nun auch ändern
117    - Bug: änderungen am HTML-Kopf funktionierten garnicht
118v0.3.3
119    - Alle URLs sollten nun durch urllib.quote() angezeigt werden
120v0.3.2
121    - Umstellung von subprocess auf os.popen, weil's subprocess erst ab 2.4 gibt
122    - ZIPfile-Viewer zeigt nun mehr an
123v0.3.1
124    - Umstrukturierung des Codes
125    - NEU: Download-Proxy für Scriptdateien, damit sie nicht auf dem Server ausgeführt
126        werden, sondern runterladen ;)
127    - NEU: Renderzeit anzeige... wow =:-0
128v0.3.0
129    - Komplette Änderung der Konfiguration
130v0.2.2
131    - NEU: BackLink
132v0.2.1
133    - Downloadlinks nun immer mit MIME-type="application/octet-stream" - danke Leonidas
134    - Fehler: Downloadlinks korrigiert
135v0.2
136    - NEU: zipfile "Viewer"
137v0.1.1
138    - Darstellung der KBytes für Dateien mit "de_DE.UTF-8"
139v0.1
140    - erste Version
141"""
142
143__info__ = '<a href="%s">FileBrowser</a> %s' % (__url__, __version__)
144
145import cgitb; cgitb.enable()
146
147import time
148start_time = time.time()
149
150import os, sys, cgi, stat, re, urllib
151import locale
152import posixpath
153import zipfile
154
155
156
157
158
159
160CSS_style = """<style type="text/css">
161@media all {
162/*
163Mac IE Filter
164http://w3development.de/css/hide_css_from_browsers/media/
165*/
166    body {
167        font-size: 0.9em;
168    }
169    html, body {
170        margin: 10px;
171        padding: 0;
172        font-family: tahoma, arial, sans-serif;
173        color: #000000;
174        background-color: #FFFFFF;
175    }
176    body {
177        min-height: 500px;
178    }
179    a {         /* Link allgemein */
180        text-decoration:none;
181    }
182    a:link {    /* noch nicht besuchter Link */
183        color: #000088;
184    }
185    a:visited { /* schon besuchter Link */
186        color: #000000;
187    }
188    a:hover {   /* Maus über dem Link */
189        color: #4444FF;
190        text-decoration:underline;
191    }
192    a:active {  /* Link wird angeklickt */
193        color: #FF0000;
194    }
195    h1 { /* Seitenüberschrift */
196        border-bottom: 1px solid #000000;
197    }
198    /*
199        _________________________________________
200        Verzeichnis-Liste
201    */
202    .gallery_dirs ul {
203    }
204    .gallery_dirs li {
205        list-style-type: none;
206    }
207    .gallery_dirs li:hover {
208        list-style-type:disc;
209    }
210    /*
211        _________________________________________
212        Datei-Liste
213    */
214    /*
215        Dateiliste allgemein
216    */
217    .gallery_files ul {
218        text-align: center;
219        margin: 0px;
220        padding: 0px;
221    }
222    .gallery_files li {
223        float: left;
224        border: 1px solid #DDDDDD;
225        height: 150px;
226        width: auto;
227        text-align: center;
228        margin: 5px;
229        padding: 5px;
230        list-style-type: none;
231        background-color: #EEEEEE;
232    }
233    /*
234        nur normale Dateien
235    */
236    .normal a {
237        padding: 0 5px 0px 5px;
238    }
239    /*
240        nur Bilder mit Thumnailansicht
241    */
242    .gallery_pic a {
243        text-decoration:none;
244    }
245    .gallery_pic img {
246        border: 0px;
247    }
248    #dir_counter, #file_counter, #zip_counter {
249        font-size: 0.6em;
250        clear: both;
251        text-align: center;
252        color: #CCCCCC;
253    }
254    #zip_counter {
255        text-align: left;
256    }
257    /*
258        _________________________________________
259        Footer
260    */
261    #gallery_clear {
262        clear: both;
263        width: auto;
264        border: none;
265    }
266    #gallery_footer {
267        border-top: 2px solid #CCCCCC;
268        font-size: 0.6em;
269        text-align: center;
270        color: #CCCCCC;
271    }
272    #gallery_footer a {
273        padding: 0;
274        color: #CCCCCC;
275        text-decoration:none;
276    }
277    /*
278        _________________________________________
279        ZIP
280    */
281    .zipinfo td {
282        padding-right: 0.7em;
283        padding-left: 0.7em;
284    }
285    /*
286        _________________________________________
287        Image View - Ein Bild wird Angezeit
288    */
289    #img_view {
290        text-align:center;
291        width:auto;
292        margin:auto;
293        display:block;
294        text-decoration:none;
295    }
296    #img_view img {
297        border: 1px solid #000000;
298    }
299    #img_view a, #img_view a:hover {
300        text-decoration:none;
301    }
302}
303</style>
304"""
305
306
307
308
309
310
311
312class cfg:
313    """
314    Pseudo Klasse zum 'speichern' der Konfiguration.
315
316    Hierhin wird die Basis Konfiguration gespeichert, die
317    von außerhalb verändert werden sollte.
318    """
319
320    # Nur Endungen anzeigen, die in der Liste vorkommen
321    ext_whitelist       = [ ".jpg",".mpg",".avi" ]
322
323    # Downloadbare Textdateien über eingebaute Downloadproxy
324    ext_download_proxy  = [ ".py",".php",".php4",".php5" ]
325
326    # Dateiendungen, bei denen nicht der file-Befehl ausgeführt werden soll,
327    # sondern der eingebaute "Analysator", der aber bisher nur *.py Dateien kann ;)
328    override_fileinfo = {
329            ".py"   : "Python Script",
330        }
331
332    # Hauptverz.: Navigation nur innerhalb diese Verz. mit Unterverz. erlauben
333    base_path           = "MeinDownloadVerzeichnis"
334
335    # Verz. in der Liste auslassen:
336    dir_filter          = ["PyFileCenter"]
337
338    # Dateien die nicht angezeigt werden sollen
339    file_filter         = [ "index.py", ".htaccess" ]
340
341    # =False -> Nur Dateien im aktuellen Verz. anzeigen
342    allow_subdirs       = True
343
344    ## Thumb-Gallerie-Einstellung
345    # pic_ext           = Dateiendungen, die als Bilder behandelt werden sollen
346    # thumb_pic_filter  = Filter, der aus den Dateinamen rausgeschnitten werden soll, um
347    #                     damit das passende Thumbnail zu finden
348    # thumb_suffix      = Liste der Suffixe im Dateiname mit dem ein Thumbnail markiert ist
349    # resize_thumb_size = Wird kein Thumbnail gefunden, wird das original Bild auf diese Werte
350    #                     verkleinert als Thumb genommen
351    #
352    # Bsp.:
353    # Urlaub01_WEB.jpg   -> Bild zu dem ein Thumbnail gesucht wird
354    # Urlaub01_thumb.jpg -> Das passende Thumbnail
355    pic_ext             = ( ".jpg", ".jpeg" )
356    thumb_pic_filter    = ( "_WEB", )
357    thumb_suffix        = ( "_thumb", )
358    resize_thumb_size   = ( 100,60 )
359
360    # zurück-Link der auf jeder Seite eingeblendes werden soll
361    backlink = {
362            "txt"   : "< zurück zur Homepage", # wird HTML-escaped!
363            "url"   : "http://www.jensdiemer.de/"
364        }
365
366    # Verzeichnis bezogene zurück-Links
367    dir_backlinks_code = '<p><a href="%(url)s">%(txt)s</a></p>'
368    dir_backlinks = {}
369
370    # Link auf der Seite unten
371    footer_backlink_code = '<p><small><a href="%(url)s">%(txt)s</a></small></p>'
372    footer_backlink = {
373            "txt"   : "< zurück zur Homepage", # wird HTML-escaped!
374            "url"   : "http://www.jensdiemer.de/"
375        }
376
377    # HTML Header Informationen
378    html_head = {
379            "robots"      : "noindex,nofollow",
380            "keywords"    : "",
381            "description" : "",
382            "CSS"         : CSS_style,
383        }
384
385    # Textteile vor und nach dem Title und der Überschrift
386    pre_title  = "",
387    post_title = "",
388
389    ## Counter...
390    # ...in einer Dateiliste
391    dir_counter_txt = '<div id="dir_counter">(count %s)</div>'
392    # ...In Dateiliste
393    file_count_txt = '<div id="file_counter">(count %s)</div>'
394    # ...beim betrachten einer ZIP-Datei
395    zip_view_txt = '<div id="zip_counter">(count %s)</div>'
396
397    # Wird nach den Standart CSS-Tag eingefügt
398    # Damit kann man also gezielt nur ein paar CSS-Eigenschaften überschreiben
399    # Muß komplett mit Anfangs-/Endtags sein, also:
400    # <style type="text/css">...</style>
401    additional_CSS = ""
402
403    # Soll in der SQL-Datenbank ein counter Statistik geführt werden?
404    use_sql_statistic = True
405
406    # SQL db-Config
407    dbHost          = 'localhost' # Evtl. muß hier die Domain rein
408    dbDatabaseName  = 'DatabaseName'
409    dbUserName      = 'UserName'
410    dbPassword      = 'Password'
411
412    # Debug-Ausgaben
413    debug = False
414
415
416
417HTML_head = """<?xml version="1.0" encoding="UTF-8"?>
418<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "xhtml1-strict.dtd">
419<html xmlns="http://www.w3.org/1999/xhtml">
420<head>
421<title>%(pre_title)sFileBrowser%(post_title)s</title>
422<meta name="robots"                    content="%(robots)s" />
423<meta name="keywords"                  content="%(keywords)s" />
424<meta name="description"               content="%(description)s" />
425<meta http-equiv="Content-Type"        content="text/html; charset=utf-8" />
426<meta name="MSSmartTagsPreventParsing" content="TRUE" />
427<meta http-equiv="imagetoolbar"        content="no" />
428%(CSS)s
429%(additional_CSS)s
430</head>
431<body>"""
432
433
434
435formatter_regex = re.compile("^ *\d*\D\d{1,3}|\d{1,3} *")
436def formatter(number, format = "%.2f", decChar = ",", groupChar = "."):
437    """
438    Convert to required format
439    by HarryH modify by Jens Diemer
440
441    number =      number to convert
442    format =      python string formatting (%)
443    decChar =     decimal char for the converted format
444    groupChar =   group char for the converted format
445
446    For example:
447    formatter(1234567890.987, "%.2f", ",", ".")
448    ==> 1.234.567.890,99
449    formatter( 1234567890.987, "%i")
450    ==> 1.234.567.890
451
452    """
453    def reverse(s):
454        # ersatz für string[::-1] welches erst ab v2.3 gibt :(
455        # Nach einer Idee von Milan
456        l = map(None, s)
457        l.reverse()
458        return ('').join(l)
459
460    return reverse(
461        groupChar.join(
462            formatter_regex.findall(
463                reverse( (format % number).replace(".", decChar) )
464            )
465        )
466    )
467
468
469#____________________________________________________________________________________
470
471
472class zipmanager:
473    def __init__( self, workdir, filename ):
474        self.filename = filename
475
476        try:
477            self.zipobj = zipfile.ZipFile( posixpath.join(workdir, self.filename), "r" )
478        except Exception,e:
479            print "<p>Error: %s</p>" % e
480            return
481
482        self.view()
483
484    def view( self ):
485        print '<table class="zipinfo">'
486        print "<tr>"
487        print "<th>filename</th>"
488        print "<th>compress_size</th>"
489        print "<th>size</th>"
490        print "<th>ratio</th>"
491        print "<th>date_time</th>"
492        print "</tr>"
493        total_compress_size = 0
494        total_file_size = 0
495        for fileinfo in self.zipobj.infolist():
496            if fileinfo.filename.endswith("/"):
497                # Ist ein Verzeichniss
498                continue
499
500            print "<tr>"
501            print "<td>%s</td>" % fileinfo.filename
502
503            total_compress_size += fileinfo.compress_size
504            print '<td style="text-align: right;">%s</td>' % formatter( fileinfo.compress_size, "%i" )
505
506            total_file_size += fileinfo.file_size
507            print '<td style="text-align: right;">%s</td>' % formatter( fileinfo.file_size, "%i" )
508
509            try:
510                ratio = float(fileinfo.compress_size)/fileinfo.file_size*100
511            except ZeroDivisionError:
512                ratio = 100.0
513            print '<td style="text-align: right;">%s%%</td>' % formatter( ratio, "%0.1f" )
514
515            d = fileinfo.date_time
516            print '<td>%0.2i.%0.2i.%i %0.2i:%0.2i:%0.2i</td>' % (d[2],d[1],d[0],d[3],d[4],d[5])
517
518            print "</tr>"
519        print "</table>"
520        print "<ul>"
521        print "<li>total compress size: %s Bytes</li>" % formatter( total_compress_size, "%i" )
522        print "<li>total size: %s Bytes</li>" % formatter( total_file_size, "%i" )
523        try:
524            print "<li>Ratio: %s%%</li>" % formatter( float(total_compress_size)/total_file_size*100 )
525        except ZeroDivisionError:
526            pass
527        print "</ul>"
528
529
530
531#____________________________________________________________________________________
532
533class mySQL_statistic:
534    def __init__( self ):
535        self.db = self.connect()
536        self.db_conf = {
537            "path" : "FileCenter_Stat_Path",
538            "item" : "FileCenter_Stat_Item",
539        }
540
541    def connect( self ):
542        import mySQL
543        try:
544            # SQL connection aufbauen
545            return mySQL.mySQL(
546                    host    = cfg.dbHost,
547                    user    = cfg.dbUserName,
548                    passwd  = cfg.dbPassword,
549                    db      = cfg.dbDatabaseName,
550                )
551        except Exception, e:
552            print "Content-type: text/html\n"
553            print "<h1>PyLucid - Error</h1>"
554            print "<h2>Can't connect to SQL-DB: '%s'</h2>" % e
555            import sys
556            sys.exit()
557
558    def get_path_id( self, path, auto_create=True ):
559        """ id eines Eintrages, existiert der Eintrag noch nicht, wird er angelegt """
560
561        def id( path ):
562            return self.db.select(
563                select_items    = ["id"],
564                from_table      = self.db_conf["path"],
565                where           = ( "name", path )
566            )[0]["id"]
567
568        try:
569            return id( path )
570        except IndexError:
571            # Path existiert noch nicht
572            if auto_create != True:
573                return False
574            # Path wird angelegt
575            self.db.insert(
576                    table = self.db_conf["path"],
577                    data  = { "name" : path }
578                )
579            return id( path )
580
581    def get_item( self, path_id, item_name, auto_create=True ):
582
583        def id( path_id, item_name ):
584            result = self.db.select(
585                select_items    = ["id","count"],
586                from_table      = self.db_conf["item"],
587                where           = [ ("path_id", path_id), ("name",item_name) ]
588            )[0]
589            return result["id"], result["count"]
590
591        try:
592            return id( path_id, item_name )
593        except IndexError:
594            # Item existiert noch nicht
595            if auto_create != True:
596                return False
597            # Eintrag für Item wird angelegt
598            self.db.insert(
599                    table = self.db_conf["item"],
600                    data  = { "path_id": path_id, "name": item_name }
601                )
602            return id( path_id, item_name )
603
604    def only_get_count( self, path, item_name ):
605        """ Nur den Counterwert zurückliefern, ohne den Zähler zu erhöhen """
606
607        path_id = self.get_path_id( path, auto_create=False )
608        if path_id == False:
609            return "Path unknow!"
610            return False
611
612        item_info = self.get_item( path_id, item_name, auto_create=False )
613        if item_info == False:
614            print "Kein item_info [%s] path_id:%s" % (item_name, path_id)
615            return 0
616        else:
617            return item_info[1]
618
619    def count( self, path, item_name ):
620        """ Zähler eines Eintrages hochsetzten oder erstellen, wenn noch nicht existend. """
621
622        try:
623            path_id = self.get_path_id( path )
624        except Exception, e:
625            print "<small>[db Error: %s]</small>" % e
626            return
627        item_id, item_count = self.get_item( path_id, item_name )
628
629        item_count += 1
630
631        self.db.update(
632            table   = self.db_conf["item"],
633            data    = { "count" : item_count },
634            where   = ( "id", item_id),
635            limit   = 1
636        )
637        return item_count
638
639
640#____________________________________________________________________________________
641
642
643class FileBrowser:
644    def __init__( self ):
645        if cfg.use_sql_statistic == True:
646            # SQL connection aufbauen
647            self.statistic = mySQL_statistic()
648
649        self.thumbnails = {} # wird von read_dir gefüllt, wenn Thumbnails gefunden wurden
650
651        self.absolute_basepath = posixpath.join(os.environ["DOCUMENT_ROOT"], cfg.base_path)
652        self.absolute_basepath = posixpath.normpath(self.absolute_basepath)
653
654        self.CGIdata = self.getCGIdata()
655
656        (self.relativ_path, self.absolute_path) = self.get_current_path()
657        # Wird vom Counter verwendet:
658        self.root_path = self.absolute_path[len(os.environ["DOCUMENT_ROOT"]):]
659
660        if self.CGIdata.has_key("download"):
661            # ist ein Download-Proxy-Link aufruf
662            # Davor darf noch kein Header gesendet worden sein!
663            self.download_proxy( self.CGIdata["download"] )
664
665        # Erst der Header hier ausgeben, weil der Download-Proxy evtl. ausgeführt wurde!!!
666        print "Content-type: text/html; charset=utf-8\r\n"
667        self.print_head()
668        if cfg.debug:
669            print "absolute_basepath:", self.absolute_basepath
670
671        if self.CGIdata.has_key("zipfile") and (cfg.allow_zipview == True):
672            # Es soll eine ZIP-Datei angezeigt werden.
673            self.print_back_link()
674            zm = zipmanager( self.absolute_path, self.CGIdata["zipfile"] )
675
676            if cfg.use_sql_statistic == True:
677                # Aktueller Counterwert, anzeigen und in DB um eins erhöhen
678                print cfg.zip_view_txt % self.statistic.count( self.root_path, "<view %s>" % self.CGIdata["zipfile"] )
679
680            self.print_back_link()
681
682        elif self.CGIdata.has_key("img"):
683            # Ein Bild soll angezeigt werden
684            self.print_back_link()
685            self.print_img_view( self.CGIdata["img"] )
686            self.print_back_link()
687
688        elif self.CGIdata.has_key("next_img"):
689            # Das nächste Bild soll angezeigt werden
690            self.handle_next_img( self.CGIdata["next_img"] )
691
692        else:
693            # Normale Brower-Seite
694            files, dirs = self._read_dir()
695            self.print_body( files, dirs )
696
697        self.print_footer()
698
699    ##_____________________________________________________________________________
700    ## Allgemeine Routinen
701
702    def print_back_link( self ):
703        print '<p><a href="?path=%s">&lt; back</a></p>' % urllib.quote_plus(self.relativ_path)
704
705    def get_current_path( self ):
706        if self.CGIdata.has_key( "path" ):
707            relativ_path = posixpath.normpath( self.CGIdata["path"] )
708        else:
709            relativ_path = "."
710
711        cfg.base_path = posixpath.normpath( cfg.base_path )
712
713        absolute_path = posixpath.join( self.absolute_basepath, relativ_path )
714        absolute_path = posixpath.normpath( absolute_path )
715
716        self.check_absolute_path( absolute_path )
717
718        return relativ_path, absolute_path
719
720
721    def error( self, txt ):
722        """Fehlermeldung mit anschließendem sys.exit()"""
723        print "Content-Type: text/html\n"
724        print HTML_head
725        print "<h2>%s</h2>" % txt
726        print '<a href="?">back</a>'
727        self.print_footer()
728        sys.exit()
729
730    def check_absolute_path( self, absolute_path ):
731        """
732        Überprüft einen absoluten Pfad
733        """
734        if (absolute_path.find("..") != -1) or (absolute_path.find("//") != -1):
735            # Hackerscheiß schon mal ausschließen
736            self.error( "not allowed!" )
737
738        if not absolute_path.startswith(self.absolute_basepath):
739            # Fängt nicht wie Basis-Pfad an... Da stimmt was nicht
740            #~ print "Content-Type: text/html\n"
741            #~ print "X%sX" % absolute_path
742            #~ print "X%sX" % self.absolute_basepath
743            self.error( "permission deny." )
744
745        if not posixpath.isdir( absolute_path ):
746            # Den Pfad gibt es nicht
747            self.error( "'%s' not exists" % absolute_path )
748
749    def getCGIdata( self ):
750        "CGI-POST Daten auswerten"
751        data = {}
752        FieldStorageData = cgi.FieldStorage( keep_blank_values=True )
753        for i in FieldStorageData.keys(): data[i] = FieldStorageData.getvalue(i)
754        return data
755
756
757    def _read_dir( self ):
758        "Einlesen des Verzeichnisses"
759
760        def is_thumb( name, file_name ):
761            "Kleine Hilfsfunktion um Thumbnails raus zu filtern"
762            for suffix in cfg.thumb_suffix:
763                if name[-len(suffix):] == suffix:
764                    # Aktuelle Datei ist ein Thumbnail!
765                    clean_name = name[:-len(suffix)]
766                    self.thumbnails[clean_name] = file_name
767                    return True
768            return False
769
770        if cfg.debug:
771            print "read '%s'..." % self.absolute_path
772
773        files = []
774        dirs = []
775        for item in os.listdir( self.absolute_path ):
776            abs_path = posixpath.join( self.absolute_path, item )
777            if posixpath.isfile( abs_path ):
778                # Dateien verarbeiten
779
780                if item in cfg.file_filter:
781                    # Datei soll nicht angezeigt werden
782                    continue
783
784                name, ext = posixpath.splitext( item )
785
786                # Thumbnails rausfiltern
787                if is_thumb( name, item ):
788                    # Ist ein Thumbnail -> soll nicht in die files-Liste!
789                    continue
790
791                if ext in cfg.ext_whitelist:
792                    files.append( item )
793
794            else:
795                # Verzeichnis verarbeiten
796
797                if cfg.allow_subdirs:
798                    # Unterverz. sollen angezeigt werden
799                    if not item in cfg.dir_filter:
800                        dirs.append( item )
801
802        files.sort()
803        dirs.sort()
804
805        if self.relativ_path != ".":
806            # Nur erweitern, wenn man nicht schon im Hauptverzeichnis ist
807            dirs.insert(0,"..")
808
809        return files, dirs
810
811
812    ##_____________________________________________________________________________
813    ## Datei-Routinen
814
815    def get_charset( self, absolute_filepath ):
816        """Ermittelt das charset im Dateiheader: z.b.: # -*- coding: UTF-8 -*- """
817
818        f = file( absolute_filepath, "rU" )
819
820        headlines = []
821        for i in range(2):
822            # ersten zwei Zeilen lesen
823            headlines.append( f.readline() )
824
825        f.close()
826
827        return re.findall(r"- coding: (.+?) -", "".join(headlines) )[0]
828
829    def process_file_command( self, filename ):
830        """ Datei Information mit Linux 'file' Befehl zusammentragen """
831
832        command = "file %s" % posixpath.join(self.absolute_path,filename)
833        fileinfo = os.popen( command ).readlines()[0]
834
835        fileinfo = fileinfo.split(":",1)[1]
836        if fileinfo.find("ERROR") != -1:
837            # Ersatz für >"ERROR" in fileinfo< ;)
838            return ""
839        else:
840            return fileinfo
841
842
843    def print_fileinfo( self, filename, absolute_filepath, base, ext ):
844        """ file-Informationen mittels file oder manuell """
845
846        print "<small>"
847
848        if ext in cfg.override_fileinfo:
849            # Es soll kein 'file'-Befehl ausgeführt werden
850            print cfg.override_fileinfo[ext]
851            if ext == ".py":
852                try:
853                    print " - Charset: %s" % self.get_charset( absolute_filepath )
854                except:
855                    pass
856        else:
857            # Holt informationen über den 'file'-Befehl
858            try:
859                print self.process_file_command( filename )
860            except Exception, e:
861                print "FileInfo Error:<br/>%s" % e
862
863        print "</small>"
864
865
866    ##_____________________________________________________________________________
867    ## Seiten generierung
868
869    def print_head( self ):
870        """Kopf + Überschrift + backLink ausgeben"""
871
872        # Key für zusätzliche CSS Einträge ans Dict hinzufügen
873        cfg.html_head.update(
874            {
875                "additional_CSS"    : cfg.additional_CSS,
876                "pre_title"         : cfg.pre_title,
877                "post_title"        : cfg.post_title,
878            }
879        )
880        print HTML_head % cfg.html_head
881
882        current_path = posixpath.join(cfg.base_path, self.relativ_path)
883        current_path = posixpath.normpath(current_path)
884        if current_path == ".": current_path = "/"
885        print "<h1>%s%s%s</h1>" % (cfg.pre_title, current_path, cfg.post_title)
886
887        if self.relativ_path in cfg.dir_backlinks:
888            print cfg.dir_backlinks_code % {
889                "url" : cfg.dir_backlinks[ self.relativ_path ][1],
890                "txt" : cgi.escape( cfg.dir_backlinks[ self.relativ_path ][0] )
891            }
892
893
894    def print_dir( self, dir ):
895        """Verzeichnis auflisten"""
896
897        if dir != "..":
898            before  = ""
899            after   = " &gt;"
900        else:
901            before  = "&lt; "
902            after   = ""
903
904        print '<li><a href="?path=%(url)s">%(before)s%(txt)s%(after)s</a></li>' % {
905                "url"       : urllib.quote( posixpath.normpath( posixpath.join( self.relativ_path, dir ) ) ),
906                "before"    : before,
907                "txt"       : dir,
908                "after"     : after
909            }
910
911
912    def print_body( self, files, dirs ):
913        """Schleife für die eigentliche Seite"""
914
915        if cfg.allow_subdirs:
916            print '<ul class="gallery_dirs">'
917            for dir in dirs:
918                self.print_dir( dir )
919            print "</ul>"
920
921        print '<ul class="gallery_files">'
922        for file in files:
923            self.print_file( file )
924
925        print "</ul>"
926
927        if cfg.use_sql_statistic == True:
928            # Aktueller Counterwert, anzeigen und in DB um eins erhöhen
929            print cfg.dir_counter_txt % self.statistic.count( self.root_path, "<dir %s>" % self.root_path )
930
931
932    def print_gallery_pic( self, pic_base_name, img_name, file_url ):
933
934        print '<li class="gallery_pic">'
935
936        print '<a href="?path=%s&img=%s">' % (
937            urllib.quote( self.relativ_path ),
938            urllib.quote( img_name )
939        )
940
941        if pic_base_name in self.thumbnails:
942            # Es gibt ein Thumbnail
943            img_path = posixpath.join( self.relativ_path, self.thumbnails[pic_base_name] )
944            print '<img src="%s" />' % img_path
945        else:
946            # Kein passendes Thumb. gefunden -> Original Bild wird als Thumb. verwendet
947            print '<img src="%s" width="%s" height="%s" />' % (
948                file_url, cfg.resize_thumb_size[0], cfg.resize_thumb_size[1]
949            )
950
951        print "<br/>"
952        print "<small>%s</small>" % pic_base_name.replace("_"," ")
953        print "</a>"
954        print "</li>"
955
956    def _is_gallery_img( self, base, ext ):
957        """ Unterscheidet Bilder für Gallerymodus """
958        if not (ext in cfg.pic_ext):
959            # Ist überhaupt kein Bild
960            return False
961
962        if base in self.thumbnails:
963            # Ein Thumbnail ist für das aktuelle Bild vorhanden
964            return base
965
966        # Ist ein Bild, könnte für eine Thumbnailansicht sein.
967        for suffix in cfg.thumb_pic_filter:
968            # Testet ob das Bild ein suffix besitzt (z.B. *_WEB.jpg)
969            if base[-len(suffix):] == suffix:
970                # Aktuelle Datei ist ein Gallerie-Bild
971                return base[:-len(suffix)]
972
973        return False
974
975
976    def print_file( self, filename ):
977        """ Dateiinfromationen ermitteln und auflisten """
978
979        absolute_filepath   = posixpath.join(self.absolute_path,filename)
980        base, ext           = posixpath.splitext( filename )
981        file_url            = posixpath.join( self.relativ_path, filename )
982
983        clean_base_name = self._is_gallery_img( base, ext )
984        if clean_base_name != False:
985            # Aktuelle Datei ist ein Gallerie-Bild
986            self.print_gallery_pic( clean_base_name, filename, file_url )
987            return
988
989        print '<li class="normal"><br/>'
990
991        #~ if ext in cfg.ext_download_proxy:
992        # Download-Proxy-Link
993        print '<a href="?download=%s">%s</a>' % ( urllib.quote(file_url), urllib.quote(filename) )
994        #~ else:
995            #~ # Normaler Link
996            #~ print '<a href="%s" type="application/octet-stream">%s</a>' % \
997                #~ ( urllib.quote(file_url), urllib.quote(filename) )
998
999        print "<br/>"
1000
1001        self.print_fileinfo( filename, absolute_filepath, base, ext )
1002
1003        print "<br/>"
1004
1005        if cfg.allow_zipview and (ext == ".zip"):
1006            print '<a href="?path=%s&zipfile=%s">view ZIP file</a>' % \
1007                ( urllib.quote(self.relativ_path) , urllib.quote(filename) )
1008            print "<br/>"
1009
1010        print "<br/>"
1011
1012        item_stat = os.stat( absolute_filepath )
1013        print "<small>%s</small>" % time.strftime(
1014                '%d.%m.%Y %H:%M',
1015                time.localtime(item_stat[stat.ST_MTIME])
1016            )
1017        print "<br/>"
1018
1019        file_KBytes = item_stat[stat.ST_SIZE]/1024.0
1020        try:
1021            locale.setlocale(locale.LC_ALL, "de_DE.UTF-8")
1022            print "%s KB" % locale.format("%0.1f", file_KBytes, True)
1023        except:
1024            print "%0.1f KB" % file_KBytes
1025
1026        if cfg.use_sql_statistic == True:
1027            print "<br/>"
1028            print cfg.file_count_txt % self.statistic.only_get_count( self.root_path, filename )
1029
1030        print "</li>"
1031
1032
1033    def print_footer( self ):
1034        """zurück-Link + HTML-Fussdaten"""
1035        print '<hr id="gallery_clear">'
1036
1037        # zurück-Link
1038        cfg.footer_backlink["txt"] = cgi.escape( cfg.footer_backlink["txt"] )
1039        print cfg.footer_backlink_code % cfg.footer_backlink
1040
1041        print '<div id="gallery_footer">'
1042        print "Generated by %s" % __info__
1043        print "| render time %0.2fsec." % (time.time() - start_time)
1044        print "</div></body></html>"
1045
1046
1047    ##_____________________________________________________________________________
1048    ## Zusätzliche Funktionen
1049
1050    def download_proxy( self, filename ):
1051        abs_filepath = posixpath.join( os.environ["DOCUMENT_ROOT"], cfg.base_path, filename )
1052        abs_filepath = posixpath.normpath( abs_filepath )
1053
1054        abs_path, filename = posixpath.split( abs_filepath )
1055
1056        self.check_absolute_path( abs_path )
1057
1058        #~ print 'Content-Type: text/html\n\n<pre>' # Debug
1059
1060        try:
1061            charset = self.get_charset( abs_filepath )
1062        except:
1063            charset = []
1064
1065        #~ print 'Cache-Control: no-cache, must-revalidate'
1066        #~ print 'Pragma: no-cache'
1067        print 'Content-Length: %s' % posixpath.getsize( abs_filepath )
1068        print 'Content-Disposition: attachment; filename=%s' % filename
1069        print 'Content-Transfer-Encoding: binary'
1070        if charset != []:
1071            print 'Content-Type: text/plain; charset=%s\n' % charset
1072        else:
1073            print 'Content-Type: text/plain\n'
1074
1075        f = file( abs_filepath, "rb" )
1076        while True:
1077            # In Blöcken, die Datei rausprinten...
1078            data = f.read(8192)
1079            if not data:
1080                break
1081            sys.stdout.write(data)
1082        f.close()
1083
1084        if cfg.use_sql_statistic == True:
1085            # Counter um eins erhöhen
1086            self.statistic.count( self.root_path, filename )
1087
1088        sys.exit()
1089
1090    def print_img_view( self, img_name ):
1091        """ Anzeigen eines angeklickten Bildes """
1092
1093        if cfg.use_sql_statistic == True:
1094            # Aktueller Counterwert, wird in DB um eins erhöht
1095            count = self.statistic.count( self.root_path, img_name )
1096
1097        print '<a href="?path=%s&next_img=%s" title="next &gt;" id="img_view">' % (
1098            urllib.quote(self.relativ_path), urllib.quote(img_name)
1099        )
1100        print '<img src="%s"/><br/>' % posixpath.join( self.relativ_path, img_name )
1101        print '%s<br/>' % img_name
1102        print "<small>Counter: %s</small>" % count
1103        print '</a>'
1104
1105    def handle_next_img( self, old_img_name ):
1106        """ Das nächste Bild soll angezeigt werden """
1107
1108        files, dirs = self._read_dir()
1109
1110        if not old_img_name in files:
1111            # Der alte Name ist wohl falsch -> Normale Seite aufbauen
1112            self.print_body( files, dirs )
1113            return
1114
1115        current_pos = files.index( old_img_name )
1116
1117        try:
1118            next_img = files[ current_pos + 1 ]
1119        except IndexError:
1120            # Kein neues Image vorhanden -> Normale Seite aufbauen
1121            self.print_body( files, dirs )
1122            return
1123
1124        base, ext = posixpath.splitext( next_img )
1125        if self._is_gallery_img( base, ext ) == False:
1126            # Aktuelle Datei ist kein Gallery-Bild -> Normale Seite aufbauen
1127            self.print_body( files, dirs )
1128            return
1129
1130        # Alles klar, nächstes Bild wird gezeigt
1131        self.print_back_link()
1132        self.print_img_view( next_img )
1133        self.print_back_link()
1134
1135
1136
1137
1138
1139
1140
1141
Note: See TracBrowser for help on using the browser.