#!/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 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(' -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 (
'
{}| Name | Size | Size B | Modified |
|---|