1122 lines
30 KiB
Python
Executable File
1122 lines
30 KiB
Python
Executable File
#!/usr/bin/env python3
|
|
# coding=utf-8
|
|
""" A script that creates an index for a folder.
|
|
"""
|
|
|
|
import os
|
|
import sys
|
|
import time
|
|
import string
|
|
import json
|
|
import re
|
|
import urllib.parse
|
|
from glob import fnmatch
|
|
import base64
|
|
import random
|
|
from pprint import pprint
|
|
|
|
try:
|
|
import markdown
|
|
|
|
MARKDOWN_AVAILABLE = True
|
|
except ImportError:
|
|
MARKDOWN_AVAILABLE = False
|
|
|
|
VERSION = "20240626"
|
|
IMAGE_EXTENSIONS = ["png", "gif", "jpg", "jpeg", "tif", "tiff"]
|
|
AUDIO_EXTENSIONS = ["wav", "mp3", "ogg"]
|
|
VIDEO_EXTENSIONS = ["mp4", "ogg", "webm"]
|
|
SAFE_OPTS = (
|
|
"hidden",
|
|
"title",
|
|
"parent",
|
|
"recursive",
|
|
"images",
|
|
"media",
|
|
"includes",
|
|
"excludes",
|
|
"no_readme",
|
|
"no_wget",
|
|
"reverse",
|
|
)
|
|
|
|
|
|
def setup():
|
|
"""Setup the command line options"""
|
|
from argparse import ArgumentParser
|
|
|
|
parser = ArgumentParser(
|
|
epilog="Recursively generate indexes: \n# find . -type d -not -path '*/.*' -exec SimpleWebPage \{\} \;"
|
|
)
|
|
parser.add_argument(
|
|
"-f",
|
|
"--force",
|
|
action="store_true",
|
|
dest="overwrite",
|
|
default=False,
|
|
help="Overwrite existing index file, even if it's not generated with SimpleWebPage. By default, if file is generated with SimpleWebPage it will be overwritten! This option will _not_ read existing config, but will replace it with command line switches.",
|
|
)
|
|
parser.add_argument(
|
|
"-H",
|
|
action="store_true",
|
|
dest="hidden",
|
|
default=False,
|
|
help="Show hidden files",
|
|
)
|
|
parser.add_argument(
|
|
"--no-readme",
|
|
action="store_true",
|
|
dest="no_readme",
|
|
default=False,
|
|
help="Do not show README.md on the page",
|
|
)
|
|
parser.add_argument(
|
|
"--no-wget",
|
|
action="store_true",
|
|
dest="no_wget",
|
|
default=False,
|
|
help="Do not show wget download links",
|
|
)
|
|
parser.add_argument(
|
|
"-t",
|
|
type=str,
|
|
dest="title",
|
|
default=None,
|
|
help="Name for the title (Default: Folder name)",
|
|
)
|
|
parser.add_argument(
|
|
"-o",
|
|
type=str,
|
|
dest="filename",
|
|
default="index.html",
|
|
help="Output filename (Default: index.html)",
|
|
)
|
|
parser.add_argument(
|
|
"-p",
|
|
action="store_false",
|
|
dest="parent",
|
|
default=True,
|
|
help="Do no print .. link for parent folder.",
|
|
)
|
|
parser.add_argument(
|
|
"--password",
|
|
type=str,
|
|
dest="password",
|
|
default=None,
|
|
help="Set a password to view page. The file list will be written to a randomly generated filename. Note: this is not secure, as the target link will still be unprotected if known.",
|
|
)
|
|
parser.add_argument(
|
|
"-r",
|
|
action="store_true",
|
|
dest="recursive",
|
|
default=False,
|
|
help="Include all files recursively in the list. Folders are not shown as links.",
|
|
)
|
|
parser.add_argument(
|
|
"--reverse",
|
|
action="store_true",
|
|
dest="reverse",
|
|
default=False,
|
|
help="Reverse sort",
|
|
)
|
|
parser.add_argument(
|
|
"--images",
|
|
action="store_true",
|
|
dest="images",
|
|
default=False,
|
|
help="Show images with <img> tags",
|
|
)
|
|
parser.add_argument(
|
|
"--media",
|
|
action="store_true",
|
|
dest="media",
|
|
default=False,
|
|
help="Make media playable",
|
|
)
|
|
parser.add_argument(
|
|
"--include",
|
|
"-i",
|
|
action="store",
|
|
dest="includes",
|
|
default=["*"],
|
|
help="Glob match for files to be included in the table. ex. *.jpg. You can pass several includes.",
|
|
nargs="*",
|
|
)
|
|
parser.add_argument(
|
|
"--exclude",
|
|
"-e",
|
|
action="store",
|
|
dest="excludes",
|
|
default=[""],
|
|
help="Glob match for files to be excluded from the table. ex. *.jpg. You can pass several excludes.",
|
|
nargs="*",
|
|
)
|
|
parser.add_argument("--version", action="version", version=VERSION)
|
|
parser.add_argument(
|
|
"path",
|
|
type=str,
|
|
action="store",
|
|
default=os.path.abspath("."),
|
|
nargs="?",
|
|
help="Root path of the index",
|
|
)
|
|
|
|
options = parser.parse_args()
|
|
options.path = os.path.abspath(options.path)
|
|
if options.title == None:
|
|
options.title = os.path.basename(options.path)
|
|
return options
|
|
|
|
|
|
def print_setup(opts):
|
|
print("Current configuration:")
|
|
pprint(setup2safe(opts))
|
|
|
|
|
|
def setup2safe(opts):
|
|
safe_opts = {}
|
|
opts_dict = vars(opts)
|
|
for key in opts_dict:
|
|
if key in SAFE_OPTS:
|
|
safe_opts[key] = opts_dict[key]
|
|
return safe_opts
|
|
|
|
|
|
def setup2JSON(opts):
|
|
return json.dumps(setup2safe(opts))
|
|
|
|
|
|
def HTML2setup(opts, overwrite):
|
|
"""returns new opts and was it able to read HTML, if overwriting, do not update config"""
|
|
|
|
try:
|
|
read_config = False
|
|
with open(os.path.join(opts.path, opts.filename), "rt") as f:
|
|
for l in f.readlines():
|
|
if l.find('<meta name="SimpleWebPageSetup"') > -1:
|
|
content = l[l.find("content=") :].rstrip("'/>\n")
|
|
config = json.loads(content[9:])
|
|
for key in config:
|
|
if key in SAFE_OPTS:
|
|
if not overwrite:
|
|
setattr(opts, key, config[key])
|
|
read_config = True
|
|
if not overwrite:
|
|
print("Read options from existing " + opts.filename)
|
|
print("Command line switches are not used.")
|
|
break
|
|
return (opts, read_config)
|
|
except Exception as e:
|
|
print("Error parsing configuration")
|
|
print(e)
|
|
return (opts, False)
|
|
|
|
|
|
def get_files_and_folders(opts):
|
|
|
|
if opts.recursive:
|
|
rdirs = []
|
|
rfiles = []
|
|
rpath = None
|
|
for path, dirs, files in os.walk(opts.path):
|
|
if rpath == None:
|
|
rpath = path
|
|
if not opts.hidden:
|
|
files = [f for f in files if not f.startswith(".")]
|
|
dirs[:] = [d for d in dirs if not d.startswith(".")]
|
|
files = [os.path.join(os.path.relpath(path, opts.path), f) for f in files]
|
|
files = [f[2:] if f.startswith("./") else f for f in files]
|
|
rfiles.extend(files)
|
|
|
|
return rdirs, rfiles, rpath
|
|
|
|
else:
|
|
for path, dirs, files in os.walk(opts.path):
|
|
if not opts.hidden:
|
|
files = [f for f in files if not f.startswith(".")]
|
|
dirs = [d for d in dirs if not d.startswith(".")]
|
|
return dirs, files, path
|
|
|
|
|
|
def generate_index(opts):
|
|
opts.password_filename = ""
|
|
dirs, files, path = get_files_and_folders(opts)
|
|
if opts.password != None:
|
|
opts.password_filename = opts.filename
|
|
opts.filename = generate_password_page(path, opts.filename, opts.password)
|
|
if opts.filename in files:
|
|
opts, existing_config = HTML2setup(opts, opts.overwrite)
|
|
if not existing_config and not opts.overwrite:
|
|
print(
|
|
opts.filename
|
|
+ " exists, and not generated with SimpleWebPage. Exiting."
|
|
)
|
|
sys.exit(1)
|
|
# Re-read files, with probably changed opts
|
|
dirs, files, path = get_files_and_folders(opts)
|
|
print_setup(opts)
|
|
files = [f for f in files if f != opts.filename]
|
|
files = [f for f in files if f != opts.password_filename]
|
|
files = match_files(files, opts.includes, opts.excludes)
|
|
dirs = match_files(dirs, opts.includes, opts.excludes)
|
|
dirs.sort(reverse=opts.reverse)
|
|
files.sort(reverse=opts.reverse)
|
|
files.sort(key=lambda x: x.find("/") > 0)
|
|
readme = get_readme(path, opts.no_readme)
|
|
with open(os.path.join(path, opts.filename), "wt") as f:
|
|
f.write(get_header(opts))
|
|
if opts.parent:
|
|
f.write(get_pathlink(path, ".."))
|
|
for di in dirs:
|
|
f.write(get_pathlink(path, di))
|
|
for fi in files:
|
|
f.write(get_filelink(path, fi, opts.images, opts.media))
|
|
f.write(get_footer(readme, show_wget=not opts.no_wget))
|
|
if not opts.no_wget:
|
|
f.write(get_download_lines(files, recursive=opts.recursive))
|
|
f.close()
|
|
return
|
|
|
|
|
|
def generate_password_page(path, password_file, password):
|
|
def ha(p):
|
|
h = 0
|
|
for c in p:
|
|
h = ((h << 5) - h) + ord(c)
|
|
h = h & 0xFFFFFFFF
|
|
if h > (1 << 31) - 1:
|
|
h -= 1 << 32
|
|
return h
|
|
|
|
def scramble(p, t):
|
|
p += t
|
|
s = ""
|
|
for i in range(len(t)):
|
|
s += chr(ord(p[i]) + ord(t[i]))
|
|
return s
|
|
|
|
def enc(s):
|
|
return base64.b64encode(s.encode("latin1")).decode("ascii")
|
|
|
|
def random_string(stringLength=16):
|
|
letters = string.ascii_lowercase + string.digits
|
|
return "".join(random.choice(letters) for i in range(stringLength))
|
|
|
|
def get_target(filename):
|
|
splitted = os.path.splitext(filename)
|
|
return (splitted[0], random_string(), splitted[1])
|
|
|
|
target_base, target_middle, target_ext = get_target(password_file)
|
|
secret = "{}:{}".format(ha(password), enc(scramble(password, target_middle)))
|
|
with open(os.path.join(path, password_file), "wt") as f:
|
|
f.write(get_password_page(secret, target_base, target_ext))
|
|
return "{}.{}{}".format(target_base, target_middle, target_ext)
|
|
|
|
|
|
def get_filelink(path, fname, images=False, media=False):
|
|
if os.path.islink(os.path.join(path, fname)) and not os.path.exists(
|
|
os.path.join(path, fname)
|
|
):
|
|
(fsize, fsstr, fsstrb, fdstr) = (0, "NA", "NA", "NA")
|
|
else:
|
|
fsize = os.path.getsize(os.path.join(path, fname))
|
|
fsstr = sizeof(fsize)
|
|
fsstrb = str(fsize)
|
|
fdate = time.localtime(os.path.getmtime(os.path.join(path, fname)))
|
|
fdstr = time.strftime("%Y/%m/%d %H:%M:%S", fdate)
|
|
fname_str = fname
|
|
fname_media = ""
|
|
if images and is_imagefile(fname):
|
|
fname_media = get_imagestr(fname)
|
|
if media and is_audiofile(fname):
|
|
fname_media = get_audiostr(fname)
|
|
if media and is_videofile(fname):
|
|
fname_media = get_videostr(fname)
|
|
return (
|
|
'<tr class="row_file"><td><a class="link_file" href="%s">%s</a>%s<td class="right">%s</td><td class="right bytes">%s</td><td class="right">%s</td></tr>\n'
|
|
% (urllib.parse.quote(fname), fname_str, fname_media, fsstr, fsstrb, fdstr)
|
|
)
|
|
|
|
|
|
def get_wget_command():
|
|
return 'wget -qO - "$URL" | grep "^#FILE " | cut -c7- | sed "s,^,$URL," | xargs -n1 wget -Nc'
|
|
|
|
|
|
def get_download_lines(files, recursive=False):
|
|
s = "\n<!--\n"
|
|
for f in files:
|
|
s += "#FILE %s\n" % (urllib.parse.quote(f),)
|
|
s += "#DL-CMD:\n#URL=[insert URL] && " + get_wget_command()
|
|
if recursive:
|
|
s += "x"
|
|
s += "\n-->\n"
|
|
return s
|
|
|
|
|
|
def get_imagestr(fname):
|
|
return '<img class=image src="%s" title="%s"/>' % (
|
|
urllib.parse.quote(fname),
|
|
fname,
|
|
)
|
|
|
|
|
|
def get_audiostr(fname):
|
|
return '<audio src="%s" controls class=audio></audio>' % (
|
|
urllib.parse.quote(fname),
|
|
)
|
|
|
|
|
|
def get_videostr(fname):
|
|
return '<video class=video controls><source src="%s"></video>' % (
|
|
urllib.parse.quote(fname),
|
|
)
|
|
|
|
|
|
def get_pathlink(path, dname):
|
|
fdate = time.localtime(os.path.getmtime(os.path.join(path, dname)))
|
|
fdstr = time.strftime("%Y/%m/%d %H:%M:%S", fdate)
|
|
return (
|
|
'<tr class="row_dir"><td><a class="link_dir" href="%s">↳ %s/</a><td class="right">[DIR]</td><td class="right bytes">0</td><td class="right">%s</td></tr>\n'
|
|
% (urllib.parse.quote(dname), dname, fdstr)
|
|
)
|
|
|
|
|
|
def get_password_page(secret, target_base, target_ext):
|
|
return (
|
|
"""<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 3.2 Final//EN">
|
|
<html>
|
|
<head>
|
|
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
|
|
<link rel="shortcut icon" href="data:image/x-icon;," type="image/x-icon">
|
|
<meta name="viewport" content="width=device-width, initial-scale=1, height=device-height, viewport-fit=cover">
|
|
<title>Password required</title>
|
|
<script language="JavaScript">
|
|
Object.defineProperty(String.prototype, 'hashCode', {
|
|
value: function() {
|
|
var hash = 0, i, chr;
|
|
for (i = 0; i < this.length; i++) {
|
|
chr = this.charCodeAt(i);
|
|
hash = ((hash << 5) - hash) + chr;
|
|
hash |= 0; // Convert to 32bit integer
|
|
}
|
|
return hash;
|
|
}
|
|
});
|
|
function isMobile () {
|
|
return /Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test(navigator.userAgent);
|
|
}
|
|
function scramble(p,t) {
|
|
p += t;
|
|
var sstr = '';
|
|
for (var i=0; i < t.length; i++) {
|
|
sstr += String.fromCharCode(p.charCodeAt(i) + t.charCodeAt(i));
|
|
}
|
|
return sstr;
|
|
}
|
|
function unscramble(p,t) {
|
|
var sstr = '';
|
|
var schr = '';
|
|
for (var i=0; i < t.length; i++) {
|
|
schr = String.fromCharCode(t.charCodeAt(i) - p.charCodeAt(i));
|
|
sstr += schr;
|
|
p += schr;
|
|
}
|
|
return sstr;
|
|
}
|
|
function pw_event(element) {
|
|
if(event.key === 'Enter') {
|
|
pw(element);
|
|
}
|
|
}
|
|
function pw(element) {
|
|
var pw = "SECRET";
|
|
if (element.value) {
|
|
pw = pw.split(':',2);
|
|
if (element.value.hashCode() == parseInt(pw[0])) {
|
|
let unscrambled = "TARGET_BASE." + unscramble(element.value, atob(pw[1])) + "TARGET_EXT"
|
|
if (isMobile()) {
|
|
document.location.replace(unscrambled);
|
|
return
|
|
}
|
|
document.getElementById("body").innerHTML = '';
|
|
var frame = document.createElement("iframe");
|
|
frame.src = unscrambled;
|
|
document.getElementById("body").appendChild(frame);
|
|
} else {
|
|
document.getElementById("message").innerHTML = "No match";
|
|
setTimeout(clear_message, 5000);
|
|
}
|
|
}
|
|
}
|
|
function clear_message() {
|
|
document.getElementById("message").innerHTML = '';
|
|
}
|
|
function newPass(password, target) {
|
|
console.log(
|
|
' var pw = "' + password.hashCode().toString() + ':' +
|
|
btoa(scramble(password, target)) + '";'
|
|
);
|
|
}
|
|
//console.log("Use newPass('pass','page.html'); to get a new hash");
|
|
</script>
|
|
<style>
|
|
iframe {
|
|
width: 100vw;
|
|
height: 100vh;
|
|
}
|
|
#body {
|
|
margin: 0;
|
|
overflow-x: hidden;
|
|
overflow-y: hidden;
|
|
}
|
|
#content {
|
|
text-align: center;
|
|
padding-top: 4rem;
|
|
font-family: "Lucida Sans Unicode", "Lucida Grande", sans-serif;
|
|
font-weight: bold;
|
|
}
|
|
#message {
|
|
margin-top: 1rem;
|
|
}
|
|
#button {
|
|
margin-top: 1rem;
|
|
}
|
|
</style>
|
|
</head>
|
|
<body id="body">
|
|
<div id="content">
|
|
<input type="password" placeholder="Password" class="password" onkeydown="pw_event(this)" id="password" onFocus="this.select()" autofocus/>
|
|
<br><button id="button" type="button" onclick="pw(document.getElementById('password'));">Enter</button>
|
|
<div id="message"></div>
|
|
<noscript><br>Javascript is required to access this area. Yours seems to be disabled.</noscript>
|
|
</div>
|
|
</body>
|
|
</html>""".replace(
|
|
"TARGET_EXT", target_ext, 1
|
|
)
|
|
.replace("TARGET_BASE", target_base, 1)
|
|
.replace("SECRET", secret, 1)
|
|
)
|
|
|
|
|
|
def get_readme(path, no_read):
|
|
|
|
if no_read:
|
|
return ""
|
|
if not os.path.exists(os.path.join(path, "README.md")):
|
|
return ""
|
|
with open(os.path.join(path, "README.md"), "rt") as fp:
|
|
if MARKDOWN_AVAILABLE:
|
|
return "<div class=readme>{}</div>".format(
|
|
markdown.markdown(fp.read().strip(), extensions=["extra"])
|
|
)
|
|
|
|
else:
|
|
sys.stderr.write("import markdown failed, using simple regex\n")
|
|
return "<div class=readme><pre>{}</pre></div>".format(
|
|
re.sub(
|
|
r"(https?:\/\/[\w\.,\-\@?^=%&:/~\+#]+)",
|
|
'<a href="\\1">\\1</a>',
|
|
fp.read().strip(),
|
|
flags=re.IGNORECASE,
|
|
)
|
|
)
|
|
|
|
|
|
def get_header(opts):
|
|
opts_str = setup2JSON(opts)
|
|
|
|
js_code = (
|
|
"""
|
|
<script type="text/javascript">
|
|
/*
|
|
Table sorting script by Joost de Valk, check it out at http://www.joostdevalk.nl/code/sortable-table/.
|
|
Based on a script from http://www.kryogenix.org/code/browser/sorttable/.
|
|
Distributed under the MIT license: http://www.kryogenix.org/code/browser/licence.html .
|
|
Edited, removed image support and a tags from headers, Ville Rantanen 2015
|
|
|
|
Copyright (c) 1997-2007 Stuart Langridge, Joost de Valk.
|
|
|
|
Version 1.5.7
|
|
*/
|
|
|
|
/* You can change these values */
|
|
var europeandate = true;
|
|
var alternate_row_colors = false;
|
|
|
|
/* Don't change anything below this unless you know what you're doing */
|
|
addEvent(window, "load", sortables_init);
|
|
|
|
var SORT_COLUMN_INDEX;
|
|
var thead = false;
|
|
|
|
function sortables_init() {
|
|
// Find all tables with class sortable and make them sortable
|
|
if (!document.getElementsByTagName) return;
|
|
tbls = document.getElementsByTagName("table");
|
|
for (ti=0;ti<tbls.length;ti++) {
|
|
thisTbl = tbls[ti];
|
|
if (((' '+thisTbl.className+' ').indexOf("sortable") != -1) && (thisTbl.id)) {
|
|
ts_makeSortable(thisTbl);
|
|
}
|
|
}
|
|
}
|
|
|
|
function ts_makeSortable(t) {
|
|
if (t.rows && t.rows.length > 0) {
|
|
if (t.tHead && t.tHead.rows.length > 0) {
|
|
var firstRow = t.tHead.rows[t.tHead.rows.length-1];
|
|
thead = true;
|
|
} else {
|
|
var firstRow = t.rows[0];
|
|
}
|
|
}
|
|
if (!firstRow) return;
|
|
|
|
// We have a first row: assume it's the header, and make its contents clickable links
|
|
for (var i=0;i<firstRow.cells.length;i++) {
|
|
var cell = firstRow.cells[i];
|
|
var txt = ts_getInnerText(cell);
|
|
if (cell.className != "unsortable" && cell.className.indexOf("unsortable") == -1) {
|
|
cell.innerHTML = '<span class="sortheader" onclick="ts_resortTable(this, '+i+');return false;">'+txt+'<span class="sortarrow"> ↕</span></span>';
|
|
}
|
|
}
|
|
if (alternate_row_colors) {
|
|
alternate(t);
|
|
}
|
|
}
|
|
|
|
function ts_getInnerText(el) {
|
|
if (typeof el == "string") return el;
|
|
if (typeof el == "undefined") { return el };
|
|
if (el.innerText) return el.innerText; //Not needed but it is faster
|
|
var str = "";
|
|
|
|
var cs = el.childNodes;
|
|
var l = cs.length;
|
|
for (var i = 0; i < l; i++) {
|
|
switch (cs[i].nodeType) {
|
|
case 1: //ELEMENT_NODE
|
|
str += ts_getInnerText(cs[i]);
|
|
break;
|
|
case 3: //TEXT_NODE
|
|
str += cs[i].nodeValue;
|
|
break;
|
|
}
|
|
}
|
|
return str;
|
|
}
|
|
|
|
function ts_resortTable(lnk, clid) {
|
|
var span;
|
|
for (var ci=0;ci<lnk.childNodes.length;ci++) {
|
|
if (lnk.childNodes[ci].tagName && lnk.childNodes[ci].tagName.toLowerCase() == 'span') span = lnk.childNodes[ci];
|
|
}
|
|
var spantext = ts_getInnerText(span);
|
|
var td = lnk.parentNode;
|
|
var column = clid || td.cellIndex;
|
|
var t = getParent(td,'TABLE');
|
|
// Work out a type for the column
|
|
if (t.rows.length <= 1) return;
|
|
var itm = "";
|
|
var i = 0;
|
|
while (itm == "" && i < t.tBodies[0].rows.length) {
|
|
var itm = ts_getInnerText(t.tBodies[0].rows[i].cells[column]);
|
|
itm = trim(itm);
|
|
if (itm.substr(0,4) == "<!--" || itm.length == 0) {
|
|
itm = "";
|
|
}
|
|
i++;
|
|
}
|
|
if (itm == "") return;
|
|
sortfn = ts_sort_caseinsensitive;
|
|
if (itm.match(/^\d\d[\/\.-][a-zA-z][a-zA-Z][a-zA-Z][\/\.-]\d\d\d\d$/)) sortfn = ts_sort_date;
|
|
if (itm.match(/^\d\d[\/\.-]\d\d[\/\.-]\d\d\d{2}?$/)) sortfn = ts_sort_date;
|
|
if (itm.match(/^-?[£$Û¢´]\d/)) sortfn = ts_sort_numeric;
|
|
if (itm.match(/^-?(\d+[,\.]?)+(E[-+][\d]+)?%?$/)) sortfn = ts_sort_numeric;
|
|
SORT_COLUMN_INDEX = column;
|
|
var firstRow = new Array();
|
|
var newRows = new Array();
|
|
for (k=0;k<t.tBodies.length;k++) {
|
|
for (i=0;i<t.tBodies[k].rows[0].length;i++) {
|
|
firstRow[i] = t.tBodies[k].rows[0][i];
|
|
}
|
|
}
|
|
for (k=0;k<t.tBodies.length;k++) {
|
|
if (!thead) {
|
|
// Skip the first row
|
|
for (j=1;j<t.tBodies[k].rows.length;j++) {
|
|
newRows[j-1] = t.tBodies[k].rows[j];
|
|
}
|
|
} else {
|
|
// Do NOT skip the first row
|
|
for (j=0;j<t.tBodies[k].rows.length;j++) {
|
|
newRows[j] = t.tBodies[k].rows[j];
|
|
}
|
|
}
|
|
}
|
|
newRows.sort(sortfn);
|
|
if (span.getAttribute("sortdir") == 'down') {
|
|
ARROW = ' ↑';
|
|
newRows.reverse();
|
|
span.setAttribute('sortdir','up');
|
|
} else {
|
|
ARROW = ' ↓';
|
|
span.setAttribute('sortdir','down');
|
|
}
|
|
// We appendChild rows that already exist to the tbody, so it moves them rather than creating new ones
|
|
// don't do sortbottom rows
|
|
for (i=0; i<newRows.length; i++) {
|
|
if (!newRows[i].className || (newRows[i].className && (newRows[i].className.indexOf('sortbottom') == -1))) {
|
|
t.tBodies[0].appendChild(newRows[i]);
|
|
}
|
|
}
|
|
// do sortbottom rows only
|
|
for (i=0; i<newRows.length; i++) {
|
|
if (newRows[i].className && (newRows[i].className.indexOf('sortbottom') != -1))
|
|
t.tBodies[0].appendChild(newRows[i]);
|
|
}
|
|
// Delete any other arrows there may be showing
|
|
var allspans = document.getElementsByTagName("span");
|
|
for (var ci=0;ci<allspans.length;ci++) {
|
|
if (allspans[ci].className == 'sortarrow') {
|
|
if (getParent(allspans[ci],"table") == getParent(lnk,"table")) { // in the same table as us?
|
|
allspans[ci].innerHTML = ' ↕';
|
|
}
|
|
}
|
|
}
|
|
span.innerHTML = ARROW;
|
|
alternate(t);
|
|
}
|
|
|
|
function getParent(el, pTagName) {
|
|
if (el == null) {
|
|
return null;
|
|
} else if (el.nodeType == 1 && el.tagName.toLowerCase() == pTagName.toLowerCase()) {
|
|
return el;
|
|
} else {
|
|
return getParent(el.parentNode, pTagName);
|
|
}
|
|
}
|
|
|
|
function sort_date(date) {
|
|
// y2k notes: two digit years less than 50 are treated as 20XX, greater than 50 are treated as 19XX
|
|
dt = "00000000";
|
|
if (date.length == 11) {
|
|
mtstr = date.substr(3,3);
|
|
mtstr = mtstr.toLowerCase();
|
|
switch(mtstr) {
|
|
case "jan": var mt = "01"; break;
|
|
case "feb": var mt = "02"; break;
|
|
case "mar": var mt = "03"; break;
|
|
case "apr": var mt = "04"; break;
|
|
case "may": var mt = "05"; break;
|
|
case "jun": var mt = "06"; break;
|
|
case "jul": var mt = "07"; break;
|
|
case "aug": var mt = "08"; break;
|
|
case "sep": var mt = "09"; break;
|
|
case "oct": var mt = "10"; break;
|
|
case "nov": var mt = "11"; break;
|
|
case "dec": var mt = "12"; break;
|
|
// default: var mt = "00";
|
|
}
|
|
dt = date.substr(7,4)+mt+date.substr(0,2);
|
|
return dt;
|
|
} else if (date.length == 10) {
|
|
if (europeandate == false) {
|
|
dt = date.substr(6,4)+date.substr(0,2)+date.substr(3,2);
|
|
return dt;
|
|
} else {
|
|
dt = date.substr(6,4)+date.substr(3,2)+date.substr(0,2);
|
|
return dt;
|
|
}
|
|
} else if (date.length == 8) {
|
|
yr = date.substr(6,2);
|
|
if (parseInt(yr) < 50) {
|
|
yr = '20'+yr;
|
|
} else {
|
|
yr = '19'+yr;
|
|
}
|
|
if (europeandate == true) {
|
|
dt = yr+date.substr(3,2)+date.substr(0,2);
|
|
return dt;
|
|
} else {
|
|
dt = yr+date.substr(0,2)+date.substr(3,2);
|
|
return dt;
|
|
}
|
|
}
|
|
return dt;
|
|
}
|
|
|
|
function ts_sort_date(a,b) {
|
|
dt1 = sort_date(ts_getInnerText(a.cells[SORT_COLUMN_INDEX]));
|
|
dt2 = sort_date(ts_getInnerText(b.cells[SORT_COLUMN_INDEX]));
|
|
|
|
if (dt1==dt2) {
|
|
return 0;
|
|
}
|
|
if (dt1<dt2) {
|
|
return -1;
|
|
}
|
|
return 1;
|
|
}
|
|
function ts_sort_numeric(a,b) {
|
|
var aa = ts_getInnerText(a.cells[SORT_COLUMN_INDEX]);
|
|
aa = clean_num(aa);
|
|
var bb = ts_getInnerText(b.cells[SORT_COLUMN_INDEX]);
|
|
bb = clean_num(bb);
|
|
return compare_numeric(aa,bb);
|
|
}
|
|
function compare_numeric(a,b) {
|
|
var a = parseFloat(a);
|
|
a = (isNaN(a) ? 0 : a);
|
|
var b = parseFloat(b);
|
|
b = (isNaN(b) ? 0 : b);
|
|
return a - b;
|
|
}
|
|
function ts_sort_caseinsensitive(a,b) {
|
|
aa = ts_getInnerText(a.cells[SORT_COLUMN_INDEX]).toLowerCase();
|
|
bb = ts_getInnerText(b.cells[SORT_COLUMN_INDEX]).toLowerCase();
|
|
if (aa==bb) {
|
|
return 0;
|
|
}
|
|
if (aa<bb) {
|
|
return -1;
|
|
}
|
|
return 1;
|
|
}
|
|
function ts_sort_default(a,b) {
|
|
aa = ts_getInnerText(a.cells[SORT_COLUMN_INDEX]);
|
|
bb = ts_getInnerText(b.cells[SORT_COLUMN_INDEX]);
|
|
if (aa==bb) {
|
|
return 0;
|
|
}
|
|
if (aa<bb) {
|
|
return -1;
|
|
}
|
|
return 1;
|
|
}
|
|
function addEvent(elm, evType, fn, useCapture)
|
|
// addEvent and removeEvent
|
|
// cross-browser event handling for IE5+, NS6 and Mozilla
|
|
// By Scott Andrew
|
|
{
|
|
if (elm.addEventListener){
|
|
elm.addEventListener(evType, fn, useCapture);
|
|
return true;
|
|
} else if (elm.attachEvent){
|
|
var r = elm.attachEvent("on"+evType, fn);
|
|
return r;
|
|
} else {
|
|
alert("Handler could not be removed");
|
|
}
|
|
}
|
|
function clean_num(str) {
|
|
str = str.replace(new RegExp(/[^-?0-9.]/g),"");
|
|
return str;
|
|
}
|
|
function trim(s) {
|
|
return s.replace(/^\s+|\s+$/g, "");
|
|
}
|
|
function alternate(table) {
|
|
// Take object table and get all it's tbodies.
|
|
var tableBodies = table.getElementsByTagName("tbody");
|
|
// Loop through these tbodies
|
|
for (var i = 0; i < tableBodies.length; i++) {
|
|
// Take the tbody, and get all it's rows
|
|
var tableRows = tableBodies[i].getElementsByTagName("tr");
|
|
// Loop through these rows
|
|
// Start at 1 because we want to leave the heading row untouched
|
|
for (var j = 0; j < tableRows.length; j++) {
|
|
// Check if j is even, and apply classes for both possible results
|
|
if ( (j % 2) == 0 ) {
|
|
if ( !(tableRows[j].className.indexOf('odd') == -1) ) {
|
|
tableRows[j].className = tableRows[j].className.replace('odd', 'even');
|
|
} else {
|
|
if ( tableRows[j].className.indexOf('even') == -1 ) {
|
|
tableRows[j].className += " even";
|
|
}
|
|
}
|
|
} else {
|
|
if ( !(tableRows[j].className.indexOf('even') == -1) ) {
|
|
tableRows[j].className = tableRows[j].className.replace('even', 'odd');
|
|
} else {
|
|
if ( tableRows[j].className.indexOf('odd') == -1 ) {
|
|
tableRows[j].className += " odd";
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
function show_wget_magic() {
|
|
document.getElementById("wget_magic").innerHTML = 'URL="' + document.URL +'" && ' +
|
|
'"""
|
|
+ get_wget_command()
|
|
+ """';
|
|
document.getElementById("wget_magic").classList.toggle("hidden");
|
|
window.scrollTo(0,document.body.scrollHeight);
|
|
}
|
|
</script>
|
|
"""
|
|
)
|
|
css_style = """
|
|
<style>
|
|
/* Style modified from: https://css-tricks.com/snippets/php/display-styled-directory-contents/ */
|
|
body {
|
|
color: #222;
|
|
font: 14px monospace;
|
|
padding: 0;
|
|
background: #CCC;
|
|
margin: 0;
|
|
}
|
|
.main_h1 {
|
|
padding: 20px 0 12px 0;
|
|
margin: 0 0 0 70px;
|
|
font-family: sans-serif;
|
|
}
|
|
h2 {
|
|
padding: 10px 0 8px 0;
|
|
margin: 0 0 0 30px;
|
|
}
|
|
.container {
|
|
margin: 20px;
|
|
margin-bottom: 0px;
|
|
box-shadow: 0 5px 10px -5px rgba(0,0,0,0.5);
|
|
position: relative;
|
|
background: #eee;
|
|
border-top-left-radius: 70px;
|
|
display: inline-block;
|
|
min-width: calc(100% - 40px);
|
|
padding: 0;
|
|
}
|
|
table {
|
|
background-color: #F3F3F3;
|
|
border-collapse: collapse;
|
|
width: 100%;
|
|
margin: 15px 0;
|
|
padding: 0;
|
|
}
|
|
th {
|
|
background-color: #EB6000;
|
|
color: #FFF;
|
|
cursor: pointer;
|
|
padding: 5px 10px;
|
|
}
|
|
td {
|
|
padding-right: 5px;
|
|
}
|
|
a {
|
|
color: #EB6000;
|
|
text-decoration: none;
|
|
}
|
|
a:visited {
|
|
color: #AB2000;
|
|
}
|
|
|
|
td a {
|
|
display: block;
|
|
padding: 5px 10px;
|
|
}
|
|
td a.link_dir {
|
|
font-weight: bold;
|
|
}
|
|
td a.link_file {
|
|
margin-left: 2ex;
|
|
}
|
|
th a {
|
|
padding-left: 0
|
|
}
|
|
td:first-of-type a {
|
|
padding-left: 5px;
|
|
}
|
|
th:first-of-type {
|
|
padding-left: 5px;
|
|
}
|
|
tr:nth-of-type(odd) {
|
|
background-color: #e1e1e1;
|
|
}
|
|
tr:nth-of-type(even) {
|
|
background-color: #f1f1f1;
|
|
}
|
|
tr.row_dir:hover td {
|
|
background-color: #fbe6b3;
|
|
}
|
|
tr.row_file:hover td {
|
|
background-color: #f9cba2;
|
|
}
|
|
tr:hover td a {
|
|
color: #222;
|
|
}
|
|
.right {
|
|
text-align: right;
|
|
}
|
|
.bytes {
|
|
font-size: xx-small;
|
|
}
|
|
.image {
|
|
max-width: 90%;
|
|
}
|
|
.audio {
|
|
width: 90%;
|
|
padding-left: 5px;
|
|
}
|
|
.video {
|
|
width: 320;
|
|
height: 240;
|
|
}
|
|
.container pre {
|
|
margin: 10px 10px 10px 10px;
|
|
}
|
|
.readme {
|
|
padding: 0;
|
|
margin: 5px;
|
|
}
|
|
.readme code {
|
|
color: #0060EB;
|
|
}
|
|
@media only screen and (max-width: 600px) {
|
|
.container {
|
|
margin: 0;
|
|
min-width: 800px;
|
|
display: block;
|
|
}
|
|
table {
|
|
margin: 0;
|
|
width: 800px;monilla yrityksi
|
|
}
|
|
td {
|
|
padding-top: 10px;
|
|
padding-bottom: 10px;
|
|
max-width: 400px;
|
|
}
|
|
td a {
|
|
max-width: 400px;
|
|
word-break: break-all;
|
|
}
|
|
}
|
|
#wget_container {
|
|
display: inline-block;
|
|
margin-left: 20px;
|
|
margin-right: 20px;
|
|
margin-bottom: 20px;
|
|
}
|
|
#wget_toggle {
|
|
display: inline-block;
|
|
cursor: pointer;
|
|
background-color: #EB6000;
|
|
color: white;
|
|
padding: 5px;
|
|
box-shadow: 0 5px 10px -5px rgba(0,0,0,0.5);
|
|
}
|
|
#wget_magic {
|
|
white-space: pre-wrap;
|
|
word-wrap: break-word;
|
|
padding: 20px;
|
|
background-color: #eee;
|
|
margin: 0px;
|
|
box-shadow: 0 5px 10px -5px rgba(0,0,0,0.5);
|
|
}
|
|
.hidden {
|
|
display: none;
|
|
}
|
|
</style>
|
|
"""
|
|
header = (
|
|
"""<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 3.2 Final//EN">
|
|
<html>
|
|
<head>
|
|
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
|
|
<meta name="viewport" content="width=device-width, initial-scale=1, height=device-height, viewport-fit=cover">
|
|
<meta name="generator" content="{program}"/>
|
|
<meta name="SimpleWebPageVersion" content="{version}"/>
|
|
<meta name="SimpleWebPageSetup" content='{config}'/>
|
|
|
|
<title>{title}</title>
|
|
{css_style}
|
|
{js_code}
|
|
</head>
|
|
<body>
|
|
<div class="container">
|
|
<h1 class="main_h1">{title}</h1>
|
|
<table class="sortable" id="fileList"><thead><tr><th>Name</th><th class="right">Size</th><th class="right">Size B</th><th class="right">Modified</th></tr></thead><tbody>
|
|
"""
|
|
).format(
|
|
title=opts.title,
|
|
config=opts_str,
|
|
program="SimpleWebPage",
|
|
version=VERSION,
|
|
js_code=js_code,
|
|
css_style=css_style,
|
|
)
|
|
return header
|
|
|
|
|
|
def get_footer(readme, show_wget=True):
|
|
if show_wget:
|
|
wget_str = """
|
|
<div id=wget_container>
|
|
<div id=wget_toggle onclick="show_wget_magic()">wget?</div>
|
|
<pre id=wget_magic class=hidden></pre>
|
|
</div>
|
|
"""
|
|
else:
|
|
wget_str = ""
|
|
|
|
return """</tbody></table>{README}
|
|
</div>{WGET_STR}
|
|
</body></html>""".format(
|
|
README=readme, WGET_STR=wget_str
|
|
)
|
|
|
|
|
|
def is_imagefile(fname):
|
|
for ext in IMAGE_EXTENSIONS:
|
|
if fname.lower().endswith(ext):
|
|
return True
|
|
return False
|
|
|
|
|
|
def is_audiofile(fname):
|
|
for ext in AUDIO_EXTENSIONS:
|
|
if fname.lower().endswith(ext):
|
|
return True
|
|
return False
|
|
|
|
|
|
def is_videofile(fname):
|
|
for ext in VIDEO_EXTENSIONS:
|
|
if fname.lower().endswith(ext):
|
|
return True
|
|
return False
|
|
|
|
|
|
def match_files(files, include_list, exclude_list):
|
|
matched = []
|
|
for f in files:
|
|
for in_g in include_list:
|
|
if fnmatch.fnmatch(f, in_g):
|
|
for ex_g in exclude_list:
|
|
if not fnmatch.fnmatch(f, ex_g):
|
|
matched.append(f)
|
|
break
|
|
return matched
|
|
|
|
|
|
def sizeof(num):
|
|
for x in [" B", "KB", "MB", "GB", "TB"]:
|
|
if num < 1024.0:
|
|
if x == " B":
|
|
return "%d %s" % (num, x)
|
|
return "%3.1f %s" % (num, x)
|
|
num /= 1024.0
|
|
|
|
|
|
def main():
|
|
opts = setup()
|
|
generate_index(opts)
|
|
|
|
|
|
if __name__ == "__main__":
|
|
main()
|