#!/usr/bin/env python3
# Copyright (c) 2012, Marcus Carlsson <carlsson.marcus@gmail.com>
# All rights reserved.
# 
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions are met:
#     * Redistributions of source code must retain the above copyright
#       notice, this list of conditions and the following disclaimer.
#     * Redistributions in binary form must reproduce the above copyright
#       notice, this list of conditions and the following disclaimer in the
#       documentation and/or other materials provided with the distribution.
#     * Neither the name of the Marcus Carlsson nor the
#       names of its contributors may be used to endorse or promote products
#       derived from this software without specific prior written permission.
# 
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
# ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
# DISCLAIMED. IN NO EVENT SHALL <COPYRIGHT HOLDER> BE LIABLE FOR ANY
# DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
# (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
# LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
# ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
__version__ = '0.1.0'
import os
import sys
import io
import math
import time
import argparse
import logging
import urllib
import html
import http.server

def human_size(num):
    for x in ['B','K','M','G','T', 'P', 'E', 'Z', 'Y']:
        if num < 1024.0:
            return '{:3.1f}{:s}'.format(num, x)
        num /= 1024.0

class ListingHTTPRequestHandler(http.server.SimpleHTTPRequestHandler):

    """Basic HTTP request handler for GET-requests"""
    server_version = "PyServe/" + __version__
    protocol_version = "HTTP/1.0"
    length = 48
    hide = False

    def list_directory(self, path):
        """Helper to list a directory"""
        if self.hide and os.path.basename(os.path.abspath(path)).startswith('.'):
            self.send_error(404, "No permission to list directory")
            return None
        try:
            list = os.listdir(path)
        except os.error:
            self.send_error(404, "No permission to list directory")
            return None
        list.sort(key=lambda a: a.lower())
        r = []
        displaypath = html.escape(urllib.parse.unquote(self.path))
        r.append('<!DOCTYPE html>')
        r.append("<html>\n<title>Index of {}</title>\n".format(displaypath))
        r.append("<body>\n<h1>Index of {}</h1>\n".format(displaypath))
        r.append("<hr>\n<pre>\n")
        for fd in list:
            if self.hide and fd.startswith('.'):
                continue
            fullname = os.path.join(path, fd)
            displayname = linkname = fd
            size = '-'
            if os.path.isdir(fullname):
                displayname = linkname = fd + "/"
            if os.path.islink(fullname):
                displayname = "@" + fd
            if os.path.isfile(fullname):
                size = human_size(os.path.getsize(fullname))
            dn = html.escape(displayname)
            if len(dn) > self.length-3:
                dn = dn[:self.length-3] + '..>'

            tm = time.localtime(os.path.getmtime(fullname))
            tm = time.strftime('%d-%b-%y %H:%M:%S', tm)
            r.append(
                '<a href="{0}">{1:s}</a>{2}{3}\t{4:>7}\n'.format(
                        urllib.parse.quote(linkname),
                        dn,
                        '\t'*(math.ceil(self.length/8)-(math.floor(len(dn)/8))),
                        tm,
                        size))
        r.append('</pre>\n<hr>\n</body>\n</html>')
        enc = sys.getfilesystemencoding()
        encoded = ''.join(r).encode(enc)
        f = io.BytesIO()
        f.write(encoded)
        f.seek(0)
        self.send_response(200)
        self.send_header("Content-type", "text/html; charset=%s" % enc)
        self.send_header("Content-Length", str(len(encoded)))
        self.end_headers()
        return f

    def log_message(self, format, *args):
        logging.debug('[{}] {}'.format(self.client_address[0], format%args))

def main():
    parser = argparse.ArgumentParser()
    parser.add_argument('-b', '--bind', default='',
            help='Address to bind to. Empty means all addresses')
    parser.add_argument('-p', '--port', type=int)
    parser.add_argument('--hide', action='store_true', default=False)
    args = parser.parse_args()

    if args.port:
        port = args.port
    else:
        port = 8000

    server_address = (args.bind, port)
    lh = ListingHTTPRequestHandler
    lh.hide = args.hide
    httpd = http.server.HTTPServer(server_address, lh)

    sa = httpd.socket.getsockname()
    logger.info('Serving HTTP on: {}, port: {}'.format(sa[0], sa[1]))
    try:
        httpd.serve_forever()
    except KeyboardInterrupt:
        logging.debug("Keyboard interrupt received, exiting.")
        httpd.server_close()
        sys.exit(0)


if __name__ == '__main__':
    logging.basicConfig(level=logging.DEBUG,
            format='%(asctime)s [%(levelname)s] %(name)s - %(message)s')
    logger = logging.getLogger()
    main()
