root/CodeSnippets/PyFileCenter/FileBrowser.py

Revision 175, 35.1 kB (checked in by pylucid, 3 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: