mirror of
https://github.com/9001/copyparty.git
synced 2025-11-01 12:33:36 +00:00
Compare commits
13 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
cd52dea488 | ||
|
|
6ea75df05d | ||
|
|
4846e1e8d6 | ||
|
|
fc024f789d | ||
|
|
473e773aea | ||
|
|
48a2e1a353 | ||
|
|
6da63fbd79 | ||
|
|
5bec37fcee | ||
|
|
3fd0ba0a31 | ||
|
|
241a143366 | ||
|
|
a537064da7 | ||
|
|
f3dfd24c92 | ||
|
|
fa0a7f50bb |
@@ -106,6 +106,7 @@ summary: all planned features work! now please enjoy the bloatening
|
||||
* ☑ images using Pillow
|
||||
* ☑ videos using FFmpeg
|
||||
* ☑ cache eviction (max-age; maybe max-size eventually)
|
||||
* ☑ image gallery
|
||||
* ☑ SPA (browse while uploading)
|
||||
* if you use the file-tree on the left only, not folders in the file list
|
||||
* server indexing
|
||||
|
||||
@@ -1,3 +1,8 @@
|
||||
# when running copyparty behind a reverse-proxy,
|
||||
# make sure that copyparty allows at least as many clients as the proxy does,
|
||||
# so run copyparty with -nc 512 if your nginx has the default limits
|
||||
# (worker_processes 1, worker_connections 512)
|
||||
|
||||
upstream cpp {
|
||||
server 127.0.0.1:3923;
|
||||
keepalive 120;
|
||||
|
||||
@@ -23,7 +23,7 @@ from textwrap import dedent
|
||||
from .__init__ import E, WINDOWS, VT100, PY2
|
||||
from .__version__ import S_VERSION, S_BUILD_DT, CODENAME
|
||||
from .svchub import SvcHub
|
||||
from .util import py_desc, align_tab, IMPLICATIONS
|
||||
from .util import py_desc, align_tab, IMPLICATIONS, alltrace
|
||||
|
||||
HAVE_SSL = True
|
||||
try:
|
||||
@@ -182,6 +182,16 @@ def sighandler(sig=None, frame=None):
|
||||
print("\n".join(msg))
|
||||
|
||||
|
||||
def stackmon(fp, ival):
|
||||
ctr = 0
|
||||
while True:
|
||||
ctr += 1
|
||||
time.sleep(ival)
|
||||
st = "{}, {}\n{}".format(ctr, time.time(), alltrace())
|
||||
with open(fp, "wb") as f:
|
||||
f.write(st.encode("utf-8", "replace"))
|
||||
|
||||
|
||||
def run_argparse(argv, formatter):
|
||||
ap = argparse.ArgumentParser(
|
||||
formatter_class=formatter,
|
||||
@@ -222,10 +232,6 @@ def run_argparse(argv, formatter):
|
||||
"print,get" prints the data in the log and returns GET
|
||||
(leave out the ",get" to return an error instead)
|
||||
|
||||
--ciphers help = available ssl/tls ciphers,
|
||||
--ssl-ver help = available ssl/tls versions,
|
||||
default is what python considers safe, usually >= TLS1
|
||||
|
||||
values for --ls:
|
||||
"USR" is a user to browse as; * is anonymous, ** is all users
|
||||
"VOL" is a single volume to scan, default is * (all vols)
|
||||
@@ -244,27 +250,45 @@ def run_argparse(argv, formatter):
|
||||
)
|
||||
# fmt: off
|
||||
ap.add_argument("-c", metavar="PATH", type=str, action="append", help="add config file")
|
||||
ap.add_argument("-i", metavar="IP", type=str, default="0.0.0.0", help="ip to bind (comma-sep.)")
|
||||
ap.add_argument("-p", metavar="PORT", type=str, default="3923", help="ports to bind (comma/range)")
|
||||
ap.add_argument("-nc", metavar="NUM", type=int, default=64, help="max num clients")
|
||||
ap.add_argument("-j", metavar="CORES", type=int, default=1, help="max num cpu cores")
|
||||
ap.add_argument("-a", metavar="ACCT", type=str, action="append", help="add account")
|
||||
ap.add_argument("-v", metavar="VOL", type=str, action="append", help="add volume")
|
||||
ap.add_argument("-q", action="store_true", help="quiet")
|
||||
ap.add_argument("-a", metavar="ACCT", type=str, action="append", help="add account, USER:PASS; example [ed:wark")
|
||||
ap.add_argument("-v", metavar="VOL", type=str, action="append", help="add volume, SRC:DST:FLAG; example [.::r], [/mnt/nas/music:/music:r:aed")
|
||||
ap.add_argument("-ed", action="store_true", help="enable ?dots")
|
||||
ap.add_argument("-emp", action="store_true", help="enable markdown plugins")
|
||||
ap.add_argument("-mcr", metavar="SEC", type=int, default=60, help="md-editor mod-chk rate")
|
||||
ap.add_argument("-nw", action="store_true", help="disable writes (benchmark)")
|
||||
ap.add_argument("-nih", action="store_true", help="no info hostname")
|
||||
ap.add_argument("-nid", action="store_true", help="no info disk-usage")
|
||||
ap.add_argument("--dotpart", action="store_true", help="dotfile incomplete uploads")
|
||||
ap.add_argument("--no-zip", action="store_true", help="disable download as zip/tar")
|
||||
ap.add_argument("--sparse", metavar="MiB", type=int, default=4, help="up2k min.size threshold (mswin-only)")
|
||||
ap.add_argument("--urlform", metavar="MODE", type=str, default="print,get", help="how to handle url-forms")
|
||||
ap.add_argument("--salt", type=str, default="hunter2", help="up2k file-hash salt")
|
||||
ap.add_argument("--urlform", metavar="MODE", type=str, default="print,get", help="how to handle url-forms; examples: [stash], [save,get]")
|
||||
|
||||
ap2 = ap.add_argument_group('appearance options')
|
||||
ap2.add_argument("--css-browser", metavar="L", help="URL to additional CSS to include")
|
||||
ap2 = ap.add_argument_group('network options')
|
||||
ap2.add_argument("-i", metavar="IP", type=str, default="0.0.0.0", help="ip to bind (comma-sep.)")
|
||||
ap2.add_argument("-p", metavar="PORT", type=str, default="3923", help="ports to bind (comma/range)")
|
||||
ap2.add_argument("--rproxy", metavar="DEPTH", type=int, default=1, help="which ip to keep; 0 = tcp, 1 = origin (first x-fwd), 2 = cloudflare, 3 = nginx, -1 = closest proxy")
|
||||
|
||||
ap2 = ap.add_argument_group('SSL/TLS options')
|
||||
ap2.add_argument("--http-only", action="store_true", help="disable ssl/tls")
|
||||
ap2.add_argument("--https-only", action="store_true", help="disable plaintext")
|
||||
ap2.add_argument("--ssl-ver", metavar="LIST", type=str, help="set allowed ssl/tls versions; [help] shows available versions; default is what your python version considers safe")
|
||||
ap2.add_argument("--ciphers", metavar="LIST", help="set allowed ssl/tls ciphers; [help] shows available ciphers")
|
||||
ap2.add_argument("--ssl-dbg", action="store_true", help="dump some tls info")
|
||||
ap2.add_argument("--ssl-log", metavar="PATH", help="log master secrets")
|
||||
|
||||
ap2 = ap.add_argument_group('opt-outs')
|
||||
ap2.add_argument("-nw", action="store_true", help="disable writes (benchmark)")
|
||||
ap2.add_argument("-nih", action="store_true", help="no info hostname")
|
||||
ap2.add_argument("-nid", action="store_true", help="no info disk-usage")
|
||||
ap2.add_argument("--no-zip", action="store_true", help="disable download as zip/tar")
|
||||
|
||||
ap2 = ap.add_argument_group('safety options')
|
||||
ap2.add_argument("--ls", metavar="U[,V[,F]]", help="scan all volumes; arguments USER,VOL,FLAGS; example [**,*,ln,p,r]")
|
||||
ap2.add_argument("--salt", type=str, default="hunter2", help="up2k file-hash salt")
|
||||
|
||||
ap2 = ap.add_argument_group('logging options')
|
||||
ap2.add_argument("-q", action="store_true", help="quiet")
|
||||
ap2.add_argument("--log-conn", action="store_true", help="print tcp-server msgs")
|
||||
ap2.add_argument("--ihead", metavar="HEADER", action='append', help="dump incoming header")
|
||||
ap2.add_argument("--lf-url", metavar="RE", type=str, default=r"^/\.cpr/|\?th=[wj]$", help="dont log URLs matching")
|
||||
|
||||
ap2 = ap.add_argument_group('admin panel options')
|
||||
ap2.add_argument("--no-rescan", action="store_true", help="disable ?scan (volume reindexing)")
|
||||
@@ -299,22 +323,14 @@ def run_argparse(argv, formatter):
|
||||
ap2.add_argument("-mtp", metavar="M=[f,]bin", action="append", type=str, help="read tag M using bin")
|
||||
ap2.add_argument("--srch-time", metavar="SEC", type=int, default=30, help="search deadline")
|
||||
|
||||
ap2 = ap.add_argument_group('SSL/TLS options')
|
||||
ap2.add_argument("--http-only", action="store_true", help="disable ssl/tls")
|
||||
ap2.add_argument("--https-only", action="store_true", help="disable plaintext")
|
||||
ap2.add_argument("--ssl-ver", metavar="LIST", type=str, help="ssl/tls versions to allow")
|
||||
ap2.add_argument("--ciphers", metavar="LIST", help="set allowed ciphers")
|
||||
ap2.add_argument("--ssl-dbg", action="store_true", help="dump some tls info")
|
||||
ap2.add_argument("--ssl-log", metavar="PATH", help="log master secrets")
|
||||
ap2 = ap.add_argument_group('appearance options')
|
||||
ap2.add_argument("--css-browser", metavar="L", help="URL to additional CSS to include")
|
||||
|
||||
ap2 = ap.add_argument_group('debug options')
|
||||
ap2.add_argument("--ls", metavar="U[,V[,F]]", help="scan all volumes")
|
||||
ap2.add_argument("--log-conn", action="store_true", help="print tcp-server msgs")
|
||||
ap2.add_argument("--no-sendfile", action="store_true", help="disable sendfile")
|
||||
ap2.add_argument("--no-scandir", action="store_true", help="disable scandir")
|
||||
ap2.add_argument("--no-fastboot", action="store_true", help="wait for up2k indexing")
|
||||
ap2.add_argument("--ihead", metavar="HEADER", action='append', help="dump incoming header")
|
||||
ap2.add_argument("--lf-url", metavar="RE", type=str, default=r"^/\.cpr/|\?th=[wj]$", help="dont log URLs matching")
|
||||
ap2.add_argument("--stackmon", metavar="P,S", help="write stacktrace to Path every S second")
|
||||
|
||||
return ap.parse_args(args=argv[1:])
|
||||
# fmt: on
|
||||
@@ -354,6 +370,16 @@ def main(argv=None):
|
||||
except AssertionError:
|
||||
al = run_argparse(argv, Dodge11874)
|
||||
|
||||
if al.stackmon:
|
||||
fp, f = al.stackmon.rsplit(",", 1)
|
||||
f = int(f)
|
||||
t = threading.Thread(
|
||||
target=stackmon,
|
||||
args=(fp, f),
|
||||
)
|
||||
t.daemon = True
|
||||
t.start()
|
||||
|
||||
# propagate implications
|
||||
for k1, k2 in IMPLICATIONS:
|
||||
if getattr(al, k1):
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
# coding: utf-8
|
||||
|
||||
VERSION = (0, 11, 12)
|
||||
VERSION = (0, 11, 15)
|
||||
CODENAME = "the grid"
|
||||
BUILD_DT = (2021, 6, 12)
|
||||
BUILD_DT = (2021, 6, 15)
|
||||
|
||||
S_VERSION = ".".join(map(str, VERSION))
|
||||
S_BUILD_DT = "{0:04d}-{1:02d}-{2:02d}".format(*BUILD_DT)
|
||||
|
||||
@@ -104,10 +104,21 @@ class HttpCli(object):
|
||||
v = self.headers.get("connection", "").lower()
|
||||
self.keepalive = not v.startswith("close") and self.http_ver != "HTTP/1.0"
|
||||
|
||||
v = self.headers.get("x-forwarded-for", None)
|
||||
if v is not None and self.conn.addr[0] in ["127.0.0.1", "::1"]:
|
||||
self.ip = v.split(",")[0]
|
||||
self.log_src = self.conn.set_rproxy(self.ip)
|
||||
n = self.args.rproxy
|
||||
if n:
|
||||
v = self.headers.get("x-forwarded-for")
|
||||
if v and self.conn.addr[0] in ["127.0.0.1", "::1"]:
|
||||
if n > 0:
|
||||
n -= 1
|
||||
|
||||
vs = v.split(",")
|
||||
try:
|
||||
self.ip = vs[n].strip()
|
||||
except:
|
||||
self.ip = vs[-1].strip()
|
||||
self.log("rproxy={} oob x-fwd {}".format(self.args.rproxy, v), c=3)
|
||||
|
||||
self.log_src = self.conn.set_rproxy(self.ip)
|
||||
|
||||
if self.args.ihead:
|
||||
keys = self.args.ihead
|
||||
@@ -1423,33 +1434,8 @@ class HttpCli(object):
|
||||
if self.args.no_stack:
|
||||
raise Pebkac(403, "disabled by argv")
|
||||
|
||||
threads = {}
|
||||
names = dict([(t.ident, t.name) for t in threading.enumerate()])
|
||||
for tid, stack in sys._current_frames().items():
|
||||
name = "{} ({:x})".format(names.get(tid), tid)
|
||||
threads[name] = stack
|
||||
|
||||
rret = []
|
||||
bret = []
|
||||
for name, stack in sorted(threads.items()):
|
||||
ret = ["\n\n# {}".format(name)]
|
||||
pad = None
|
||||
for fn, lno, name, line in traceback.extract_stack(stack):
|
||||
fn = os.sep.join(fn.split(os.sep)[-3:])
|
||||
ret.append('File: "{}", line {}, in {}'.format(fn, lno, name))
|
||||
if line:
|
||||
ret.append(" " + str(line.strip()))
|
||||
if "self.not_empty.wait()" in line:
|
||||
pad = " " * 4
|
||||
|
||||
if pad:
|
||||
bret += [ret[0]] + [pad + x for x in ret[1:]]
|
||||
else:
|
||||
rret += ret
|
||||
|
||||
ret = rret + bret
|
||||
ret = ("<pre>" + "\n".join(ret)).encode("utf-8")
|
||||
self.reply(ret)
|
||||
ret = "<pre>{}\n{}".format(time.time(), alltrace())
|
||||
self.reply(ret.encode("utf-8"))
|
||||
|
||||
def tx_tree(self):
|
||||
top = self.uparam["tree"] or ""
|
||||
|
||||
@@ -154,7 +154,8 @@ class ThumbSrv(object):
|
||||
histpath = self.asrv.vfs.histtab[ptop]
|
||||
tpath = thumb_path(histpath, rem, mtime, fmt)
|
||||
abspath = os.path.join(ptop, rem)
|
||||
cond = threading.Condition()
|
||||
cond = threading.Condition(self.mutex)
|
||||
do_conv = False
|
||||
with self.mutex:
|
||||
try:
|
||||
self.busy[tpath].append(cond)
|
||||
@@ -172,8 +173,11 @@ class ThumbSrv(object):
|
||||
f.write(fsenc(os.path.dirname(abspath)))
|
||||
|
||||
self.busy[tpath] = [cond]
|
||||
self.q.put([abspath, tpath])
|
||||
self.log("conv {} \033[0m{}".format(tpath, abspath), c=6)
|
||||
do_conv = True
|
||||
|
||||
if do_conv:
|
||||
self.q.put([abspath, tpath])
|
||||
self.log("conv {} \033[0m{}".format(tpath, abspath), c=6)
|
||||
|
||||
while not self.stopping:
|
||||
with self.mutex:
|
||||
@@ -181,7 +185,7 @@ class ThumbSrv(object):
|
||||
break
|
||||
|
||||
with cond:
|
||||
cond.wait()
|
||||
cond.wait(3)
|
||||
|
||||
try:
|
||||
st = os.stat(tpath)
|
||||
|
||||
@@ -1022,7 +1022,7 @@ class Up2k(object):
|
||||
now = time.time()
|
||||
job = None
|
||||
with self.mutex:
|
||||
cur = self.cur.get(cj["ptop"], None)
|
||||
cur = self.cur.get(cj["ptop"])
|
||||
reg = self.registry[cj["ptop"]]
|
||||
if cur:
|
||||
if self.no_expr_idx:
|
||||
@@ -1180,7 +1180,7 @@ class Up2k(object):
|
||||
|
||||
def handle_chunk(self, ptop, wark, chash):
|
||||
with self.mutex:
|
||||
job = self.registry[ptop].get(wark, None)
|
||||
job = self.registry[ptop].get(wark)
|
||||
if not job:
|
||||
known = " ".join([x for x in self.registry[ptop].keys()])
|
||||
self.log("unknown wark [{}], known: {}".format(wark, known))
|
||||
@@ -1245,7 +1245,7 @@ class Up2k(object):
|
||||
return ret, dst
|
||||
|
||||
def idx_wark(self, ptop, wark, rd, fn, lmod, sz):
|
||||
cur = self.cur.get(ptop, None)
|
||||
cur = self.cur.get(ptop)
|
||||
if not cur:
|
||||
return False
|
||||
|
||||
@@ -1414,7 +1414,7 @@ class Up2k(object):
|
||||
|
||||
newest = max(x["poke"] for _, x in reg.items()) if reg else 0
|
||||
etag = [len(reg), newest]
|
||||
if etag == prev.get(ptop, None):
|
||||
if etag == prev.get(ptop):
|
||||
return
|
||||
|
||||
try:
|
||||
|
||||
@@ -254,6 +254,34 @@ def trace(*args, **kwargs):
|
||||
nuprint(msg)
|
||||
|
||||
|
||||
def alltrace():
|
||||
threads = {}
|
||||
names = dict([(t.ident, t.name) for t in threading.enumerate()])
|
||||
for tid, stack in sys._current_frames().items():
|
||||
name = "{} ({:x})".format(names.get(tid), tid)
|
||||
threads[name] = stack
|
||||
|
||||
rret = []
|
||||
bret = []
|
||||
for name, stack in sorted(threads.items()):
|
||||
ret = ["\n\n# {}".format(name)]
|
||||
pad = None
|
||||
for fn, lno, name, line in traceback.extract_stack(stack):
|
||||
fn = os.sep.join(fn.split(os.sep)[-3:])
|
||||
ret.append('File: "{}", line {}, in {}'.format(fn, lno, name))
|
||||
if line:
|
||||
ret.append(" " + str(line.strip()))
|
||||
if "self.not_empty.wait()" in line:
|
||||
pad = " " * 4
|
||||
|
||||
if pad:
|
||||
bret += [ret[0]] + [pad + x for x in ret[1:]]
|
||||
else:
|
||||
rret += ret
|
||||
|
||||
return "\n".join(rret + bret)
|
||||
|
||||
|
||||
def min_ex():
|
||||
et, ev, tb = sys.exc_info()
|
||||
tb = traceback.extract_tb(tb, 2)
|
||||
|
||||
583
copyparty/web/baguettebox.js
Normal file
583
copyparty/web/baguettebox.js
Normal file
@@ -0,0 +1,583 @@
|
||||
/*!
|
||||
* baguetteBox.js
|
||||
* @author feimosi
|
||||
* @version 1.11.1-mod
|
||||
* @url https://github.com/feimosi/baguetteBox.js
|
||||
*/
|
||||
|
||||
window.baguetteBox = (function () {
|
||||
'use strict';
|
||||
|
||||
var options = {},
|
||||
defaults = {
|
||||
captions: true,
|
||||
buttons: 'auto',
|
||||
noScrollbars: false,
|
||||
bodyClass: 'baguetteBox-open',
|
||||
titleTag: false,
|
||||
async: false,
|
||||
preload: 2,
|
||||
animation: 'slideIn',
|
||||
afterShow: null,
|
||||
afterHide: null,
|
||||
onChange: null,
|
||||
},
|
||||
overlay, slider, previousButton, nextButton, closeButton,
|
||||
currentGallery = [],
|
||||
currentIndex = 0,
|
||||
isOverlayVisible = false,
|
||||
touch = {}, // start-pos
|
||||
touchFlag = false, // busy
|
||||
regex = /.+\.(gif|jpe?g|png|webp)/i,
|
||||
data = {}, // all galleries
|
||||
imagesElements = [],
|
||||
documentLastFocus = null;
|
||||
|
||||
var overlayClickHandler = function (event) {
|
||||
if (event.target.id.indexOf('baguette-img') !== -1) {
|
||||
hideOverlay();
|
||||
}
|
||||
};
|
||||
|
||||
var touchstartHandler = function (event) {
|
||||
touch.count++;
|
||||
if (touch.count > 1) {
|
||||
touch.multitouch = true;
|
||||
}
|
||||
touch.startX = event.changedTouches[0].pageX;
|
||||
touch.startY = event.changedTouches[0].pageY;
|
||||
};
|
||||
var touchmoveHandler = function (event) {
|
||||
if (touchFlag || touch.multitouch) {
|
||||
return;
|
||||
}
|
||||
event.preventDefault ? event.preventDefault() : event.returnValue = false;
|
||||
var touchEvent = event.touches[0] || event.changedTouches[0];
|
||||
if (touchEvent.pageX - touch.startX > 40) {
|
||||
touchFlag = true;
|
||||
showPreviousImage();
|
||||
} else if (touchEvent.pageX - touch.startX < -40) {
|
||||
touchFlag = true;
|
||||
showNextImage();
|
||||
} else if (touch.startY - touchEvent.pageY > 100) {
|
||||
hideOverlay();
|
||||
}
|
||||
};
|
||||
var touchendHandler = function () {
|
||||
touch.count--;
|
||||
if (touch.count <= 0) {
|
||||
touch.multitouch = false;
|
||||
}
|
||||
touchFlag = false;
|
||||
};
|
||||
var contextmenuHandler = function () {
|
||||
touchendHandler();
|
||||
};
|
||||
|
||||
var trapFocusInsideOverlay = function (event) {
|
||||
if (overlay.style.display === 'block' && (overlay.contains && !overlay.contains(event.target))) {
|
||||
event.stopPropagation();
|
||||
initFocus();
|
||||
}
|
||||
};
|
||||
|
||||
function run(selector, userOptions) {
|
||||
buildOverlay();
|
||||
removeFromCache(selector);
|
||||
return bindImageClickListeners(selector, userOptions);
|
||||
}
|
||||
|
||||
function bindImageClickListeners(selector, userOptions) {
|
||||
var galleryNodeList = document.querySelectorAll(selector);
|
||||
var selectorData = {
|
||||
galleries: [],
|
||||
nodeList: galleryNodeList
|
||||
};
|
||||
data[selector] = selectorData;
|
||||
|
||||
[].forEach.call(galleryNodeList, function (galleryElement) {
|
||||
if (userOptions && userOptions.filter) {
|
||||
regex = userOptions.filter;
|
||||
}
|
||||
|
||||
var tagsNodeList = [];
|
||||
if (galleryElement.tagName === 'A') {
|
||||
tagsNodeList = [galleryElement];
|
||||
} else {
|
||||
tagsNodeList = galleryElement.getElementsByTagName('a');
|
||||
}
|
||||
|
||||
tagsNodeList = [].filter.call(tagsNodeList, function (element) {
|
||||
if (element.className.indexOf(userOptions && userOptions.ignoreClass) === -1) {
|
||||
return regex.test(element.href);
|
||||
}
|
||||
});
|
||||
if (tagsNodeList.length === 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
var gallery = [];
|
||||
[].forEach.call(tagsNodeList, function (imageElement, imageIndex) {
|
||||
var imageElementClickHandler = function (event) {
|
||||
if (event && event.ctrlKey)
|
||||
return true;
|
||||
|
||||
event.preventDefault ? event.preventDefault() : event.returnValue = false;
|
||||
prepareOverlay(gallery, userOptions);
|
||||
showOverlay(imageIndex);
|
||||
};
|
||||
var imageItem = {
|
||||
eventHandler: imageElementClickHandler,
|
||||
imageElement: imageElement
|
||||
};
|
||||
bind(imageElement, 'click', imageElementClickHandler);
|
||||
gallery.push(imageItem);
|
||||
});
|
||||
selectorData.galleries.push(gallery);
|
||||
});
|
||||
|
||||
return selectorData.galleries;
|
||||
}
|
||||
|
||||
function clearCachedData() {
|
||||
for (var selector in data) {
|
||||
if (data.hasOwnProperty(selector)) {
|
||||
removeFromCache(selector);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function removeFromCache(selector) {
|
||||
if (!data.hasOwnProperty(selector)) {
|
||||
return;
|
||||
}
|
||||
var galleries = data[selector].galleries;
|
||||
[].forEach.call(galleries, function (gallery) {
|
||||
[].forEach.call(gallery, function (imageItem) {
|
||||
unbind(imageItem.imageElement, 'click', imageItem.eventHandler);
|
||||
});
|
||||
|
||||
if (currentGallery === gallery) {
|
||||
currentGallery = [];
|
||||
}
|
||||
});
|
||||
|
||||
delete data[selector];
|
||||
}
|
||||
|
||||
function buildOverlay() {
|
||||
overlay = ebi('baguetteBox-overlay');
|
||||
if (overlay) {
|
||||
slider = ebi('baguetteBox-slider');
|
||||
previousButton = ebi('previous-button');
|
||||
nextButton = ebi('next-button');
|
||||
closeButton = ebi('close-button');
|
||||
return;
|
||||
}
|
||||
overlay = mknod('div');
|
||||
overlay.setAttribute('role', 'dialog');
|
||||
overlay.id = 'baguetteBox-overlay';
|
||||
document.getElementsByTagName('body')[0].appendChild(overlay);
|
||||
|
||||
slider = mknod('div');
|
||||
slider.id = 'baguetteBox-slider';
|
||||
overlay.appendChild(slider);
|
||||
|
||||
previousButton = mknod('button');
|
||||
previousButton.setAttribute('type', 'button');
|
||||
previousButton.id = 'previous-button';
|
||||
previousButton.setAttribute('aria-label', 'Previous');
|
||||
previousButton.innerHTML = '<';
|
||||
overlay.appendChild(previousButton);
|
||||
|
||||
nextButton = mknod('button');
|
||||
nextButton.setAttribute('type', 'button');
|
||||
nextButton.id = 'next-button';
|
||||
nextButton.setAttribute('aria-label', 'Next');
|
||||
nextButton.innerHTML = '>';
|
||||
overlay.appendChild(nextButton);
|
||||
|
||||
closeButton = mknod('button');
|
||||
closeButton.setAttribute('type', 'button');
|
||||
closeButton.id = 'close-button';
|
||||
closeButton.setAttribute('aria-label', 'Close');
|
||||
closeButton.innerHTML = '×';
|
||||
overlay.appendChild(closeButton);
|
||||
|
||||
previousButton.className = nextButton.className = closeButton.className = 'baguetteBox-button';
|
||||
|
||||
bindEvents();
|
||||
}
|
||||
|
||||
function keyDownHandler(event) {
|
||||
switch (event.keyCode) {
|
||||
case 37: // Left
|
||||
showPreviousImage();
|
||||
break;
|
||||
case 39: // Right
|
||||
showNextImage();
|
||||
break;
|
||||
case 27: // Esc
|
||||
hideOverlay();
|
||||
break;
|
||||
case 36: // Home
|
||||
showFirstImage(event);
|
||||
break;
|
||||
case 35: // End
|
||||
showLastImage(event);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
var passiveSupp = false;
|
||||
try {
|
||||
var opts = {
|
||||
get passive() {
|
||||
passiveSupp = true;
|
||||
return false;
|
||||
}
|
||||
};
|
||||
window.addEventListener('test', null, opts);
|
||||
window.removeEventListener('test', null, opts);
|
||||
}
|
||||
catch (ex) {
|
||||
passiveSupp = false;
|
||||
}
|
||||
var passiveEvent = passiveSupp ? { passive: false } : null;
|
||||
var nonPassiveEvent = passiveSupp ? { passive: true } : null;
|
||||
|
||||
function bindEvents() {
|
||||
bind(overlay, 'click', overlayClickHandler);
|
||||
bind(previousButton, 'click', showPreviousImage);
|
||||
bind(nextButton, 'click', showNextImage);
|
||||
bind(closeButton, 'click', hideOverlay);
|
||||
bind(slider, 'contextmenu', contextmenuHandler);
|
||||
bind(overlay, 'touchstart', touchstartHandler, nonPassiveEvent);
|
||||
bind(overlay, 'touchmove', touchmoveHandler, passiveEvent);
|
||||
bind(overlay, 'touchend', touchendHandler);
|
||||
bind(document, 'focus', trapFocusInsideOverlay, true);
|
||||
}
|
||||
|
||||
function unbindEvents() {
|
||||
unbind(overlay, 'click', overlayClickHandler);
|
||||
unbind(previousButton, 'click', showPreviousImage);
|
||||
unbind(nextButton, 'click', showNextImage);
|
||||
unbind(closeButton, 'click', hideOverlay);
|
||||
unbind(slider, 'contextmenu', contextmenuHandler);
|
||||
unbind(overlay, 'touchstart', touchstartHandler, nonPassiveEvent);
|
||||
unbind(overlay, 'touchmove', touchmoveHandler, passiveEvent);
|
||||
unbind(overlay, 'touchend', touchendHandler);
|
||||
unbind(document, 'focus', trapFocusInsideOverlay, true);
|
||||
}
|
||||
|
||||
function prepareOverlay(gallery, userOptions) {
|
||||
if (currentGallery === gallery) {
|
||||
return;
|
||||
}
|
||||
currentGallery = gallery;
|
||||
setOptions(userOptions);
|
||||
slider.innerHTML = '';
|
||||
imagesElements.length = 0;
|
||||
|
||||
var imagesFiguresIds = [];
|
||||
var imagesCaptionsIds = [];
|
||||
for (var i = 0, fullImage; i < gallery.length; i++) {
|
||||
fullImage = mknod('div');
|
||||
fullImage.className = 'full-image';
|
||||
fullImage.id = 'baguette-img-' + i;
|
||||
imagesElements.push(fullImage);
|
||||
|
||||
imagesFiguresIds.push('baguetteBox-figure-' + i);
|
||||
imagesCaptionsIds.push('baguetteBox-figcaption-' + i);
|
||||
slider.appendChild(imagesElements[i]);
|
||||
}
|
||||
overlay.setAttribute('aria-labelledby', imagesFiguresIds.join(' '));
|
||||
overlay.setAttribute('aria-describedby', imagesCaptionsIds.join(' '));
|
||||
}
|
||||
|
||||
function setOptions(newOptions) {
|
||||
if (!newOptions) {
|
||||
newOptions = {};
|
||||
}
|
||||
for (var item in defaults) {
|
||||
options[item] = defaults[item];
|
||||
if (typeof newOptions[item] !== 'undefined') {
|
||||
options[item] = newOptions[item];
|
||||
}
|
||||
}
|
||||
slider.style.transition = (options.animation === 'fadeIn' ? 'opacity .4s ease' :
|
||||
options.animation === 'slideIn' ? '' : 'none');
|
||||
|
||||
if (options.buttons === 'auto' && ('ontouchstart' in window || currentGallery.length === 1)) {
|
||||
options.buttons = false;
|
||||
}
|
||||
|
||||
previousButton.style.display = nextButton.style.display = (options.buttons ? '' : 'none');
|
||||
}
|
||||
|
||||
function showOverlay(chosenImageIndex) {
|
||||
if (options.noScrollbars) {
|
||||
document.documentElement.style.overflowY = 'hidden';
|
||||
document.body.style.overflowY = 'scroll';
|
||||
}
|
||||
if (overlay.style.display === 'block') {
|
||||
return;
|
||||
}
|
||||
|
||||
bind(document, 'keydown', keyDownHandler);
|
||||
currentIndex = chosenImageIndex;
|
||||
touch = {
|
||||
count: 0,
|
||||
startX: null,
|
||||
startY: null
|
||||
};
|
||||
loadImage(currentIndex, function () {
|
||||
preloadNext(currentIndex);
|
||||
preloadPrev(currentIndex);
|
||||
});
|
||||
|
||||
updateOffset();
|
||||
overlay.style.display = 'block';
|
||||
// Fade in overlay
|
||||
setTimeout(function () {
|
||||
overlay.className = 'visible';
|
||||
if (options.bodyClass && document.body.classList) {
|
||||
document.body.classList.add(options.bodyClass);
|
||||
}
|
||||
if (options.afterShow) {
|
||||
options.afterShow();
|
||||
}
|
||||
}, 50);
|
||||
if (options.onChange) {
|
||||
options.onChange(currentIndex, imagesElements.length);
|
||||
}
|
||||
documentLastFocus = document.activeElement;
|
||||
initFocus();
|
||||
isOverlayVisible = true;
|
||||
}
|
||||
|
||||
function initFocus() {
|
||||
if (options.buttons) {
|
||||
previousButton.focus();
|
||||
} else {
|
||||
closeButton.focus();
|
||||
}
|
||||
}
|
||||
|
||||
function hideOverlay(e) {
|
||||
ev(e);
|
||||
if (options.noScrollbars) {
|
||||
document.documentElement.style.overflowY = 'auto';
|
||||
document.body.style.overflowY = 'auto';
|
||||
}
|
||||
if (overlay.style.display === 'none') {
|
||||
return;
|
||||
}
|
||||
|
||||
unbind(document, 'keydown', keyDownHandler);
|
||||
// Fade out and hide the overlay
|
||||
overlay.className = '';
|
||||
setTimeout(function () {
|
||||
overlay.style.display = 'none';
|
||||
if (options.bodyClass && document.body.classList) {
|
||||
document.body.classList.remove(options.bodyClass);
|
||||
}
|
||||
if (options.afterHide) {
|
||||
options.afterHide();
|
||||
}
|
||||
documentLastFocus && documentLastFocus.focus();
|
||||
isOverlayVisible = false;
|
||||
}, 500);
|
||||
}
|
||||
|
||||
function loadImage(index, callback) {
|
||||
var imageContainer = imagesElements[index];
|
||||
var galleryItem = currentGallery[index];
|
||||
|
||||
if (typeof imageContainer === 'undefined' || typeof galleryItem === 'undefined') {
|
||||
return; // out-of-bounds or gallery dirty
|
||||
}
|
||||
|
||||
if (imageContainer.getElementsByTagName('img')[0]) {
|
||||
// image is loaded, cb and bail
|
||||
if (callback) {
|
||||
callback();
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
var imageElement = galleryItem.imageElement,
|
||||
imageSrc = imageElement.href,
|
||||
thumbnailElement = imageElement.getElementsByTagName('img')[0],
|
||||
imageCaption = typeof options.captions === 'function' ?
|
||||
options.captions.call(currentGallery, imageElement) :
|
||||
imageElement.getAttribute('data-caption') || imageElement.title;
|
||||
|
||||
var figure = mknod('figure');
|
||||
figure.id = 'baguetteBox-figure-' + index;
|
||||
figure.innerHTML = '<div class="baguetteBox-spinner">' +
|
||||
'<div class="baguetteBox-double-bounce1"></div>' +
|
||||
'<div class="baguetteBox-double-bounce2"></div>' +
|
||||
'</div>';
|
||||
|
||||
if (options.captions && imageCaption) {
|
||||
var figcaption = mknod('figcaption');
|
||||
figcaption.id = 'baguetteBox-figcaption-' + index;
|
||||
figcaption.innerHTML = imageCaption;
|
||||
figure.appendChild(figcaption);
|
||||
}
|
||||
imageContainer.appendChild(figure);
|
||||
|
||||
var image = mknod('img');
|
||||
image.onload = function () {
|
||||
// Remove loader element
|
||||
var spinner = document.querySelector('#baguette-img-' + index + ' .baguetteBox-spinner');
|
||||
figure.removeChild(spinner);
|
||||
if (!options.async && callback) {
|
||||
callback();
|
||||
}
|
||||
};
|
||||
image.setAttribute('src', imageSrc);
|
||||
image.alt = thumbnailElement ? thumbnailElement.alt || '' : '';
|
||||
if (options.titleTag && imageCaption) {
|
||||
image.title = imageCaption;
|
||||
}
|
||||
figure.appendChild(image);
|
||||
|
||||
if (options.async && callback) {
|
||||
callback();
|
||||
}
|
||||
}
|
||||
|
||||
function showNextImage(e) {
|
||||
ev(e);
|
||||
return show(currentIndex + 1);
|
||||
}
|
||||
|
||||
function showPreviousImage(e) {
|
||||
ev(e);
|
||||
return show(currentIndex - 1);
|
||||
}
|
||||
|
||||
function showFirstImage(event) {
|
||||
if (event) {
|
||||
event.preventDefault();
|
||||
}
|
||||
return show(0);
|
||||
}
|
||||
|
||||
function showLastImage(event) {
|
||||
if (event) {
|
||||
event.preventDefault();
|
||||
}
|
||||
return show(currentGallery.length - 1);
|
||||
}
|
||||
|
||||
/**
|
||||
* Move the gallery to a specific index
|
||||
* @param `index` {number} - the position of the image
|
||||
* @param `gallery` {array} - gallery which should be opened, if omitted assumes the currently opened one
|
||||
* @return {boolean} - true on success or false if the index is invalid
|
||||
*/
|
||||
function show(index, gallery) {
|
||||
if (!isOverlayVisible && index >= 0 && index < gallery.length) {
|
||||
prepareOverlay(gallery, options);
|
||||
showOverlay(index);
|
||||
return true;
|
||||
}
|
||||
if (index < 0) {
|
||||
if (options.animation) {
|
||||
bounceAnimation('left');
|
||||
}
|
||||
return false;
|
||||
}
|
||||
if (index >= imagesElements.length) {
|
||||
if (options.animation) {
|
||||
bounceAnimation('right');
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
currentIndex = index;
|
||||
loadImage(currentIndex, function () {
|
||||
preloadNext(currentIndex);
|
||||
preloadPrev(currentIndex);
|
||||
});
|
||||
updateOffset();
|
||||
|
||||
if (options.onChange) {
|
||||
options.onChange(currentIndex, imagesElements.length);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Triggers the bounce animation
|
||||
* @param {('left'|'right')} direction - Direction of the movement
|
||||
*/
|
||||
function bounceAnimation(direction) {
|
||||
slider.className = 'bounce-from-' + direction;
|
||||
setTimeout(function () {
|
||||
slider.className = '';
|
||||
}, 400);
|
||||
}
|
||||
|
||||
function updateOffset() {
|
||||
var offset = -currentIndex * 100 + '%';
|
||||
if (options.animation === 'fadeIn') {
|
||||
slider.style.opacity = 0;
|
||||
setTimeout(function () {
|
||||
slider.style.transform = 'translate3d(' + offset + ',0,0)';
|
||||
slider.style.opacity = 1;
|
||||
}, 400);
|
||||
} else {
|
||||
slider.style.transform = 'translate3d(' + offset + ',0,0)';
|
||||
}
|
||||
}
|
||||
|
||||
function preloadNext(index) {
|
||||
if (index - currentIndex >= options.preload) {
|
||||
return;
|
||||
}
|
||||
loadImage(index + 1, function () {
|
||||
preloadNext(index + 1);
|
||||
});
|
||||
}
|
||||
|
||||
function preloadPrev(index) {
|
||||
if (currentIndex - index >= options.preload) {
|
||||
return;
|
||||
}
|
||||
loadImage(index - 1, function () {
|
||||
preloadPrev(index - 1);
|
||||
});
|
||||
}
|
||||
|
||||
function bind(element, event, callback, options) {
|
||||
element.addEventListener(event, callback, options);
|
||||
}
|
||||
|
||||
function unbind(element, event, callback, options) {
|
||||
element.removeEventListener(event, callback, options);
|
||||
}
|
||||
|
||||
function destroyPlugin() {
|
||||
unbindEvents();
|
||||
clearCachedData();
|
||||
unbind(document, 'keydown', keyDownHandler);
|
||||
document.getElementsByTagName('body')[0].removeChild(ebi('baguetteBox-overlay'));
|
||||
data = {};
|
||||
currentGallery = [];
|
||||
currentIndex = 0;
|
||||
}
|
||||
|
||||
return {
|
||||
run: run,
|
||||
show: show,
|
||||
showNext: showNextImage,
|
||||
showPrevious: showPreviousImage,
|
||||
hide: hideOverlay,
|
||||
destroy: destroyPlugin
|
||||
};
|
||||
})();
|
||||
@@ -497,6 +497,27 @@ input[type="checkbox"]+label {
|
||||
input[type="checkbox"]:checked+label {
|
||||
color: #fc5;
|
||||
}
|
||||
input.eq_gain {
|
||||
width: 3em;
|
||||
text-align: center;
|
||||
margin: 0 .6em;
|
||||
}
|
||||
#audio_eq table {
|
||||
border-collapse: collapse;
|
||||
}
|
||||
#audio_eq td {
|
||||
text-align: center;
|
||||
}
|
||||
#audio_eq a.eq_step {
|
||||
font-size: 1.5em;
|
||||
display: block;
|
||||
padding: 0;
|
||||
}
|
||||
#au_eq {
|
||||
display: block;
|
||||
margin-top: .5em;
|
||||
padding: 1.3em .3em;
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -750,9 +771,12 @@ input[type="checkbox"]:checked+label {
|
||||
font-family: monospace, monospace;
|
||||
line-height: 2em;
|
||||
}
|
||||
#griden.on+#thumbs {
|
||||
#thumbs {
|
||||
opacity: .3;
|
||||
}
|
||||
#griden.on+#thumbs {
|
||||
opacity: 1;
|
||||
}
|
||||
#ghead {
|
||||
background: #3c3c3c;
|
||||
border: 1px solid #444;
|
||||
@@ -801,8 +825,7 @@ html.light #ghead {
|
||||
content: '📂';
|
||||
line-height: 0;
|
||||
font-size: 2em;
|
||||
display: inline-block;
|
||||
margin: -.7em 0 -.5em -.3em;
|
||||
margin: -.7em .1em -.5em -.3em;
|
||||
}
|
||||
#ggrid a:hover {
|
||||
background: #444;
|
||||
@@ -1028,4 +1051,161 @@ html.light #tree::-webkit-scrollbar {
|
||||
}
|
||||
#tree::-webkit-scrollbar-thumb {
|
||||
background: #da0;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
#baguetteBox-overlay {
|
||||
display: none;
|
||||
opacity: 0;
|
||||
position: fixed;
|
||||
overflow: hidden;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
z-index: 1000000;
|
||||
background: rgba(0, 0, 0, 0.8);
|
||||
transition: opacity .3s ease;
|
||||
}
|
||||
#baguetteBox-overlay.visible {
|
||||
opacity: 1;
|
||||
}
|
||||
#baguetteBox-overlay .full-image {
|
||||
display: inline-block;
|
||||
position: relative;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
text-align: center;
|
||||
}
|
||||
#baguetteBox-overlay .full-image figure {
|
||||
display: inline;
|
||||
margin: 0;
|
||||
height: 100%;
|
||||
}
|
||||
#baguetteBox-overlay .full-image img {
|
||||
display: inline-block;
|
||||
width: auto;
|
||||
height: auto;
|
||||
max-height: 100%;
|
||||
max-width: 100%;
|
||||
vertical-align: middle;
|
||||
box-shadow: 0 0 8px rgba(0, 0, 0, 0.6);
|
||||
}
|
||||
#baguetteBox-overlay .full-image figcaption {
|
||||
display: block;
|
||||
position: absolute;
|
||||
bottom: 0;
|
||||
width: 100%;
|
||||
text-align: center;
|
||||
line-height: 1.8;
|
||||
white-space: normal;
|
||||
color: #ccc;
|
||||
}
|
||||
#baguetteBox-overlay figcaption a {
|
||||
background: rgba(0, 0, 0, 0.6);
|
||||
border-radius: .4em;
|
||||
padding: .3em .6em;
|
||||
}
|
||||
#baguetteBox-overlay .full-image:before {
|
||||
content: "";
|
||||
display: inline-block;
|
||||
height: 50%;
|
||||
width: 1px;
|
||||
margin-right: -1px;
|
||||
}
|
||||
#baguetteBox-slider {
|
||||
position: absolute;
|
||||
left: 0;
|
||||
top: 0;
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
white-space: nowrap;
|
||||
transition: left .2s ease, transform .2s ease;
|
||||
}
|
||||
#baguetteBox-slider.bounce-from-right {
|
||||
animation: bounceFromRight .4s ease-out;
|
||||
}
|
||||
#baguetteBox-slider.bounce-from-left {
|
||||
animation: bounceFromLeft .4s ease-out;
|
||||
}
|
||||
@keyframes bounceFromRight {
|
||||
0% {margin-left: 0}
|
||||
50% {margin-left: -30px}
|
||||
100% {margin-left: 0}
|
||||
}
|
||||
@keyframes bounceFromLeft {
|
||||
0% {margin-left: 0}
|
||||
50% {margin-left: 30px}
|
||||
100% {margin-left: 0}
|
||||
}
|
||||
.baguetteBox-button#next-button,
|
||||
.baguetteBox-button#previous-button {
|
||||
top: 50%;
|
||||
top: calc(50% - 30px);
|
||||
width: 44px;
|
||||
height: 60px;
|
||||
}
|
||||
.baguetteBox-button {
|
||||
position: absolute;
|
||||
cursor: pointer;
|
||||
outline: none;
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
border: 0;
|
||||
border-radius: 15%;
|
||||
background: rgba(50, 50, 50, 0.5);
|
||||
color: #ddd;
|
||||
font: 1.6em sans-serif;
|
||||
transition: background-color .3s ease;
|
||||
}
|
||||
.baguetteBox-button:focus,
|
||||
.baguetteBox-button:hover {
|
||||
background: rgba(50, 50, 50, 0.9);
|
||||
}
|
||||
#next-button {
|
||||
right: 2%;
|
||||
}
|
||||
#previous-button {
|
||||
left: 2%;
|
||||
}
|
||||
#close-button {
|
||||
top: 20px;
|
||||
right: 2%;
|
||||
width: 30px;
|
||||
height: 30px;
|
||||
}
|
||||
.baguetteBox-button svg {
|
||||
position: absolute;
|
||||
left: 0;
|
||||
top: 0;
|
||||
}
|
||||
.baguetteBox-spinner {
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
display: inline-block;
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
margin-top: -20px;
|
||||
margin-left: -20px;
|
||||
}
|
||||
.baguetteBox-double-bounce1,
|
||||
.baguetteBox-double-bounce2 {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
border-radius: 50%;
|
||||
background-color: #fff;
|
||||
opacity: .6;
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
animation: bounce 2s infinite ease-in-out;
|
||||
}
|
||||
.baguetteBox-double-bounce2 {
|
||||
animation-delay: -1s;
|
||||
}
|
||||
@keyframes bounce {
|
||||
0%, 100% {transform: scale(0)}
|
||||
50% {transform: scale(1)}
|
||||
}
|
||||
|
||||
@@ -55,6 +55,8 @@
|
||||
{%- endif %}
|
||||
<h3>key notation</h3>
|
||||
<div id="key_notation"></div>
|
||||
<h3>audio equalizer</h3>
|
||||
<div id="audio_eq"></div>
|
||||
</div>
|
||||
|
||||
<h1 id="path">
|
||||
|
||||
@@ -508,6 +508,167 @@ try {
|
||||
catch (ex) { }
|
||||
|
||||
|
||||
var audio_eq = (function () {
|
||||
var r = {
|
||||
"en": false,
|
||||
"bands": [31.25, 62.5, 125, 250, 500, 1000, 2000, 4000, 8000, 16000],
|
||||
"gains": [0, -1, -2, -3, -4, -4, -3, -2, -1, 0],
|
||||
"filters": [],
|
||||
"last_au": null
|
||||
};
|
||||
|
||||
try {
|
||||
r.gains = jread('au_eq_gain', r.gains);
|
||||
}
|
||||
catch (ex) { }
|
||||
|
||||
r.draw = function () {
|
||||
var max = 0;
|
||||
for (var a = 0; a < r.gains.length; a++)
|
||||
if (max < r.gains[a])
|
||||
max = r.gains[a];
|
||||
|
||||
if (max > 0)
|
||||
for (var a = 0; a < r.gains.length; a++)
|
||||
r.gains[a] -= max;
|
||||
|
||||
jwrite('au_eq_gain', r.gains);
|
||||
|
||||
var txt = QSA('input.eq_gain');
|
||||
for (var a = 0; a < r.bands.length; a++)
|
||||
txt[a].value = r.gains[a];
|
||||
};
|
||||
|
||||
r.apply = function () {
|
||||
r.draw();
|
||||
|
||||
var Ctx = window.AudioContext || window.webkitAudioContext;
|
||||
if (!Ctx)
|
||||
bcfg_set('au_eq', false);
|
||||
|
||||
if (!Ctx || !mp.au)
|
||||
return;
|
||||
|
||||
if (!r.en && !mp.ac)
|
||||
return;
|
||||
|
||||
if (mp.ac) {
|
||||
for (var a = 0; a < r.filters.length; a++)
|
||||
r.filters[a].disconnect();
|
||||
|
||||
mp.acs.disconnect();
|
||||
}
|
||||
|
||||
if (!mp.ac || mp.au != r.last_au) {
|
||||
if (mp.ac)
|
||||
mp.ac.close();
|
||||
|
||||
r.last_au = mp.au;
|
||||
mp.ac = new Ctx();
|
||||
mp.acs = mp.ac.createMediaElementSource(mp.au);
|
||||
}
|
||||
|
||||
if (!r.en) {
|
||||
mp.acs.connect(mp.ac.destination);
|
||||
return;
|
||||
}
|
||||
|
||||
r.filters = [];
|
||||
for (var a = 0; a < r.bands.length; a++) {
|
||||
var fi = mp.ac.createBiquadFilter();
|
||||
fi.frequency.value = r.bands[a];
|
||||
fi.gain.value = r.gains[a];
|
||||
fi.Q.value = a == 0 ? 0 : 1;
|
||||
fi.type = a == 0 ? 'lowshelf' : a == r.bands.length - 1 ? 'highshelf' : 'peaking';
|
||||
r.filters.push(fi);
|
||||
}
|
||||
for (var a = r.bands.length - 1; a >= 0; a--) {
|
||||
r.filters[a].connect(a > 0 ? r.filters[a - 1] : mp.ac.destination);
|
||||
}
|
||||
mp.acs.connect(r.filters[r.filters.length - 1]);
|
||||
}
|
||||
|
||||
function eq_step(e) {
|
||||
ev(e);
|
||||
var band = parseInt(this.getAttribute('band')),
|
||||
step = parseFloat(this.getAttribute('step'));
|
||||
|
||||
r.gains[band] += step;
|
||||
r.apply();
|
||||
}
|
||||
|
||||
function adj_band(that, step) {
|
||||
try {
|
||||
var band = parseInt(that.getAttribute('band')),
|
||||
v = parseFloat(that.value);
|
||||
|
||||
if (isNaN(v))
|
||||
throw 42;
|
||||
|
||||
r.gains[band] = v + step;
|
||||
}
|
||||
catch (ex) {
|
||||
return;
|
||||
}
|
||||
r.apply();
|
||||
}
|
||||
|
||||
function eq_mod(e) {
|
||||
ev(e);
|
||||
adj_band(this, 0);
|
||||
}
|
||||
|
||||
function eq_keydown(e) {
|
||||
var step = e.key == 'ArrowUp' ? 0.25 : e.key == 'ArrowDown' ? -0.25 : 0;
|
||||
if (step != 0)
|
||||
adj_band(this, step);
|
||||
}
|
||||
|
||||
var html = ['<table><tr><td rowspan="4">',
|
||||
'<a id="au_eq" class="tgl btn" href="#">enable</a></td>'],
|
||||
h2 = [], h3 = [], h4 = [];
|
||||
|
||||
for (var a = 0; a < r.bands.length; a++) {
|
||||
var hz = r.bands[a];
|
||||
if (hz >= 1000)
|
||||
hz = (hz / 1000) + 'k';
|
||||
|
||||
hz = (hz + '').split('.')[0];
|
||||
html.push('<td><a href="#" class="eq_step" step="0.5" band="' + a + '">+</a></td>');
|
||||
h2.push('<td>' + hz + '</td>');
|
||||
h4.push('<td><a href="#" class="eq_step" step="-0.5" band="' + a + '">–</a></td>');
|
||||
h3.push('<td><input type="text" class="eq_gain" band="' + a + '" value="' + r.gains[a] + '" /></td>');
|
||||
}
|
||||
html.push('</tr><tr>');
|
||||
html = html.join('\n');
|
||||
html += h2.join('\n') + '</tr><tr>';
|
||||
html += h3.join('\n') + '</tr><tr>';
|
||||
html += h4.join('\n') + '</tr><table>';
|
||||
ebi('audio_eq').innerHTML = html;
|
||||
|
||||
var stp = QSA('a.eq_step');
|
||||
for (var a = 0, aa = stp.length; a < aa; a++)
|
||||
stp[a].onclick = eq_step;
|
||||
|
||||
var txt = QSA('input.eq_gain');
|
||||
for (var a = 0; a < r.gains.length; a++) {
|
||||
txt[a].oninput = eq_mod;
|
||||
txt[a].onkeydown = eq_keydown;
|
||||
}
|
||||
|
||||
r.en = bcfg_get('au_eq', false);
|
||||
ebi('au_eq').onclick = function (e) {
|
||||
ev(e);
|
||||
r.en = !r.en;
|
||||
bcfg_set('au_eq', r.en);
|
||||
r.apply();
|
||||
};
|
||||
|
||||
r.draw();
|
||||
return r;
|
||||
})();
|
||||
|
||||
|
||||
// plays the tid'th audio file on the page
|
||||
function play(tid, seek, call_depth) {
|
||||
if (mp.order.length == 0)
|
||||
@@ -568,6 +729,8 @@ function play(tid, seek, call_depth) {
|
||||
mp.au = mp.au_native;
|
||||
}
|
||||
|
||||
audio_eq.apply();
|
||||
|
||||
mp.au.tid = tid;
|
||||
mp.au.src = url;
|
||||
mp.au.volume = mp.expvol();
|
||||
@@ -709,8 +872,9 @@ function autoplay_blocked(seek) {
|
||||
|
||||
|
||||
var thegrid = (function () {
|
||||
var lfiles = ebi('files');
|
||||
var gfiles = document.createElement('div');
|
||||
var lfiles = ebi('files'),
|
||||
gfiles = document.createElement('div');
|
||||
|
||||
gfiles.setAttribute('id', 'gfiles');
|
||||
gfiles.style.display = 'none';
|
||||
gfiles.innerHTML = (
|
||||
@@ -732,7 +896,8 @@ var thegrid = (function () {
|
||||
'en': bcfg_get('griden', false),
|
||||
'sel': bcfg_get('gridsel', false),
|
||||
'sz': fcfg_get('gridsz', 10),
|
||||
'isdirty': true
|
||||
'isdirty': true,
|
||||
'bbox': null
|
||||
};
|
||||
|
||||
ebi('thumbs').onclick = function (e) {
|
||||
@@ -891,9 +1056,37 @@ var thegrid = (function () {
|
||||
lfiles.style.display = 'none';
|
||||
gfiles.style.display = 'block';
|
||||
ebi('ggrid').innerHTML = html.join('\n');
|
||||
r.bagit();
|
||||
r.loadsel();
|
||||
}
|
||||
|
||||
r.bagit = function () {
|
||||
if (!window.baguetteBox)
|
||||
return;
|
||||
|
||||
if (r.bbox)
|
||||
baguetteBox.destroy();
|
||||
|
||||
r.bbox = baguetteBox.run('#ggrid', {
|
||||
captions: function (g) {
|
||||
var idx = -1,
|
||||
h = '' + g;
|
||||
|
||||
for (var a = 0; a < r.bbox.length; a++)
|
||||
if (r.bbox[a].imageElement == g)
|
||||
idx = a;
|
||||
|
||||
return '<a download href="' + h +
|
||||
'">' + (idx + 1) + ' / ' + r.bbox.length + ' -- ' +
|
||||
esc(uricom_dec(h.split('/').slice(-1)[0])[0]) + '</a>';
|
||||
}
|
||||
})[0];
|
||||
};
|
||||
|
||||
setTimeout(function () {
|
||||
import_js('/.cpr/baguettebox.js', r.bagit);
|
||||
}, 1);
|
||||
|
||||
if (r.en) {
|
||||
loadgrid();
|
||||
}
|
||||
|
||||
@@ -59,7 +59,7 @@
|
||||
|
||||
<h1>login for more:</h1>
|
||||
<ul>
|
||||
<form method="post" enctype="multipart/form-data" action="/{{ url_suf }}">
|
||||
<form method="post" enctype="multipart/form-data" action="/">
|
||||
<input type="hidden" name="act" value="login" />
|
||||
<input type="password" name="cppwd" />
|
||||
<input type="submit" value="Login" />
|
||||
|
||||
@@ -804,6 +804,14 @@ function up2k_init(subtle) {
|
||||
|
||||
var mou_ikkai = false;
|
||||
|
||||
if (st.busy.handshake.length > 0 &&
|
||||
st.busy.handshake[0].busied < Date.now() - 30 * 1000
|
||||
) {
|
||||
console.log("retrying stuck handshake");
|
||||
var t = st.busy.handshake.shift();
|
||||
st.todo.handshake.unshift(t);
|
||||
}
|
||||
|
||||
if (st.todo.handshake.length > 0 &&
|
||||
st.busy.handshake.length == 0 && (
|
||||
st.todo.handshake[0].t4 || (
|
||||
@@ -1019,11 +1027,27 @@ function up2k_init(subtle) {
|
||||
//
|
||||
|
||||
function exec_handshake() {
|
||||
var t = st.todo.handshake.shift();
|
||||
var t = st.todo.handshake.shift(),
|
||||
me = Date.now();
|
||||
|
||||
st.busy.handshake.push(t);
|
||||
t.busied = me;
|
||||
|
||||
var xhr = new XMLHttpRequest();
|
||||
xhr.onerror = function () {
|
||||
if (t.busied != me) {
|
||||
console.log('zombie handshake onerror,', t);
|
||||
return;
|
||||
}
|
||||
console.log('handshake onerror, retrying');
|
||||
st.busy.handshake.splice(st.busy.handshake.indexOf(t), 1);
|
||||
st.todo.handshake.unshift(t);
|
||||
};
|
||||
xhr.onload = function (e) {
|
||||
if (t.busied != me) {
|
||||
console.log('zombie handshake onload,', t);
|
||||
return;
|
||||
}
|
||||
if (xhr.status == 200) {
|
||||
var response = JSON.parse(xhr.responseText);
|
||||
|
||||
|
||||
@@ -1,8 +1,19 @@
|
||||
## [`minimal-up2k.html`](minimal-up2k.html)
|
||||
* save as `.epilogue.html` inside a folder to [simplify the ui](https://user-images.githubusercontent.com/241032/118311195-dd6ca380-b4ef-11eb-86f3-75a3ff2e1332.png)
|
||||
# example `.epilogue.html`
|
||||
save one of these as `.epilogue.html` inside a folder to customize it:
|
||||
|
||||
## [`browser.css`](browser.css)
|
||||
* example for `--css-browser`
|
||||
* [`minimal-up2k.html`](minimal-up2k.html) will [simplify the upload ui](https://user-images.githubusercontent.com/241032/118311195-dd6ca380-b4ef-11eb-86f3-75a3ff2e1332.png)
|
||||
|
||||
|
||||
|
||||
# example browser-css
|
||||
point `--css-browser` to one of these by URL:
|
||||
|
||||
* [`browser.css`](browser.css) changes the background
|
||||
* [`browser-icons.css`](browser-icons.css) adds filetype icons
|
||||
|
||||
|
||||
|
||||
# other stuff
|
||||
|
||||
## [`rclone.md`](rclone.md)
|
||||
* notes on using rclone as a fuse client/server
|
||||
|
||||
68
docs/browser-icons.css
Normal file
68
docs/browser-icons.css
Normal file
@@ -0,0 +1,68 @@
|
||||
/* put filetype icons inline with text
|
||||
#ggrid>a>span:before,
|
||||
#ggrid>a>span.dir:before {
|
||||
display: inline;
|
||||
line-height: 0;
|
||||
font-size: 1.7em;
|
||||
margin: -.7em .1em -.5em -.6em;
|
||||
}
|
||||
*/
|
||||
|
||||
|
||||
/* move folder icons top-left */
|
||||
#ggrid>a>span.dir:before {
|
||||
content: initial;
|
||||
}
|
||||
#ggrid>a[href$="/"]:before {
|
||||
content: '📂';
|
||||
display: block;
|
||||
position: absolute;
|
||||
margin: -.1em -.4em;
|
||||
text-shadow: 0 0 .1em #000;
|
||||
font-size: 2em;
|
||||
}
|
||||
|
||||
|
||||
/* put filetype icons top-left */
|
||||
#ggrid>a:before {
|
||||
display: block;
|
||||
position: absolute;
|
||||
margin: -.1em -.4em;
|
||||
text-shadow: 0 0 .1em #000;
|
||||
font-size: 2em;
|
||||
}
|
||||
|
||||
|
||||
/* video */
|
||||
#ggrid>a:is(
|
||||
[href$=".mkv"i],
|
||||
[href$=".mp4"i],
|
||||
[href$=".webm"i],
|
||||
):before {
|
||||
content: '📺';
|
||||
}
|
||||
|
||||
|
||||
/* audio */
|
||||
#ggrid>a:is(
|
||||
[href$=".mp3"i],
|
||||
[href$=".ogg"i],
|
||||
[href$=".opus"i],
|
||||
[href$=".flac"i],
|
||||
[href$=".m4a"i],
|
||||
[href$=".aac"i],
|
||||
):before {
|
||||
content: '🎵';
|
||||
}
|
||||
|
||||
|
||||
/* image */
|
||||
#ggrid>a:is(
|
||||
[href$=".jpg"i],
|
||||
[href$=".jpeg"i],
|
||||
[href$=".png"i],
|
||||
[href$=".gif"i],
|
||||
[href$=".webp"i],
|
||||
):before {
|
||||
content: '🎨';
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
html {
|
||||
background: url('/wp/wallhaven-mdjrqy.jpg') center / cover no-repeat fixed;
|
||||
background: #333 url('/wp/wallhaven-mdjrqy.jpg') center / cover no-repeat fixed;
|
||||
}
|
||||
#files th {
|
||||
background: rgba(32, 32, 32, 0.9) !important;
|
||||
@@ -12,7 +12,7 @@ html {
|
||||
|
||||
|
||||
html.light {
|
||||
background: url('/wp/wallhaven-dpxl6l.png') center / cover no-repeat fixed;
|
||||
background: #eee url('/wp/wallhaven-dpxl6l.png') center / cover no-repeat fixed;
|
||||
}
|
||||
html.light #files th {
|
||||
background: rgba(255, 255, 255, 0.9) !important;
|
||||
|
||||
@@ -86,6 +86,9 @@ var t=[]; var b=document.location.href.split('#')[0].slice(0, -1); document.quer
|
||||
# get the size and video-id of all youtube vids in folder, assuming filename ends with -id.ext, and create a copyparty search query
|
||||
find -maxdepth 1 -printf '%s %p\n' | sort -n | awk '!/-([0-9a-zA-Z_-]{11})\.(mkv|mp4|webm)$/{next} {sub(/\.[^\.]+$/,"");n=length($0);v=substr($0,n-10);print $1, v}' | tee /dev/stderr | awk 'BEGIN {p="("} {printf("%s name like *-%s.* ",p,$2);p="or"} END {print ")\n"}' | cat >&2
|
||||
|
||||
# unique stacks in a stackdump
|
||||
f=a; rm -rf stacks; mkdir stacks; grep -E '^#' $f | while IFS= read -r n; do awk -v n="$n" '!$0{o=0} o; $0==n{o=1}' <$f >stacks/f; h=$(sha1sum <stacks/f | cut -c-16); mv stacks/f stacks/$h-"$n"; done ; find stacks/ | sort | uniq -cw24
|
||||
|
||||
|
||||
##
|
||||
## sqlite3 stuff
|
||||
|
||||
32
docs/tcp-debug.sh
Normal file
32
docs/tcp-debug.sh
Normal file
@@ -0,0 +1,32 @@
|
||||
(cd ~/dev/copyparty && strace -Tttyyvfs 256 -o strace.strace python3 -um copyparty -i 127.0.0.1 --http-only --stackmon /dev/shm/cpps,10 ) 2>&1 | tee /dev/stderr > ~/log-copyparty-$(date +%Y-%m%d-%H%M%S).txt
|
||||
|
||||
14/Jun/2021:16:34:02 1623688447.212405 death
|
||||
14/Jun/2021:16:35:02 1623688502.420860 back
|
||||
|
||||
tcpdump -nni lo -w /home/ed/lo.pcap
|
||||
|
||||
# 16:35:25.324662 IP 127.0.0.1.48632 > 127.0.0.1.3920: Flags [F.], seq 849, ack 544, win 359, options [nop,nop,TS val 809396796 ecr 809396796], length 0
|
||||
|
||||
tcpdump -nnr /home/ed/lo.pcap | awk '/ > 127.0.0.1.3920: /{sub(/ > .*/,"");sub(/.*\./,"");print}' | sort -n | uniq | while IFS= read -r port; do echo; tcpdump -nnr /home/ed/lo.pcap 2>/dev/null | grep -E "\.$port( > |: F)" | sed -r 's/ > .*, /, /'; done | grep -E '^16:35:0.*length [^0]' -C50
|
||||
|
||||
16:34:02.441732 IP 127.0.0.1.48638, length 0
|
||||
16:34:02.441738 IP 127.0.0.1.3920, length 0
|
||||
16:34:02.441744 IP 127.0.0.1.48638, length 0
|
||||
16:34:02.441756 IP 127.0.0.1.48638, length 791
|
||||
16:34:02.441759 IP 127.0.0.1.3920, length 0
|
||||
16:35:02.445529 IP 127.0.0.1.48638, length 0
|
||||
16:35:02.489194 IP 127.0.0.1.3920, length 0
|
||||
16:35:02.515595 IP 127.0.0.1.3920, length 216
|
||||
16:35:02.515600 IP 127.0.0.1.48638, length 0
|
||||
|
||||
grep 48638 "$(find ~ -maxdepth 1 -name log-copyparty-\*.txt | sort | tail -n 1)"
|
||||
|
||||
1623688502.510380 48638 rh
|
||||
1623688502.511291 48638 Unrecv direct ...
|
||||
1623688502.511827 48638 rh = 791
|
||||
16:35:02.518 127.0.0.1 48638 shut(8): [Errno 107] Socket not connected
|
||||
Exception in thread httpsrv-0.1-48638:
|
||||
|
||||
grep 48638 ~/dev/copyparty/strace.strace
|
||||
14561 16:35:02.506310 <... accept4 resumed> {sa_family=AF_INET, sin_port=htons(48638), sin_addr=inet_addr("127.0.0.1")}, [16], SOCK_CLOEXEC) = 8<TCP:[127.0.0.1:3920->127.0.0.1:48638]> <0.000012>
|
||||
15230 16:35:02.510725 write(1<pipe:[256639555]>, "1623688502.510380 48638 rh\n", 27 <unfinished ...>
|
||||
@@ -208,7 +208,7 @@ find | grep -E '\.css$' | while IFS= read -r f; do
|
||||
}
|
||||
!/\}$/ {printf "%s",$0;next}
|
||||
1
|
||||
' <$f | gsed 's/;\}$/}/' >t
|
||||
' <$f | sed 's/;\}$/}/' >t
|
||||
tmv "$f"
|
||||
done
|
||||
find | grep -E '\.(js|html)$' | while IFS= read -r f; do
|
||||
@@ -223,7 +223,7 @@ command -v pigz &&
|
||||
pk='gzip'
|
||||
|
||||
echo "$pk"
|
||||
find | grep -E '\.(js|css)$' | while IFS= read -r f; do
|
||||
find | grep -E '\.(js|css)$' | grep -vF /deps/ | while IFS= read -r f; do
|
||||
echo -n .
|
||||
$pk "$f"
|
||||
done
|
||||
|
||||
@@ -28,6 +28,7 @@ class Cfg(Namespace):
|
||||
a=a,
|
||||
v=v,
|
||||
c=c,
|
||||
rproxy=0,
|
||||
ed=False,
|
||||
no_zip=False,
|
||||
no_scandir=False,
|
||||
@@ -39,6 +40,7 @@ class Cfg(Namespace):
|
||||
mte="a",
|
||||
hist=None,
|
||||
no_hash=False,
|
||||
css_browser=None,
|
||||
**{k: False for k in "e2d e2ds e2dsa e2t e2ts e2tsr".split()}
|
||||
)
|
||||
|
||||
|
||||
@@ -18,7 +18,14 @@ from copyparty import util
|
||||
class Cfg(Namespace):
|
||||
def __init__(self, a=[], v=[], c=None):
|
||||
ex = {k: False for k in "e2d e2ds e2dsa e2t e2ts e2tsr".split()}
|
||||
ex2 = {"mtp": [], "mte": "a", "hist": None, "no_hash": False}
|
||||
ex2 = {
|
||||
"mtp": [],
|
||||
"mte": "a",
|
||||
"hist": None,
|
||||
"no_hash": False,
|
||||
"css_browser": None,
|
||||
"rproxy": 0,
|
||||
}
|
||||
ex.update(ex2)
|
||||
super(Cfg, self).__init__(a=a, v=v, c=c, **ex)
|
||||
|
||||
|
||||
Reference in New Issue
Block a user