Compare commits
26 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
dcaf7b0a20 | ||
|
|
f982cdc178 | ||
|
|
b265e59834 | ||
|
|
4a843a6624 | ||
|
|
241ef5b99d | ||
|
|
f39f575a9c | ||
|
|
1521307f1e | ||
|
|
dd122111e6 | ||
|
|
00c177fa74 | ||
|
|
f6c7e49eb8 | ||
|
|
1a8dc3d18a | ||
|
|
38a163a09a | ||
|
|
8f031246d2 | ||
|
|
8f3d97dde7 | ||
|
|
4acaf24d65 | ||
|
|
9a8dbbbcf8 | ||
|
|
a3efc4c726 | ||
|
|
0278bf328f | ||
|
|
17ddd96cc6 | ||
|
|
0e82e79aea | ||
|
|
30f124c061 | ||
|
|
e19d90fcfc | ||
|
|
184bbdd23d | ||
|
|
30b50aec95 | ||
|
|
c3c3d81db1 | ||
|
|
49b7231283 |
12
README.md
12
README.md
@@ -52,7 +52,7 @@ turn your phone or raspi into a portable file server with resumable uploads/down
|
||||
* [compress uploads](#compress-uploads) - files can be autocompressed on upload
|
||||
* [database location](#database-location) - in-volume (`.hist/up2k.db`, default) or somewhere else
|
||||
* [metadata from audio files](#metadata-from-audio-files) - set `-e2t` to index tags on upload
|
||||
* [file parser plugins](#file-parser-plugins) - provide custom parsers to index additional tags
|
||||
* [file parser plugins](#file-parser-plugins) - provide custom parsers to index additional tags, also see [./bin/mtag/README.md](./bin/mtag/README.md)
|
||||
* [upload events](#upload-events) - trigger a script/program on each upload
|
||||
* [complete examples](#complete-examples)
|
||||
* [browser support](#browser-support) - TLDR: yes
|
||||
@@ -571,6 +571,8 @@ and there are *two* editors
|
||||
|
||||
* you can link a particular timestamp in an audio file by adding it to the URL, such as `&20` / `&20s` / `&1m20` / `&t=1:20` after the `.../#af-c8960dab`
|
||||
|
||||
* enabling the audio equalizer can help make gapless albums fully gapless in some browsers (chrome), so consider leaving it on with all the values at zero
|
||||
|
||||
* get a plaintext file listing by adding `?ls=t` to a URL, or a compact colored one with `?ls=v` (for unix terminals)
|
||||
|
||||
* if you are using media hotkeys to switch songs and are getting tired of seeing the OSD popup which Windows doesn't let you disable, consider https://ocv.me/dev/?media-osd-bgone.ps1
|
||||
@@ -677,6 +679,12 @@ things to note,
|
||||
* the files will be indexed after compression, so dupe-detection and file-search will not work as expected
|
||||
|
||||
some examples,
|
||||
* `-v inc:inc:w:c,pk=xz,0`
|
||||
folder named inc, shared at inc, write-only for everyone, forces xz compression at level 0
|
||||
* `-v inc:inc:w:c,pk`
|
||||
same write-only inc, but forces gz compression (default) instead of xz
|
||||
* `-v inc:inc:w:c,gz`
|
||||
allows (but does not force) gz compression if client uploads to `/inc?pk` or `/inc?gz` or `/inc?gz=4`
|
||||
|
||||
|
||||
## database location
|
||||
@@ -721,7 +729,7 @@ see the beautiful mess of a dictionary in [mtag.py](https://github.com/9001/copy
|
||||
|
||||
## file parser plugins
|
||||
|
||||
provide custom parsers to index additional tags
|
||||
provide custom parsers to index additional tags, also see [./bin/mtag/README.md](./bin/mtag/README.md)
|
||||
|
||||
copyparty can invoke external programs to collect additional metadata for files using `mtp` (either as argument or volume flag), there is a default timeout of 30sec
|
||||
|
||||
|
||||
@@ -6,9 +6,13 @@ some of these rely on libraries which are not MIT-compatible
|
||||
|
||||
* [audio-bpm.py](./audio-bpm.py) detects the BPM of music using the BeatRoot Vamp Plugin; imports GPL2
|
||||
* [audio-key.py](./audio-key.py) detects the melodic key of music using the Mixxx fork of keyfinder; imports GPL3
|
||||
* [media-hash.py](./media-hash.py) generates checksums for audio and video streams; uses FFmpeg (LGPL or GPL)
|
||||
|
||||
these do not have any problematic dependencies:
|
||||
these invoke standalone programs which are GPL or similar, so is legally fine for most purposes:
|
||||
|
||||
* [media-hash.py](./media-hash.py) generates checksums for audio and video streams; uses FFmpeg (LGPL or GPL)
|
||||
* [image-noexif.py](./image-noexif.py) removes exif tags from images; uses exiftool (GPLv1 or artistic-license)
|
||||
|
||||
these do not have any problematic dependencies at all:
|
||||
|
||||
* [cksum.py](./cksum.py) computes various checksums
|
||||
* [exe.py](./exe.py) grabs metadata from .exe and .dll files (example for retrieving multiple tags with one parser)
|
||||
|
||||
@@ -19,18 +19,18 @@ dep: ffmpeg
|
||||
def det(tf):
|
||||
# fmt: off
|
||||
sp.check_call([
|
||||
"ffmpeg",
|
||||
"-nostdin",
|
||||
"-hide_banner",
|
||||
"-v", "fatal",
|
||||
"-ss", "13",
|
||||
"-y", "-i", fsenc(sys.argv[1]),
|
||||
"-map", "0:a:0",
|
||||
"-ac", "1",
|
||||
"-ar", "22050",
|
||||
"-t", "300",
|
||||
"-f", "f32le",
|
||||
tf
|
||||
b"ffmpeg",
|
||||
b"-nostdin",
|
||||
b"-hide_banner",
|
||||
b"-v", b"fatal",
|
||||
b"-ss", b"13",
|
||||
b"-y", b"-i", fsenc(sys.argv[1]),
|
||||
b"-map", b"0:a:0",
|
||||
b"-ac", b"1",
|
||||
b"-ar", b"22050",
|
||||
b"-t", b"300",
|
||||
b"-f", b"f32le",
|
||||
fsenc(tf)
|
||||
])
|
||||
# fmt: on
|
||||
|
||||
|
||||
@@ -23,15 +23,15 @@ dep: ffmpeg
|
||||
def det(tf):
|
||||
# fmt: off
|
||||
sp.check_call([
|
||||
"ffmpeg",
|
||||
"-nostdin",
|
||||
"-hide_banner",
|
||||
"-v", "fatal",
|
||||
"-y", "-i", fsenc(sys.argv[1]),
|
||||
"-map", "0:a:0",
|
||||
"-t", "300",
|
||||
"-sample_fmt", "s16",
|
||||
tf
|
||||
b"ffmpeg",
|
||||
b"-nostdin",
|
||||
b"-hide_banner",
|
||||
b"-v", b"fatal",
|
||||
b"-y", b"-i", fsenc(sys.argv[1]),
|
||||
b"-map", b"0:a:0",
|
||||
b"-t", b"300",
|
||||
b"-sample_fmt", b"s16",
|
||||
fsenc(tf)
|
||||
])
|
||||
# fmt: on
|
||||
|
||||
|
||||
93
bin/mtag/image-noexif.py
Normal file
93
bin/mtag/image-noexif.py
Normal file
@@ -0,0 +1,93 @@
|
||||
#!/usr/bin/env python3
|
||||
|
||||
"""
|
||||
remove exif tags from uploaded images
|
||||
|
||||
dependencies:
|
||||
exiftool
|
||||
|
||||
about:
|
||||
creates a "noexif" subfolder and puts exif-stripped copies of each image there,
|
||||
the reason for the subfolder is to avoid issues with the up2k.db / deduplication:
|
||||
|
||||
if the original image is modified in-place, then copyparty will keep the original
|
||||
hash in up2k.db for a while (until the next volume rescan), so if the image is
|
||||
reuploaded after a rescan then the upload will be renamed and kept as a dupe
|
||||
|
||||
alternatively you could switch the logic around, making a copy of the original
|
||||
image into a subfolder named "exif" and modify the original in-place, but then
|
||||
up2k.db will be out of sync until the next rescan, so any additional uploads
|
||||
of the same image will get symlinked (deduplicated) to the modified copy
|
||||
instead of the original in "exif"
|
||||
|
||||
or maybe delete the original image after processing, that would kinda work too
|
||||
|
||||
example copyparty config to use this:
|
||||
-v/mnt/nas/pics:pics:rwmd,ed:c,e2ts,mte=+noexif:c,mtp=noexif=ejpg,ejpeg,ad,bin/mtag/image-noexif.py
|
||||
|
||||
explained:
|
||||
for realpath /mnt/nas/pics (served at /pics) with read-write-modify-delete for ed,
|
||||
enable file analysis on upload (e2ts),
|
||||
append "noexif" to the list of known tags (mtp),
|
||||
and use mtp plugin "bin/mtag/image-noexif.py" to provide that tag,
|
||||
do this on all uploads with the file extension "jpg" or "jpeg",
|
||||
ad = parse file regardless if FFmpeg thinks it is audio or not
|
||||
|
||||
PS: this requires e2ts to be functional,
|
||||
meaning you need to do at least one of these:
|
||||
* apt install ffmpeg
|
||||
* pip3 install mutagen
|
||||
and your python must have sqlite3 support compiled in
|
||||
"""
|
||||
|
||||
|
||||
import os
|
||||
import sys
|
||||
import time
|
||||
import filecmp
|
||||
import subprocess as sp
|
||||
|
||||
try:
|
||||
from copyparty.util import fsenc
|
||||
except:
|
||||
|
||||
def fsenc(p):
|
||||
return p.encode("utf-8")
|
||||
|
||||
|
||||
def main():
|
||||
cwd, fn = os.path.split(sys.argv[1])
|
||||
if os.path.basename(cwd) == "noexif":
|
||||
return
|
||||
|
||||
os.chdir(cwd)
|
||||
f1 = fsenc(fn)
|
||||
f2 = os.path.join(b"noexif", f1)
|
||||
cmd = [
|
||||
b"exiftool",
|
||||
b"-exif:all=",
|
||||
b"-iptc:all=",
|
||||
b"-xmp:all=",
|
||||
b"-P",
|
||||
b"-o",
|
||||
b"noexif/",
|
||||
b"--",
|
||||
f1,
|
||||
]
|
||||
sp.check_output(cmd)
|
||||
if not os.path.exists(f2):
|
||||
print("failed")
|
||||
return
|
||||
|
||||
if filecmp.cmp(f1, f2, shallow=False):
|
||||
print("clean")
|
||||
else:
|
||||
print("exif")
|
||||
|
||||
# lastmod = os.path.getmtime(f1)
|
||||
# times = (int(time.time()), int(lastmod))
|
||||
# os.utime(f2, times)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
@@ -13,7 +13,7 @@ try:
|
||||
except:
|
||||
|
||||
def fsenc(p):
|
||||
return p
|
||||
return p.encode("utf-8")
|
||||
|
||||
|
||||
"""
|
||||
@@ -24,13 +24,13 @@ dep: ffmpeg
|
||||
def det():
|
||||
# fmt: off
|
||||
cmd = [
|
||||
"ffmpeg",
|
||||
"-nostdin",
|
||||
"-hide_banner",
|
||||
"-v", "fatal",
|
||||
"-i", fsenc(sys.argv[1]),
|
||||
"-f", "framemd5",
|
||||
"-"
|
||||
b"ffmpeg",
|
||||
b"-nostdin",
|
||||
b"-hide_banner",
|
||||
b"-v", b"fatal",
|
||||
b"-i", fsenc(sys.argv[1]),
|
||||
b"-f", b"framemd5",
|
||||
b"-"
|
||||
]
|
||||
# fmt: on
|
||||
|
||||
|
||||
12
bin/up2k.py
12
bin/up2k.py
@@ -3,7 +3,7 @@ from __future__ import print_function, unicode_literals
|
||||
|
||||
"""
|
||||
up2k.py: upload to copyparty
|
||||
2021-11-16, v0.12, ed <irc.rizon.net>, MIT-Licensed
|
||||
2021-11-28, v0.13, ed <irc.rizon.net>, MIT-Licensed
|
||||
https://github.com/9001/copyparty/blob/hovudstraum/bin/up2k.py
|
||||
|
||||
- dependencies: requests
|
||||
@@ -390,7 +390,7 @@ def handshake(req_ses, url, file, pw, search):
|
||||
r = req_ses.post(url, headers=headers, json=req)
|
||||
break
|
||||
except:
|
||||
eprint("handshake failed, retry...\n")
|
||||
eprint("handshake failed, retrying: {0}\n".format(file.name))
|
||||
time.sleep(1)
|
||||
|
||||
try:
|
||||
@@ -470,11 +470,11 @@ class Ctl(object):
|
||||
nbytes += inf.st_size
|
||||
|
||||
if err:
|
||||
eprint("\n# failed to access {} paths:\n".format(len(err)))
|
||||
eprint("\n# failed to access {0} paths:\n".format(len(err)))
|
||||
for x in err:
|
||||
eprint(x.decode("utf-8", "replace") + "\n")
|
||||
|
||||
eprint("^ failed to access those {} paths ^\n\n".format(len(err)))
|
||||
eprint("^ failed to access those {0} paths ^\n\n".format(len(err)))
|
||||
if not ar.ok:
|
||||
eprint("aborting because --ok is not set\n")
|
||||
return
|
||||
@@ -505,7 +505,7 @@ class Ctl(object):
|
||||
print("{0} {1}\n hash...".format(self.nfiles - nf, upath))
|
||||
get_hashlist(file, None)
|
||||
|
||||
burl = self.ar.url[:8] + self.ar.url[8:].split("/")[0] + "/"
|
||||
burl = self.ar.url[:12] + self.ar.url[8:].split("/")[0] + "/"
|
||||
while True:
|
||||
print(" hs...")
|
||||
hs = handshake(req_ses, self.ar.url, file, self.ar.a, search)
|
||||
@@ -773,7 +773,7 @@ class Ctl(object):
|
||||
try:
|
||||
upload(req_ses, file, cid, self.ar.a)
|
||||
except:
|
||||
eprint("upload failed, retry...\n")
|
||||
eprint("upload failed, retrying: {0} #{1}\n".format(file.name, cid[:8]))
|
||||
pass # handshake will fix it
|
||||
|
||||
with self.mutex:
|
||||
|
||||
@@ -13,7 +13,7 @@
|
||||
|
||||
upstream cpp {
|
||||
server 127.0.0.1:3923;
|
||||
keepalive 120;
|
||||
keepalive 1;
|
||||
}
|
||||
server {
|
||||
listen 443 ssl;
|
||||
|
||||
@@ -23,7 +23,7 @@ from textwrap import dedent
|
||||
from .__init__ import E, WINDOWS, ANYWIN, VT100, PY2, unicode
|
||||
from .__version__ import S_VERSION, S_BUILD_DT, CODENAME
|
||||
from .svchub import SvcHub
|
||||
from .util import py_desc, align_tab, IMPLICATIONS, ansi_re
|
||||
from .util import py_desc, align_tab, IMPLICATIONS, ansi_re, min_ex
|
||||
from .authsrv import re_vol
|
||||
|
||||
HAVE_SSL = True
|
||||
@@ -222,6 +222,54 @@ def sighandler(sig=None, frame=None):
|
||||
print("\n".join(msg))
|
||||
|
||||
|
||||
def disable_quickedit():
|
||||
import ctypes
|
||||
import atexit
|
||||
from ctypes import wintypes
|
||||
|
||||
def ecb(ok, fun, args):
|
||||
if not ok:
|
||||
err = ctypes.get_last_error()
|
||||
if err:
|
||||
raise ctypes.WinError(err)
|
||||
return args
|
||||
|
||||
k32 = ctypes.WinDLL("kernel32", use_last_error=True)
|
||||
if PY2:
|
||||
wintypes.LPDWORD = ctypes.POINTER(wintypes.DWORD)
|
||||
|
||||
k32.GetStdHandle.errcheck = ecb
|
||||
k32.GetConsoleMode.errcheck = ecb
|
||||
k32.SetConsoleMode.errcheck = ecb
|
||||
k32.GetConsoleMode.argtypes = (wintypes.HANDLE, wintypes.LPDWORD)
|
||||
k32.SetConsoleMode.argtypes = (wintypes.HANDLE, wintypes.DWORD)
|
||||
|
||||
def cmode(out, mode=None):
|
||||
h = k32.GetStdHandle(-11 if out else -10)
|
||||
if mode:
|
||||
return k32.SetConsoleMode(h, mode)
|
||||
|
||||
mode = wintypes.DWORD()
|
||||
k32.GetConsoleMode(h, ctypes.byref(mode))
|
||||
return mode.value
|
||||
|
||||
# disable quickedit
|
||||
mode = orig_in = cmode(False)
|
||||
quickedit = 0x40
|
||||
extended = 0x80
|
||||
mask = quickedit + extended
|
||||
if mode & mask != extended:
|
||||
atexit.register(cmode, False, orig_in)
|
||||
cmode(False, mode & ~mask | extended)
|
||||
|
||||
# enable colors in case the os.system("rem") trick ever stops working
|
||||
if VT100:
|
||||
mode = orig_out = cmode(True)
|
||||
if mode & 4 != 4:
|
||||
atexit.register(cmode, True, orig_out)
|
||||
cmode(True, mode | 4)
|
||||
|
||||
|
||||
def run_argparse(argv, formatter):
|
||||
ap = argparse.ArgumentParser(
|
||||
formatter_class=formatter,
|
||||
@@ -396,6 +444,7 @@ def run_argparse(argv, formatter):
|
||||
|
||||
ap2 = ap.add_argument_group('opt-outs')
|
||||
ap2.add_argument("-nw", action="store_true", help="disable writes (benchmark)")
|
||||
ap2.add_argument("--keep-qem", action="store_true", help="do not disable quick-edit-mode on windows")
|
||||
ap2.add_argument("--no-del", action="store_true", help="disable delete operations")
|
||||
ap2.add_argument("--no-mv", action="store_true", help="disable move/rename operations")
|
||||
ap2.add_argument("-nih", action="store_true", help="no info hostname")
|
||||
@@ -550,6 +599,12 @@ def main(argv=None):
|
||||
except AssertionError:
|
||||
al = run_argparse(argv, Dodge11874)
|
||||
|
||||
if WINDOWS and not al.keep_qem:
|
||||
try:
|
||||
disable_quickedit()
|
||||
except:
|
||||
print("\nfailed to disable quick-edit-mode:\n" + min_ex() + "\n")
|
||||
|
||||
nstrs = []
|
||||
anymod = False
|
||||
for ostr in al.v or []:
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
# coding: utf-8
|
||||
|
||||
VERSION = (1, 1, 3)
|
||||
VERSION = (1, 1, 5)
|
||||
CODENAME = "opus"
|
||||
BUILD_DT = (2021, 11, 20)
|
||||
BUILD_DT = (2021, 12, 4)
|
||||
|
||||
S_VERSION = ".".join(map(str, VERSION))
|
||||
S_BUILD_DT = "{0:04d}-{1:02d}-{2:02d}".format(*BUILD_DT)
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
from __future__ import print_function, unicode_literals
|
||||
|
||||
import os
|
||||
from ..util import fsenc, fsdec
|
||||
from ..util import fsenc, fsdec, SYMTIME
|
||||
from . import path
|
||||
|
||||
|
||||
@@ -55,5 +55,8 @@ def unlink(p):
|
||||
return os.unlink(fsenc(p))
|
||||
|
||||
|
||||
def utime(p, times=None):
|
||||
return os.utime(fsenc(p), times)
|
||||
def utime(p, times=None, follow_symlinks=True):
|
||||
if SYMTIME:
|
||||
return os.utime(fsenc(p), times, follow_symlinks=follow_symlinks)
|
||||
else:
|
||||
return os.utime(fsenc(p), times)
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
from __future__ import print_function, unicode_literals
|
||||
|
||||
import os
|
||||
from ..util import fsenc, fsdec
|
||||
from ..util import fsenc, fsdec, SYMTIME
|
||||
|
||||
|
||||
def abspath(p):
|
||||
@@ -13,8 +13,11 @@ def exists(p):
|
||||
return os.path.exists(fsenc(p))
|
||||
|
||||
|
||||
def getmtime(p):
|
||||
return os.path.getmtime(fsenc(p))
|
||||
def getmtime(p, follow_symlinks=True):
|
||||
if not follow_symlinks and SYMTIME:
|
||||
return os.lstat(fsenc(p)).st_mtime
|
||||
else:
|
||||
return os.path.getmtime(fsenc(p))
|
||||
|
||||
|
||||
def getsize(p):
|
||||
|
||||
@@ -60,6 +60,7 @@ class HttpCli(object):
|
||||
self.bufsz = 1024 * 32
|
||||
self.hint = None
|
||||
self.trailing_slash = True
|
||||
self.out_headerlist = []
|
||||
self.out_headers = {
|
||||
"Access-Control-Allow-Origin": "*",
|
||||
"Cache-Control": "no-store; max-age=0",
|
||||
@@ -226,7 +227,7 @@ class HttpCli(object):
|
||||
self.gvol = self.asrv.vfs.aget[self.uname]
|
||||
|
||||
if pwd and "pw" in self.ouparam and pwd != cookies.get("cppwd"):
|
||||
self.out_headers["Set-Cookie"] = self.get_pwd_cookie(pwd)[0]
|
||||
self.out_headerlist.append(("Set-Cookie", self.get_pwd_cookie(pwd)[0]))
|
||||
|
||||
self.ua = self.headers.get("user-agent", "")
|
||||
self.is_rclone = self.ua.startswith("rclone/")
|
||||
@@ -285,7 +286,7 @@ class HttpCli(object):
|
||||
self.out_headers["Cache-Control"] = "max-age=" + n
|
||||
|
||||
def k304(self):
|
||||
k304 = self.cookies.get("k304", "")
|
||||
k304 = self.cookies.get("k304")
|
||||
return k304 == "y" or ("; Trident/" in self.ua and not k304)
|
||||
|
||||
def send_headers(self, length, status=200, mime=None, headers=None):
|
||||
@@ -310,7 +311,7 @@ class HttpCli(object):
|
||||
|
||||
self.out_headers["Content-Type"] = mime
|
||||
|
||||
for k, v in self.out_headers.items():
|
||||
for k, v in list(self.out_headers.items()) + self.out_headerlist:
|
||||
response.append("{}: {}".format(k, v))
|
||||
|
||||
try:
|
||||
@@ -439,6 +440,12 @@ class HttpCli(object):
|
||||
if "k304" in self.uparam:
|
||||
return self.set_k304()
|
||||
|
||||
if "am_js" in self.uparam:
|
||||
return self.set_am_js()
|
||||
|
||||
if "reset" in self.uparam:
|
||||
return self.set_cfg_reset()
|
||||
|
||||
if "h" in self.uparam:
|
||||
return self.tx_mounts()
|
||||
|
||||
@@ -601,8 +608,8 @@ class HttpCli(object):
|
||||
alg = alg or "gz" # def.pk
|
||||
try:
|
||||
# config-forced opts
|
||||
alg, lv = pk.split(",")
|
||||
lv[alg] = int(lv)
|
||||
alg, nlv = pk.split(",")
|
||||
lv[alg] = int(nlv)
|
||||
except:
|
||||
pass
|
||||
|
||||
@@ -1717,7 +1724,19 @@ class HttpCli(object):
|
||||
|
||||
def set_k304(self):
|
||||
ck = gencookie("k304", self.uparam["k304"], 60 * 60 * 24 * 365)
|
||||
self.out_headers["Set-Cookie"] = ck
|
||||
self.out_headerlist.append(("Set-Cookie", ck))
|
||||
self.redirect("", "?h#cc")
|
||||
|
||||
def set_am_js(self):
|
||||
v = "n" if self.uparam["am_js"] == "n" else "y"
|
||||
ck = gencookie("js", v, 60 * 60 * 24 * 365)
|
||||
self.out_headerlist.append(("Set-Cookie", ck))
|
||||
self.reply(b"promoted\n")
|
||||
|
||||
def set_cfg_reset(self):
|
||||
for k in ("k304", "js", "cppwd"):
|
||||
self.out_headerlist.append(("Set-Cookie", gencookie(k, "x", None)))
|
||||
|
||||
self.redirect("", "?h#cc")
|
||||
|
||||
def tx_404(self, is_403=False):
|
||||
@@ -2113,6 +2132,7 @@ class HttpCli(object):
|
||||
"vdir": quotep(self.vpath),
|
||||
"vpnodes": vpnodes,
|
||||
"files": [],
|
||||
"ls0": None,
|
||||
"acct": self.uname,
|
||||
"perms": json.dumps(perms),
|
||||
"taglist": [],
|
||||
@@ -2336,7 +2356,12 @@ class HttpCli(object):
|
||||
|
||||
dirs.sort(key=itemgetter("name"))
|
||||
|
||||
j2a["files"] = dirs + files
|
||||
if self.cookies.get("js") == "y":
|
||||
j2a["ls0"] = {"dirs": dirs, "files": files, "taglist": taglist}
|
||||
j2a["files"] = []
|
||||
else:
|
||||
j2a["files"] = dirs + files
|
||||
|
||||
j2a["logues"] = logues
|
||||
j2a["taglist"] = taglist
|
||||
j2a["txt_ext"] = self.args.textfiles.replace(",", " ")
|
||||
|
||||
@@ -117,7 +117,16 @@ class U2idx(object):
|
||||
if ok:
|
||||
continue
|
||||
|
||||
v, uq = (uq + " ").split(" ", 1)
|
||||
if uq.startswith('"'):
|
||||
v, uq = uq[1:].split('"', 1)
|
||||
while v.endswith("\\"):
|
||||
v2, uq = uq.split('"', 1)
|
||||
v = v[:-1] + '"' + v2
|
||||
uq = uq.strip()
|
||||
else:
|
||||
v, uq = (uq + " ").split(" ", 1)
|
||||
v = v.replace('\\"', '"')
|
||||
|
||||
if is_key:
|
||||
is_key = False
|
||||
|
||||
|
||||
@@ -21,6 +21,7 @@ from .util import (
|
||||
Pebkac,
|
||||
Queue,
|
||||
ProgressPrinter,
|
||||
SYMTIME,
|
||||
fsdec,
|
||||
fsenc,
|
||||
absreal,
|
||||
@@ -1307,7 +1308,7 @@ class Up2k(object):
|
||||
err = "partial upload exists at a different location; please resume uploading here instead:\n"
|
||||
err += "/" + quotep(vsrc) + " "
|
||||
|
||||
dupe = [cj["prel"], cj["name"]]
|
||||
dupe = [cj["prel"], cj["name"], cj["lmod"]]
|
||||
try:
|
||||
self.dupesched[src].append(dupe)
|
||||
except:
|
||||
@@ -1332,7 +1333,7 @@ class Up2k(object):
|
||||
dst = os.path.join(job["ptop"], job["prel"], job["name"])
|
||||
if not self.args.nw:
|
||||
bos.unlink(dst) # TODO ed pls
|
||||
self._symlink(src, dst)
|
||||
self._symlink(src, dst, lmod=cj["lmod"])
|
||||
|
||||
if cur:
|
||||
a = [cj[x] for x in "prel name lmod size addr".split()]
|
||||
@@ -1404,13 +1405,14 @@ class Up2k(object):
|
||||
with ren_open(fname, "wb", fdir=fdir, suffix=suffix) as f:
|
||||
return f["orz"][1]
|
||||
|
||||
def _symlink(self, src, dst, verbose=True):
|
||||
def _symlink(self, src, dst, verbose=True, lmod=None):
|
||||
if verbose:
|
||||
self.log("linking dupe:\n {0}\n {1}".format(src, dst))
|
||||
|
||||
if self.args.nw:
|
||||
return
|
||||
|
||||
linked = False
|
||||
try:
|
||||
if self.args.no_symlink:
|
||||
raise Exception("disabled in config")
|
||||
@@ -1441,10 +1443,18 @@ class Up2k(object):
|
||||
hops = len(ndst[nc:]) - 1
|
||||
lsrc = "../" * hops + "/".join(lsrc)
|
||||
os.symlink(fsenc(lsrc), fsenc(ldst))
|
||||
linked = True
|
||||
except Exception as ex:
|
||||
self.log("cannot symlink; creating copy: " + repr(ex))
|
||||
shutil.copy2(fsenc(src), fsenc(dst))
|
||||
|
||||
if lmod and (not linked or SYMTIME):
|
||||
times = (int(time.time()), int(lmod))
|
||||
if ANYWIN:
|
||||
self.lastmod_q.put([dst, 0, times])
|
||||
else:
|
||||
bos.utime(dst, times, False)
|
||||
|
||||
def handle_chunk(self, ptop, wark, chash):
|
||||
with self.mutex:
|
||||
job = self.registry[ptop].get(wark)
|
||||
@@ -1551,12 +1561,12 @@ class Up2k(object):
|
||||
return
|
||||
|
||||
cur = self.cur.get(ptop)
|
||||
for rd, fn in dupes:
|
||||
for rd, fn, lmod in dupes:
|
||||
d2 = os.path.join(ptop, rd, fn)
|
||||
if os.path.exists(d2):
|
||||
continue
|
||||
|
||||
self._symlink(dst, d2)
|
||||
self._symlink(dst, d2, lmod=lmod)
|
||||
if cur:
|
||||
self.db_rm(cur, rd, fn)
|
||||
self.db_add(cur, wark, rd, fn, *a[-4:])
|
||||
@@ -1773,8 +1783,9 @@ class Up2k(object):
|
||||
dlabs = absreal(sabs)
|
||||
m = "moving symlink from [{}] to [{}], target [{}]"
|
||||
self.log(m.format(sabs, dabs, dlabs))
|
||||
os.unlink(sabs)
|
||||
self._symlink(dlabs, dabs, False)
|
||||
mt = bos.path.getmtime(sabs, False)
|
||||
bos.unlink(sabs)
|
||||
self._symlink(dlabs, dabs, False, lmod=mt)
|
||||
|
||||
# folders are too scary, schedule rescan of both vols
|
||||
self.need_rescan[svn.vpath] = 1
|
||||
@@ -1904,25 +1915,30 @@ class Up2k(object):
|
||||
slabs = list(sorted(links.keys()))[0]
|
||||
ptop, rem = links.pop(slabs)
|
||||
self.log("linkswap [{}] and [{}]".format(sabs, slabs))
|
||||
mt = bos.path.getmtime(slabs, False)
|
||||
bos.unlink(slabs)
|
||||
bos.rename(sabs, slabs)
|
||||
bos.utime(slabs, (int(time.time()), int(mt)), False)
|
||||
self._symlink(slabs, sabs, False)
|
||||
full[slabs] = [ptop, rem]
|
||||
sabs = slabs
|
||||
|
||||
if not dabs:
|
||||
dabs = list(sorted(full.keys()))[0]
|
||||
|
||||
for alink in links.keys():
|
||||
lmod = None
|
||||
try:
|
||||
if alink != sabs and absreal(alink) != sabs:
|
||||
continue
|
||||
|
||||
self.log("relinking [{}] to [{}]".format(alink, dabs))
|
||||
lmod = bos.path.getmtime(alink, False)
|
||||
bos.unlink(alink)
|
||||
except:
|
||||
pass
|
||||
|
||||
self._symlink(dabs, alink, False)
|
||||
self._symlink(dabs, alink, False, lmod=lmod)
|
||||
|
||||
return len(full) + len(links)
|
||||
|
||||
@@ -2028,7 +2044,7 @@ class Up2k(object):
|
||||
for path, sz, times in ready:
|
||||
self.log("lmod: setting times {} on {}".format(times, path))
|
||||
try:
|
||||
bos.utime(path, times)
|
||||
bos.utime(path, times, False)
|
||||
except:
|
||||
self.log("lmod: failed to utime ({}, {})".format(path, times))
|
||||
|
||||
|
||||
@@ -67,8 +67,9 @@ if WINDOWS and PY2:
|
||||
FS_ENCODING = "utf-8"
|
||||
|
||||
|
||||
HTTP_TS_FMT = "%a, %d %b %Y %H:%M:%S GMT"
|
||||
SYMTIME = sys.version_info >= (3, 6) and os.supports_follow_symlinks
|
||||
|
||||
HTTP_TS_FMT = "%a, %d %b %Y %H:%M:%S GMT"
|
||||
|
||||
HTTPCODE = {
|
||||
200: "OK",
|
||||
|
||||
@@ -79,6 +79,27 @@ a, #files tbody div a:last-child {
|
||||
color: #999;
|
||||
font-weight: normal;
|
||||
}
|
||||
.s0:after,
|
||||
.s1:after {
|
||||
content: '⌄';
|
||||
margin-left: -.1em;
|
||||
}
|
||||
.s0r:after,
|
||||
.s1r:after {
|
||||
content: '⌃';
|
||||
margin-left: -.1em;
|
||||
}
|
||||
.s0:after,
|
||||
.s0r:after {
|
||||
color: #fb0;
|
||||
}
|
||||
.s1:after,
|
||||
.s1r:after {
|
||||
color: #d09;
|
||||
}
|
||||
#files thead th:after {
|
||||
margin-right: -.7em;
|
||||
}
|
||||
#files tbody tr:hover td {
|
||||
background: #1c1c1c;
|
||||
}
|
||||
@@ -240,6 +261,8 @@ html.light #ggrid>a[tt].sel {
|
||||
#files tbody tr.sel:hover td,
|
||||
#files tbody tr.sel:focus td,
|
||||
#ggrid>a.sel:hover,
|
||||
#ggrid>a.sel:focus,
|
||||
html.light #ggrid>a.sel:focus,
|
||||
html.light #ggrid>a.sel:hover {
|
||||
color: #fff;
|
||||
background: #d39;
|
||||
@@ -295,6 +318,8 @@ html.light #ggrid>a.sel {
|
||||
width: 100%;
|
||||
z-index: 3;
|
||||
touch-action: none;
|
||||
}
|
||||
#widget.anim {
|
||||
transition: bottom 0.15s;
|
||||
}
|
||||
#widget.open {
|
||||
@@ -948,6 +973,12 @@ html.light .ghead {
|
||||
#ggrid>a.dir:before {
|
||||
content: '📂';
|
||||
}
|
||||
#ggrid>a.au:before {
|
||||
content: '💾';
|
||||
}
|
||||
html.np_open #ggrid>a.au:before {
|
||||
content: '▶';
|
||||
}
|
||||
#ggrid>a:before {
|
||||
display: block;
|
||||
position: absolute;
|
||||
@@ -957,6 +988,12 @@ html.light .ghead {
|
||||
background: linear-gradient(135deg,rgba(255,255,255,0) 50%,rgba(255,255,255,0.2));
|
||||
border-radius: .3em;
|
||||
font-size: 2em;
|
||||
transition: font-size .15s, margin .15s;
|
||||
}
|
||||
#ggrid>a:focus:before,
|
||||
#ggrid>a:hover:before {
|
||||
font-size: 2.5em;
|
||||
margin: -.2em;
|
||||
}
|
||||
#op_unpost {
|
||||
padding: 1em;
|
||||
@@ -1026,7 +1063,6 @@ html.light #rui {
|
||||
font-size: 1.5em;
|
||||
}
|
||||
#doc {
|
||||
background: none;
|
||||
overflow: visible;
|
||||
margin: -1em 0 .5em 0;
|
||||
padding: 1em 0 1em 0;
|
||||
@@ -1114,6 +1150,7 @@ a.btn,
|
||||
|
||||
|
||||
html,
|
||||
#doc,
|
||||
#rui,
|
||||
#files td,
|
||||
#files thead th,
|
||||
@@ -1161,6 +1198,7 @@ html,
|
||||
#ggrid>a[tt] {
|
||||
background: linear-gradient(135deg, #2c2c2c 95%, #444 95%);
|
||||
}
|
||||
#ggrid>a:focus,
|
||||
#ggrid>a:hover {
|
||||
background: #383838;
|
||||
border-color: #555;
|
||||
@@ -1174,6 +1212,7 @@ html.light #ggrid>a {
|
||||
html.light #ggrid>a[tt] {
|
||||
background: linear-gradient(135deg, #f7f7f7 95%, #ccc 95%);
|
||||
}
|
||||
html.light #ggrid>a:focus,
|
||||
html.light #ggrid>a:hover {
|
||||
background: #fff;
|
||||
border-color: #ccc;
|
||||
@@ -1193,6 +1232,7 @@ html.light {
|
||||
html.light #ops,
|
||||
html.light .opbox,
|
||||
html.light #path,
|
||||
html.light #doc,
|
||||
html.light #srch_form,
|
||||
html.light .ghead,
|
||||
html.light #u2etas {
|
||||
@@ -1270,6 +1310,14 @@ html.light #ops a,
|
||||
html.light #files tbody div a:last-child {
|
||||
color: #06a;
|
||||
}
|
||||
html.light .s0:after,
|
||||
html.light .s0r:after {
|
||||
color: #059;
|
||||
}
|
||||
html.light .s1:after,
|
||||
html.light .s1r:after {
|
||||
color: #f5d;
|
||||
}
|
||||
html.light #files thead th {
|
||||
background: #eaeaea;
|
||||
border-color: #ccc;
|
||||
@@ -1833,12 +1881,13 @@ html.light #u2err.err {
|
||||
#u2tabw {
|
||||
min-height: 0;
|
||||
transition: min-height .2s;
|
||||
margin: 3em auto;
|
||||
margin: 3em 0;
|
||||
}
|
||||
#u2tab {
|
||||
border-collapse: collapse;
|
||||
width: calc(100% - 2em);
|
||||
max-width: 100em;
|
||||
margin: 0 auto;
|
||||
}
|
||||
#op_up2k.srch #u2tabf {
|
||||
max-width: none;
|
||||
|
||||
@@ -143,7 +143,8 @@
|
||||
have_zip = {{ have_zip|tojson }},
|
||||
txt_ext = "{{ txt_ext }}",
|
||||
{% if no_prism %}no_prism = 1,{% endif %}
|
||||
readme = {{ readme|tojson }};
|
||||
readme = {{ readme|tojson }},
|
||||
ls0 = {{ ls0|tojson }};
|
||||
|
||||
document.documentElement.setAttribute("class", localStorage.lightmode == 1 ? "light" : "dark");
|
||||
</script>
|
||||
|
||||
@@ -9,7 +9,7 @@ function dbg(msg) {
|
||||
ebi('ops').innerHTML = (
|
||||
'<a href="#" data-dest="" tt="close submenu">--</a>\n' +
|
||||
(have_up2k_idx ? (
|
||||
'<a href="#" data-perm="read" data-dest="search" tt="search for files by attributes, path/name, music tags, or any combination of those$N$N<code>foo bar</code> = must contain both foo and bar,$N<code>foo -bar</code> = must contain foo but not bar,$N<code>^yana .opus$</code> = must start with yana and have the opus extension">🔎</a>\n' +
|
||||
'<a href="#" data-perm="read" data-dest="search" tt="search for files by attributes, path/name, music tags, or any combination of those$N$N<code>foo bar</code> = must contain both foo and bar,$N<code>foo -bar</code> = must contain foo but not bar,$N<code>^yana .opus$</code> = start with yana and be an opus file$N<code>"try unite"</code> = contain exactly «try unite»">🔎</a>\n' +
|
||||
(have_del && have_unpost ? '<a href="#" data-dest="unpost" tt="unpost: delete your recent uploads">🧯</a>\n' : '') +
|
||||
'<a href="#" data-dest="up2k" tt="up2k: upload files (if you have write-access) or toggle into the search-mode to see if they exist somewhere on the server">🚀</a>\n'
|
||||
) : (
|
||||
@@ -157,6 +157,7 @@ ebi('op_cfg').innerHTML = (
|
||||
' <a id="thumbs" class="tgl btn" href="#" tt="in icon view, toggle icons or thumbnails$NHotkey: T">🖼️ thumbs</a>\n' +
|
||||
' <a id="dotfiles" class="tgl btn" href="#" tt="show hidden files (if server permits)">dotfiles</a>\n' +
|
||||
' <a id="ireadme" class="tgl btn" href="#" tt="show README.md in folder listings">📜 readme</a>\n' +
|
||||
' <a id="spafiles" class="tgl btn" href="#" tt="speedboost when not using the navpane;$Nturn it off if things arent loading somehow">spa</a>\n' +
|
||||
' </div>\n' +
|
||||
'</div>\n' +
|
||||
(have_zip ? (
|
||||
@@ -306,7 +307,9 @@ function set_files_html(html) {
|
||||
|
||||
|
||||
var ACtx = window.AudioContext || window.webkitAudioContext,
|
||||
actx = ACtx && new ACtx();
|
||||
actx = ACtx && new ACtx(),
|
||||
hash0 = location.hash,
|
||||
mp;
|
||||
|
||||
|
||||
var mpl = (function () {
|
||||
@@ -539,6 +542,7 @@ function MPlayer() {
|
||||
r.tracks[tid] = url;
|
||||
tds[0].innerHTML = '<a id="a' + tid + '" href="#a' + tid + '" class="play">play</a></td>';
|
||||
ebi('a' + tid).onclick = ev_play;
|
||||
clmod(trs[a], 'au', 1);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -651,14 +655,11 @@ function MPlayer() {
|
||||
};
|
||||
}
|
||||
|
||||
addcrc();
|
||||
var mp = new MPlayer();
|
||||
makeSortable(ebi('files'), mp.read_order.bind(mp));
|
||||
|
||||
|
||||
function ft2dict(tr) {
|
||||
var th = ebi('files').tHead.rows[0].cells,
|
||||
rv = [],
|
||||
rh = [],
|
||||
ra = [],
|
||||
rt = {};
|
||||
|
||||
@@ -670,10 +671,11 @@ function ft2dict(tr) {
|
||||
if (!tv)
|
||||
continue;
|
||||
|
||||
(vis ? rv : ra).push(tk);
|
||||
(vis ? rv : rh).push(tk);
|
||||
ra.push(tk);
|
||||
rt[tk] = tv;
|
||||
}
|
||||
return [rt, rv, ra];
|
||||
return [rt, rv, rh, ra];
|
||||
}
|
||||
|
||||
|
||||
@@ -693,24 +695,19 @@ var widget = (function () {
|
||||
touchmode = false,
|
||||
was_paused = true;
|
||||
|
||||
r.is_open = false;
|
||||
|
||||
r.open = function () {
|
||||
if (r.is_open)
|
||||
return false;
|
||||
|
||||
clmod(document.documentElement, 'np_open', 1);
|
||||
widget.className = 'open';
|
||||
r.is_open = true;
|
||||
return true;
|
||||
return r.set(true);
|
||||
};
|
||||
r.close = function () {
|
||||
if (!r.is_open)
|
||||
return r.set(false);
|
||||
};
|
||||
r.set = function (is_open) {
|
||||
if (r.is_open == is_open)
|
||||
return false;
|
||||
|
||||
clmod(document.documentElement, 'np_open');
|
||||
widget.className = '';
|
||||
r.is_open = false;
|
||||
clmod(document.documentElement, 'np_open', is_open);
|
||||
widget.className = is_open ? 'open' : '';
|
||||
bcfg_set('au_open', r.is_open = is_open);
|
||||
return true;
|
||||
};
|
||||
r.toggle = function (e) {
|
||||
@@ -757,6 +754,10 @@ var widget = (function () {
|
||||
document.body.removeChild(o);
|
||||
}, 500);
|
||||
};
|
||||
r.set(sread('au_open') == 1);
|
||||
setTimeout(function () {
|
||||
clmod(ebi('widget'), 'anim', 1);
|
||||
}, 10);
|
||||
return r;
|
||||
})();
|
||||
|
||||
@@ -808,7 +809,7 @@ var pbar = (function () {
|
||||
|
||||
bctx.clearRect(0, 0, bc.w, bc.h);
|
||||
|
||||
if (!mp.au)
|
||||
if (!mp || !mp.au)
|
||||
return;
|
||||
|
||||
var sm = bc.w * 1.0 / mp.au.duration,
|
||||
@@ -835,7 +836,7 @@ var pbar = (function () {
|
||||
|
||||
pctx.clearRect(0, 0, pc.w, pc.h);
|
||||
|
||||
if (!mp.au || isNaN(adur = mp.au.duration) || isNaN(apos = mp.au.currentTime) || apos < 0 || adur < apos)
|
||||
if (!mp || !mp.au || isNaN(adur = mp.au.duration) || isNaN(apos = mp.au.currentTime) || apos < 0 || adur < apos)
|
||||
return; // not-init || unsupp-codec
|
||||
|
||||
var sm = bc.w * 1.0 / adur;
|
||||
@@ -906,6 +907,9 @@ var vbar = (function () {
|
||||
}
|
||||
|
||||
r.draw = function () {
|
||||
if (!mp)
|
||||
return;
|
||||
|
||||
var gh = h + '' + light;
|
||||
if (gradh != gh) {
|
||||
gradh = gh;
|
||||
@@ -1132,7 +1136,6 @@ var mpui = (function () {
|
||||
if (mp.au.paused)
|
||||
timer.rm(updater_impl);
|
||||
}
|
||||
r.progress_updater();
|
||||
return r;
|
||||
})();
|
||||
|
||||
@@ -1343,7 +1346,7 @@ var audio_eq = (function () {
|
||||
}
|
||||
|
||||
var html = ['<table><tr><td rowspan="4">',
|
||||
'<a id="au_eq" class="tgl btn" href="#" tt="enables the equalizer and gain control;$Nboost 0 = unmodified 100% volume">enable</a></td>'],
|
||||
'<a id="au_eq" class="tgl btn" href="#" tt="enables the equalizer and gain control;$Nboost 0 = unmodified 100% volume$N$Nenabling the equalizer makes gapless albums fully gapless, so leave it on with all the values at zero if you care about that">enable</a></td>'],
|
||||
h2 = [], h3 = [], h4 = [];
|
||||
|
||||
var vs = [];
|
||||
@@ -1492,7 +1495,8 @@ function play(tid, is_ev, seek, call_depth) {
|
||||
}
|
||||
|
||||
mpui.progress_updater();
|
||||
pbar.drawbuf();
|
||||
pbar.onresize();
|
||||
vbar.onresize();
|
||||
mpl.announce();
|
||||
return true;
|
||||
}
|
||||
@@ -1567,7 +1571,8 @@ function autoplay_blocked(seek) {
|
||||
|
||||
|
||||
function eval_hash() {
|
||||
var v = location.hash;
|
||||
var v = hash0;
|
||||
hash0 = null;
|
||||
if (!v)
|
||||
return;
|
||||
|
||||
@@ -2559,9 +2564,9 @@ var thegrid = (function () {
|
||||
'<a href="#" class="btn" z="1.2" tt="Hotkey: shift-D">+</a></span> <span>chop: ' +
|
||||
'<a href="#" class="btn" l="-1" tt="truncate filenames more (show less)">–</a> ' +
|
||||
'<a href="#" class="btn" l="1" tt="truncate filenames less (show more)">+</a></span> <span>sort by: ' +
|
||||
'<a href="#" s="href">name</a>, ' +
|
||||
'<a href="#" s="sz">size</a>, ' +
|
||||
'<a href="#" s="ts">date</a>, ' +
|
||||
'<a href="#" s="href">name</a> ' +
|
||||
'<a href="#" s="sz">size</a> ' +
|
||||
'<a href="#" s="ts">date</a> ' +
|
||||
'<a href="#" s="ext">type</a>' +
|
||||
'</span></div>' +
|
||||
'<div id="ggrid"></div>'
|
||||
@@ -2675,14 +2680,12 @@ var thegrid = (function () {
|
||||
href = noq_href(this),
|
||||
aplay = ebi('a' + oth.getAttribute('id')),
|
||||
is_img = /\.(gif|jpe?g|png|webp|webm|mp4)(\?|$)/i.test(href),
|
||||
in_tree = null,
|
||||
is_dir = href.endsWith('/'),
|
||||
in_tree = is_dir && treectl.find(oth.textContent.slice(0, -1)),
|
||||
have_sel = QS('#files tr.sel'),
|
||||
td = oth.closest('td').nextSibling,
|
||||
tr = td.parentNode;
|
||||
|
||||
if (href.endsWith('/'))
|
||||
in_tree = treectl.find(oth.textContent.slice(0, -1));
|
||||
|
||||
if (r.sel && !dbl) {
|
||||
td.click();
|
||||
clmod(this, 'sel', clgot(tr, 'sel'));
|
||||
@@ -2693,6 +2696,9 @@ var thegrid = (function () {
|
||||
else if (in_tree && !have_sel)
|
||||
in_tree.click();
|
||||
|
||||
else if (is_dir && !have_sel && treectl.spa)
|
||||
treectl.reqls(href, true, true);
|
||||
|
||||
else if (!is_img && have_sel)
|
||||
window.open(href, '_blank');
|
||||
|
||||
@@ -2867,10 +2873,6 @@ var thegrid = (function () {
|
||||
import_js('/.cpr/baguettebox.js', r.bagit);
|
||||
}, 1);
|
||||
|
||||
if (r.en) {
|
||||
loadgrid();
|
||||
}
|
||||
|
||||
return r;
|
||||
})();
|
||||
|
||||
@@ -3234,7 +3236,34 @@ document.onkeydown = function (e) {
|
||||
for (var b = 1; b < sconf[a].length; b++) {
|
||||
var k = sconf[a][b][0],
|
||||
chk = 'srch_' + k + 'c',
|
||||
tvs = ebi('srch_' + k + 'v').value.split(/ +/g);
|
||||
vs = ebi('srch_' + k + 'v').value,
|
||||
tvs = [];
|
||||
|
||||
if (k == 'name')
|
||||
console.log('a');
|
||||
while (vs) {
|
||||
vs = vs.trim();
|
||||
if (!vs)
|
||||
break;
|
||||
|
||||
var v = '';
|
||||
if (vs.startsWith('"')) {
|
||||
var vp = vs.slice(1).split(/"(.*)/);
|
||||
v = vp[0];
|
||||
vs = vp[1] || '';
|
||||
while (v.endsWith('\\')) {
|
||||
vp = vs.split(/"(.*)/);
|
||||
v = v.slice(0, -1) + '"' + vp[0];
|
||||
vs = vp[1] || '';
|
||||
}
|
||||
}
|
||||
else {
|
||||
var vp = vs.split(/ +(.*)/);
|
||||
v = vp[0].replace(/\\"/g, '"');
|
||||
vs = vp[1] || '';
|
||||
}
|
||||
tvs.push(v);
|
||||
}
|
||||
|
||||
if (!ebi(chk).checked)
|
||||
continue;
|
||||
@@ -3277,6 +3306,10 @@ document.onkeydown = function (e) {
|
||||
tv += '*';
|
||||
}
|
||||
|
||||
if (tv.indexOf(' ') + 1) {
|
||||
tv = '"' + tv + '"';
|
||||
}
|
||||
|
||||
q += k + not + 'like ' + tv;
|
||||
}
|
||||
}
|
||||
@@ -3334,7 +3367,7 @@ document.onkeydown = function (e) {
|
||||
ts = parseInt(r.ts),
|
||||
sz = esc(r.sz + ''),
|
||||
rp = esc(uricom_dec(r.rp + '')[0]),
|
||||
ext = rp.lastIndexOf('.') > 0 ? rp.split('.').pop() : '%',
|
||||
ext = rp.lastIndexOf('.') > 0 ? rp.split('.').pop().split('?')[0] : '%',
|
||||
links = linksplit(r.rp + '');
|
||||
|
||||
if (ext.length > 8)
|
||||
@@ -3403,6 +3436,7 @@ var treectl = (function () {
|
||||
mentered = null,
|
||||
treesz = clamp(icfg_get('treesz', 16), 10, 50);
|
||||
|
||||
bcfg_bind(r, 'spa', 'spafiles', true);
|
||||
bcfg_bind(r, 'ireadme', 'ireadme', true);
|
||||
bcfg_bind(r, 'dyn', 'dyntree', true, onresize);
|
||||
bcfg_bind(r, 'dots', 'dotfiles', false, function (v) {
|
||||
@@ -3583,6 +3617,7 @@ var treectl = (function () {
|
||||
if (!QS(q))
|
||||
break;
|
||||
}
|
||||
nq = Math.max(nq, get_evpath().split('/').length - 2);
|
||||
var iw = (treesz + Math.max(0, nq)),
|
||||
w = iw + 'em',
|
||||
w2 = (iw + 2) + 'em';
|
||||
@@ -3605,7 +3640,7 @@ var treectl = (function () {
|
||||
|
||||
r.goto = function (url, push) {
|
||||
get_tree("", url, true);
|
||||
reqls(url, push, true);
|
||||
r.reqls(url, push, true);
|
||||
};
|
||||
|
||||
function get_tree(top, dst, rst) {
|
||||
@@ -3774,12 +3809,12 @@ var treectl = (function () {
|
||||
treegrow.call(this.previousSibling, e);
|
||||
return;
|
||||
}
|
||||
reqls(this.getAttribute('href'), true);
|
||||
r.reqls(this.getAttribute('href'), true);
|
||||
r.dir_cb = tree_scrollto;
|
||||
thegrid.setvis(true);
|
||||
}
|
||||
|
||||
function reqls(url, hpush, no_tree) {
|
||||
r.reqls = function (url, hpush, no_tree) {
|
||||
var xhr = new XMLHttpRequest();
|
||||
xhr.top = url;
|
||||
xhr.hpush = hpush;
|
||||
@@ -3834,8 +3869,34 @@ var treectl = (function () {
|
||||
|
||||
ebi('srv_info').innerHTML = '<span>' + res.srvinf + '</span>';
|
||||
|
||||
var top = this.top,
|
||||
nodes = res.dirs.concat(res.files),
|
||||
if (this.hpush && !showfile.active())
|
||||
hist_push(this.top);
|
||||
|
||||
r.gentab(this.top, res);
|
||||
|
||||
acct = res.acct;
|
||||
apply_perms(res.perms);
|
||||
despin('#files');
|
||||
despin('#gfiles');
|
||||
|
||||
ebi('pro').innerHTML = res.logues ? res.logues[0] || "" : "";
|
||||
ebi('epi').innerHTML = res.logues ? res.logues[1] || "" : "";
|
||||
|
||||
clmod(ebi('epi'), 'mdo');
|
||||
if (res.readme)
|
||||
show_readme(res.readme);
|
||||
|
||||
wintitle();
|
||||
var fun = r.ls_cb;
|
||||
if (fun) {
|
||||
r.ls_cb = null;
|
||||
fun();
|
||||
}
|
||||
eval_hash();
|
||||
}
|
||||
|
||||
r.gentab = function (top, res) {
|
||||
var nodes = res.dirs.concat(res.files),
|
||||
html = mk_files_header(res.taglist),
|
||||
seen = {};
|
||||
|
||||
@@ -3851,7 +3912,7 @@ var treectl = (function () {
|
||||
id = 'f-' + ('00000000' + crc32(fname)).slice(-8),
|
||||
lang = showfile.getlang(fname);
|
||||
|
||||
while (seen[id])
|
||||
while (seen[id]) // ejyefs ev69gg y9j8sg .opus
|
||||
id += 'a';
|
||||
seen[id] = 1;
|
||||
|
||||
@@ -3883,37 +3944,33 @@ var treectl = (function () {
|
||||
html = html.join('\n');
|
||||
set_files_html(html);
|
||||
|
||||
if (this.hpush && !showfile.active())
|
||||
hist_push(this.top);
|
||||
|
||||
acct = res.acct;
|
||||
apply_perms(res.perms);
|
||||
despin('#files');
|
||||
despin('#gfiles');
|
||||
|
||||
ebi('pro').innerHTML = res.logues ? res.logues[0] || "" : "";
|
||||
ebi('epi').innerHTML = res.logues ? res.logues[1] || "" : "";
|
||||
|
||||
clmod(ebi('epi'), 'mdo');
|
||||
if (res.readme)
|
||||
show_readme(res.readme);
|
||||
|
||||
document.title = '⇆🎉 ' + uricom_dec(document.location.pathname.slice(1, -1))[0];
|
||||
|
||||
filecols.set_style();
|
||||
showfile.mktree();
|
||||
mukey.render();
|
||||
reload_tree();
|
||||
reload_browser();
|
||||
tree_scrollto();
|
||||
|
||||
var fun = r.ls_cb;
|
||||
if (fun) {
|
||||
r.ls_cb = null;
|
||||
fun();
|
||||
}
|
||||
}
|
||||
|
||||
r.hydrate = function () {
|
||||
if (ls0 === null) {
|
||||
var xhr = new XMLHttpRequest();
|
||||
xhr.open('GET', '/?am_js', true);
|
||||
xhr.send();
|
||||
|
||||
return r.reqls(get_evpath(), false, true);
|
||||
}
|
||||
|
||||
r.gentab(get_evpath(), ls0);
|
||||
reload_browser();
|
||||
pbar.onresize();
|
||||
vbar.onresize();
|
||||
mukey.render();
|
||||
showfile.addlinks();
|
||||
thegrid.setdirty();
|
||||
setTimeout(eval_hash, 1);
|
||||
};
|
||||
|
||||
function parsetree(res, top) {
|
||||
var ret = '';
|
||||
for (var a = 0; a < res.a.length; a++) {
|
||||
@@ -4374,27 +4431,6 @@ var mukey = (function () {
|
||||
})();
|
||||
|
||||
|
||||
function addcrc() {
|
||||
var links = QSA(
|
||||
'#files>tbody>tr>td:first-child+td>' + (
|
||||
ebi('unsearch') ? 'div>a:last-child' : 'a'));
|
||||
|
||||
var seen = {}; // ejyefs ev69gg y9j8sg .opus
|
||||
for (var a = 0, aa = links.length; a < aa; a++) {
|
||||
var id = links[a].getAttribute('id');
|
||||
if (!id) {
|
||||
var crc = crc32(links[a].textContent || links[a].innerText);
|
||||
id = 'f-' + ('00000000' + crc).slice(-8);
|
||||
while (seen[id])
|
||||
id += 'a';
|
||||
|
||||
links[a].setAttribute('id', id);
|
||||
}
|
||||
seen[id] = 1;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
var light;
|
||||
(function () {
|
||||
function freshen() {
|
||||
@@ -4948,15 +4984,34 @@ function goto_unpost(e) {
|
||||
}
|
||||
|
||||
|
||||
function wintitle(txt) {
|
||||
document.title = (txt ? txt : '') + get_vpath().slice(1, -1).split('/').pop();
|
||||
}
|
||||
|
||||
|
||||
ebi('path').onclick = function (e) {
|
||||
var a = e.target.closest('a[href]');
|
||||
if (!treectl.spa || !a || !(a = a.getAttribute('href') + '') || !a.endsWith('/'))
|
||||
return;
|
||||
|
||||
treectl.reqls(a, true, true);
|
||||
return ev(e);
|
||||
};
|
||||
|
||||
|
||||
ebi('files').onclick = ebi('docul').onclick = function (e) {
|
||||
var tgt = e.target.closest('a[id]');
|
||||
if (tgt && tgt.getAttribute('id').indexOf('f-') === 0 && tgt.textContent.endsWith('/')) {
|
||||
var el = treectl.find(tgt.textContent.slice(0, -1));
|
||||
if (!el)
|
||||
return;
|
||||
|
||||
el.click();
|
||||
return ev(e);
|
||||
if (el) {
|
||||
el.click();
|
||||
return ev(e);
|
||||
}
|
||||
if (treectl.spa) {
|
||||
treectl.reqls(tgt.getAttribute('href'), true, true);
|
||||
return ev(e);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
tgt = e.target.closest('a[hl]');
|
||||
@@ -4964,7 +5019,7 @@ ebi('files').onclick = ebi('docul').onclick = function (e) {
|
||||
showfile.show(noq_href(ebi(tgt.getAttribute('hl'))), tgt.getAttribute('lang'));
|
||||
return ev(e);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
function reload_mp() {
|
||||
@@ -4972,25 +5027,25 @@ function reload_mp() {
|
||||
audio_eq.stop();
|
||||
mp.au.pause();
|
||||
mp.au = null;
|
||||
mpl.unbuffer();
|
||||
}
|
||||
mpl.stop();
|
||||
widget.close();
|
||||
var plays = QSA('tr>td:first-child>a.play');
|
||||
for (var a = plays.length - 1; a >= 0; a--)
|
||||
plays[a].parentNode.innerHTML = '-';
|
||||
|
||||
mpl.unbuffer();
|
||||
mp = new MPlayer();
|
||||
audio_eq.acst = {};
|
||||
setTimeout(pbar.onresize, 1);
|
||||
}
|
||||
|
||||
|
||||
function reload_browser(not_mp) {
|
||||
function reload_browser() {
|
||||
filecols.set_style();
|
||||
|
||||
var parts = get_evpath().split('/'),
|
||||
rm = QSA('#path>a+a+a');
|
||||
rm = QSA('#path>a+a+a'),
|
||||
ftab = ebi('files');
|
||||
|
||||
for (a = rm.length - 1; a >= 0; a--)
|
||||
rm[a].parentNode.removeChild(rm[a]);
|
||||
@@ -5012,11 +5067,9 @@ function reload_browser(not_mp) {
|
||||
oo[a].textContent = hsz;
|
||||
}
|
||||
|
||||
if (!not_mp) {
|
||||
addcrc();
|
||||
reload_mp();
|
||||
makeSortable(ebi('files'), mp.read_order.bind(mp));
|
||||
}
|
||||
reload_mp();
|
||||
try { showsort(ftab); } catch (ex) { }
|
||||
makeSortable(ftab, mp.read_order.bind(mp));
|
||||
|
||||
for (var a = 0; a < 2; a++)
|
||||
clmod(ebi(a ? 'pro' : 'epi'), 'hidden', ebi('unsearch'));
|
||||
@@ -5027,7 +5080,4 @@ function reload_browser(not_mp) {
|
||||
thegrid.setdirty();
|
||||
msel.render();
|
||||
}
|
||||
reload_browser(true);
|
||||
showfile.addlinks();
|
||||
mukey.render();
|
||||
setTimeout(eval_hash, 1);
|
||||
treectl.hydrate();
|
||||
|
||||
@@ -81,9 +81,10 @@ table {
|
||||
text-align: right;
|
||||
}
|
||||
blockquote {
|
||||
margin: 0 0 0 .6em;
|
||||
padding: .7em 1em;
|
||||
margin: 0 0 1.6em .6em;
|
||||
padding: .7em 1em 0 1em;
|
||||
border-left: .3em solid rgba(128,128,128,0.5);
|
||||
border-radius: 0 0 0 .25em;
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -75,11 +75,13 @@
|
||||
<h1 id="cc">client config:</h1>
|
||||
<ul>
|
||||
{% if k304 %}
|
||||
<li><a href="/?k304=n" class="r">disable k304</a> (currently enabled)
|
||||
<li><a href="/?k304=n">disable k304</a> (currently enabled)
|
||||
{%- else %}
|
||||
<li><a href="/?k304=y">enable k304</a> (currently disabled)
|
||||
<li><a href="/?k304=y" class="r">enable k304</a> (currently disabled)
|
||||
{% endif %}
|
||||
<blockquote>enabling this will disconnect your client on every HTTP 304, which can prevent some buggy browsers/proxies from getting stuck (suddenly not being able to load pages), <em>but</em> it will also make things slower in general</blockquote></li>
|
||||
|
||||
<li><a href="/?reset" class="r" onclick="localStorage.clear();return true">reset client settings</a></li>
|
||||
</ul>
|
||||
|
||||
<h1>login for more:</h1>
|
||||
|
||||
@@ -128,6 +128,7 @@ html {
|
||||
}
|
||||
#tth.act {
|
||||
display: block;
|
||||
z-index: 9001;
|
||||
}
|
||||
#tt.b {
|
||||
padding: 0 2em;
|
||||
|
||||
@@ -525,13 +525,15 @@ function Donut(uc, st) {
|
||||
}
|
||||
|
||||
r.on = function (ya) {
|
||||
r.fc = 99;
|
||||
r.fc = r.tc = 99;
|
||||
r.eta = null;
|
||||
r.base = pos();
|
||||
optab.innerHTML = ya ? svg() : optab.getAttribute('ico');
|
||||
el = QS('#ops a .donut');
|
||||
if (!ya)
|
||||
if (!ya) {
|
||||
favico.upd();
|
||||
wintitle();
|
||||
}
|
||||
};
|
||||
r.do = function () {
|
||||
if (!el)
|
||||
@@ -541,6 +543,11 @@ function Donut(uc, st) {
|
||||
v = pos() - r.base,
|
||||
ofs = el.style.strokeDashoffset = o - o * v / t;
|
||||
|
||||
if (++r.tc >= 10) {
|
||||
wintitle(f2f(v * 100 / t, 1) + '%, ' + r.eta + 's, ', true);
|
||||
r.tc = 0;
|
||||
}
|
||||
|
||||
if (favico.txt) {
|
||||
if (++r.fc < 10 && r.eta && r.eta > 99)
|
||||
return;
|
||||
@@ -728,7 +735,6 @@ function up2k_init(subtle) {
|
||||
if (++nenters <= 0)
|
||||
nenters = 1;
|
||||
|
||||
//console.log(nenters, Date.now(), 'enter', this, e.target);
|
||||
if (onover.bind(this)(e))
|
||||
return true;
|
||||
|
||||
@@ -750,12 +756,19 @@ function up2k_init(subtle) {
|
||||
ebi('up_dz').setAttribute('err', mup || '');
|
||||
ebi('srch_dz').setAttribute('err', msr || '');
|
||||
}
|
||||
function onoverb(e) {
|
||||
// zones are alive; disable cuo2duo branch
|
||||
document.body.ondragover = document.body.ondrop = null;
|
||||
return onover.bind(this)(e);
|
||||
}
|
||||
function onover(e) {
|
||||
try {
|
||||
var ok = false, dt = e.dataTransfer.types;
|
||||
for (var a = 0; a < dt.length; a++)
|
||||
if (dt[a] == 'Files')
|
||||
ok = true;
|
||||
else if (dt[a] == 'text/uri-list')
|
||||
return true;
|
||||
|
||||
if (!ok)
|
||||
return true;
|
||||
@@ -781,17 +794,20 @@ function up2k_init(subtle) {
|
||||
clmod(ebi('drops'), 'vis');
|
||||
clmod(ebi('up_dz'), 'hl');
|
||||
clmod(ebi('srch_dz'), 'hl');
|
||||
// cuo2duo:
|
||||
document.body.ondragover = onover;
|
||||
document.body.ondrop = gotfile;
|
||||
}
|
||||
|
||||
//console.log(nenters, Date.now(), 'leave', this, e && e.target);
|
||||
}
|
||||
document.body.ondragenter = ondrag;
|
||||
document.body.ondragleave = offdrag;
|
||||
document.body.ondragover = onover;
|
||||
document.body.ondrop = gotfile;
|
||||
|
||||
var drops = [ebi('up_dz'), ebi('srch_dz')];
|
||||
for (var a = 0; a < 2; a++) {
|
||||
drops[a].ondragenter = ondrag;
|
||||
drops[a].ondragover = onover;
|
||||
drops[a].ondragover = onoverb;
|
||||
drops[a].ondragleave = offdrag;
|
||||
drops[a].ondrop = gotfile;
|
||||
}
|
||||
@@ -801,7 +817,10 @@ function up2k_init(subtle) {
|
||||
ev(e);
|
||||
nenters = 0;
|
||||
offdrag.bind(this)();
|
||||
var dz = (this && this.getAttribute('id'));
|
||||
var dz = this && this.getAttribute('id');
|
||||
if (!dz && e && e.clientY)
|
||||
// cuo2duo fallback
|
||||
dz = e.clientY < window.innerHeight / 2 ? 'up_dz' : 'srch_dz';
|
||||
|
||||
var err = this.getAttribute('err');
|
||||
if (err)
|
||||
|
||||
@@ -329,14 +329,45 @@ function clgot(el, cls) {
|
||||
}
|
||||
|
||||
|
||||
function showsort(tab) {
|
||||
var v, vn, v1, v2, th = tab.tHead,
|
||||
sopts = jread('fsort', [["href", 1, ""]]);
|
||||
|
||||
th && (th = th.rows[0]) && (th = th.cells);
|
||||
|
||||
for (var a = sopts.length - 1; a >= 0; a--) {
|
||||
if (!sopts[a][0])
|
||||
continue;
|
||||
|
||||
v2 = v1;
|
||||
v1 = sopts[a];
|
||||
}
|
||||
|
||||
v = [v1, v2];
|
||||
vn = [v1 ? v1[0] : '', v2 ? v2[0] : ''];
|
||||
|
||||
var ga = QSA('#ghead a[s]');
|
||||
for (var a = 0; a < ga.length; a++)
|
||||
ga[a].className = '';
|
||||
|
||||
for (var a = 0; a < th.length; a++) {
|
||||
var n = vn.indexOf(th[a].getAttribute('name')),
|
||||
cl = n < 0 ? ' ' : ' s' + n + (v[n][1] > 0 ? ' ' : 'r ');
|
||||
|
||||
th[a].className = th[a].className.replace(/ *s[01]r? */, ' ') + cl;
|
||||
if (n + 1) {
|
||||
ga = QS('#ghead a[s="' + vn[n] + '"]');
|
||||
if (ga)
|
||||
ga.className = cl;
|
||||
}
|
||||
}
|
||||
}
|
||||
function sortTable(table, col, cb) {
|
||||
var tb = table.tBodies[0],
|
||||
th = table.tHead.rows[0].cells,
|
||||
tr = Array.prototype.slice.call(tb.rows, 0),
|
||||
i, reverse = th[col].className.indexOf('sort1') !== -1 ? -1 : 1;
|
||||
for (var a = 0, thl = th.length; a < thl; a++)
|
||||
th[a].className = th[a].className.replace(/ *sort-?1 */, " ");
|
||||
th[col].className += ' sort' + reverse;
|
||||
i, reverse = /s0[^r]/.exec(th[col].className + ' ') ? -1 : 1;
|
||||
|
||||
var stype = th[col].getAttribute('sort');
|
||||
try {
|
||||
var nrules = [], rules = jread("fsort", []);
|
||||
@@ -354,6 +385,7 @@ function sortTable(table, col, cb) {
|
||||
break;
|
||||
}
|
||||
jwrite("fsort", nrules);
|
||||
try { showsort(table); } catch (ex) { }
|
||||
}
|
||||
catch (ex) {
|
||||
console.log("failed to persist sort rules, resetting: " + ex);
|
||||
|
||||
@@ -193,6 +193,11 @@ git pull; git reset --hard origin/HEAD && git log --format=format:"%H %ai %d" --
|
||||
# download all sfx versions
|
||||
curl https://api.github.com/repos/9001/copyparty/releases?per_page=100 | jq -r '.[] | .tag_name + " " + .name' | tr -d '\r' | while read v t; do fn="$(printf '%s\n' "copyparty $v $t.py" | tr / -)"; [ -e "$fn" ] || curl https://github.com/9001/copyparty/releases/download/$v/copyparty-sfx.py -Lo "$fn"; done
|
||||
|
||||
# push to multiple git remotes
|
||||
git config -l | grep '^remote'
|
||||
git remote add all git@github.com:9001/copyparty.git
|
||||
git remote set-url --add --push all git@gitlab.com:9001/copyparty.git
|
||||
git remote set-url --add --push all git@github.com:9001/copyparty.git
|
||||
|
||||
##
|
||||
## http 206
|
||||
|
||||
@@ -7,8 +7,9 @@ v=$1
|
||||
printf '%s\n' "$v" | grep -qE '^[0-9\.]+$' || exit 1
|
||||
grep -E "(${v//./, })" ../copyparty/__version__.py || exit 1
|
||||
|
||||
git push all
|
||||
git tag v$v
|
||||
git push origin --tags
|
||||
git push all --tags
|
||||
|
||||
rm -rf ../dist
|
||||
|
||||
|
||||
Reference in New Issue
Block a user