Commit 58b09fa5 authored by Michael Murtaugh's avatar Michael Murtaugh

redirection to login for auth

parent a1a5e504
node_modules/
package-lock.json
cgi-bin/aa_password.py
__pycache__
__pycache__
......@@ -44,3 +44,17 @@ Make viewer respond to "formats" available including "original".
## Resources
* <https://github.com/marcj/css-element-queries> Great shim to support responsive elements
## YAGNI?
* https://photoswipe.com/documentation/custom-html-in-slides.html
* https://lokeshdhakar.com/projects/lightbox2/
* https://developer.mozilla.org/en-US/docs/Web/CSS/pointer-events
* the ever useful: https://css-tricks.com/snippets/css/a-guide-to-flexbox/
## CGI
Produced by Bobby Orlando (original single) and Steven Hague (later album version)
\ No newline at end of file
#!/usr/bin/env python3
import cgitb; cgitb.enable()
import cgi, os, sys, json
from xml.sax.saxutils import quoteattr
from urllib.parse import quote as urlquote, unquote as urlunquote, urljoin
from os import environ
from base64 import b64encode as encode, b64decode as decode
import binascii
from directory import *
from aa_password import PASSWORD
from login import authenticate
def get_current_url (environ):
request_scheme = environ.get("REQUEST_SCHEME")
server_name = environ.get("SERVER_NAME")
# http_host = environ.get("HTTP_HOST")
server_port = int(environ.get("SERVER_PORT", "80"))
request_uri = environ.get("REQUEST_URI")
if server_port != 80:
return "{0}://{1}:{2}{3}".format(request_scheme, server_name, server_port, request_uri)
else:
return "{0}://{1}{2}".format(request_scheme, server_name, request_uri)
env = os.environ
method = os.environ.get("REQUEST_METHOD", "GET").upper()
docroot = os.environ.get("DOCUMENT_ROOT", "/var/www/html")
fs = cgi.FieldStorage()
url = fs.getvalue("u", "/")
assert(url.startswith("/"))
fullpath = os.path.join(docroot, urlunquote(url).strip("/"))
# compute normalized_url
normalized_url = urlquote(os.path.relpath(fullpath, docroot))
if normalized_url == ".":
normalized_url = "/"
else:
normalized_url = "/"+normalized_url
if os.path.isdir(fullpath):
normalized_url += "/"
# get the dispatch function (default json)
f = fs.getvalue("f", "json")
import shutil
def upload (file, pathname):
with open (pathname, "wb") as fout:
shutil.copyfileobj(file.file, fout)
# while 1:
# chunk = file.file.read(100000)
# if not chunk: break
# bytes += len(chunk)
# fout.write (chunk)
return True
def get_child_datum (data, url, create=False):
if create and not 'children' in data:
data['children'] = []
if 'children' in data:
for c in data['children']:
if c.get("url") == url:
return c
if create:
cdata = {'url': url}
data['children'].append(cdata)
return cdata
if f == "json":
if not os.path.isdir(fullpath):
print ("Content-type: application/json")
print()
print (json.dumps({"error": "Cannot load json of a file"}, indent=2))
sys.exit(0)
print ("Content-type: application/json")
print()
fs_data = listdir(fullpath, normalized_url)
user_data = load_index_json(fullpath)
merge_data(fs_data, user_data)
print (json.dumps(fs_data, indent=2))
# TODO MERGE WITH user json
elif f == "annotate":
# print ("Content-type: text/html; charset=utf8")
# print ()
authorized = authenticate(environ)
if not authorized:
current_url = get_current_url(env)
login_url = urljoin(current_url, "/cgi-bin/login.cgi")
login_url += "?next="+urlquote(current_url)
print ("Location: {0}".format(login_url))
print ()
sys.exit(0)
# print ("Not authorized")
# cgi.print_environ()
if os.path.isfile(fullpath):
parentpath = os.path.split(fullpath)[0]
user_data_path = index_json_path(parentpath)
user_data = load_index_json(parentpath)
item = get_child_datum(user_data, normalized_url, create=True)
fs_data = listdir(parentpath, os.path.split(normalized_url.rstrip("/"))[0]+"/")
fs_item = get_child_datum(fs_data, normalized_url)
else:
user_data_path = index_json_path(fullpath)
user_data = load_index_json(fullpath)
item = user_data
fs_data = listdir(fullpath, normalized_url)
fs_item = fs_data
if method=="POST":
print ("Content-type: text/html; charset=utf8")
print ()
# check for uploaded files
files = fs["file"]
if not isinstance(files, list):
files = [files]
files = [x for x in files if x.filename]
description = fs.getvalue("description")
messages = []
if os.path.isfile(fullpath):
# FILE
if files:
# these get added as "formats" to the file
formatpath = file_formats_path(fullpath, ensureCreate=True)
# print ("files {0}".format(len(files)))
results = []
for f in files:
savepath = os.path.join(formatpath, f.filename)
count = upload(f, savepath)
results.append((f.filename, os.path.getsize(savepath)))
messages.append("Uploaded {0} files".format(len(files)))
else:
# Upload files to directory
if files:
results = []
for f in files:
savepath = os.path.join(fullpath, f.filename)
count = upload(f, savepath)
results.append((f.filename, os.path.getsize(savepath)))
messages.append("Uploaded {0} files".format(len(files)))
# APPLY DATA AND SAVE BACK IF CHANGED
changed = False
if description and (item.get("description") != description):
item['description'] = description
changed = True
if changed:
try:
os.makedirs(os.path.split(user_data_path)[0])
except OSError:
pass
with open(user_data_path, "w") as f:
json.dump(user_data, f)
messages.append("Updated item")
print ("<div>post processed</div>")
for msg in messages:
print ("""<div class="message">{0}</div>""".format(msg))
else:
print ("Content-type: text/html; charset=utf8")
print ()
send_form(item, fs_item)
# APPLY NEW DATA TO ITEM
# save the json
# evt. RECEIVE FILES FOR ITEM
# record data for them (data is considered "bulk" labels for each received file?)
# parent, filename = os.path.split(fullpath)
# aadir = os.path.join(parent, ".aa")
# filepath = os.path.join(aadir, filename)
import os, json
from urllib.parse import quote as urlquote, unquote as urlunquote
def index_json_path (path):
return os.path.join(path, ".aa", "index.json")
def load_index_json (path):
try:
with open(index_json_path(path)) as f:
return json.load(f)
except OSError:
return {}
except json.decoder.JSONDecodeError:
return {}
def file_formats_path (path, ensureCreate=False):
parent, filename = os.path.split(path)
ret = os.path.join(parent, ".aa", filename)
if ensureCreate:
try:
os.makedirs(ret)
except OSError:
pass
return ret
def calc_formats_json (path, file_normalized_url):
""" Check the .aa/FILENAME folder for this path's derived/formats files.
Return a list suitable for JSON inclusion as value for the formats key """
fpath = file_formats_path (path)
if os.path.isdir(fpath):
files = os.listdir(fpath)
files.sort()
parent, filename = os.path.split(file_normalized_url)
ret = []
for x in files:
fp = os.path.join(fpath, x)
d= {}
d['url'] = parent+"/.aa/"+filename+"/"+urlquote(x)
dtype = guess_item_type(fp)
if dtype:
d['type'] = dtype
d['size'] = os.path.getsize(fp)
ret.append(d)
# return [item_datum(os.path.join(fpath, x), parent_nurl, x, is_format=True) for x in files]
return ret
def guess_item_type (name):
ext = os.path.splitext(name)[1].lower()[1:]
if ext in ["ogg", "mp3", "wav"]:
return "audio"
elif ext in ["webm", "mp4", "mpv", "ogv"]:
return "video"
elif ext in ["pdf"]:
return "pdf"
elif ext in ["png", "jpg", "jpeg", "svg", "bmp"]:
return "image"
elif ext in ["html"]:
return "html"
def item_datum (fullpath, normalized_url, filename, is_format=False):
item = {}
fp = os.path.join(fullpath, filename)
if os.path.isdir(fp):
item['url'] = normalized_url + filename + "/"
item['type'] = "folder"
item['info'] = "/cgi-bin/directory.cgi?f=json&u="+item['url']
item['edit'] = "/cgi-bin/directory.cgi?f=annotate&u="+item['url']
# attempt to read data from inner user json file
ddata = load_index_json(fp)
if ddata:
for key in ddata:
if key != "children":
item[key] = ddata[key]
else:
item['url' ] = normalized_url + filename
itype = guess_item_type(item['url'])
if itype:
item['type'] = itype
item['size'] = os.path.getsize(fp)
if not is_format:
formats = calc_formats_json(fp, item['url'])
if formats:
item['formats'] = formats
return item
def listdir (fullpath, normalized_url):
ret = {}
ret['url'] = normalized_url
ret['type'] = "folder"
items = os.listdir(fullpath)
items.sort()
items = [x for x in items if not x.startswith(".")]
ret['children'] = [item_datum(fullpath, normalized_url, filename) for filename in items]
return ret
def merge_data (filesystem, user):
""" takes the output of listdir + load_index_json & merges them,
keeping the structure / elements of the filesystem with any additions from user shadowing
modifies: filesystem object
"""
def merge (a, b):
for key in b:
if key != "children" and key != "url":
a[key] = b[key]
merge(filesystem, user)
children_by_url = {}
if 'children' in filesystem:
for c in filesystem['children']:
if 'url' in c:
children_by_url[c['url']] = c
if 'children' in user:
for c in user['children']:
if 'url' in c:
achild = children_by_url.get(c['url'])
if achild:
merge(achild, c)
def send_form(item, fs_item):
# # add this data to the appropriate index.json ... creating if necessary
# name = os.path.splitext(filename)[0].replace("_", " ")
# title = fs.getvalue("title", "")
if fs_item.get("type") == "folder":
label = "Upload files"
else:
label = "Add versions"
description = item.get("description", "")
print ("""<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>edit description</title>
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<style>
body {{
margin: 1em;
}}
input {{
margin: 0;
}}
</style>
</head>
<body>
<form method="post" action="" enctype="multipart/form-data">
<textarea name="description" style="width: 95%; height: 4em;" placeholder="Description" autofocus>{0}</textarea>
<div><input type="submit" value="save" /> <label for="file">{1}:</label> <input id="file" type="file" name="file" multiple="multiple"></div>
</form>
</body>
</html>
""".format(description, label))
......@@ -4,28 +4,10 @@ import cgi
# import cgitb; cgitb.enable()
from os import environ
from base64 import b64encode as encode, b64decode as decode
import binascii
import sys
from login import authenticate
from aa_password import PASSWORD
def read_cookie(env, name):
cookiestr = env.get("HTTP_COOKIE", "")
if cookiestr:
for cookie in cookiestr.split(';'):
(key, value ) = cookie.strip().split('=');
if key == name:
return value
def authenticate (env):
aa_auth = read_cookie(env, "aa_auth")
if aa_auth:
# attempt to authorize
try:
if decode(aa_auth).decode("utf-8") == PASSWORD:
return True
except binascii.Error:
return False
if __name__ == "__main__":
method = environ.get("REQUEST_METHOD", "GET").upper()
......@@ -83,6 +65,11 @@ form.error input {{
"class": formclass
}))
else:
nexturl = fs.getvalue("next")
if method=="POST" and nexturl:
print ("Location: {0}".format(nexturl))
print ()
sys.exit(0)
print ("Content-type:text/html; charset=utf-8")
print ()
print ("""<!DOCTYPE html>
......
from base64 import b64encode as encode, b64decode as decode
import binascii
from aa_password import PASSWORD
def read_cookie(env, name):
cookiestr = env.get("HTTP_COOKIE", "")
if cookiestr:
for cookie in cookiestr.split(';'):
(key, value ) = cookie.strip().split('=');
if key == name:
return value
def authenticate (env):
aa_auth = read_cookie(env, "aa_auth")
if aa_auth:
# attempt to authorize
try:
if decode(aa_auth).decode("utf-8") == PASSWORD:
return True
except binascii.Error:
return False
......@@ -6,6 +6,9 @@ dist/feedplayer.js: src/feedplayer.js src/*.js
node_modules/.bin/webpack-cli --config feedplayer.config.js
upload: dist/directory.js dist/directory.css
cp dist/directory.js dist/directory.css ~/mnt/1/public_html/lib/directory/
# SERVER
# cp dist/directory.js dist/directory.css ~/mnt/1/public_html/lib/directory/
# cp cgi-bin/*.cgi ~/mnt/1/cgi-bin
# LOCAL
cp dist/directory.js dist/directory.css /var/www/html/lib/directory/
cp cgi-bin/*.cgi /usr/lib/cgi-bin
cp cgi-bin/*.cgi cgi-bin/*.py /usr/lib/cgi-bin
......@@ -9,6 +9,7 @@
"author": "",
"license": "ISC",
"dependencies": {
"css-element-queries": "^1.2.2",
"d3": "^5.14.2",
"video.js": "^7.6.6",
"webpack": "^4.41.2",
......
......@@ -8,104 +8,111 @@ function $button (parent, textContent) {
return ret;
}
/* Need abstraction: play [{} data + elt] + index ... and events for observers */
function controller () {
var elt = document.createElement("div"),
playbutton = $button(elt, "play"),
// nextbutton = document.createElement("button"),
// prevbutton = document.createElement("button"),
timeslider = document.createElement("input"),
source = document.createElement("select"),
// login = document.createElement("button"),
audio = document.createElement("audio"),
editing = document.createElement("span"),
format = $button(editing, "upload format"),
cover = $button(editing, "upload cover"),
ret = {},
dragging = false,
playAfterDrag = false;
ret.elt = elt;
audio.addEventListener("play", x => {
playbutton.textContent = "pause";
});
audio.addEventListener("pause", x => {
playbutton.textContent = "play";
});
audio.addEventListener("timeupdate", x => {
if (dragging) { return; }
if (audio.duration) {
var curval = (audio.currentTime / audio.duration) * 10000;
timeslider.value = curval;
}
});
audio.addEventListener("ended", x=> {
console.log("audio.ended");
var next = d3.select("tr.file.playing + tr.file");
console.log("next");
if (next) {
play.call(next.node(), next.datum());
}
})
timeslider.setAttribute("min", 0);
timeslider.setAttribute("max", 10000);
timeslider.setAttribute("step", 1);
timeslider.addEventListener("mousedown", x=> {
dragging = true;
if (!audio.paused) {
audio.pause();
playAfterDrag = true;
} else {
playAfterDrag = false;
}
});
timeslider.addEventListener("mouseup", x=> {
if (dragging) {
// console.log("stop dragging");
dragging = false;
if (audio.duration) {
var curtime = (timeslider.value / 10000) * audio.duration;
audio.currentTime = curtime;
if (playAfterDrag) {
var elt = d3.select(document.body)
.append("div")
.attr("class", "aacontroller"),
ret = {elt: elt},
edit_iframe_div = elt.append("div")
.attr("id", "edit_iframe_div"),
edit_iframe,
contents = elt.append("div")
.attr("class", "contents"),
playbutton = contents.append("button")
.text("play")
.on("click", d => {
if (audio.paused) {
audio.play();
} else {
audio.pause();
}
}
}),
timeslider = contents.append("input")
.attr("type", "range")
.attr("min", 0)
.attr("max", 10000)
.attr("step", 1)
.on("mousedown", x=> {
dragging = true;
if (!audio.paused) {
audio.pause();
playAfterDrag = true;
} else {
playAfterDrag = false;
}
})
.on("mouseup", x=> {
if (dragging) {
// console.log("stop dragging");
dragging = false;
if (audio.duration) {
var curtime = (timeslider.value / 10000) * audio.duration;
audio.currentTime = curtime;
if (playAfterDrag) {
audio.play();
}
}
}
}),
edit_buttons = contents.append("span")
.attr("class", "edit_buttons"),
edit_button = edit_buttons.append("button")
.text("edit")
.on("click", d=> {
if (current_url) {
if (edit_iframe === undefined) { init_edit_iframe() };
console.log("edit_iframe", edit_iframe);
edit_iframe.attr("src", "/cgi-bin/description.cgi?path="+current_url);
edit_iframe_div.classed("open", true);
}
}),
audio,
dragging = false,
playAfterDrag = false,
current_url;
}
});
playbutton.addEventListener("click", x=> {
if (audio.paused) {
audio.play();
} else {
audio.pause();
}
});
function init_edit_iframe () {
edit_iframe = edit_iframe_div
.append("iframe")
.attr("id", "edit_iframe");
}
timeslider.setAttribute("type", "range");
elt.appendChild(playbutton);
//div.appendChild(prevbutton);
//div.appendChild(nextbutton);
elt.appendChild(timeslider);
//div.appendChild(source);
//div.appendChild(login);
editing.classList.add("editing");
elt.appendChild(editing);
function init_audio () {
audio = document.createElement("audio");
audio.addEventListener("play", x => {
playbutton.text("pause");
});
audio.addEventListener("pause", x => {
playbutton.text("play");
});
audio.addEventListener("timeupdate", x => {
if (dragging) { return; }
if (audio.duration) {
var curval = (audio.currentTime / audio.duration) * 10000;
timeslider.value = curval;
}
});
audio.addEventListener("ended", x=> {
console.log("audio.ended");
var next = d3.select("tr.file.playing + tr.file");
console.log("next");
if (next) {
play.call(next.node(), next.datum());
}
})
}
// login.addEventListener("click", x=> {
// loadscript.loadscript("/cgi-bin/aa/droptoupload.cgi?javascript").then(x=> {
// console.log("loaded droptoupload");
// });
// });
document.body.appendChild(elt);
elt.classList.add("aacontroller");
// console.log("controller", div);
function play (d) {
console.log("play", this, d);
current_url = window.location.pathname + d.url;
console.log("play", this, current_url, d);
d3.selectAll(".playing").classed("playing", false);
d3.select(this).classed("playing", true);
if (d.type == "audio") {
......@@ -115,6 +122,7 @@ function controller () {
ret.play = play;
function play_audio (d) {
if (audio === undefined) { init_audio(); }
audio.src = d.url;
audio.play();
}
......
......@@ -119,4 +119,13 @@ async function init () {
//});
}
document.addEventListener("DOMContentLoaded", init);
\ No newline at end of file
// <meta name="viewport" content="width=device-width, initial-scale=1.0">
document.addEventListener("DOMContentLoaded", init);
// https://github.com/marcj/css-element-queries
var ElementQueries = require('css-element-queries/src/ElementQueries');
// attaches to DOMLoadContent and does anything for you
ElementQueries.listen();
......@@ -46,6 +46,7 @@ body {
-moz-user-select: none;
-ms-user-select: none;
user-select: none;
border: 1px solid #ddd;
}
.mr-3, .mx-3 {
margin-right: 1rem !important;
......@@ -65,26 +66,64 @@ body {
.media-body {
-ms-flex: 1;
flex: 1;
padding-right: 1em;
}
svg {
overflow: hidden;
vertical-align: middle;
}
input[type=range] {
margin-left: 0;
margin-right: 0;
width: 100%;
}
div.slider {
display: flex;
}
.sliderelt {
flex: 1;
}
.timeslider {
}
.fg0 {
flex-grow: 0;
}
.fg1 {
flex-grow: 1;
}
.fw {
flex-wrap: wrap;
}
img.zoomed {
width: 100%;
height: auto;
}
</style>
</head>
<body>
</body>
</html>
<div class="bd-example">
<div class="media">
<svg class="bd-placeholder-img mr-3" width="64" height="64" xmlns="http://www.w3.org/2000/svg" preserveAspectRatio="xMidYMid slice" focusable="false" role="img" aria-label="Placeholder: 64x64"><title>Placeholder</title><rect width="100%" height="100%" fill="#868e96"></rect><text x="50%" y="50%" fill="#dee2e6" dy=".3em">64x64</text></svg>
<div class="player">
<img src="../img/back.png">
<img src="../img/next.png">
<img src="../img/play.png"><br>
<div class="slider" style="align-items: center">
<img class="sliderelt fg0" width="32" height="32" src="../img/audio.png">
<input class="sliderelt fg1" type="range">
</div>
</div>
<div class="media fw">
<img class="bd-placeholder-img mr-3 zoomtoggle" width="64" height="64" src="please.png" />
<div class="media-body">
<h5 class="mt-0">Media heading</h5>
Cras sit amet nibh libero, in gravida nulla. Nulla vel metus scelerisque ante sollicitudin. Cras purus odio, vestibulum in vulputate at, tempus viverra turpis. Fusce condimentum nunc ac nisi vulputate fringilla. Donec lacinia congue felis in faucibus.
<h5 class="mt-0">01 - Two Divided by Zero</h5>