mirror of
				https://github.com/9001/copyparty.git
				synced 2025-11-04 13:53:18 +00:00 
			
		
		
		
	Compare commits
	
		
			58 Commits
		
	
	
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
| 
						 | 
					bd49979f4a | ||
| 
						 | 
					7e606cdd9f | ||
| 
						 | 
					8b4b7fa794 | ||
| 
						 | 
					05345ddf8b | ||
| 
						 | 
					66adb470ad | ||
| 
						 | 
					e15c8fd146 | ||
| 
						 | 
					0f09b98a39 | ||
| 
						 | 
					b4d6f4e24d | ||
| 
						 | 
					3217fa625b | ||
| 
						 | 
					e719ff8a47 | ||
| 
						 | 
					9fcf528d45 | ||
| 
						 | 
					1ddbf5a158 | ||
| 
						 | 
					64bf4574b0 | ||
| 
						 | 
					5649d26077 | ||
| 
						 | 
					92f923effe | ||
| 
						 | 
					0d46d548b9 | ||
| 
						 | 
					062df3f0c3 | ||
| 
						 | 
					789fb53b8e | ||
| 
						 | 
					351db5a18f | ||
| 
						 | 
					aabbd271c8 | ||
| 
						 | 
					aae8e0171e | ||
| 
						 | 
					45827a2458 | ||
| 
						 | 
					726030296f | ||
| 
						 | 
					6659ab3881 | ||
| 
						 | 
					c6a103609e | ||
| 
						 | 
					c6b3f035e5 | ||
| 
						 | 
					2b0a7e378e | ||
| 
						 | 
					b75ce909c8 | ||
| 
						 | 
					229c3f5dab | ||
| 
						 | 
					ec73094506 | ||
| 
						 | 
					c7650c9326 | ||
| 
						 | 
					d94c6d4e72 | ||
| 
						 | 
					3cc8760733 | ||
| 
						 | 
					a2f6973495 | ||
| 
						 | 
					f8648fa651 | ||
| 
						 | 
					177aa038df | ||
| 
						 | 
					e0a14ec881 | ||
| 
						 | 
					9366512f2f | ||
| 
						 | 
					ea38b8041a | ||
| 
						 | 
					f1870daf0d | ||
| 
						 | 
					9722441aad | ||
| 
						 | 
					9d014087f4 | ||
| 
						 | 
					83b4038b85 | ||
| 
						 | 
					1e0a448feb | ||
| 
						 | 
					fb81de3b36 | ||
| 
						 | 
					aa4f352301 | ||
| 
						 | 
					f1a1c2ea45 | ||
| 
						 | 
					6249bd4163 | ||
| 
						 | 
					2579dc64ce | ||
| 
						 | 
					356512270a | ||
| 
						 | 
					bed27f2b43 | ||
| 
						 | 
					54013d861b | ||
| 
						 | 
					ec100210dc | ||
| 
						 | 
					3ab1acf32c | ||
| 
						 | 
					8c28266418 | ||
| 
						 | 
					7f8b8dcb92 | ||
| 
						 | 
					6dd39811d4 | ||
| 
						 | 
					35e2138e3e | 
							
								
								
									
										39
									
								
								README.md
									
									
									
									
									
								
							
							
						
						
									
										39
									
								
								README.md
									
									
									
									
									
								
							@@ -20,6 +20,7 @@ turn your phone or raspi into a portable file server with resumable uploads/down
 | 
			
		||||
 | 
			
		||||
* top
 | 
			
		||||
    * [quickstart](#quickstart)
 | 
			
		||||
        * [on debian](#on-debian)
 | 
			
		||||
    * [notes](#notes)
 | 
			
		||||
    * [status](#status)
 | 
			
		||||
* [bugs](#bugs)
 | 
			
		||||
@@ -68,6 +69,7 @@ some recommended options:
 | 
			
		||||
* `-e2dsa` enables general file indexing, see [search configuration](#search-configuration)
 | 
			
		||||
* `-e2ts` enables audio metadata indexing (needs either FFprobe or mutagen), see [optional dependencies](#optional-dependencies)
 | 
			
		||||
* `-v /mnt/music:/music:r:afoo -a foo:bar` shares `/mnt/music` as `/music`, `r`eadable by anyone, with user `foo` as `a`dmin (read/write), password `bar`
 | 
			
		||||
  * the syntax is `-v src:dst:perm:perm:...` so local-path, url-path, and one or more permissions to set
 | 
			
		||||
  * replace `:r:afoo` with `:rfoo` to only make the folder readable by `foo` and nobody else
 | 
			
		||||
  * in addition to `r`ead and `a`dmin, `w`rite makes a folder write-only, so cannot list/access files in it
 | 
			
		||||
* `--ls '**,*,ln,p,r'` to crash on startup if any of the volumes contain a symlink which point outside the volume, as that could give users unintended access
 | 
			
		||||
@@ -77,6 +79,19 @@ you may also want these, especially on servers:
 | 
			
		||||
* [contrib/nginx/copyparty.conf](contrib/nginx/copyparty.conf) to reverse-proxy behind nginx (for better https)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
### on debian
 | 
			
		||||
 | 
			
		||||
recommended steps to enable audio metadata and thumbnails (from images and videos):
 | 
			
		||||
 | 
			
		||||
* as root, run the following:  
 | 
			
		||||
  `apt install python3 python3-pip python3-dev ffmpeg`
 | 
			
		||||
 | 
			
		||||
* then, as the user which will be running copyparty (so hopefully not root), run this:  
 | 
			
		||||
  `python3 -m pip install --user -U Pillow pillow-avif-plugin`
 | 
			
		||||
 | 
			
		||||
(skipped `pyheif-pillow-opener` because apparently debian is too old to build it)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
## notes
 | 
			
		||||
 | 
			
		||||
general:
 | 
			
		||||
@@ -111,7 +126,7 @@ summary: all planned features work! now please enjoy the bloatening
 | 
			
		||||
  * ☑ FUSE client (read-only)
 | 
			
		||||
* browser
 | 
			
		||||
  * ☑ tree-view
 | 
			
		||||
  * ☑ audio player
 | 
			
		||||
  * ☑ audio player (with OS media controls)
 | 
			
		||||
  * ☑ thumbnails
 | 
			
		||||
    * ☑ images using Pillow
 | 
			
		||||
    * ☑ videos using FFmpeg
 | 
			
		||||
@@ -139,6 +154,8 @@ summary: all planned features work! now please enjoy the bloatening
 | 
			
		||||
 | 
			
		||||
* all volumes must exist / be available on startup; up2k (mtp especially) gets funky otherwise
 | 
			
		||||
* cannot mount something at `/d1/d2/d3` unless `d2` exists inside `d1`
 | 
			
		||||
* dupe files will not have metadata (audio tags etc) displayed in the file listing
 | 
			
		||||
  * because they don't get `up` entries in the db (probably best fix) and `tx_browser` does not `lstat`
 | 
			
		||||
* probably more, pls let me know
 | 
			
		||||
 | 
			
		||||
## not my bugs
 | 
			
		||||
@@ -168,25 +185,28 @@ summary: all planned features work! now please enjoy the bloatening
 | 
			
		||||
## hotkeys
 | 
			
		||||
 | 
			
		||||
the browser has the following hotkeys
 | 
			
		||||
* `B` toggle breadcrumbs / directory tree
 | 
			
		||||
* `I/K` prev/next folder
 | 
			
		||||
* `P` parent folder
 | 
			
		||||
* `M` parent folder
 | 
			
		||||
* `G` toggle list / grid view
 | 
			
		||||
* `T` toggle thumbnails / icons
 | 
			
		||||
* when playing audio:
 | 
			
		||||
  * `0..9` jump to 10%..90%
 | 
			
		||||
  * `U/O` skip 10sec back/forward
 | 
			
		||||
  * `J/L` prev/next song
 | 
			
		||||
  * `M` play/pause (also starts playing the folder)
 | 
			
		||||
  * `P` play/pause (also starts playing the folder)
 | 
			
		||||
* when tree-sidebar is open:
 | 
			
		||||
  * `A/D` adjust tree width
 | 
			
		||||
* in the grid view:
 | 
			
		||||
  * `S` toggle multiselect
 | 
			
		||||
  * `A/D` zoom
 | 
			
		||||
  * shift+`A/D` zoom
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
## tree-mode
 | 
			
		||||
 | 
			
		||||
by default there's a breadcrumbs path; you can replace this with a tree-browser sidebar thing by clicking the 🌲
 | 
			
		||||
by default there's a breadcrumbs path; you can replace this with a tree-browser sidebar thing by clicking the `🌲` or pressing the `B` hotkey
 | 
			
		||||
 | 
			
		||||
click `[-]` and `[+]` to adjust the size, and the `[a]` toggles if the tree should widen dynamically as you go deeper or stay fixed-size
 | 
			
		||||
click `[-]` and `[+]` (or hotkeys `A`/`D`) to adjust the size, and the `[a]` toggles if the tree should widen dynamically as you go deeper or stay fixed-size
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
## thumbnails
 | 
			
		||||
@@ -197,6 +217,8 @@ it does static images with Pillow and uses FFmpeg for video files, so you may wa
 | 
			
		||||
 | 
			
		||||
images named `folder.jpg` and `folder.png` become the thumbnail of the folder they're in
 | 
			
		||||
 | 
			
		||||
in the grid/thumbnail view, if the audio player panel is open, songs will start playing when clicked
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
## zip downloads
 | 
			
		||||
 | 
			
		||||
@@ -280,6 +302,8 @@ up2k has saved a few uploads from becoming corrupted in-transfer already; caught
 | 
			
		||||
 | 
			
		||||
* 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`
 | 
			
		||||
 | 
			
		||||
* 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
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
# searching
 | 
			
		||||
 | 
			
		||||
@@ -591,13 +615,14 @@ in the `scripts` folder:
 | 
			
		||||
roughly sorted by priority
 | 
			
		||||
 | 
			
		||||
* readme.md as epilogue
 | 
			
		||||
* single sha512 across all up2k chunks? maybe
 | 
			
		||||
* reduce up2k roundtrips
 | 
			
		||||
  * start from a chunk index and just go
 | 
			
		||||
  * terminate client on bad data
 | 
			
		||||
 | 
			
		||||
discarded ideas
 | 
			
		||||
 | 
			
		||||
* single sha512 across all up2k chunks?
 | 
			
		||||
  * crypto.subtle cannot into streaming, would have to use hashwasm, expensive
 | 
			
		||||
* separate sqlite table per tag
 | 
			
		||||
  * performance fixed by skipping some indexes (`+mt.k`)
 | 
			
		||||
* audio fingerprinting
 | 
			
		||||
 
 | 
			
		||||
@@ -60,7 +60,7 @@ def main():
 | 
			
		||||
    try:
 | 
			
		||||
        det(tf)
 | 
			
		||||
    except:
 | 
			
		||||
        pass
 | 
			
		||||
        pass  # mute
 | 
			
		||||
    finally:
 | 
			
		||||
        os.unlink(tf)
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										123
									
								
								bin/mtag/audio-key-slicing.py
									
									
									
									
									
										Executable file
									
								
							
							
						
						
									
										123
									
								
								bin/mtag/audio-key-slicing.py
									
									
									
									
									
										Executable file
									
								
							@@ -0,0 +1,123 @@
 | 
			
		||||
#!/usr/bin/env python
 | 
			
		||||
 | 
			
		||||
import re
 | 
			
		||||
import os
 | 
			
		||||
import sys
 | 
			
		||||
import tempfile
 | 
			
		||||
import subprocess as sp
 | 
			
		||||
 | 
			
		||||
import keyfinder
 | 
			
		||||
 | 
			
		||||
from copyparty.util import fsenc
 | 
			
		||||
 | 
			
		||||
"""
 | 
			
		||||
dep: github/mixxxdj/libkeyfinder
 | 
			
		||||
dep: pypi/keyfinder
 | 
			
		||||
dep: ffmpeg
 | 
			
		||||
 | 
			
		||||
note: this is a janky edition of the regular audio-key.py,
 | 
			
		||||
  slicing the files at 20sec intervals and keeping 5sec from each,
 | 
			
		||||
  surprisingly accurate but still garbage (446 ok, 69 bad, 13% miss)
 | 
			
		||||
 | 
			
		||||
  it is fast tho
 | 
			
		||||
"""
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def get_duration():
 | 
			
		||||
    # TODO provide ffprobe tags to mtp as json
 | 
			
		||||
 | 
			
		||||
    # fmt: off
 | 
			
		||||
    dur = sp.check_output([
 | 
			
		||||
        "ffprobe",
 | 
			
		||||
        "-hide_banner",
 | 
			
		||||
        "-v", "fatal",
 | 
			
		||||
        "-show_streams",
 | 
			
		||||
        "-show_format",
 | 
			
		||||
        fsenc(sys.argv[1])
 | 
			
		||||
    ])
 | 
			
		||||
    # fmt: on
 | 
			
		||||
 | 
			
		||||
    dur = dur.decode("ascii", "replace").split("\n")
 | 
			
		||||
    dur = [x.split("=")[1] for x in dur if x.startswith("duration=")]
 | 
			
		||||
    dur = [float(x) for x in dur if re.match(r"^[0-9\.,]+$", x)]
 | 
			
		||||
    return list(sorted(dur))[-1] if dur else None
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def get_segs(dur):
 | 
			
		||||
    # keep first 5s of each 20s,
 | 
			
		||||
    # keep entire last segment
 | 
			
		||||
    ofs = 0
 | 
			
		||||
    segs = []
 | 
			
		||||
    while True:
 | 
			
		||||
        seg = [ofs, 5]
 | 
			
		||||
        segs.append(seg)
 | 
			
		||||
        if dur - ofs < 20:
 | 
			
		||||
            seg[-1] = int(dur - seg[0])
 | 
			
		||||
            break
 | 
			
		||||
 | 
			
		||||
        ofs += 20
 | 
			
		||||
 | 
			
		||||
    return segs
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def slice(tf):
 | 
			
		||||
    dur = get_duration()
 | 
			
		||||
    dur = min(dur, 600)  # max 10min
 | 
			
		||||
    segs = get_segs(dur)
 | 
			
		||||
 | 
			
		||||
    # fmt: off
 | 
			
		||||
    cmd = [
 | 
			
		||||
        "ffmpeg",
 | 
			
		||||
        "-nostdin",
 | 
			
		||||
        "-hide_banner",
 | 
			
		||||
        "-v", "fatal",
 | 
			
		||||
        "-y"
 | 
			
		||||
    ]
 | 
			
		||||
 | 
			
		||||
    for seg in segs:
 | 
			
		||||
        cmd.extend([
 | 
			
		||||
            "-ss", str(seg[0]),
 | 
			
		||||
            "-i", fsenc(sys.argv[1])
 | 
			
		||||
        ])
 | 
			
		||||
    
 | 
			
		||||
    filt = ""
 | 
			
		||||
    for n, seg in enumerate(segs):
 | 
			
		||||
        filt += "[{}:a:0]atrim=duration={}[a{}]; ".format(n, seg[1], n)
 | 
			
		||||
    
 | 
			
		||||
    prev = "a0"
 | 
			
		||||
    for n in range(1, len(segs)):
 | 
			
		||||
        nxt = "b{}".format(n)
 | 
			
		||||
        filt += "[{}][a{}]acrossfade=d=0.5[{}]; ".format(prev, n, nxt)
 | 
			
		||||
        prev = nxt
 | 
			
		||||
 | 
			
		||||
    cmd.extend([
 | 
			
		||||
        "-filter_complex", filt[:-2],
 | 
			
		||||
        "-map", "[{}]".format(nxt),
 | 
			
		||||
        "-sample_fmt", "s16",
 | 
			
		||||
        tf
 | 
			
		||||
    ])
 | 
			
		||||
    # fmt: on
 | 
			
		||||
 | 
			
		||||
    # print(cmd)
 | 
			
		||||
    sp.check_call(cmd)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def det(tf):
 | 
			
		||||
    slice(tf)
 | 
			
		||||
    print(keyfinder.key(tf).camelot())
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def main():
 | 
			
		||||
    with tempfile.NamedTemporaryFile(suffix=".flac", delete=False) as f:
 | 
			
		||||
        f.write(b"h")
 | 
			
		||||
        tf = f.name
 | 
			
		||||
 | 
			
		||||
    try:
 | 
			
		||||
        det(tf)
 | 
			
		||||
    finally:
 | 
			
		||||
        os.unlink(tf)
 | 
			
		||||
        pass
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
if __name__ == "__main__":
 | 
			
		||||
    main()
 | 
			
		||||
@@ -1,18 +1,54 @@
 | 
			
		||||
#!/usr/bin/env python
 | 
			
		||||
 | 
			
		||||
import os
 | 
			
		||||
import sys
 | 
			
		||||
import tempfile
 | 
			
		||||
import subprocess as sp
 | 
			
		||||
import keyfinder
 | 
			
		||||
 | 
			
		||||
from copyparty.util import fsenc
 | 
			
		||||
 | 
			
		||||
"""
 | 
			
		||||
dep: github/mixxxdj/libkeyfinder
 | 
			
		||||
dep: pypi/keyfinder
 | 
			
		||||
dep: ffmpeg
 | 
			
		||||
 | 
			
		||||
note: cannot fsenc
 | 
			
		||||
"""
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
try:
 | 
			
		||||
    print(keyfinder.key(sys.argv[1]).camelot())
 | 
			
		||||
except:
 | 
			
		||||
    pass
 | 
			
		||||
# tried trimming the first/last 5th, bad idea,
 | 
			
		||||
# misdetects 9a law field (Sphere Caliber) as 10b,
 | 
			
		||||
# obvious when mixing 9a ghostly parapara ship
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def det(tf):
 | 
			
		||||
    # fmt: off
 | 
			
		||||
    sp.check_call([
 | 
			
		||||
        "ffmpeg",
 | 
			
		||||
        "-nostdin",
 | 
			
		||||
        "-hide_banner",
 | 
			
		||||
        "-v", "fatal",
 | 
			
		||||
        "-y", "-i", fsenc(sys.argv[1]),
 | 
			
		||||
        "-t", "300",
 | 
			
		||||
        "-sample_fmt", "s16",
 | 
			
		||||
        tf
 | 
			
		||||
    ])
 | 
			
		||||
    # fmt: on
 | 
			
		||||
 | 
			
		||||
    print(keyfinder.key(tf).camelot())
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def main():
 | 
			
		||||
    with tempfile.NamedTemporaryFile(suffix=".flac", delete=False) as f:
 | 
			
		||||
        f.write(b"h")
 | 
			
		||||
        tf = f.name
 | 
			
		||||
 | 
			
		||||
    try:
 | 
			
		||||
        det(tf)
 | 
			
		||||
    except:
 | 
			
		||||
        pass  # mute
 | 
			
		||||
    finally:
 | 
			
		||||
        os.unlink(tf)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
if __name__ == "__main__":
 | 
			
		||||
    main()
 | 
			
		||||
 
 | 
			
		||||
@@ -305,6 +305,7 @@ def run_argparse(argv, formatter):
 | 
			
		||||
    ap2.add_argument("--th-poke", metavar="SEC", type=int, default=300, help="activity labeling cooldown")
 | 
			
		||||
    ap2.add_argument("--th-clean", metavar="SEC", type=int, default=43200, help="cleanup interval")
 | 
			
		||||
    ap2.add_argument("--th-maxage", metavar="SEC", type=int, default=604800, help="max folder age")
 | 
			
		||||
    ap2.add_argument("--th-covers", metavar="N,N", type=str, default="folder.png,folder.jpg,cover.png,cover.jpg", help="folder thumbnails to stat for")
 | 
			
		||||
 | 
			
		||||
    ap2 = ap.add_argument_group('database options')
 | 
			
		||||
    ap2.add_argument("-e2d", action="store_true", help="enable up2k database")
 | 
			
		||||
@@ -410,7 +411,7 @@ def main(argv=None):
 | 
			
		||||
            + "  (if you crash with codec errors then that is why)"
 | 
			
		||||
        )
 | 
			
		||||
 | 
			
		||||
    if WINDOWS and sys.version_info < (3, 6):
 | 
			
		||||
    if sys.version_info < (3, 6):
 | 
			
		||||
        al.no_scandir = True
 | 
			
		||||
 | 
			
		||||
    # signal.signal(signal.SIGINT, sighandler)
 | 
			
		||||
 
 | 
			
		||||
@@ -1,8 +1,8 @@
 | 
			
		||||
# coding: utf-8
 | 
			
		||||
 | 
			
		||||
VERSION = (0, 11, 21)
 | 
			
		||||
VERSION = (0, 11, 29)
 | 
			
		||||
CODENAME = "the grid"
 | 
			
		||||
BUILD_DT = (2021, 6, 20)
 | 
			
		||||
BUILD_DT = (2021, 6, 30)
 | 
			
		||||
 | 
			
		||||
S_VERSION = ".".join(map(str, VERSION))
 | 
			
		||||
S_BUILD_DT = "{0:04d}-{1:02d}-{2:02d}".format(*BUILD_DT)
 | 
			
		||||
 
 | 
			
		||||
@@ -693,6 +693,11 @@ class AuthSrv(object):
 | 
			
		||||
            self.user = user
 | 
			
		||||
            self.iuser = {v: k for k, v in user.items()}
 | 
			
		||||
 | 
			
		||||
            self.re_pwd = None
 | 
			
		||||
            pwds = [re.escape(x) for x in self.iuser.keys()]
 | 
			
		||||
            if pwds:
 | 
			
		||||
                self.re_pwd = re.compile("=(" + "|".join(pwds) + ")([]&; ]|$)")
 | 
			
		||||
 | 
			
		||||
        # import pprint
 | 
			
		||||
        # pprint.pprint({"usr": user, "rd": mread, "wr": mwrite, "mnt": mount})
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -10,7 +10,6 @@ import json
 | 
			
		||||
import string
 | 
			
		||||
import socket
 | 
			
		||||
import ctypes
 | 
			
		||||
import traceback
 | 
			
		||||
from datetime import datetime
 | 
			
		||||
import calendar
 | 
			
		||||
 | 
			
		||||
@@ -50,12 +49,21 @@ class HttpCli(object):
 | 
			
		||||
        self.tls = hasattr(self.s, "cipher")
 | 
			
		||||
 | 
			
		||||
        self.bufsz = 1024 * 32
 | 
			
		||||
        self.hint = None
 | 
			
		||||
        self.absolute_urls = False
 | 
			
		||||
        self.out_headers = {"Access-Control-Allow-Origin": "*"}
 | 
			
		||||
 | 
			
		||||
    def log(self, msg, c=0):
 | 
			
		||||
        ptn = self.asrv.re_pwd
 | 
			
		||||
        if ptn and ptn.search(msg):
 | 
			
		||||
            msg = ptn.sub(self.unpwd, msg)
 | 
			
		||||
 | 
			
		||||
        self.log_func(self.log_src, msg, c)
 | 
			
		||||
 | 
			
		||||
    def unpwd(self, m):
 | 
			
		||||
        a, b = m.groups()
 | 
			
		||||
        return "=\033[7m {} \033[27m{}".format(self.asrv.iuser[a], b)
 | 
			
		||||
 | 
			
		||||
    def _check_nonfatal(self, ex):
 | 
			
		||||
        return ex.code < 400 or ex.code in [404, 429]
 | 
			
		||||
 | 
			
		||||
@@ -72,6 +80,7 @@ class HttpCli(object):
 | 
			
		||||
        """returns true if connection can be reused"""
 | 
			
		||||
        self.keepalive = False
 | 
			
		||||
        self.headers = {}
 | 
			
		||||
        self.hint = None
 | 
			
		||||
        try:
 | 
			
		||||
            headerlines = read_header(self.sr)
 | 
			
		||||
            if not headerlines:
 | 
			
		||||
@@ -130,6 +139,9 @@ class HttpCli(object):
 | 
			
		||||
                if v is not None:
 | 
			
		||||
                    self.log("[H] {}: \033[33m[{}]".format(k, v), 6)
 | 
			
		||||
 | 
			
		||||
        if "&" in self.req and "?" not in self.req:
 | 
			
		||||
            self.hint = "did you mean '?' instead of '&'"
 | 
			
		||||
 | 
			
		||||
        # split req into vpath + uparam
 | 
			
		||||
        uparam = {}
 | 
			
		||||
        if "?" not in self.req:
 | 
			
		||||
@@ -169,6 +181,9 @@ class HttpCli(object):
 | 
			
		||||
        self.rvol, self.wvol, self.avol = [[], [], []]
 | 
			
		||||
        self.asrv.vfs.user_tree(self.uname, self.rvol, self.wvol, self.avol)
 | 
			
		||||
 | 
			
		||||
        if pwd and "pw" in self.ouparam and pwd != cookies.get("cppwd"):
 | 
			
		||||
            self.out_headers["Set-Cookie"] = self.get_pwd_cookie(pwd)[0]
 | 
			
		||||
 | 
			
		||||
        ua = self.headers.get("user-agent", "")
 | 
			
		||||
        self.is_rclone = ua.startswith("rclone/")
 | 
			
		||||
        if self.is_rclone:
 | 
			
		||||
@@ -199,6 +214,9 @@ class HttpCli(object):
 | 
			
		||||
 | 
			
		||||
                self.log("{}\033[0m, {}".format(str(ex), self.vpath), 3)
 | 
			
		||||
                msg = "<pre>{}\r\nURL: {}\r\n".format(str(ex), self.vpath)
 | 
			
		||||
                if self.hint:
 | 
			
		||||
                    msg += "hint: {}\r\n".format(self.hint)
 | 
			
		||||
 | 
			
		||||
                self.reply(msg.encode("utf-8", "replace"), status=ex.code)
 | 
			
		||||
                return self.keepalive
 | 
			
		||||
            except Pebkac:
 | 
			
		||||
@@ -481,7 +499,7 @@ class HttpCli(object):
 | 
			
		||||
 | 
			
		||||
        spd1 = get_spd(nbytes, self.t0)
 | 
			
		||||
        spd2 = get_spd(self.conn.nbyte, self.conn.t0)
 | 
			
		||||
        return spd1 + " " + spd2
 | 
			
		||||
        return "{} {} n{}".format(spd1, spd2, self.conn.nreq)
 | 
			
		||||
 | 
			
		||||
    def handle_post_multipart(self):
 | 
			
		||||
        self.parser = MultipartParser(self.log, self.sr, self.headers)
 | 
			
		||||
@@ -623,7 +641,7 @@ class HttpCli(object):
 | 
			
		||||
            penalty = 0.7
 | 
			
		||||
            t_idle = t0 - idx.p_end
 | 
			
		||||
            if idx.p_dur > 0.7 and t_idle < penalty:
 | 
			
		||||
                m = "rate-limit ({:.1f} sec), cost {:.2f}, idle {:.2f}"
 | 
			
		||||
                m = "rate-limit {:.1f} sec, cost {:.2f}, idle {:.2f}"
 | 
			
		||||
                raise Pebkac(429, m.format(penalty, idx.p_dur, t_idle))
 | 
			
		||||
 | 
			
		||||
        if "srch" in body:
 | 
			
		||||
@@ -743,6 +761,12 @@ class HttpCli(object):
 | 
			
		||||
        pwd = self.parser.require("cppwd", 64)
 | 
			
		||||
        self.parser.drop()
 | 
			
		||||
 | 
			
		||||
        ck, msg = self.get_pwd_cookie(pwd)
 | 
			
		||||
        html = self.j2("msg", h1=msg, h2='<a href="/">ack</a>', redir="/")
 | 
			
		||||
        self.reply(html.encode("utf-8"), headers={"Set-Cookie": ck})
 | 
			
		||||
        return True
 | 
			
		||||
 | 
			
		||||
    def get_pwd_cookie(self, pwd):
 | 
			
		||||
        if pwd in self.asrv.iuser:
 | 
			
		||||
            msg = "login ok"
 | 
			
		||||
            dt = datetime.utcfromtimestamp(time.time() + 60 * 60 * 24 * 365)
 | 
			
		||||
@@ -753,9 +777,7 @@ class HttpCli(object):
 | 
			
		||||
            exp = "Fri, 15 Aug 1997 01:00:00 GMT"
 | 
			
		||||
 | 
			
		||||
        ck = "cppwd={}; Path=/; Expires={}; SameSite=Lax".format(pwd, exp)
 | 
			
		||||
        html = self.j2("msg", h1=msg, h2='<a href="/">ack</a>', redir="/")
 | 
			
		||||
        self.reply(html.encode("utf-8"), headers={"Set-Cookie": ck})
 | 
			
		||||
        return True
 | 
			
		||||
        return [ck, msg]
 | 
			
		||||
 | 
			
		||||
    def handle_mkdir(self):
 | 
			
		||||
        new_dir = self.parser.require("name", 512)
 | 
			
		||||
@@ -1305,7 +1327,7 @@ class HttpCli(object):
 | 
			
		||||
            ext = "folder"
 | 
			
		||||
            exact = True
 | 
			
		||||
 | 
			
		||||
        bad = re.compile(r"[](){}/[]|^[0-9_-]*$")
 | 
			
		||||
        bad = re.compile(r"[](){}/ []|^[0-9_-]*$")
 | 
			
		||||
        n = ext.split(".")[::-1]
 | 
			
		||||
        if not exact:
 | 
			
		||||
            n = n[:-1]
 | 
			
		||||
@@ -1540,7 +1562,7 @@ class HttpCli(object):
 | 
			
		||||
            th_fmt = self.uparam.get("th")
 | 
			
		||||
            if th_fmt is not None:
 | 
			
		||||
                if is_dir:
 | 
			
		||||
                    for fn in ["folder.png", "folder.jpg"]:
 | 
			
		||||
                    for fn in self.args.th_covers.split(","):
 | 
			
		||||
                        fp = os.path.join(abspath, fn)
 | 
			
		||||
                        if os.path.exists(fp):
 | 
			
		||||
                            vrem = "{}/{}".format(vrem.rstrip("/"), fn)
 | 
			
		||||
 
 | 
			
		||||
@@ -3,7 +3,6 @@ from __future__ import print_function, unicode_literals
 | 
			
		||||
 | 
			
		||||
import re
 | 
			
		||||
import os
 | 
			
		||||
import sys
 | 
			
		||||
import time
 | 
			
		||||
import socket
 | 
			
		||||
 | 
			
		||||
@@ -44,6 +43,7 @@ class HttpConn(object):
 | 
			
		||||
 | 
			
		||||
        self.t0 = time.time()
 | 
			
		||||
        self.stopping = False
 | 
			
		||||
        self.nreq = 0
 | 
			
		||||
        self.nbyte = 0
 | 
			
		||||
        self.workload = 0
 | 
			
		||||
        self.u2idx = None
 | 
			
		||||
@@ -189,6 +189,7 @@ class HttpConn(object):
 | 
			
		||||
                if self.workload >= 2 ** 31:
 | 
			
		||||
                    self.workload = 100
 | 
			
		||||
 | 
			
		||||
            self.nreq += 1
 | 
			
		||||
            cli = HttpCli(self)
 | 
			
		||||
            if not cli.run():
 | 
			
		||||
                return
 | 
			
		||||
 
 | 
			
		||||
@@ -115,6 +115,19 @@ def parse_ffprobe(txt):
 | 
			
		||||
    ret = {}  # processed
 | 
			
		||||
    md = {}  # raw tags
 | 
			
		||||
 | 
			
		||||
    is_audio = fmt.get("format_name") in ["mp3", "ogg", "flac", "wav"]
 | 
			
		||||
    if fmt.get("filename", "").split(".")[-1].lower() in ["m4a", "aac"]:
 | 
			
		||||
        is_audio = True
 | 
			
		||||
 | 
			
		||||
    # if audio file, ensure audio stream appears first
 | 
			
		||||
    if (
 | 
			
		||||
        is_audio
 | 
			
		||||
        and len(streams) > 2
 | 
			
		||||
        and streams[1].get("codec_type") != "audio"
 | 
			
		||||
        and streams[2].get("codec_type") == "audio"
 | 
			
		||||
    ):
 | 
			
		||||
        streams = [fmt, streams[2], streams[1]] + streams[3:]
 | 
			
		||||
 | 
			
		||||
    have = {}
 | 
			
		||||
    for strm in streams:
 | 
			
		||||
        typ = strm.get("codec_type")
 | 
			
		||||
@@ -134,9 +147,7 @@ def parse_ffprobe(txt):
 | 
			
		||||
            ]
 | 
			
		||||
 | 
			
		||||
        if typ == "video":
 | 
			
		||||
            if strm.get("DISPOSITION:attached_pic") == "1" or fmt.get(
 | 
			
		||||
                "format_name"
 | 
			
		||||
            ) in ["mp3", "ogg", "flac"]:
 | 
			
		||||
            if strm.get("DISPOSITION:attached_pic") == "1" or is_audio:
 | 
			
		||||
                continue
 | 
			
		||||
 | 
			
		||||
            kvm = [
 | 
			
		||||
@@ -180,7 +191,7 @@ def parse_ffprobe(txt):
 | 
			
		||||
 | 
			
		||||
            k = k[4:].strip()
 | 
			
		||||
            v = v.strip()
 | 
			
		||||
            if k and v:
 | 
			
		||||
            if k and v and k not in md:
 | 
			
		||||
                md[k] = [v]
 | 
			
		||||
 | 
			
		||||
    for k in [".q", ".vq", ".aq"]:
 | 
			
		||||
 
 | 
			
		||||
@@ -26,7 +26,7 @@ class U2idx(object):
 | 
			
		||||
        self.timeout = self.args.srch_time
 | 
			
		||||
 | 
			
		||||
        if not HAVE_SQLITE3:
 | 
			
		||||
            self.log("could not load sqlite3; searchign wqill be disabled")
 | 
			
		||||
            self.log("your python does not have sqlite3; searching will be disabled")
 | 
			
		||||
            return
 | 
			
		||||
 | 
			
		||||
        self.cur = {}
 | 
			
		||||
@@ -57,6 +57,9 @@ class U2idx(object):
 | 
			
		||||
            raise Pebkac(500, min_ex())
 | 
			
		||||
 | 
			
		||||
    def get_cur(self, ptop):
 | 
			
		||||
        if not HAVE_SQLITE3:
 | 
			
		||||
            return None
 | 
			
		||||
 | 
			
		||||
        cur = self.cur.get(ptop)
 | 
			
		||||
        if cur:
 | 
			
		||||
            return cur
 | 
			
		||||
 
 | 
			
		||||
@@ -653,7 +653,7 @@ class Up2k(object):
 | 
			
		||||
            try:
 | 
			
		||||
                parser = MParser(parser)
 | 
			
		||||
            except:
 | 
			
		||||
                self.log("invalid argument: " + parser, 1)
 | 
			
		||||
                self.log("invalid argument (could not find program): " + parser, 1)
 | 
			
		||||
                return
 | 
			
		||||
 | 
			
		||||
            for tag in entags:
 | 
			
		||||
@@ -901,7 +901,7 @@ class Up2k(object):
 | 
			
		||||
            except:
 | 
			
		||||
                self.log("WARN: could not list files; DB corrupt?\n" + min_ex())
 | 
			
		||||
 | 
			
		||||
        elif ver > DB_VER:
 | 
			
		||||
        if (ver or 0) > DB_VER:
 | 
			
		||||
            m = "database is version {}, this copyparty only supports versions <= {}"
 | 
			
		||||
            raise Exception(m.format(ver, DB_VER))
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -1030,7 +1030,13 @@ def guess_mime(url, fallback="application/octet-stream"):
 | 
			
		||||
    except:
 | 
			
		||||
        return fallback
 | 
			
		||||
 | 
			
		||||
    return MIMES.get(ext) or mimetypes.guess_type(url)[0] or fallback
 | 
			
		||||
    ret = MIMES.get(ext) or mimetypes.guess_type(url)[0] or fallback
 | 
			
		||||
 | 
			
		||||
    if ";" not in ret:
 | 
			
		||||
        if ret.startswith("text/") or ret.endswith("/javascript"):
 | 
			
		||||
            ret += "; charset=UTF-8"
 | 
			
		||||
    
 | 
			
		||||
    return ret
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def runcmd(*argv):
 | 
			
		||||
 
 | 
			
		||||
@@ -119,7 +119,7 @@ window.baguetteBox = (function () {
 | 
			
		||||
            var gallery = [];
 | 
			
		||||
            [].forEach.call(tagsNodeList, function (imageElement, imageIndex) {
 | 
			
		||||
                var imageElementClickHandler = function (event) {
 | 
			
		||||
                    if (event && event.ctrlKey)
 | 
			
		||||
                    if (event && (event.ctrlKey || event.metaKey))
 | 
			
		||||
                        return true;
 | 
			
		||||
 | 
			
		||||
                    event.preventDefault ? event.preventDefault() : event.returnValue = false;
 | 
			
		||||
 
 | 
			
		||||
@@ -607,7 +607,7 @@ input.eq_gain {
 | 
			
		||||
#srch_q {
 | 
			
		||||
	white-space: pre;
 | 
			
		||||
	color: #f80;
 | 
			
		||||
	height: 1em;
 | 
			
		||||
	min-height: 1em;
 | 
			
		||||
	margin: .2em 0 -1em 1.6em;
 | 
			
		||||
}
 | 
			
		||||
#tq_raw {
 | 
			
		||||
@@ -811,10 +811,12 @@ input.eq_gain {
 | 
			
		||||
	padding: 0;
 | 
			
		||||
	border-bottom: 1px solid #555;
 | 
			
		||||
}
 | 
			
		||||
#thumbs {
 | 
			
		||||
#thumbs,
 | 
			
		||||
#au_osd_cv {
 | 
			
		||||
	opacity: .3;
 | 
			
		||||
}
 | 
			
		||||
#griden.on+#thumbs {
 | 
			
		||||
#griden.on+#thumbs,
 | 
			
		||||
#au_os_ctl.on+#au_osd_cv {
 | 
			
		||||
	opacity: 1;
 | 
			
		||||
}
 | 
			
		||||
#ghead {
 | 
			
		||||
 
 | 
			
		||||
@@ -110,7 +110,7 @@
 | 
			
		||||
	
 | 
			
		||||
	<div id="epi" class="logue">{{ logues[1] }}</div>
 | 
			
		||||
 | 
			
		||||
	<h2><a href="?h">control-panel</a></h2>
 | 
			
		||||
	<h2><a href="/?h">control-panel</a></h2>
 | 
			
		||||
 | 
			
		||||
</div>
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -78,7 +78,7 @@ ebi('op_up2k').innerHTML = (
 | 
			
		||||
	'	<tr>\n' +
 | 
			
		||||
	'		<td>\n' +
 | 
			
		||||
	'			<a href="#" id="nthread_sub">–</a><input\n' +
 | 
			
		||||
	'				class="txtbox" id="nthread" value="2"/><a\n' +
 | 
			
		||||
	'				class="txtbox" id="nthread" value="2" tt="pause uploads by setting it to 0"/><a\n' +
 | 
			
		||||
	'				href="#" id="nthread_add">+</a><br /> \n' +
 | 
			
		||||
	'		</td>\n' +
 | 
			
		||||
	'	</tr>\n' +
 | 
			
		||||
@@ -222,10 +222,14 @@ var have_webp = null;
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
var mpl = (function () {
 | 
			
		||||
	var have_mctl = 'mediaSession' in navigator && window.MediaMetadata;
 | 
			
		||||
 | 
			
		||||
	ebi('op_player').innerHTML = (
 | 
			
		||||
		'<div><h3>switches</h3><div>' +
 | 
			
		||||
		'<a href="#" class="tgl btn" id="au_preload" tt="start loading the next song near the end for gapless playback">preload</a>' +
 | 
			
		||||
		'<a href="#" class="tgl btn" id="au_npclip" tt="show buttons for clipboarding the currently playing song">/np clip</a>' +
 | 
			
		||||
		'<a href="#" class="tgl btn" id="au_os_ctl" tt="os integration (media hotkeys / osd)">os-ctl</a>' +
 | 
			
		||||
		'<a href="#" class="tgl btn" id="au_osd_cv" tt="show album cover in osd">osd-cv</a>' +
 | 
			
		||||
		'</div></div>' +
 | 
			
		||||
 | 
			
		||||
		'<div><h3>playback mode</h3><div id="pb_mode">' +
 | 
			
		||||
@@ -233,12 +237,18 @@ var mpl = (function () {
 | 
			
		||||
		'<a href="#" class="tgl btn" tt="load the next folder and continue">📂 next-folder</a>' +
 | 
			
		||||
		'</div></div>' +
 | 
			
		||||
 | 
			
		||||
		'<div><h3>tint</h3><div>' +
 | 
			
		||||
		'<input type="text" id="pb_tint" size="3" value="0" tt="background level (0-100) on the seekbar$Nto make buffering less distracting" />' +
 | 
			
		||||
		'</div></div>' +
 | 
			
		||||
 | 
			
		||||
		'<div><h3>audio equalizer</h3><div id="audio_eq"></div></div>');
 | 
			
		||||
 | 
			
		||||
	var r = {
 | 
			
		||||
		"pb_mode": sread('pb_mode') || 'loop-folder',
 | 
			
		||||
		"preload": bcfg_get('au_preload', true),
 | 
			
		||||
		"clip": bcfg_get('au_npclip', false)
 | 
			
		||||
		"clip": bcfg_get('au_npclip', false),
 | 
			
		||||
		"os_ctl": bcfg_get('au_os_ctl', have_mctl) && have_mctl,
 | 
			
		||||
		"osd_cv": bcfg_get('au_osd_cv', true),
 | 
			
		||||
	};
 | 
			
		||||
 | 
			
		||||
	ebi('au_preload').onclick = function (e) {
 | 
			
		||||
@@ -254,6 +264,20 @@ var mpl = (function () {
 | 
			
		||||
		clmod(ebi('wtoggle'), 'np', r.clip && mp.au);
 | 
			
		||||
	};
 | 
			
		||||
 | 
			
		||||
	ebi('au_os_ctl').onclick = function (e) {
 | 
			
		||||
		ev(e);
 | 
			
		||||
		r.os_ctl = !r.os_ctl && have_mctl;
 | 
			
		||||
		bcfg_set('au_os_ctl', r.os_ctl);
 | 
			
		||||
		if (!have_mctl)
 | 
			
		||||
			alert('need firefox 82+ or chrome 73+');
 | 
			
		||||
	};
 | 
			
		||||
 | 
			
		||||
	ebi('au_osd_cv').onclick = function (e) {
 | 
			
		||||
		ev(e);
 | 
			
		||||
		r.osd_cv = !r.osd_cv;
 | 
			
		||||
		bcfg_set('au_osd_cv', r.osd_cv);
 | 
			
		||||
	};
 | 
			
		||||
 | 
			
		||||
	function draw_pb_mode() {
 | 
			
		||||
		var btns = QSA('#pb_mode>a');
 | 
			
		||||
		for (var a = 0, aa = btns.length; a < aa; a++) {
 | 
			
		||||
@@ -270,20 +294,98 @@ var mpl = (function () {
 | 
			
		||||
		draw_pb_mode();
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	function set_tint() {
 | 
			
		||||
		var tint = icfg_get('pb_tint', 0);
 | 
			
		||||
		if (!tint)
 | 
			
		||||
			ebi('barbuf').style.removeProperty('background');
 | 
			
		||||
		else
 | 
			
		||||
			ebi('barbuf').style.background = 'rgba(126,163,75,' + (tint / 100.0) + ')';
 | 
			
		||||
	}
 | 
			
		||||
	ebi('pb_tint').oninput = function (e) {
 | 
			
		||||
		swrite('pb_tint', this.value);
 | 
			
		||||
		set_tint();
 | 
			
		||||
	};
 | 
			
		||||
	set_tint();
 | 
			
		||||
 | 
			
		||||
	r.pp = function () {
 | 
			
		||||
		if (!r.os_ctl)
 | 
			
		||||
			return;
 | 
			
		||||
 | 
			
		||||
		navigator.mediaSession.playbackState = mp.au && !mp.au.paused ? "playing" : "paused";
 | 
			
		||||
	};
 | 
			
		||||
 | 
			
		||||
	r.announce = function () {
 | 
			
		||||
		if (!r.os_ctl)
 | 
			
		||||
			return;
 | 
			
		||||
 | 
			
		||||
		var np = get_np()[0],
 | 
			
		||||
			fns = np.file.split(' - '),
 | 
			
		||||
			artist = (np.circle ? np.circle + ' // ' : '') + (np.artist || (fns.length > 1 ? fns[0] : '')),
 | 
			
		||||
			tags = {
 | 
			
		||||
				title: np.title || fns.slice(-1)[0]
 | 
			
		||||
			};
 | 
			
		||||
 | 
			
		||||
		if (artist)
 | 
			
		||||
			tags.artist = artist;
 | 
			
		||||
 | 
			
		||||
		if (np.album)
 | 
			
		||||
			tags.album = np.album;
 | 
			
		||||
 | 
			
		||||
		if (r.osd_cv) {
 | 
			
		||||
			var files = QSA("#files tr>td:nth-child(2)>a[id]"),
 | 
			
		||||
				cover = null;
 | 
			
		||||
 | 
			
		||||
			for (var a = 0, aa = files.length; a < aa; a++) {
 | 
			
		||||
				if (/^(cover|folder)\.(jpe?g|png|gif)$/.test(files[a].textContent)) {
 | 
			
		||||
					cover = files[a].getAttribute('href');
 | 
			
		||||
					break;
 | 
			
		||||
				}
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			if (cover) {
 | 
			
		||||
				cover += (cover.indexOf('?') === -1 ? '?' : '&') + 'th=j';
 | 
			
		||||
 | 
			
		||||
				var pwd = get_pwd();
 | 
			
		||||
				if (pwd)
 | 
			
		||||
					cover += '&pw=' + uricom_enc(pwd);
 | 
			
		||||
 | 
			
		||||
				tags.artwork = [{ "src": cover, type: "image/jpeg" }];
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		navigator.mediaSession.metadata = new MediaMetadata(tags);
 | 
			
		||||
		navigator.mediaSession.setActionHandler('play', playpause);
 | 
			
		||||
		navigator.mediaSession.setActionHandler('pause', playpause);
 | 
			
		||||
		navigator.mediaSession.setActionHandler('seekbackward', function () { seek_au_rel(-10); });
 | 
			
		||||
		navigator.mediaSession.setActionHandler('seekforward', function () { seek_au_rel(10); });
 | 
			
		||||
		navigator.mediaSession.setActionHandler('previoustrack', prev_song);
 | 
			
		||||
		navigator.mediaSession.setActionHandler('nexttrack', next_song);
 | 
			
		||||
		r.pp();
 | 
			
		||||
	};
 | 
			
		||||
 | 
			
		||||
	r.stop = function () {
 | 
			
		||||
		if (!r.os_ctl || !navigator.mediaSession.metadata)
 | 
			
		||||
			return;
 | 
			
		||||
 | 
			
		||||
		navigator.mediaSession.metadata = null;
 | 
			
		||||
		navigator.mediaSession.playbackState = "paused";
 | 
			
		||||
	};
 | 
			
		||||
 | 
			
		||||
	return r;
 | 
			
		||||
})();
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
// extract songs + add play column
 | 
			
		||||
function MPlayer() {
 | 
			
		||||
	this.id = Date.now();
 | 
			
		||||
	this.au = null;
 | 
			
		||||
	this.au_native = null;
 | 
			
		||||
	this.au_native2 = null;
 | 
			
		||||
	this.au_ogvjs = null;
 | 
			
		||||
	this.au_ogvjs2 = null;
 | 
			
		||||
	this.tracks = {};
 | 
			
		||||
	this.order = [];
 | 
			
		||||
	var r = this;
 | 
			
		||||
	r.id = Date.now();
 | 
			
		||||
	r.au = null;
 | 
			
		||||
	r.au_native = null;
 | 
			
		||||
	r.au_native2 = null;
 | 
			
		||||
	r.au_ogvjs = null;
 | 
			
		||||
	r.au_ogvjs2 = null;
 | 
			
		||||
	r.tracks = {};
 | 
			
		||||
	r.order = [];
 | 
			
		||||
 | 
			
		||||
	var re_audio = /\.(opus|ogg|m4a|aac|mp3|wav|flac)$/i,
 | 
			
		||||
		trs = QSA('#files tbody tr');
 | 
			
		||||
@@ -298,32 +400,33 @@ function MPlayer() {
 | 
			
		||||
 | 
			
		||||
		if (m) {
 | 
			
		||||
			var tid = link.getAttribute('id');
 | 
			
		||||
			this.order.push(tid);
 | 
			
		||||
			this.tracks[tid] = url;
 | 
			
		||||
			r.order.push(tid);
 | 
			
		||||
			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;
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	this.vol = sread('vol');
 | 
			
		||||
	if (this.vol !== null)
 | 
			
		||||
		this.vol = parseFloat(this.vol);
 | 
			
		||||
	r.vol = sread('vol');
 | 
			
		||||
	if (r.vol !== null)
 | 
			
		||||
		r.vol = parseFloat(r.vol);
 | 
			
		||||
	else
 | 
			
		||||
		this.vol = 0.5;
 | 
			
		||||
		r.vol = 0.5;
 | 
			
		||||
 | 
			
		||||
	this.expvol = function () {
 | 
			
		||||
		return 0.5 * this.vol + 0.5 * this.vol * this.vol;
 | 
			
		||||
	r.expvol = function (v) {
 | 
			
		||||
		return 0.5 * v + 0.5 * v * v;
 | 
			
		||||
	};
 | 
			
		||||
 | 
			
		||||
	this.setvol = function (vol) {
 | 
			
		||||
		this.vol = Math.max(Math.min(vol, 1), 0);
 | 
			
		||||
	r.setvol = function (vol) {
 | 
			
		||||
		r.vol = Math.max(Math.min(vol, 1), 0);
 | 
			
		||||
		swrite('vol', vol);
 | 
			
		||||
		r.stopfade(true);
 | 
			
		||||
 | 
			
		||||
		if (this.au)
 | 
			
		||||
			this.au.volume = this.expvol();
 | 
			
		||||
		if (r.au)
 | 
			
		||||
			r.au.volume = r.expvol(r.vol);
 | 
			
		||||
	};
 | 
			
		||||
 | 
			
		||||
	this.read_order = function () {
 | 
			
		||||
	r.read_order = function () {
 | 
			
		||||
		var order = [],
 | 
			
		||||
			links = QSA('#files>tbody>tr>td:nth-child(1)>a');
 | 
			
		||||
 | 
			
		||||
@@ -334,24 +437,71 @@ function MPlayer() {
 | 
			
		||||
 | 
			
		||||
			order.push(tid.slice(1));
 | 
			
		||||
		}
 | 
			
		||||
		this.order = order;
 | 
			
		||||
		r.order = order;
 | 
			
		||||
	};
 | 
			
		||||
 | 
			
		||||
	this.preload = function (url) {
 | 
			
		||||
	r.fdir = 0;
 | 
			
		||||
	r.fvol = -1;
 | 
			
		||||
	r.ftid = -1;
 | 
			
		||||
	r.ftimer = null;
 | 
			
		||||
	r.fade_in = function () {
 | 
			
		||||
		r.fvol = 0;
 | 
			
		||||
		r.fdir = 0.025;
 | 
			
		||||
		if (r.au) {
 | 
			
		||||
			r.ftid = r.au.tid;
 | 
			
		||||
			r.au.play();
 | 
			
		||||
			mpl.pp();
 | 
			
		||||
			fader();
 | 
			
		||||
		}
 | 
			
		||||
	};
 | 
			
		||||
	r.fade_out = function () {
 | 
			
		||||
		r.fvol = r.vol;
 | 
			
		||||
		r.fdir = -0.05;
 | 
			
		||||
		r.ftid = r.au.tid;
 | 
			
		||||
		fader();
 | 
			
		||||
	};
 | 
			
		||||
	r.stopfade = function (hard) {
 | 
			
		||||
		clearTimeout(r.ftimer);
 | 
			
		||||
		if (hard)
 | 
			
		||||
			r.ftid = -1;
 | 
			
		||||
	}
 | 
			
		||||
	function fader() {
 | 
			
		||||
		r.stopfade();
 | 
			
		||||
		if (!r.au || r.au.tid !== r.ftid)
 | 
			
		||||
			return;
 | 
			
		||||
 | 
			
		||||
		var done = true;
 | 
			
		||||
		r.fvol += r.fdir;
 | 
			
		||||
		if (r.fvol < 0) {
 | 
			
		||||
			r.fvol = 0;
 | 
			
		||||
			r.au.pause();
 | 
			
		||||
			mpl.pp();
 | 
			
		||||
		}
 | 
			
		||||
		else if (r.fvol > r.vol)
 | 
			
		||||
			r.fvol = r.vol;
 | 
			
		||||
		else
 | 
			
		||||
			done = false;
 | 
			
		||||
 | 
			
		||||
		r.au.volume = r.expvol(r.fvol);
 | 
			
		||||
		if (!done)
 | 
			
		||||
			setTimeout(fader, 10);
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	r.preload = function (url) {
 | 
			
		||||
		var au = null;
 | 
			
		||||
		if (need_ogv_for(url)) {
 | 
			
		||||
			au = mp.au_ogvjs2;
 | 
			
		||||
			if (!au && window['OGVPlayer']) {
 | 
			
		||||
				au = new OGVPlayer();
 | 
			
		||||
				au.preload = "auto";
 | 
			
		||||
				this.au_ogvjs2 = au;
 | 
			
		||||
				r.au_ogvjs2 = au;
 | 
			
		||||
			}
 | 
			
		||||
		} else {
 | 
			
		||||
			au = mp.au_native2;
 | 
			
		||||
			if (!au) {
 | 
			
		||||
				au = new Audio();
 | 
			
		||||
				au.preload = "auto";
 | 
			
		||||
				this.au_native2 = au;
 | 
			
		||||
				r.au_native2 = au;
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
		if (au) {
 | 
			
		||||
@@ -365,39 +515,62 @@ var mp = new MPlayer();
 | 
			
		||||
makeSortable(ebi('files'), mp.read_order.bind(mp));
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
function get_np() {
 | 
			
		||||
	var th = ebi('files').tHead.rows[0].cells,
 | 
			
		||||
		tr = QS('#files tr.play').cells,
 | 
			
		||||
		rv = [],
 | 
			
		||||
		ra = [],
 | 
			
		||||
		rt = {};
 | 
			
		||||
 | 
			
		||||
	for (var a = 1, aa = th.length; a < aa; a++) {
 | 
			
		||||
		var tv = tr[a].textContent,
 | 
			
		||||
			tk = a == 1 ? 'file' : th[a].getAttribute('name').split('/').slice(-1)[0],
 | 
			
		||||
			vis = th[a].className.indexOf('min') === -1;
 | 
			
		||||
 | 
			
		||||
		if (!tv)
 | 
			
		||||
			continue;
 | 
			
		||||
 | 
			
		||||
		(vis ? rv : ra).push(tk);
 | 
			
		||||
		rt[tk] = tv;
 | 
			
		||||
	}
 | 
			
		||||
	return [rt, rv, ra];
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
// toggle player widget
 | 
			
		||||
var widget = (function () {
 | 
			
		||||
	var ret = {},
 | 
			
		||||
	var r = {},
 | 
			
		||||
		widget = ebi('widget'),
 | 
			
		||||
		wtico = ebi('wtico'),
 | 
			
		||||
		nptxt = ebi('nptxt'),
 | 
			
		||||
		npirc = ebi('npirc'),
 | 
			
		||||
		touchmode = false,
 | 
			
		||||
		side_open = false,
 | 
			
		||||
		was_paused = true;
 | 
			
		||||
 | 
			
		||||
	ret.open = function () {
 | 
			
		||||
		if (side_open)
 | 
			
		||||
	r.is_open = false;
 | 
			
		||||
 | 
			
		||||
	r.open = function () {
 | 
			
		||||
		if (r.is_open)
 | 
			
		||||
			return false;
 | 
			
		||||
 | 
			
		||||
		widget.className = 'open';
 | 
			
		||||
		side_open = true;
 | 
			
		||||
		r.is_open = true;
 | 
			
		||||
		return true;
 | 
			
		||||
	};
 | 
			
		||||
	ret.close = function () {
 | 
			
		||||
		if (!side_open)
 | 
			
		||||
	r.close = function () {
 | 
			
		||||
		if (!r.is_open)
 | 
			
		||||
			return false;
 | 
			
		||||
 | 
			
		||||
		widget.className = '';
 | 
			
		||||
		side_open = false;
 | 
			
		||||
		r.is_open = false;
 | 
			
		||||
		return true;
 | 
			
		||||
	};
 | 
			
		||||
	ret.toggle = function (e) {
 | 
			
		||||
		ret.open() || ret.close();
 | 
			
		||||
	r.toggle = function (e) {
 | 
			
		||||
		r.open() || r.close();
 | 
			
		||||
		ev(e);
 | 
			
		||||
		return false;
 | 
			
		||||
	};
 | 
			
		||||
	ret.paused = function (paused) {
 | 
			
		||||
	r.paused = function (paused) {
 | 
			
		||||
		if (was_paused != paused) {
 | 
			
		||||
			was_paused = paused;
 | 
			
		||||
			ebi('bplay').innerHTML = paused ? '▶' : '⏸';
 | 
			
		||||
@@ -405,28 +578,22 @@ var widget = (function () {
 | 
			
		||||
	};
 | 
			
		||||
	wtico.onclick = function (e) {
 | 
			
		||||
		if (!touchmode)
 | 
			
		||||
			ret.toggle(e);
 | 
			
		||||
			r.toggle(e);
 | 
			
		||||
 | 
			
		||||
		return false;
 | 
			
		||||
	};
 | 
			
		||||
	npirc.onclick = nptxt.onclick = function (e) {
 | 
			
		||||
		ev(e);
 | 
			
		||||
		var th = ebi('files').tHead.rows[0].cells,
 | 
			
		||||
			tr = QS('#files tr.play').cells,
 | 
			
		||||
			irc = this.getAttribute('id') == 'npirc',
 | 
			
		||||
		var irc = this.getAttribute('id') == 'npirc',
 | 
			
		||||
			ck = irc ? '06' : '',
 | 
			
		||||
			cv = irc ? '07' : '',
 | 
			
		||||
			m = ck + 'np: ';
 | 
			
		||||
			m = ck + 'np: ',
 | 
			
		||||
			npr = get_np(),
 | 
			
		||||
			npk = npr[1],
 | 
			
		||||
			np = npr[0];
 | 
			
		||||
 | 
			
		||||
		for (var a = 1, aa = th.length; a < aa; a++) {
 | 
			
		||||
			if (th[a].className.indexOf('min') !== -1)
 | 
			
		||||
				continue;
 | 
			
		||||
 | 
			
		||||
			var tv = tr[a].textContent,
 | 
			
		||||
				tk = a == 1 ? '' : th[a].getAttribute('name').split('/').slice(-1)[0];
 | 
			
		||||
 | 
			
		||||
			m += tk + '(' + cv + tv + ck + ') // ';
 | 
			
		||||
		}
 | 
			
		||||
		for (var a = 0; a < npk.length; a++)
 | 
			
		||||
			m += (npk[a] == 'file' ? '' : npk[a]) + '(' + cv + np[npk[a]] + ck + ') // ';
 | 
			
		||||
 | 
			
		||||
		m += '[' + cv + s2ms(mp.au.currentTime) + ck + '/' + cv + s2ms(mp.au.duration) + ck + ']';
 | 
			
		||||
 | 
			
		||||
@@ -442,7 +609,7 @@ var widget = (function () {
 | 
			
		||||
			document.body.removeChild(o);
 | 
			
		||||
		}, 500);
 | 
			
		||||
	};
 | 
			
		||||
	return ret;
 | 
			
		||||
	return r;
 | 
			
		||||
})();
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@@ -488,12 +655,15 @@ var pbar = (function () {
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	r.drawbuf = function () {
 | 
			
		||||
		var bc = r.buf,
 | 
			
		||||
			bctx = bc.ctx;
 | 
			
		||||
 | 
			
		||||
		bctx.clearRect(0, 0, bc.w, bc.h);
 | 
			
		||||
 | 
			
		||||
		if (!mp.au)
 | 
			
		||||
			return;
 | 
			
		||||
 | 
			
		||||
		var bc = r.buf,
 | 
			
		||||
			bctx = bc.ctx,
 | 
			
		||||
			sm = bc.w * 1.0 / mp.au.duration,
 | 
			
		||||
		var sm = bc.w * 1.0 / mp.au.duration,
 | 
			
		||||
			gk = bc.h + '' + light;
 | 
			
		||||
 | 
			
		||||
		if (gradh != gk) {
 | 
			
		||||
@@ -501,7 +671,6 @@ var pbar = (function () {
 | 
			
		||||
			grad = glossy_grad(bc, 85, [35, 40, 37, 35], light ? [45, 56, 50, 45] : [42, 51, 47, 42]);
 | 
			
		||||
		}
 | 
			
		||||
		bctx.fillStyle = grad;
 | 
			
		||||
		bctx.clearRect(0, 0, bc.w, bc.h);
 | 
			
		||||
		for (var a = 0; a < mp.au.buffered.length; a++) {
 | 
			
		||||
			var x1 = sm * mp.au.buffered.start(a),
 | 
			
		||||
				x2 = sm * mp.au.buffered.end(a);
 | 
			
		||||
@@ -511,15 +680,17 @@ var pbar = (function () {
 | 
			
		||||
	};
 | 
			
		||||
 | 
			
		||||
	r.drawpos = function () {
 | 
			
		||||
		var bc = r.buf,
 | 
			
		||||
			pc = r.pos,
 | 
			
		||||
			pctx = pc.ctx;
 | 
			
		||||
 | 
			
		||||
		pctx.clearRect(0, 0, pc.w, pc.h);
 | 
			
		||||
 | 
			
		||||
		if (!mp.au || isNaN(mp.au.duration) || isNaN(mp.au.currentTime))
 | 
			
		||||
			return;  // not-init || unsupp-codec
 | 
			
		||||
 | 
			
		||||
		var bc = r.buf,
 | 
			
		||||
			pc = r.pos,
 | 
			
		||||
			pctx = pc.ctx,
 | 
			
		||||
			sm = bc.w * 1.0 / mp.au.duration;
 | 
			
		||||
		var sm = bc.w * 1.0 / mp.au.duration;
 | 
			
		||||
 | 
			
		||||
		pctx.clearRect(0, 0, pc.w, pc.h);
 | 
			
		||||
		pctx.fillStyle = light ? 'rgba(0,64,0,0.15)' : 'rgba(204,255,128,0.15)';
 | 
			
		||||
		for (var p = 1, mins = mp.au.duration / 10; p <= mins; p++)
 | 
			
		||||
			pctx.fillRect(Math.floor(sm * p * 10), 0, 2, pc.h);
 | 
			
		||||
@@ -635,6 +806,11 @@ function seek_au_mul(mul) {
 | 
			
		||||
		seek_au_sec(mp.au.duration * mul);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
function seek_au_rel(sec) {
 | 
			
		||||
	if (mp.au)
 | 
			
		||||
		seek_au_sec(mp.au.currentTime + sec);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
function seek_au_sec(seek) {
 | 
			
		||||
	if (!mp.au)
 | 
			
		||||
		return;
 | 
			
		||||
@@ -645,9 +821,8 @@ function seek_au_sec(seek) {
 | 
			
		||||
 | 
			
		||||
	mp.au.currentTime = seek;
 | 
			
		||||
 | 
			
		||||
	// ogv.js breaks on .play() during playback
 | 
			
		||||
	if (mp.au === mp.au_native)
 | 
			
		||||
		mp.au.play();
 | 
			
		||||
	if (mp.au.paused)
 | 
			
		||||
		mp.fade_in();
 | 
			
		||||
 | 
			
		||||
	mpui.progress_updater();
 | 
			
		||||
}
 | 
			
		||||
@@ -669,22 +844,29 @@ function next_song(e) {
 | 
			
		||||
}
 | 
			
		||||
function prev_song(e) {
 | 
			
		||||
	ev(e);
 | 
			
		||||
 | 
			
		||||
	if (mp.au && !mp.au.paused && mp.au.currentTime > 3)
 | 
			
		||||
		return seek_au_sec(0);
 | 
			
		||||
 | 
			
		||||
	return song_skip(-1);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
function playpause(e) {
 | 
			
		||||
	// must be event-chain
 | 
			
		||||
	ev(e);
 | 
			
		||||
	if (mp.au) {
 | 
			
		||||
		if (mp.au.paused)
 | 
			
		||||
			mp.au.play();
 | 
			
		||||
			mp.fade_in();
 | 
			
		||||
		else
 | 
			
		||||
			mp.au.pause();
 | 
			
		||||
			mp.fade_out();
 | 
			
		||||
 | 
			
		||||
		mpui.progress_updater();
 | 
			
		||||
	}
 | 
			
		||||
	else
 | 
			
		||||
		play(0);
 | 
			
		||||
		play(0, true);
 | 
			
		||||
 | 
			
		||||
	mpl.pp();
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@@ -693,9 +875,13 @@ function playpause(e) {
 | 
			
		||||
	ebi('bplay').onclick = playpause;
 | 
			
		||||
	ebi('bprev').onclick = prev_song;
 | 
			
		||||
	ebi('bnext').onclick = next_song;
 | 
			
		||||
	ebi('barpos').onclick = function (e) {
 | 
			
		||||
 | 
			
		||||
	var bar = ebi('barpos');
 | 
			
		||||
 | 
			
		||||
	bar.onclick = function (e) {
 | 
			
		||||
		if (!mp.au) {
 | 
			
		||||
			return play(0);
 | 
			
		||||
			play(0, true);
 | 
			
		||||
			return mp.fade_in();
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		var rect = pbar.buf.can.getBoundingClientRect(),
 | 
			
		||||
@@ -703,6 +889,19 @@ function playpause(e) {
 | 
			
		||||
 | 
			
		||||
		seek_au_mul(x * 1.0 / rect.width);
 | 
			
		||||
	};
 | 
			
		||||
 | 
			
		||||
	if (!is_touch)
 | 
			
		||||
		bar.onwheel = function (e) {
 | 
			
		||||
			var dist = Math.sign(e.deltaY) * 10;
 | 
			
		||||
			if (Math.abs(e.deltaY) < 30 && !e.deltaMode)
 | 
			
		||||
				dist = e.deltaY;
 | 
			
		||||
 | 
			
		||||
			if (!dist || !mp.au)
 | 
			
		||||
				return true;
 | 
			
		||||
 | 
			
		||||
			seek_au_rel(dist);
 | 
			
		||||
			ev(e);
 | 
			
		||||
		};
 | 
			
		||||
})();
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@@ -770,7 +969,12 @@ var mpui = (function () {
 | 
			
		||||
// event from play button next to a file in the list
 | 
			
		||||
function ev_play(e) {
 | 
			
		||||
	ev(e);
 | 
			
		||||
	play(this.getAttribute('id').slice(1));
 | 
			
		||||
 | 
			
		||||
	var fade = !mp.au || mp.au.paused;
 | 
			
		||||
	play(this.getAttribute('id').slice(1), true);
 | 
			
		||||
	if (fade)
 | 
			
		||||
		mp.fade_in();
 | 
			
		||||
 | 
			
		||||
	return false;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@@ -1009,13 +1213,18 @@ var audio_eq = (function () {
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
// plays the tid'th audio file on the page
 | 
			
		||||
function play(tid, seek, call_depth) {
 | 
			
		||||
function play(tid, is_ev, seek, call_depth) {
 | 
			
		||||
	if (mp.order.length == 0)
 | 
			
		||||
		return console.log('no audio found wait what');
 | 
			
		||||
 | 
			
		||||
	mp.stopfade(true);
 | 
			
		||||
 | 
			
		||||
	var tn = tid;
 | 
			
		||||
	if ((tn + '').indexOf('f-') === 0)
 | 
			
		||||
	if ((tn + '').indexOf('f-') === 0) {
 | 
			
		||||
		tn = mp.order.indexOf(tn);
 | 
			
		||||
		if (tn < 0)
 | 
			
		||||
			return;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if (tn >= mp.order.length) {
 | 
			
		||||
		if (mpl.pb_mode == 'loop-folder') {
 | 
			
		||||
@@ -1054,7 +1263,7 @@ function play(tid, seek, call_depth) {
 | 
			
		||||
		}
 | 
			
		||||
		else if (window['OGVPlayer']) {
 | 
			
		||||
			mp.au = mp.au_ogvjs = new OGVPlayer();
 | 
			
		||||
			attempt_play = false;
 | 
			
		||||
			attempt_play = is_ev;
 | 
			
		||||
			mp.au.addEventListener('error', evau_error, true);
 | 
			
		||||
			mp.au.addEventListener('progress', pbar.drawpos);
 | 
			
		||||
			mp.au.addEventListener('ended', next_song);
 | 
			
		||||
@@ -1067,7 +1276,7 @@ function play(tid, seek, call_depth) {
 | 
			
		||||
			show_modal('<h1>loading ogv.js</h1><h2>thanks apple</h2>');
 | 
			
		||||
 | 
			
		||||
			import_js('/.cpr/deps/ogv.js', function () {
 | 
			
		||||
				play(tid, seek, 1);
 | 
			
		||||
				play(tid, false, seek, 1);
 | 
			
		||||
			});
 | 
			
		||||
 | 
			
		||||
			return;
 | 
			
		||||
@@ -1088,7 +1297,7 @@ function play(tid, seek, call_depth) {
 | 
			
		||||
 | 
			
		||||
	mp.au.tid = tid;
 | 
			
		||||
	mp.au.src = url + (url.indexOf('?') < 0 ? '?cache' : '&cache');
 | 
			
		||||
	mp.au.volume = mp.expvol();
 | 
			
		||||
	mp.au.volume = mp.expvol(mp.vol);
 | 
			
		||||
	var oid = 'a' + tid;
 | 
			
		||||
	setclass(oid, 'play act');
 | 
			
		||||
	var trs = ebi('files').getElementsByTagName('tbody')[0].getElementsByTagName('tr');
 | 
			
		||||
@@ -1124,6 +1333,7 @@ function play(tid, seek, call_depth) {
 | 
			
		||||
 | 
			
		||||
		mpui.progress_updater();
 | 
			
		||||
		pbar.drawbuf();
 | 
			
		||||
		mpl.announce();
 | 
			
		||||
		return true;
 | 
			
		||||
	}
 | 
			
		||||
	catch (ex) {
 | 
			
		||||
@@ -1178,7 +1388,8 @@ function show_modal(html) {
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
// hide fullscreen message
 | 
			
		||||
function unblocked() {
 | 
			
		||||
function unblocked(e) {
 | 
			
		||||
	ev(e);
 | 
			
		||||
	var dom = ebi('blocked');
 | 
			
		||||
	if (dom)
 | 
			
		||||
		dom.parentNode.removeChild(dom);
 | 
			
		||||
@@ -1193,26 +1404,25 @@ function autoplay_blocked(seek) {
 | 
			
		||||
 | 
			
		||||
	var go = ebi('blk_go'),
 | 
			
		||||
		na = ebi('blk_na'),
 | 
			
		||||
		fn = mp.tracks[mp.au.tid].split(/\//).pop();
 | 
			
		||||
		tid = mp.au.tid,
 | 
			
		||||
		fn = mp.tracks[tid].split(/\//).pop();
 | 
			
		||||
 | 
			
		||||
	fn = uricom_dec(fn.replace(/\+/g, ' '))[0];
 | 
			
		||||
 | 
			
		||||
	go.textContent = 'Play "' + fn + '"';
 | 
			
		||||
	go.onclick = function (e) {
 | 
			
		||||
		if (e) e.preventDefault();
 | 
			
		||||
		unblocked();
 | 
			
		||||
		mp.au.play();
 | 
			
		||||
		if (seek)
 | 
			
		||||
			seek_au_sec(seek);
 | 
			
		||||
		else
 | 
			
		||||
			mpui.progress_updater();
 | 
			
		||||
		unblocked(e);
 | 
			
		||||
		// chrome 91 may permanently taint on a failed play()
 | 
			
		||||
		// depending on win10 settings or something? idk
 | 
			
		||||
		mp.au_native = mp.au_ogvjs = null;
 | 
			
		||||
		play(tid, true, seek);
 | 
			
		||||
		mp.fade_in();
 | 
			
		||||
	};
 | 
			
		||||
	na.onclick = unblocked;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
// autoplay linked track
 | 
			
		||||
(function () {
 | 
			
		||||
function play_linked() {
 | 
			
		||||
	var v = location.hash;
 | 
			
		||||
	if (v && v.indexOf('#af-') === 0) {
 | 
			
		||||
		var id = v.slice(2).split('&');
 | 
			
		||||
@@ -1226,9 +1436,9 @@ function autoplay_blocked(seek) {
 | 
			
		||||
		if (!m)
 | 
			
		||||
			return play(id[0]);
 | 
			
		||||
 | 
			
		||||
		return play(id[0], parseInt(m[1] || 0) * 60 + parseInt(m[2] || 0));
 | 
			
		||||
		return play(id[0], false, parseInt(m[1] || 0) * 60 + parseInt(m[2] || 0));
 | 
			
		||||
	}
 | 
			
		||||
})();
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
var thegrid = (function () {
 | 
			
		||||
@@ -1334,35 +1544,57 @@ var thegrid = (function () {
 | 
			
		||||
	}
 | 
			
		||||
	setsz();
 | 
			
		||||
 | 
			
		||||
	function seltgl(e) {
 | 
			
		||||
		if (e && e.ctrlKey)
 | 
			
		||||
	function gclick(e) {
 | 
			
		||||
		if (e && (e.ctrlKey || e.metaKey))
 | 
			
		||||
			return true;
 | 
			
		||||
 | 
			
		||||
		ev(e);
 | 
			
		||||
		var oth = ebi(this.getAttribute('ref')),
 | 
			
		||||
			td = oth.parentNode.nextSibling,
 | 
			
		||||
			href = this.getAttribute('href'),
 | 
			
		||||
			aplay = ebi('a' + oth.getAttribute('id')),
 | 
			
		||||
			is_img = /\.(gif|jpe?g|png|webp)(\?|$)/i.test(href),
 | 
			
		||||
			in_tree = null,
 | 
			
		||||
			have_sel = QS('#files tr.sel'),
 | 
			
		||||
			td = oth.closest('td').nextSibling,
 | 
			
		||||
			tr = td.parentNode;
 | 
			
		||||
 | 
			
		||||
		if (/\/(\?|$)/.test(href)) {
 | 
			
		||||
			var ta = QSA('#treeul a.hl+ul>li>a+a'),
 | 
			
		||||
				txt = oth.textContent.slice(0, -1);
 | 
			
		||||
 | 
			
		||||
			for (var a = 0, aa = ta.length; a < aa; a++) {
 | 
			
		||||
				if (ta[a].textContent == txt) {
 | 
			
		||||
					in_tree = ta[a];
 | 
			
		||||
					break;
 | 
			
		||||
				}
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		if (r.sel) {
 | 
			
		||||
			td.click();
 | 
			
		||||
			this.setAttribute('class', tr.getAttribute('class'));
 | 
			
		||||
		}
 | 
			
		||||
		else if (widget.is_open && aplay)
 | 
			
		||||
			aplay.click();
 | 
			
		||||
 | 
			
		||||
	function bgopen(e) {
 | 
			
		||||
		else if (in_tree && !have_sel)
 | 
			
		||||
			in_tree.click();
 | 
			
		||||
 | 
			
		||||
		else if (!is_img && have_sel)
 | 
			
		||||
			window.open(href, '_blank');
 | 
			
		||||
 | 
			
		||||
		else return true;
 | 
			
		||||
		ev(e);
 | 
			
		||||
		var url = this.getAttribute('href');
 | 
			
		||||
		window.open(url, '_blank');
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	r.loadsel = function () {
 | 
			
		||||
		if (r.dirty)
 | 
			
		||||
			return;
 | 
			
		||||
 | 
			
		||||
		var ths = QSA('#ggrid>a'),
 | 
			
		||||
			have_sel = !!QS('#files tr.sel');
 | 
			
		||||
		var ths = QSA('#ggrid>a');
 | 
			
		||||
 | 
			
		||||
		for (var a = 0, aa = ths.length; a < aa; a++) {
 | 
			
		||||
			ths[a].onclick = r.sel ? seltgl : have_sel ? bgopen : null;
 | 
			
		||||
			ths[a].setAttribute('class', ebi(ths[a].getAttribute('ref')).parentNode.parentNode.getAttribute('class'));
 | 
			
		||||
			var tr = ebi(ths[a].getAttribute('ref')).closest('tr');
 | 
			
		||||
			ths[a].setAttribute('class', tr.getAttribute('class'));
 | 
			
		||||
		}
 | 
			
		||||
		var uns = QS('#ggrid a[ref="unsearch"]');
 | 
			
		||||
		if (uns)
 | 
			
		||||
@@ -1393,6 +1625,8 @@ var thegrid = (function () {
 | 
			
		||||
 | 
			
		||||
			if (r.thumbs) {
 | 
			
		||||
				ihref += (ihref.indexOf('?') === -1 ? '?' : '&') + 'th=' + (have_webp ? 'w' : 'j');
 | 
			
		||||
				if (href == "#")
 | 
			
		||||
					ihref = '/.cpr/ico/⏏️';
 | 
			
		||||
			}
 | 
			
		||||
			else if (isdir) {
 | 
			
		||||
				ihref = '/.cpr/ico/folder';
 | 
			
		||||
@@ -1420,6 +1654,11 @@ var thegrid = (function () {
 | 
			
		||||
				ihref + '" /><span' + ac + '>' + ao.innerHTML + '</span></a>');
 | 
			
		||||
		}
 | 
			
		||||
		ebi('ggrid').innerHTML = html.join('\n');
 | 
			
		||||
 | 
			
		||||
		var ths = QSA('#ggrid>a');
 | 
			
		||||
		for (var a = 0, aa = ths.length; a < aa; a++)
 | 
			
		||||
			ths[a].onclick = gclick;
 | 
			
		||||
 | 
			
		||||
		r.dirty = false;
 | 
			
		||||
		r.bagit();
 | 
			
		||||
		r.loadsel();
 | 
			
		||||
@@ -1507,26 +1746,32 @@ document.onkeydown = function (e) {
 | 
			
		||||
	if (!document.activeElement || document.activeElement != document.body && document.activeElement.nodeName.toLowerCase() != 'a')
 | 
			
		||||
		return;
 | 
			
		||||
 | 
			
		||||
	if (e.ctrlKey || e.altKey || e.shiftKey || e.metaKey || e.isComposing)
 | 
			
		||||
	if (e.ctrlKey || e.altKey || e.metaKey || e.isComposing)
 | 
			
		||||
		return;
 | 
			
		||||
 | 
			
		||||
	var k = (e.code + ''), pos = -1, n;
 | 
			
		||||
 | 
			
		||||
	if (e.shiftKey && k != 'KeyA' && k != 'KeyD')
 | 
			
		||||
		return;
 | 
			
		||||
 | 
			
		||||
	var k = (e.code + ''), pos = -1;
 | 
			
		||||
	if (k.indexOf('Digit') === 0)
 | 
			
		||||
		pos = parseInt(k.slice(-1)) * 0.1;
 | 
			
		||||
 | 
			
		||||
	if (pos !== -1)
 | 
			
		||||
		return seek_au_mul(pos);
 | 
			
		||||
		return seek_au_mul(pos) || true;
 | 
			
		||||
 | 
			
		||||
	var n = k == 'KeyJ' ? -1 : k == 'KeyL' ? 1 : 0;
 | 
			
		||||
	if (n !== 0)
 | 
			
		||||
		return song_skip(n);
 | 
			
		||||
	if (k == 'KeyJ')
 | 
			
		||||
		return prev_song() || true;
 | 
			
		||||
 | 
			
		||||
	if (k == 'KeyL')
 | 
			
		||||
		return next_song() || true;
 | 
			
		||||
 | 
			
		||||
	if (k == 'KeyP')
 | 
			
		||||
		return playpause();
 | 
			
		||||
		return playpause() || true;
 | 
			
		||||
 | 
			
		||||
	n = k == 'KeyU' ? -10 : k == 'KeyO' ? 10 : 0;
 | 
			
		||||
	if (n !== 0)
 | 
			
		||||
		return mp.au ? seek_au_sec(mp.au.currentTime + n) : true;
 | 
			
		||||
		return seek_au_rel(n) || true;
 | 
			
		||||
 | 
			
		||||
	n = k == 'KeyI' ? -1 : k == 'KeyK' ? 1 : 0;
 | 
			
		||||
	if (n !== 0)
 | 
			
		||||
@@ -1536,7 +1781,6 @@ document.onkeydown = function (e) {
 | 
			
		||||
		return tree_up();
 | 
			
		||||
 | 
			
		||||
	if (k == 'KeyB')
 | 
			
		||||
		//return treectl.hidden ? treectl.show() : treectl.hide();
 | 
			
		||||
		return treectl.hidden ? treectl.entree() : treectl.detree();
 | 
			
		||||
 | 
			
		||||
	if (k == 'KeyG')
 | 
			
		||||
@@ -1545,6 +1789,14 @@ document.onkeydown = function (e) {
 | 
			
		||||
	if (k == 'KeyT')
 | 
			
		||||
		return ebi('thumbs').click();
 | 
			
		||||
 | 
			
		||||
	if (!treectl.hidden && (!e.shiftKey || !thegrid.en)) {
 | 
			
		||||
		if (k == 'KeyA')
 | 
			
		||||
			return QS('#twig').click();
 | 
			
		||||
 | 
			
		||||
		if (k == 'KeyD')
 | 
			
		||||
			return QS('#twobytwo').click();
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if (thegrid.en) {
 | 
			
		||||
		if (k == 'KeyS')
 | 
			
		||||
			return ebi('gridsel').click();
 | 
			
		||||
@@ -1627,6 +1879,7 @@ document.onkeydown = function (e) {
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	var search_timeout,
 | 
			
		||||
		defer_timeout,
 | 
			
		||||
		search_in_progress = 0;
 | 
			
		||||
 | 
			
		||||
	function ev_search_input() {
 | 
			
		||||
@@ -1641,10 +1894,30 @@ document.onkeydown = function (e) {
 | 
			
		||||
		if (id != "q_raw")
 | 
			
		||||
			encode_query();
 | 
			
		||||
 | 
			
		||||
		set_vq();
 | 
			
		||||
 | 
			
		||||
		clearTimeout(defer_timeout);
 | 
			
		||||
		defer_timeout = setTimeout(try_search, 2000);
 | 
			
		||||
		try_search();
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	function try_search() {
 | 
			
		||||
		if (Date.now() - search_in_progress > 30 * 1000) {
 | 
			
		||||
			clearTimeout(defer_timeout);
 | 
			
		||||
			clearTimeout(search_timeout);
 | 
			
		||||
		if (Date.now() - search_in_progress > 30 * 1000)
 | 
			
		||||
			search_timeout = setTimeout(do_search, 200);
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	function set_vq() {
 | 
			
		||||
		if (search_in_progress)
 | 
			
		||||
			return;
 | 
			
		||||
 | 
			
		||||
		var q = ebi('q_raw').value,
 | 
			
		||||
			vq = ebi('files').getAttribute('q_raw');
 | 
			
		||||
 | 
			
		||||
		srch_msg(false, (q == vq) ? '' : 'search results below are from a previous query:\n  ' + (vq ? vq : '(*)'));
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	function encode_query() {
 | 
			
		||||
		var q = '';
 | 
			
		||||
@@ -1713,7 +1986,8 @@ document.onkeydown = function (e) {
 | 
			
		||||
		xhr.setRequestHeader('Content-Type', 'text/plain');
 | 
			
		||||
		xhr.onreadystatechange = xhr_search_results;
 | 
			
		||||
		xhr.ts = Date.now();
 | 
			
		||||
		xhr.send(JSON.stringify({ "q": ebi('q_raw').value }));
 | 
			
		||||
		xhr.q_raw = ebi('q_raw').value;
 | 
			
		||||
		xhr.send(JSON.stringify({ "q": xhr.q_raw }));
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	function xhr_search_results() {
 | 
			
		||||
@@ -1784,6 +2058,8 @@ document.onkeydown = function (e) {
 | 
			
		||||
 | 
			
		||||
		ofiles.innerHTML = html.join('\n');
 | 
			
		||||
		ofiles.setAttribute("ts", this.ts);
 | 
			
		||||
		ofiles.setAttribute("q_raw", this.q_raw);
 | 
			
		||||
		set_vq();
 | 
			
		||||
		mukey.render();
 | 
			
		||||
		reload_browser();
 | 
			
		||||
		filecols.set_style(['File Name']);
 | 
			
		||||
@@ -1795,6 +2071,7 @@ document.onkeydown = function (e) {
 | 
			
		||||
		ev(e);
 | 
			
		||||
		treectl.show();
 | 
			
		||||
		ebi('files').innerHTML = orig_html;
 | 
			
		||||
		ebi('files').removeAttribute('q_raw');
 | 
			
		||||
		orig_html = null;
 | 
			
		||||
		msel.render();
 | 
			
		||||
		reload_browser();
 | 
			
		||||
@@ -2013,6 +2290,9 @@ var treectl = (function () {
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	function treego(e) {
 | 
			
		||||
		if (e && (e.ctrlKey || e.metaKey))
 | 
			
		||||
			return true;
 | 
			
		||||
 | 
			
		||||
		ev(e);
 | 
			
		||||
		if (this.getAttribute('class') == 'hl' &&
 | 
			
		||||
			this.previousSibling.textContent == '-') {
 | 
			
		||||
@@ -2743,8 +3023,10 @@ function reload_mp() {
 | 
			
		||||
		mp.au.pause();
 | 
			
		||||
		mp.au = null;
 | 
			
		||||
	}
 | 
			
		||||
	mpl.stop();
 | 
			
		||||
	widget.close();
 | 
			
		||||
	mp = new MPlayer();
 | 
			
		||||
	setTimeout(pbar.onresize, 1);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@@ -2788,3 +3070,4 @@ function reload_browser(not_mp) {
 | 
			
		||||
reload_browser(true);
 | 
			
		||||
mukey.render();
 | 
			
		||||
msel.render();
 | 
			
		||||
play_linked();
 | 
			
		||||
 
 | 
			
		||||
@@ -54,7 +54,7 @@
 | 
			
		||||
	<div>{{ logues[1] }}</div><br />
 | 
			
		||||
	{%- endif %}
 | 
			
		||||
	
 | 
			
		||||
	<h2><a href="{{ url_suf }}{{ url_suf and '&' or '?' }}h">control-panel</a></h2>
 | 
			
		||||
	<h2><a href="/{{ url_suf }}{{ url_suf and '&' or '?' }}h">control-panel</a></h2>
 | 
			
		||||
 | 
			
		||||
</body>
 | 
			
		||||
</html>
 | 
			
		||||
 
 | 
			
		||||
@@ -1,7 +1,5 @@
 | 
			
		||||
"use strict";
 | 
			
		||||
 | 
			
		||||
window.onerror = vis_exh;
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
function goto_up2k() {
 | 
			
		||||
    if (up2k === false)
 | 
			
		||||
@@ -740,9 +738,17 @@ function up2k_init(subtle) {
 | 
			
		||||
 | 
			
		||||
    function handshakes_permitted() {
 | 
			
		||||
        var lim = multitask ? 1 : 0;
 | 
			
		||||
        return lim >=
 | 
			
		||||
 | 
			
		||||
        if (lim <
 | 
			
		||||
            st.todo.upload.length +
 | 
			
		||||
            st.busy.upload.length;
 | 
			
		||||
            st.busy.upload.length)
 | 
			
		||||
            return false;
 | 
			
		||||
 | 
			
		||||
        var cd = st.todo.handshake.length ? st.todo.handshake[0].cooldown : 0;
 | 
			
		||||
        if (cd && cd - Date.now() > 0)
 | 
			
		||||
            return false;
 | 
			
		||||
 | 
			
		||||
        return true;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    function hashing_permitted() {
 | 
			
		||||
@@ -1155,6 +1161,15 @@ function up2k_init(subtle) {
 | 
			
		||||
                if (rsp.indexOf('<pre>') === 0)
 | 
			
		||||
                    rsp = rsp.slice(5);
 | 
			
		||||
 | 
			
		||||
                if (rsp.indexOf('rate-limit ') !== -1) {
 | 
			
		||||
                    var penalty = rsp.replace(/.*rate-limit /, "").split(' ')[0];
 | 
			
		||||
                    console.log("rate-limit: " + penalty);
 | 
			
		||||
                    t.cooldown = Date.now() + parseFloat(penalty) * 1000;
 | 
			
		||||
                    st.busy.handshake.splice(st.busy.handshake.indexOf(t), 1);
 | 
			
		||||
                    st.todo.handshake.unshift(t);
 | 
			
		||||
                    return;
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
                st.bytes.uploaded += t.size;
 | 
			
		||||
                if (rsp.indexOf('partial upload exists') !== -1 ||
 | 
			
		||||
                    rsp.indexOf('file already exists') !== -1) {
 | 
			
		||||
@@ -1292,6 +1307,17 @@ function up2k_init(subtle) {
 | 
			
		||||
    }
 | 
			
		||||
    tt.init();
 | 
			
		||||
 | 
			
		||||
    function bumpthread2(e) {
 | 
			
		||||
        if (e.ctrlKey || e.altKey || e.metaKey || e.isComposing)
 | 
			
		||||
            return;
 | 
			
		||||
 | 
			
		||||
        if (e.code == 'ArrowUp')
 | 
			
		||||
            bumpthread(1);
 | 
			
		||||
 | 
			
		||||
        if (e.code == 'ArrowDown')
 | 
			
		||||
            bumpthread(-1);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    function bumpthread(dir) {
 | 
			
		||||
        try {
 | 
			
		||||
            dir.stopPropagation();
 | 
			
		||||
@@ -1302,7 +1328,7 @@ function up2k_init(subtle) {
 | 
			
		||||
        if (dir.target) {
 | 
			
		||||
            clmod(obj, 'err', 1);
 | 
			
		||||
            var v = Math.floor(parseInt(obj.value));
 | 
			
		||||
            if (v < 1 || v > 8 || v !== v)
 | 
			
		||||
            if (v < 0 || v > 64 || v !== v)
 | 
			
		||||
                return;
 | 
			
		||||
 | 
			
		||||
            parallel_uploads = v;
 | 
			
		||||
@@ -1313,11 +1339,11 @@ function up2k_init(subtle) {
 | 
			
		||||
 | 
			
		||||
        parallel_uploads += dir;
 | 
			
		||||
 | 
			
		||||
        if (parallel_uploads < 1)
 | 
			
		||||
            parallel_uploads = 1;
 | 
			
		||||
        if (parallel_uploads < 0)
 | 
			
		||||
            parallel_uploads = 0;
 | 
			
		||||
 | 
			
		||||
        if (parallel_uploads > 8)
 | 
			
		||||
            parallel_uploads = 8;
 | 
			
		||||
        if (parallel_uploads > 16)
 | 
			
		||||
            parallel_uploads = 16;
 | 
			
		||||
 | 
			
		||||
        obj.value = parallel_uploads;
 | 
			
		||||
        bumpthread({ "target": 1 })
 | 
			
		||||
@@ -1413,6 +1439,7 @@ function up2k_init(subtle) {
 | 
			
		||||
        bumpthread(-1);
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    ebi('nthread').onkeydown = bumpthread2;
 | 
			
		||||
    ebi('nthread').addEventListener('input', bumpthread, false);
 | 
			
		||||
    ebi('multitask').addEventListener('click', tgl_multitask, false);
 | 
			
		||||
    ebi('ask_up').addEventListener('click', tgl_ask_up, false);
 | 
			
		||||
@@ -1426,7 +1453,10 @@ function up2k_init(subtle) {
 | 
			
		||||
        nodes[a].addEventListener('touchend', nop, false);
 | 
			
		||||
 | 
			
		||||
    set_fsearch();
 | 
			
		||||
    bumpthread({ "target": 1 })
 | 
			
		||||
    bumpthread({ "target": 1 });
 | 
			
		||||
    if (parallel_uploads < 1)
 | 
			
		||||
        bumpthread(1);
 | 
			
		||||
 | 
			
		||||
    return { "init_deps": init_deps, "set_fsearch": set_fsearch }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -32,6 +32,9 @@ function esc(txt) {
 | 
			
		||||
    });
 | 
			
		||||
}
 | 
			
		||||
function vis_exh(msg, url, lineNo, columnNo, error) {
 | 
			
		||||
    if (!window.onerror)
 | 
			
		||||
        return;
 | 
			
		||||
 | 
			
		||||
    window.onerror = undefined;
 | 
			
		||||
    window['vis_exh'] = null;
 | 
			
		||||
    var html = ['<h1>you hit a bug!</h1><p>please screenshot this error and send me a copy arigathanks gozaimuch (ed/irc.rizon.net or ed#2644)</p><p>',
 | 
			
		||||
@@ -67,6 +70,9 @@ function ev(e) {
 | 
			
		||||
    if (e.stopPropagation)
 | 
			
		||||
        e.stopPropagation();
 | 
			
		||||
 | 
			
		||||
    if (e.stopImmediatePropagation)
 | 
			
		||||
        e.stopImmediatePropagation();
 | 
			
		||||
 | 
			
		||||
    e.returnValue = false;
 | 
			
		||||
    return e;
 | 
			
		||||
}
 | 
			
		||||
@@ -359,6 +365,15 @@ function get_vpath() {
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
function get_pwd() {
 | 
			
		||||
    var pwd = ('; ' + document.cookie).split('; cppwd=');
 | 
			
		||||
    if (pwd.length < 2)
 | 
			
		||||
        return null;
 | 
			
		||||
 | 
			
		||||
    return pwd[1].split(';')[0];
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
function unix2iso(ts) {
 | 
			
		||||
    return new Date(ts * 1000).toISOString().replace("T", " ").slice(0, -5);
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -15,11 +15,6 @@
 | 
			
		||||
}
 | 
			
		||||
#ggrid>a[href$="/"]:before {
 | 
			
		||||
	content: '📂';
 | 
			
		||||
    display: block;
 | 
			
		||||
    position: absolute;
 | 
			
		||||
    margin: -.1em -.4em;
 | 
			
		||||
    text-shadow: 0 0 .1em #000;
 | 
			
		||||
    font-size: 2em;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@@ -27,8 +22,11 @@
 | 
			
		||||
#ggrid>a:before {
 | 
			
		||||
    display: block;
 | 
			
		||||
    position: absolute;
 | 
			
		||||
    margin: -.1em -.4em;
 | 
			
		||||
	padding: .3em 0;
 | 
			
		||||
    margin: -.4em;
 | 
			
		||||
    text-shadow: 0 0 .1em #000;
 | 
			
		||||
	background: linear-gradient(135deg,rgba(255,255,255,0) 50%,rgba(255,255,255,0.2));
 | 
			
		||||
	border-radius: .3em;
 | 
			
		||||
    font-size: 2em;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -103,6 +103,15 @@ cat warks | while IFS= read -r x; do sqlite3 up2k.db "delete from mt where w = '
 | 
			
		||||
# dump all dbs
 | 
			
		||||
find -iname up2k.db | while IFS= read -r x; do sqlite3 "$x" 'select substr(w,1,12), rd, fn from up' | sed -r 's/\|/ \| /g' | while IFS= read -r y; do printf '%s | %s\n' "$x" "$y"; done; done
 | 
			
		||||
 | 
			
		||||
# unschedule mtp scan for all files somewhere under "enc/"
 | 
			
		||||
sqlite3 -readonly up2k.db 'select substr(up.w,1,16) from up inner join mt on mt.w = substr(up.w,1,16) where rd like "enc/%" and +mt.k = "t:mtp"' > keys; awk '{printf "delete from mt where w = \"%s\" and +k = \"t:mtp\";\n", $0}' <keys | tee /dev/stderr | sqlite3 up2k.db
 | 
			
		||||
 | 
			
		||||
# compare metadata key "key" between two databases
 | 
			
		||||
sqlite3 -readonly up2k.db.key-full 'select w, v from mt where k = "key" order by w' > k1; sqlite3 -readonly up2k.db 'select w, v from mt where k = "key" order by w' > k2; ok=0; ng=0; while IFS='|' read w k2; do k1="$(grep -E "^$w" k1 | sed -r 's/.*\|//')"; [ "$k1" = "$k2" ] && ok=$((ok+1)) || { ng=$((ng+1)); printf '%3s %3s  %s\n' "$k1" "$k2" "$(sqlite3 -readonly up2k.db.key-full "select * from up where substr(w,1,16) = '$w'" | sed -r 's/\|/ | /g')"; }; done < <(cat k2); echo "match $ok   diff $ng"
 | 
			
		||||
 | 
			
		||||
# actually this is much better
 | 
			
		||||
sqlite3 -readonly up2k.db.key-full 'select w, v from mt where k = "key" order by w' > k1; sqlite3 -readonly up2k.db 'select mt.w, mt.v, up.rd, up.fn from mt inner join up on mt.w = substr(up.w,1,16) where mt.k = "key" order by up.rd, up.fn' > k2; ok=0; ng=0; while IFS='|' read w k2 path; do k1="$(grep -E "^$w" k1 | sed -r 's/.*\|//')"; [ "$k1" = "$k2" ] && ok=$((ok+1)) || { ng=$((ng+1)); printf '%3s %3s  %s\n' "$k1" "$k2" "$path"; }; done < <(cat k2); echo "match $ok   diff $ng"
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
##
 | 
			
		||||
## media
 | 
			
		||||
@@ -157,7 +166,7 @@ dbg.asyncStore.pendingBreakpoints = {}
 | 
			
		||||
about:config >> devtools.debugger.prefs-schema-version = -1
 | 
			
		||||
 | 
			
		||||
# determine server version
 | 
			
		||||
git reset --hard origin/HEAD && git log --format=format:"%H %ai %d" --decorate=full > /dev/shm/revs && cat /dev/shm/revs | while read -r rev extra; do (git reset --hard $rev >/dev/null 2>/dev/null && dsz=$(cat copyparty/web/{util,browser,up2k}.js 2>/dev/null | diff -wNarU0 - <(cat /mnt/Users/ed/Downloads/ref/{util,browser,up2k}.js) | wc -c) && printf '%s %6s %s\n' "$rev" $dsz "$extra") </dev/null; done                
 | 
			
		||||
git pull; git reset --hard origin/HEAD && git log --format=format:"%H %ai %d" --decorate=full > ../revs && cat ../{util,browser}.js >../vr && cat ../revs | while read -r rev extra; do (git reset --hard $rev >/dev/null 2>/dev/null && dsz=$(cat copyparty/web/{util,browser}.js >../vg 2>/dev/null && diff -wNarU0 ../{vg,vr} | wc -c) && printf '%s %6s %s\n' "$rev" $dsz "$extra") </dev/null; done                
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
##
 | 
			
		||||
@@ -200,3 +209,4 @@ mk() { rm -rf /tmp/foo; sudo -u ed bash -c 'mkdir /tmp/foo; echo hi > /tmp/foo/b
 | 
			
		||||
mk && t0="$(date)" && while true; do date -s "$(date '+ 1 hour')"; systemd-tmpfiles --clean; ls -1 /tmp | grep foo || break; done; echo "$t0"
 | 
			
		||||
mk && sudo -u ed flock /tmp/foo sleep 40 & sleep 1; ps aux | grep -E 'sleep 40$' && t0="$(date)" && for n in {1..40}; do date -s "$(date '+ 1 day')"; systemd-tmpfiles --clean; ls -1 /tmp | grep foo || break; done; echo "$t0"
 | 
			
		||||
mk && t0="$(date)" && for n in {1..40}; do date -s "$(date '+ 1 day')"; systemd-tmpfiles --clean; ls -1 /tmp | grep foo || break; tar -cf/dev/null /tmp/foo; done; echo "$t0"
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -6,10 +6,10 @@ import re, os, sys, time, shutil, signal, threading, tarfile, hashlib, platform,
 | 
			
		||||
import subprocess as sp
 | 
			
		||||
 | 
			
		||||
"""
 | 
			
		||||
run me with any version of python, i will unpack and run copyparty
 | 
			
		||||
pls don't edit this file with a text editor,
 | 
			
		||||
  it breaks the compressed stuff at the end
 | 
			
		||||
 | 
			
		||||
(but please don't edit this file with a text editor
 | 
			
		||||
  since that would probably corrupt the binary stuff at the end)
 | 
			
		||||
run me with any version of python, i will unpack and run copyparty
 | 
			
		||||
 | 
			
		||||
there's zero binaries! just plaintext python scripts all the way down
 | 
			
		||||
  so you can easily unpack the archive and inspect it for shady stuff
 | 
			
		||||
 
 | 
			
		||||
@@ -121,6 +121,7 @@ class VHttpConn(object):
 | 
			
		||||
        self.log_src = "a"
 | 
			
		||||
        self.lf_url = None
 | 
			
		||||
        self.hsrv = VHttpSrv()
 | 
			
		||||
        self.nreq = 0
 | 
			
		||||
        self.nbyte = 0
 | 
			
		||||
        self.workload = 0
 | 
			
		||||
        self.ico = None
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user