#!/usr/bin/env python
#
# Copyright 2010 Wojciech 'KosciaK' Pietrzok
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#     http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
# ----------------------------------------------------------------------
#
# Icons used:
# famfamfam's "Silk" icon set - http://www.famfamfam.com/lab/icons/silk/
#

__author__ = "Wojciech 'KosciaK' Pietrzok (kosciak@kosciak.net), Tommy MacWilliam (macwilliamt@gmail.com), bspkrs (bspkrs@gmail.com)"
__version__ = "0.5.2"

import sys
import os
import time
import locale
import re
import string
from optparse import OptionParser


locale.setlocale(locale.LC_ALL, '')
LANG, ENCODING = locale.getlocale()

DATE_FORMAT = '%Y-%m-%d&nbsp;%H:%M:%S'

TABLE_HEADERS = {'en_GB': ('Name', 'Size', 'Last Modified'),
                 'pl_PL': ('Nazwa', 'Rozmiar', 'Czas modyfikacji')}

SCRIPT_WWW = 'https://github.com/bspkrs/dropbox-index'

#FILES_URL = 'http://dl.dropboxusercontent.com/u/69843/dropbox-index'
FILES_URL = 'http://bspk.rs/dropbox-index'

ICONS = (
    '%s/icons/back.png' % FILES_URL,
    '%s/icons/folder.png' % FILES_URL,
    '%s/icons/file.png' % FILES_URL,
    '%s/icons/image.png' % FILES_URL,
    '%s/icons/video.png' % FILES_URL,
    '%s/icons/music.png' % FILES_URL,
    '%s/icons/archive.png' % FILES_URL,
    '%s/icons/package.png' % FILES_URL,
    '%s/icons/pdf.png' % FILES_URL,
    '%s/icons/txt.png' % FILES_URL,
    '%s/icons/markup.png' % FILES_URL,
    '%s/icons/code.png' % FILES_URL,
    '%s/icons/font.png' % FILES_URL,
    '%s/icons/document.png' % FILES_URL,
    '%s/icons/spreadsheet.png' % FILES_URL,
    '%s/icons/presentation.png' % FILES_URL,
    '%s/icons/application.png' % FILES_URL,
    '%s/icons/plugin.png' % FILES_URL,
    '%s/icons/iso.png' % FILES_URL,
    )

FILE_TYPES = {
    ('gif', 'jpg', 'jpeg', 'png', 'bmp', 'tif', 'tiff', 'raw', 'img', 'ico', ): 'image',
    ('avi', 'ram', 'mpg', 'mpeg', 'mov', 'asf', 'wmv', 'asx', 'ogm', 'vob', '3gp', ): 'video',
    ('mp3', 'ogg', 'mpc', 'wav', 'wave', 'flac', 'shn', 'ape', 'mid', 'midi', 'wma', 'rm', 'aac', 'mka', ): 'music',
    ('tar', 'bz2', 'gz', 'arj', 'rar', 'zip', '7z', ): 'archive',
    ('deb', 'rpm', 'pkg', 'jar', 'war', 'ear', ): 'package', 
    ('pdf', ): 'pdf',
    ('txt', ): 'txt',
    ('html', 'htm', 'xml', 'css', 'rss', 'yaml', 'php', 'php3', 'php4', 'php5', ): 'markup',
    ('js', 'py', 'pl', 'java', 'c', 'h', 'cpp', 'hpp', 'sql', ): 'code',
    ('ttf', 'otf', 'fnt', ): 'font',
    ('doc', 'rtf', 'odt', 'abw', 'docx', 'sxw', ): 'document',
    ('xls', 'ods', 'csv', 'sdc', 'xlsx', ): 'spreadsheet',
    ('ppt', 'odp', 'pptx', ): 'presentation', 
    ('exe', 'msi', 'bin', 'dmg', ): 'application',
    ('xpi', ): 'plugin',
    ('iso', 'nrg', ): 'iso',
    }

HTML_STYLE = '''
    <style>
        a { text-decoration: none; color: #00A; }
        a:hover { text-decoration: underline; }
        #dropbox-index-header { padding: 0; margin: 0.5em auto 0.5em 1em; }
        table#dropbox-index-list { text-align: center; margin: 0 auto 0 0; border-collapse: collapse; }
        #dropbox-index-list thead { border-bottom: 1px solid #555; }
        #dropbox-index-list th:hover { cursor: pointer; cursor: hand; background-color: #EEE; }
        #direction { border: 0; vertical-align: bottom; margin: 0 0.5em;}
        #dropbox-index-list tbody { border-bottom: 1px solid #555;}
        #dropbox-index-list tr, th { line-height: 1.7em; min-height: 25px; }
        #dropbox-index-list tbody tr:hover { background-color: #EEE; }
        .name { text-align: left; width: 35em; }
        .name div, thead .name { padding-left: 18px; }
        .name a { display: block; }
        .size { text-align: right; width: 7em; padding-right: 1em;}
        .date { text-align: right; width: 15em; padding-right: 1em;}
        .dl { padding-left: 0em; padding-right: 0em;}
        #dropbox-index-dir-info { margin: 1em auto 0.5em 2em; }
        #dropbox-index-footer { margin: 1em auto 0.5em 2em; font-size: smaller;}
        /* Icons */
        .dir, .back, .file { background-repeat: no-repeat; background-position: 2px 9px;}
        .back { background-image: url('%s'); }
        .dir { background-image: url('%s'); }
        .file { background-image: url('%s'); }
        .image { background-image: url('%s'); }
        .video { background-image: url('%s'); }
        .music { background-image: url('%s'); }
        .archive { background-image: url('%s'); }
        .package { background-image: url('%s'); }
        .pdf { background-image: url('%s'); }
        .txt { background-image: url('%s'); }
        .markup { background-image: url('%s'); }
        .code { background-image: url('%s'); }
        .font { background-image: url('%s'); }
        .document { background-image: url('%s'); }
        .spreadsheet { background-image: url('%s'); }
        .presentation { background-image: url('%s'); }
        .application { background-image: url('%s'); }
        .plugin { background-image: url('%s'); }
        .iso { background-image: url('%s'); }
    </style>''' % ICONS

HTML_JAVASCRIPT = '''
    <script type="text/javascript" src="http://ajax.googleapis.com/ajax/libs/jquery/1.4.2/jquery.min.js"></script>
    <script>
    function sort() {
        column = $(this).attr("class").split(' ')[0];
        $("#direction").remove();
        if ($(this).hasClass("desc")) {
            $("#dropbox-index-list thead tr th").each(function(i) { $(this).removeClass("asc").removeClass("desc") });
            $(this).addClass("asc");
            reverse = -1;
        } else {
            $("#dropbox-index-list thead tr th").each(function(i) { $(this).removeClass("asc").removeClass("desc") });
            $(this).addClass("desc");
            reverse = 1;
        }
        if (column == "name") {
            $(this).append('<img src="%s/icons/'+((reverse == 1) ? 'desc' : 'asc')+'.png" id="direction" />');
        } else {
            $(this).prepend('<img src="%s/icons/'+((reverse == 1) ? 'desc' : 'asc')+'.png" id="direction" />');
        }
        rows = $("#dropbox-index-list tbody tr").detach()
        rows.sort(function(a, b) {
            result = $(a).data('type') - $(b).data('type')
            if (result != 0) { return result }
            
            return (($(a).data(column) < $(b).data(column)) - ($(a).data(column) > $(b).data(column))) * reverse
            
        });
        $("#dropbox-index-list tbody").append(rows);
    }
    
    function prepare() {
        $("#dropbox-index-list tbody tr").each(function(i) {
            if ($(this).children(".name").hasClass("back")) {
                $(this).data('type', 1);
            } else if ($(this).children(".name").hasClass("dir")) {
                $(this).data('type', 2);
            } else {
                $(this).data('type', 3);
            }
            $(this).data('name', $(this).children(".name").text().toLowerCase());
            $(this).data('size', parseInt($(this).children(".size").attr("sort")));
            $(this).data('date', parseInt($(this).children(".date").attr("sort")));
        });
        
        $("#dropbox-index-list thead tr th").each(function(i) {
            $(this).bind('click', sort);
        });
    }

    $(document).ready(function(){
        prepare();
    });
</script>''' % (FILES_URL, FILES_URL)

FAVICON = '<link rel="shortcut icon" href="%s/icons/favicon.ico"/>' % FILES_URL

HTML_START = '''<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
    <meta http-equiv="Content-Type" content="text/html; charset="%(ENCODING)s"/> 
    <title>%(PATH)s</title>
    %(FAVICON)s
    %(HTML_STYLE)s
    %(HTML_JAVASCRIPT)s
</head>
<body>
'''
HTML_HEADER = '<h1 id="dropbox-index-header">%s</h1>'
HTML_TABLE_START = '''
<table id="dropbox-index-list" class="table table-striped">
    <thead>
        <tr>
            <th class="name">%s</th><th class="size">%s</th><th class="date">%s</th><th class="dl">&nbsp;</th><th class="dl">&nbsp;</th>
        </tr>
    </thead>
    <tbody>
'''
HTML_BACK = '<tr><td class="name back"><div><small><a href="../index.html">%(back_text)s</a></small></div></td><td class="size">&nbsp;</td><td class="date">&nbsp;</td>%(file_links)s</tr>'
HTML_DIR = '<tr><td class="name dir"><div><small><a href="%(file_name)s/index.html">%(file_name)s</a></small></div></td><td class="size">&nbsp;</td><td class="date" sort="%(file_time_sort)s"><small>%(file_time)s</small></td>%(file_links)s</tr>\n'
HTML_FILE = '<tr><td class="name file%(file_type)s"><div><small>%(file_base_name)s</small></div></td><td class="size" sort="%(file_size_sort)s"><small>%(file_size)s</small></td><td class="date" sort="%(file_time_sort)s"><small>%(file_time)s</small></td>%(file_links)s</tr>\n'
HTML_TABLE_END = '''
    </tbody>
</table>
<div id="dropbox-index-footer">Generated on <strong>%s</strong> using <a href="%s">Dropbox-index</a>-%s</a></div>'''
HTML_DIR_INFO = '''
<div id="dropbox-index-dir-info">
%(DIR_INFO)s
</div>'''
HTML_END = '''
</body>
</html>'''

def sorted_nicely( l ): 
    """ Sort the given iterable in the way that humans expect.""" 
    convert = lambda text: int(text) if text.isdigit() else text 
    alphanum_key = lambda key: [ convert(c) for c in re.split('([0-9]+)', key) ] 
    return sorted(l, key = alphanum_key)


def table_headers():
    if LANG in TABLE_HEADERS:
        return TABLE_HEADERS[LANG]
    else:
        return TABLE_HEADERS['en_GB']
    

def get_size(file):
    size = os.path.getsize(file)
    
    if size < 1000:
        return '%s bytes' % size
    
    kilo = size / 1024
    if kilo < 1000:
        return '%s KB' % round(float(size) / 1024, 1)
    
    mega = kilo / 1024
    return '%s MB' % round(float(kilo) / 1024, 1)


def get_filetype(file_name):
    filetype = file_name.split('.')[-1].lower()
    for keys, value in FILE_TYPES.items():
        if filetype in keys:
            return ' %s' % value
    return ''



def get_html_title(index_file):
    index = open(os.path.abspath(index_file.name), 'r').read()
    title_start = index.find('<title>') + 7
    title_end = index.find('</title>')
    return index[title_start:title_end]


def html_render(path, back, back_text, dirs, files, force=False, template_file=None, dbi_list_file=None):
    # init dbi_list as empty list
    dbi_list = []

    # only continue if something has actually changed (including the template file)
    something_changed = force
    index_file = os.path.join(path, 'index.html')
    prev_contents_file = os.path.join(path, 'dbi_contents.txt')
    prev_contents_list = []
    if not something_changed and os.path.isfile(prev_contents_file):
        prev_contents_list = open(prev_contents_file, 'r').read().splitlines()
    if not something_changed and os.path.isfile(index_file):
        index_time = os.path.getmtime(index_file)
        if template_file:
            something_changed = os.path.getmtime(template_file) > index_time
        if not something_changed:
            for file in files:
                if os.path.getmtime(file) > index_time or os.path.abspath(file) not in prev_contents_list:
                    something_changed = True
                    break
            if not something_changed:
                for dir in dirs:
                    if os.path.getmtime(dir) > index_time or os.path.abspath(dir) not in prev_contents_list:
                        something_changed = True
                        break
            if not something_changed and len(prev_contents_list) > 0:
                start = 0
                if back and back_text:
                    if prev_contents_list[0] != back_text:
                        something_changed = True
                    start = 1
                if not something_changed and len(prev_contents_list) > start:
                    for file in prev_contents_list[start:]:
                        if not os.path.exists(file):
                            something_changed = True
    else:
        something_changed = True

    if not something_changed:
        index = open(index_file, 'r')
        index.close()
        return index

    # if the dbi_list_file is specified, read it into dbi_list
    if dbi_list_file:
        with open(dbi_list_file, 'r') as file:
            dbi_list = file.read().splitlines()
    
    # init replacements as empty dictionary
    replacements = {}
    
    # set default replacement values for all dbi_list entries to keep from generating errors if the file isn't in every folder
    for s in dbi_list:
        replacements[s] = ''
    
    # set default values for all the default dbi filename strings
    replacements['PATH'] = os.path.basename(os.path.realpath(path))
    replacements['DIR-INFO'] = ''
    replacements['HEADER-INFO'] = ''
    
    dropbox_url = None
    dl_url = 'http://dl.bspk.rs/download?u=http://' + os.environ['DROPBOXPUBLICURL']
    file_links_base = None
    public_path = os.environ['DROPBOXPUBLICHOME']
        
    use_adfly = False
    for file in files:
        file_name = string.upper(os.path.basename(file))
        if os.path.splitext(file_name)[1] == '.DBI':
            replacements[os.path.splitext(file_name)[0]] = open(file, 'r').read()
            continue
        if 'USE-ADFLY' in file_name:
            use_adfly = True
            continue
            
    if use_adfly:
        dropbox_url = 'http://' + os.environ['ADFLYURL'] + os.environ['DROPBOXPUBLICURL']
        file_links_base = '<td class="dl">%(dl_file_name)s</td><td class="dl">%(adf_file_name)s</td>'
    else:
        dropbox_url = 'http://' + os.environ['DROPBOXPUBLICURL']
        file_links_base = '<td class="dl" colspan="2">%(dl_file_name)s</td>'

    index = open(index_file, 'w')
    prev_contents = open(prev_contents_file, 'w')
    template = None
    table_start = None
    
    if template_file:
        template = open(template_file, 'r').read()
        head_start = template.find('<head>') + 6
        table_start = template.find('%(FILES)s')
        index.write(template[0:head_start] % replacements)
        index.write(HTML_STYLE + HTML_JAVASCRIPT)
        index.write(template[head_start:table_start] % replacements)
    else:
        index.write(HTML_START % replacements)
        index.write(HTML_HEADER % replacements['PATH'])
    
    index.write(HTML_TABLE_START % table_headers())
    if back and back_text:
        prev_contents.write(back_text + '\n')
    adf_file_name = '&nbsp;'
    dl_file_name = '&nbsp;'

    file_links = file_links_base % locals()
    
    if back:
        if back_text:
            index.write(HTML_BACK % locals())
        else:
            back_text = '..'
            index.write(HTML_BACK % locals())
    
    for file in dirs:
        prev_contents.write(os.path.abspath(file) + '\n')
        file_name = os.path.basename(file)
        file_time = time.strftime(DATE_FORMAT, time.localtime(os.path.getmtime(file)))
        file_time_sort = os.path.getmtime(file)
        index.write(HTML_DIR % locals())

    for file in files:
        prev_contents.write(os.path.abspath(file) + '\n')
        file_base_name = os.path.basename(file)
        adf_file_name = '<a class="btn btn-xs btn-success" href="' + dropbox_url + os.path.relpath(file, public_path).replace('\\','/') + '">adf.ly</a>'
        dl_file_name = '<a class="btn btn-xs btn-primary" href="' + dl_url + os.path.relpath(file, public_path).replace('\\', '/') + '">dl.bspk.rs</a>'
        if string.lower(os.path.splitext(file_base_name)[1]) == '.dbi' or 'no-adfly' in string.lower(file_base_name) or 'use-adfly' in string.lower(file_base_name):
            continue
        file_type = get_filetype(file_base_name)
        file_size = get_size(file)
        file_size_sort = os.path.getsize(file)
        file_time = time.strftime(DATE_FORMAT, time.localtime(os.path.getmtime(file)))
        file_time_sort = os.path.getmtime(file)
        file_links = file_links_base % locals()
        index.write(HTML_FILE % locals())
    
    now = time.strftime(DATE_FORMAT, time.localtime())
    index.write(HTML_TABLE_END % (now, SCRIPT_WWW, __version__))
    
    if template_file:
        index.write(template[table_start+9:] % replacements)
    else:
        index.write(HTML_DIR_INFO % replacements)
        index.write(HTML_END)

    prev_contents.close()
    index.close()

    return index
   

def crawl(path, back=None, back_text=None, back_text_repl='', recursive=False, force=False, template_file=None, dbi_list_file=None):
    if not os.path.exists(path):
        print 'ERROR: Path %s does not exist' % path
        return
    
    if not os.path.isdir(path):
        print 'ERROR: Path %s is not a directory' % path
        return

    # print 'back_text: ' + back_text
    
    # get contents of the directory
    contents = [os.path.join(path, file) for file in os.listdir(path) if not file.endswith('index.html') and not file.endswith('dbi_contents.txt')]
    # remove hidden files
    # TODO: identify Windows hidden files
    contents = [file for file in contents if not os.path.basename(file).startswith('.')]
    
    # get only files
    # filename sort:
    files = [file for file in contents if os.path.isfile(file)]
    # files.sort(key=str.lower)
    files = sorted_nicely(files)
    files.reverse()
    
    # get only directories
    if recursive:
        dirs = [file for file in contents if os.path.isdir(file)]
        dirs.sort(key=str.lower)
    else:
        dirs = []

    prev_mod_time = 0
    if os.path.isfile(os.path.join(path, 'index.html')):
        prev_mod_time = os.path.getmtime(os.path.join(path, 'index.html'))

    # render directory contents
    current_index = html_render(path, back, back_text_repl, dirs, files, force, template_file, dbi_list_file)

    if os.path.getmtime(os.path.join(path, 'index.html')) > prev_mod_time:
        print 'Created index.html for %s' % os.path.realpath(path)
    else:
        print 'index.html up to date for %s' % os.path.realpath(path)

    if(back_text):
        current_title = get_html_title(current_index)
        # print 'current_title: ' + current_title
        back_text_repl = back_text % current_title
        # print 'back_text_repl: ' + back_text_repl
    else:
        back_text_repl = None

    # crawl subdirectories
    for dir in dirs:
        crawl(dir, path, back_text, back_text_repl, recursive, force, template_file, dbi_list_file)
    

def run():

    epilog = '''ATTENTION: 
Script will overwrite any existing index.html file(s)!
    '''

    parser = OptionParser(version='%prog ' + __version__,
                          usage="%prog [options] DIRECTORY",
                          epilog=epilog)
    parser.add_option('-F', '--force-refresh',
                      action='store_true', default=False,
                      help='Generate new index.html even if nothing changed [default: %default]')
    parser.add_option('-R', '--recursive', 
                      action='store_true', default=False,
                      help='Include subdirectories [default: %default]')
    parser.add_option('-B', '--backtext', 
                      help='Use the specified string for the "up one directory" text. Ignored if -R is not specified.')
    parser.add_option('-T', '--template', 
                      help='Use HTML file as template')
    parser.add_option('-D', '--dbilist', 
                      help='Use the specified file as a list of .dbi file names to be used in replacement')
    
    options, args = parser.parse_args()
    if not args:
        parser.print_help()
        sys.exit()
    
    crawl(path=args[0], 
          back_text=options.backtext,
          recursive=options.recursive,
          force=options.force_refresh,
          template_file=options.template,
          dbi_list_file=options.dbilist)

if __name__ == '__main__':
    run()

