mirror of
				https://github.com/9001/copyparty.git
				synced 2025-11-04 05:43:17 +00:00 
			
		
		
		
	Compare commits
	
		
			56 Commits
		
	
	
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
| 
						 | 
					7e8daf650e | ||
| 
						 | 
					0cf737b4ce | ||
| 
						 | 
					74635e0113 | ||
| 
						 | 
					e5c4f49901 | ||
| 
						 | 
					e4654ee7f1 | ||
| 
						 | 
					e5d05c05ed | ||
| 
						 | 
					73c4f99687 | ||
| 
						 | 
					28c12ef3bf | ||
| 
						 | 
					eed82dbb54 | ||
| 
						 | 
					2c4b4ab928 | ||
| 
						 | 
					505a8fc6f6 | ||
| 
						 | 
					e4801d9b06 | ||
| 
						 | 
					04f1b2cf3a | ||
| 
						 | 
					c06d928bb5 | ||
| 
						 | 
					ab09927e7b | ||
| 
						 | 
					779437db67 | ||
| 
						 | 
					28cbdb652e | ||
| 
						 | 
					2b2415a7d8 | ||
| 
						 | 
					746a8208aa | ||
| 
						 | 
					a2a041a98a | ||
| 
						 | 
					10b436e449 | ||
| 
						 | 
					4d62b34786 | ||
| 
						 | 
					0546210687 | ||
| 
						 | 
					f8c11faada | ||
| 
						 | 
					16d6e9be1f | ||
| 
						 | 
					aff8185f2e | ||
| 
						 | 
					217d15fe81 | ||
| 
						 | 
					171e93c201 | ||
| 
						 | 
					acc1d2e9e3 | ||
| 
						 | 
					49c2f37154 | ||
| 
						 | 
					69e54497aa | ||
| 
						 | 
					9aa1885669 | ||
| 
						 | 
					4418508513 | ||
| 
						 | 
					e897df3b34 | ||
| 
						 | 
					8cd97ab0e7 | ||
| 
						 | 
					bf4949353d | ||
| 
						 | 
					98a944f7cc | ||
| 
						 | 
					7c10f81c92 | ||
| 
						 | 
					126ecc55c3 | ||
| 
						 | 
					1034a51bd2 | ||
| 
						 | 
					a2657887cc | ||
| 
						 | 
					c14b17bfaf | ||
| 
						 | 
					59ebc795e7 | ||
| 
						 | 
					8e128d917e | ||
| 
						 | 
					ea762b05e0 | ||
| 
						 | 
					db374b19f1 | ||
| 
						 | 
					ab3839ef36 | ||
| 
						 | 
					9886c442f2 | ||
| 
						 | 
					c8d1926d52 | ||
| 
						 | 
					a6bd699e52 | ||
| 
						 | 
					12143f2702 | ||
| 
						 | 
					480705dee9 | ||
| 
						 | 
					781d5094f4 | ||
| 
						 | 
					5615cb94cd | ||
| 
						 | 
					302302a2ac | ||
| 
						 | 
					9761b4e3e9 | 
							
								
								
									
										2
									
								
								.vscode/launch.json
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										2
									
								
								.vscode/launch.json
									
									
									
									
										vendored
									
									
								
							@@ -14,6 +14,8 @@
 | 
			
		||||
                "-emp",
 | 
			
		||||
                "-e2dsa",
 | 
			
		||||
                "-e2ts",
 | 
			
		||||
                "-mtp",
 | 
			
		||||
                ".bpm=f,bin/mtag/audio-bpm.py",
 | 
			
		||||
                "-a",
 | 
			
		||||
                "ed:wark",
 | 
			
		||||
                "-v",
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										35
									
								
								.vscode/launch.py
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										35
									
								
								.vscode/launch.py
									
									
									
									
										vendored
									
									
										Normal file
									
								
							@@ -0,0 +1,35 @@
 | 
			
		||||
# takes arguments from launch.json
 | 
			
		||||
# is used by no_dbg in tasks.json
 | 
			
		||||
# launches 10x faster than mspython debugpy
 | 
			
		||||
# and is stoppable with ^C
 | 
			
		||||
 | 
			
		||||
import os
 | 
			
		||||
import sys
 | 
			
		||||
import shlex
 | 
			
		||||
 | 
			
		||||
sys.path.insert(0, os.getcwd())
 | 
			
		||||
 | 
			
		||||
import jstyleson
 | 
			
		||||
from copyparty.__main__ import main as copyparty
 | 
			
		||||
 | 
			
		||||
with open(".vscode/launch.json", "r") as f:
 | 
			
		||||
    tj = f.read()
 | 
			
		||||
 | 
			
		||||
oj = jstyleson.loads(tj)
 | 
			
		||||
argv = oj["configurations"][0]["args"]
 | 
			
		||||
 | 
			
		||||
try:
 | 
			
		||||
    sargv = " ".join([shlex.quote(x) for x in argv])
 | 
			
		||||
    print(sys.executable + " -m copyparty " + sargv + "\n")
 | 
			
		||||
except:
 | 
			
		||||
    pass
 | 
			
		||||
 | 
			
		||||
argv = [os.path.expanduser(x) if x.startswith("~") else x for x in argv]
 | 
			
		||||
try:
 | 
			
		||||
    copyparty(["a"] + argv)
 | 
			
		||||
except SystemExit as ex:
 | 
			
		||||
    if ex.code:
 | 
			
		||||
        raise
 | 
			
		||||
 | 
			
		||||
print("\n\033[32mokke\033[0m")
 | 
			
		||||
sys.exit(1)
 | 
			
		||||
							
								
								
									
										4
									
								
								.vscode/tasks.json
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										4
									
								
								.vscode/tasks.json
									
									
									
									
										vendored
									
									
								
							@@ -9,9 +9,7 @@
 | 
			
		||||
        {
 | 
			
		||||
            "label": "no_dbg",
 | 
			
		||||
            "type": "shell",
 | 
			
		||||
            "command": "${config:python.pythonPath} -m copyparty -ed -emp -e2dsa -e2ts -a ed:wark -v srv::r:aed:cnodupe -v dist:dist:r ;exit 1"
 | 
			
		||||
            // -v ~/Music/mt:mt:r:cmtp=.bpm=~/dev/copyparty/bin/mtag/audio-bpm.py:cmtp=key=~/dev/copyparty/bin/mtag/audio-key.py:ce2tsr 
 | 
			
		||||
            // -v ~/Music/mt:mt:r:cmtp=.bpm=~/dev/copyparty/bin/mtag/audio-bpm.py:ce2tsr
 | 
			
		||||
            "command": "${config:python.pythonPath} .vscode/launch.py"
 | 
			
		||||
        }
 | 
			
		||||
    ]
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										65
									
								
								README.md
									
									
									
									
									
								
							
							
						
						
									
										65
									
								
								README.md
									
									
									
									
									
								
							@@ -101,6 +101,11 @@ summary: it works! you can use it! (but technically not even close to beta)
 | 
			
		||||
* hiding the contents at url `/d1/d2/d3` using `-v :d1/d2/d3:cd2d` has the side-effect of creating databases (for files/tags) inside folders d1 and d2, and those databases take precedence over the main db at the top of the vfs - this means all files in d2 and below will be reindexed unless you already had a vfs entry at or below d2
 | 
			
		||||
* probably more, pls let me know
 | 
			
		||||
 | 
			
		||||
## not my bugs
 | 
			
		||||
 | 
			
		||||
* Windows: msys2-python 3.8.6 occasionally throws "RuntimeError: release unlocked lock" when leaving a scoped mutex in up2k
 | 
			
		||||
  * this is an msys2 bug, the regular windows edition of python is fine
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
# usage
 | 
			
		||||
 | 
			
		||||
@@ -111,6 +116,8 @@ the browser has the following hotkeys
 | 
			
		||||
* `I/K` prev/next folder
 | 
			
		||||
* `P` parent folder
 | 
			
		||||
 | 
			
		||||
you can link a particular timestamp in an audio file by adding it to the URL, such as `&20` / `&20s` / `&1m20` / `&1:20` after the `.../#af-c8960dab`
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
## zip downloads
 | 
			
		||||
 | 
			
		||||
@@ -199,26 +206,38 @@ copyparty can invoke external programs to collect additional metadata for files
 | 
			
		||||
 | 
			
		||||
# browser support
 | 
			
		||||
 | 
			
		||||
| feature         | ie6 | ie9 | ie10 | ie11 | ff 52+ | chr 49+ |
 | 
			
		||||
| --------------- | --- | --- | ---- | ---- | ------ | ------- |
 | 
			
		||||
| browse files    | yep | yep | yep  | yep  | yep    | yep     |
 | 
			
		||||
| basic uploader  | yep | yep | yep  | yep  | yep    | yep     |
 | 
			
		||||
| make directory  | yep | yep | yep  | yep  | yep    | yep     |
 | 
			
		||||
| send message    | yep | yep | yep  | yep  | yep    | yep     |
 | 
			
		||||
| set sort order  |  -  | yep | yep  | yep  | yep    | yep     |
 | 
			
		||||
| zip selection   |  -  | yep | yep  | yep  | yep    | yep     |
 | 
			
		||||
| directory tree  |  -  |  -  | `*1` | yep  | yep    | yep     |
 | 
			
		||||
| up2k            |  -  |  -  | yep  | yep  | yep    | yep     |
 | 
			
		||||
| icons work      |  -  |  -  | yep  | yep  | yep    | yep     |
 | 
			
		||||
| markdown editor |  -  |  -  | yep  | yep  | yep    | yep     |
 | 
			
		||||
| markdown viewer |  -  |  -  | yep  | yep  | yep    | yep     |
 | 
			
		||||
| play mp3/mp4    |  -  | yep | yep  | yep  | yep    | yep     |
 | 
			
		||||
| play ogg/opus   |  -  |  -  |  -   |  -   | yep    | yep     |
 | 
			
		||||
`ie` = internet-explorer, `ff` = firefox, `c` = chrome, `iOS` = iPhone/iPad, `Andr` = Android
 | 
			
		||||
 | 
			
		||||
* internet explorer 6 to 8 (and netscape 4.0) behave the same
 | 
			
		||||
| feature         | ie6 | ie9 | ie10 | ie11 | ff 52 | c 49 | iOS | Andr |
 | 
			
		||||
| --------------- | --- | --- | ---- | ---- | ----- | ---- | --- | ---- |
 | 
			
		||||
| browse files    | yep | yep | yep  | yep  | yep   | yep  | yep | yep  |
 | 
			
		||||
| basic uploader  | yep | yep | yep  | yep  | yep   | yep  | yep | yep  |
 | 
			
		||||
| make directory  | yep | yep | yep  | yep  | yep   | yep  | yep | yep  |
 | 
			
		||||
| send message    | yep | yep | yep  | yep  | yep   | yep  | yep | yep  |
 | 
			
		||||
| set sort order  |  -  | yep | yep  | yep  | yep   | yep  | yep | yep  |
 | 
			
		||||
| zip selection   |  -  | yep | yep  | yep  | yep   | yep  | yep | yep  |
 | 
			
		||||
| directory tree  |  -  |  -  | `*1` | yep  | yep   | yep  | yep | yep  |
 | 
			
		||||
| up2k            |  -  |  -  | yep  | yep  | yep   | yep  | yep | yep  |
 | 
			
		||||
| icons work      |  -  |  -  | yep  | yep  | yep   | yep  | yep | yep  |
 | 
			
		||||
| markdown editor |  -  |  -  | yep  | yep  | yep   | yep  | yep | yep  |
 | 
			
		||||
| markdown viewer |  -  |  -  | yep  | yep  | yep   | yep  | yep | yep  |
 | 
			
		||||
| play mp3/m4a    |  -  | yep | yep  | yep  | yep   | yep  | yep | yep  |
 | 
			
		||||
| play ogg/opus   |  -  |  -  |  -   |  -   | yep   | yep  | `*2` | yep |
 | 
			
		||||
 | 
			
		||||
* internet explorer 6 to 8 behave the same
 | 
			
		||||
* firefox 52 and chrome 49 are the last winxp versions
 | 
			
		||||
* `*1` only public folders (login session is dropped) and no history / back-button
 | 
			
		||||
* `*2` using a wasm decoder which can sometimes get stuck and consumes a bit more power
 | 
			
		||||
 | 
			
		||||
quick summary of more eccentric web-browsers trying to view a directory index:
 | 
			
		||||
* safari (14.0.3/macos) is chrome with janky wasm, so playing opus can deadlock the javascript engine
 | 
			
		||||
* safari (14.0.1/iOS) same as macos, except it recovers from the deadlocks if you poke it a bit
 | 
			
		||||
* links (2.21/macports) can browse, login, upload/mkdir/msg
 | 
			
		||||
* lynx (2.8.9/macports) can browse, login, upload/mkdir/msg
 | 
			
		||||
* w3m (0.5.3/macports) can browse, login, upload at 100kB/s, mkdir/msg
 | 
			
		||||
* netsurf (3.10/arch) is basically ie6 with much better css (javascript has almost no effect)
 | 
			
		||||
* netscape 4.0 and 4.5 can browse (text is yellow on white), upload with `?b=u`
 | 
			
		||||
* SerenityOS (22d13d8) hits a page fault, works with `?b=u`, file input not-impl, url params are multiplying
 | 
			
		||||
 | 
			
		||||
# client examples
 | 
			
		||||
 | 
			
		||||
@@ -327,14 +346,20 @@ in the `scripts` folder:
 | 
			
		||||
 | 
			
		||||
roughly sorted by priority
 | 
			
		||||
 | 
			
		||||
* separate sqlite table per tag
 | 
			
		||||
* audio fingerprinting
 | 
			
		||||
* readme.md as epilogue
 | 
			
		||||
* reduce up2k roundtrips
 | 
			
		||||
  * start from a chunk index and just go
 | 
			
		||||
  * terminate client on bad data
 | 
			
		||||
* `os.copy_file_range` for up2k cloning
 | 
			
		||||
* up2k partials ui
 | 
			
		||||
* support pillow-simd
 | 
			
		||||
* cache sha512 chunks on client
 | 
			
		||||
* comment field
 | 
			
		||||
* ~~look into android thumbnail cache file format~~ bad idea
 | 
			
		||||
* figure out the deal with pixel3a not being connectable as hotspot
 | 
			
		||||
  * pixel3a having unpredictable 3sec latency in general :||||
 | 
			
		||||
 | 
			
		||||
discarded ideas
 | 
			
		||||
 | 
			
		||||
* up2k partials ui
 | 
			
		||||
* cache sha512 chunks on client
 | 
			
		||||
* comment field
 | 
			
		||||
* look into android thumbnail cache file format
 | 
			
		||||
 
 | 
			
		||||
@@ -16,6 +16,8 @@ if platform.system() == "Windows":
 | 
			
		||||
VT100 = not WINDOWS or WINDOWS >= [10, 0, 14393]
 | 
			
		||||
# introduced in anniversary update
 | 
			
		||||
 | 
			
		||||
ANYWIN = WINDOWS or sys.platform in ["msys"]
 | 
			
		||||
 | 
			
		||||
MACOS = platform.system() == "Darwin"
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -12,7 +12,6 @@ import re
 | 
			
		||||
import os
 | 
			
		||||
import sys
 | 
			
		||||
import time
 | 
			
		||||
import signal
 | 
			
		||||
import shutil
 | 
			
		||||
import filecmp
 | 
			
		||||
import locale
 | 
			
		||||
@@ -56,6 +55,12 @@ class RiceFormatter(argparse.HelpFormatter):
 | 
			
		||||
        return "".join(indent + line + "\n" for line in text.splitlines())
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class Dodge11874(RiceFormatter):
 | 
			
		||||
    def __init__(self, *args, **kwargs):
 | 
			
		||||
        kwargs["width"] = 9003
 | 
			
		||||
        super(Dodge11874, self).__init__(*args, **kwargs)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def warn(msg):
 | 
			
		||||
    print("\033[1mwarning:\033[0;33m {}\033[0m\n".format(msg))
 | 
			
		||||
 | 
			
		||||
@@ -167,7 +172,7 @@ def configure_ssl_ciphers(al):
 | 
			
		||||
        sys.exit(0)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def sighandler(signal=None, frame=None):
 | 
			
		||||
def sighandler(sig=None, frame=None):
 | 
			
		||||
    msg = [""] * 5
 | 
			
		||||
    for th in threading.enumerate():
 | 
			
		||||
        msg.append(str(th))
 | 
			
		||||
@@ -177,37 +182,9 @@ def sighandler(signal=None, frame=None):
 | 
			
		||||
    print("\n".join(msg))
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def main(argv=None):
 | 
			
		||||
    time.strptime("19970815", "%Y%m%d")  # python#7980
 | 
			
		||||
    if WINDOWS:
 | 
			
		||||
        os.system("rem")  # enables colors
 | 
			
		||||
 | 
			
		||||
    if argv is None:
 | 
			
		||||
        argv = sys.argv
 | 
			
		||||
 | 
			
		||||
    desc = py_desc().replace("[", "\033[1;30m[")
 | 
			
		||||
 | 
			
		||||
    f = '\033[36mcopyparty v{} "\033[35m{}\033[36m" ({})\n{}\033[0m\n'
 | 
			
		||||
    print(f.format(S_VERSION, CODENAME, S_BUILD_DT, desc))
 | 
			
		||||
 | 
			
		||||
    ensure_locale()
 | 
			
		||||
    if HAVE_SSL:
 | 
			
		||||
        ensure_cert()
 | 
			
		||||
 | 
			
		||||
    deprecated = [["-e2s", "-e2ds"]]
 | 
			
		||||
    for dk, nk in deprecated:
 | 
			
		||||
        try:
 | 
			
		||||
            idx = argv.index(dk)
 | 
			
		||||
        except:
 | 
			
		||||
            continue
 | 
			
		||||
 | 
			
		||||
        msg = "\033[1;31mWARNING:\033[0;1m\n  {} \033[0;33mwas replaced with\033[0;1m {} \033[0;33mand will be removed\n\033[0m"
 | 
			
		||||
        print(msg.format(dk, nk))
 | 
			
		||||
        argv[idx] = nk
 | 
			
		||||
        time.sleep(2)
 | 
			
		||||
 | 
			
		||||
def run_argparse(argv, formatter):
 | 
			
		||||
    ap = argparse.ArgumentParser(
 | 
			
		||||
        formatter_class=RiceFormatter,
 | 
			
		||||
        formatter_class=formatter,
 | 
			
		||||
        prog="copyparty",
 | 
			
		||||
        description="http file sharing hub v{} ({})".format(S_VERSION, S_BUILD_DT),
 | 
			
		||||
        epilog=dedent(
 | 
			
		||||
@@ -219,6 +196,9 @@ def main(argv=None):
 | 
			
		||||
            
 | 
			
		||||
            list of cflags:
 | 
			
		||||
              "cnodupe" rejects existing files (instead of symlinking them)
 | 
			
		||||
              "ce2d" sets -e2d (all -e2* args can be set using ce2* cflags)
 | 
			
		||||
              "cd2t" disables metadata collection, overrides -e2t*
 | 
			
		||||
              "cd2d" disables all database stuff, overrides -e2*
 | 
			
		||||
 | 
			
		||||
            example:\033[35m
 | 
			
		||||
              -a ed:hunter2 -v .::r:aed -v ../inc:dump:w:aed:cnodupe  \033[36m
 | 
			
		||||
@@ -267,6 +247,7 @@ def main(argv=None):
 | 
			
		||||
    ap.add_argument("--no-zip", action="store_true", help="disable download as zip/tar")
 | 
			
		||||
    ap.add_argument("--no-sendfile", action="store_true", help="disable sendfile (for debugging)")
 | 
			
		||||
    ap.add_argument("--no-scandir", action="store_true", help="disable scandir (for debugging)")
 | 
			
		||||
    ap.add_argument("--sparse", metavar="MiB", type=int, default=4, help="up2k min.size threshold (mswin-only)")
 | 
			
		||||
    ap.add_argument("--urlform", metavar="MODE", type=str, default="print,get", help="how to handle url-forms")
 | 
			
		||||
    ap.add_argument("--salt", type=str, default="hunter2", help="up2k file-hash salt")
 | 
			
		||||
 | 
			
		||||
@@ -293,9 +274,44 @@ def main(argv=None):
 | 
			
		||||
    ap2.add_argument("--ssl-dbg", action="store_true", help="dump some tls info")
 | 
			
		||||
    ap2.add_argument("--ssl-log", metavar="PATH", help="log master secrets")
 | 
			
		||||
    
 | 
			
		||||
    al = ap.parse_args(args=argv[1:])
 | 
			
		||||
    return ap.parse_args(args=argv[1:])
 | 
			
		||||
    # fmt: on
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def main(argv=None):
 | 
			
		||||
    time.strptime("19970815", "%Y%m%d")  # python#7980
 | 
			
		||||
    if WINDOWS:
 | 
			
		||||
        os.system("rem")  # enables colors
 | 
			
		||||
 | 
			
		||||
    if argv is None:
 | 
			
		||||
        argv = sys.argv
 | 
			
		||||
 | 
			
		||||
    desc = py_desc().replace("[", "\033[1;30m[")
 | 
			
		||||
 | 
			
		||||
    f = '\033[36mcopyparty v{} "\033[35m{}\033[36m" ({})\n{}\033[0m\n'
 | 
			
		||||
    print(f.format(S_VERSION, CODENAME, S_BUILD_DT, desc))
 | 
			
		||||
 | 
			
		||||
    ensure_locale()
 | 
			
		||||
    if HAVE_SSL:
 | 
			
		||||
        ensure_cert()
 | 
			
		||||
 | 
			
		||||
    deprecated = [["-e2s", "-e2ds"]]
 | 
			
		||||
    for dk, nk in deprecated:
 | 
			
		||||
        try:
 | 
			
		||||
            idx = argv.index(dk)
 | 
			
		||||
        except:
 | 
			
		||||
            continue
 | 
			
		||||
 | 
			
		||||
        msg = "\033[1;31mWARNING:\033[0;1m\n  {} \033[0;33mwas replaced with\033[0;1m {} \033[0;33mand will be removed\n\033[0m"
 | 
			
		||||
        print(msg.format(dk, nk))
 | 
			
		||||
        argv[idx] = nk
 | 
			
		||||
        time.sleep(2)
 | 
			
		||||
 | 
			
		||||
    try:
 | 
			
		||||
        al = run_argparse(argv, RiceFormatter)
 | 
			
		||||
    except AssertionError:
 | 
			
		||||
        al = run_argparse(argv, Dodge11874)
 | 
			
		||||
 | 
			
		||||
    # propagate implications
 | 
			
		||||
    for k1, k2 in IMPLICATIONS:
 | 
			
		||||
        if getattr(al, k1):
 | 
			
		||||
 
 | 
			
		||||
@@ -1,8 +1,8 @@
 | 
			
		||||
# coding: utf-8
 | 
			
		||||
 | 
			
		||||
VERSION = (0, 10, 6)
 | 
			
		||||
VERSION = (0, 10, 14)
 | 
			
		||||
CODENAME = "zip it"
 | 
			
		||||
BUILD_DT = (2021, 4, 2)
 | 
			
		||||
BUILD_DT = (2021, 4, 21)
 | 
			
		||||
 | 
			
		||||
S_VERSION = ".".join(map(str, VERSION))
 | 
			
		||||
S_BUILD_DT = "{0:04d}-{1:02d}-{2:02d}".format(*BUILD_DT)
 | 
			
		||||
 
 | 
			
		||||
@@ -111,7 +111,27 @@ class VFS(object):
 | 
			
		||||
        if rem:
 | 
			
		||||
            rp += "/" + rem
 | 
			
		||||
 | 
			
		||||
        return fsdec(os.path.realpath(fsenc(rp)))
 | 
			
		||||
        try:
 | 
			
		||||
            return fsdec(os.path.realpath(fsenc(rp)))
 | 
			
		||||
        except:
 | 
			
		||||
            if not WINDOWS:
 | 
			
		||||
                raise
 | 
			
		||||
 | 
			
		||||
            # cpython bug introduced in 3.8, still exists in 3.9.1;
 | 
			
		||||
            # some win7sp1 and win10:20H2 boxes cannot realpath a
 | 
			
		||||
            # networked drive letter such as b"n:" or b"n:\\"
 | 
			
		||||
            #
 | 
			
		||||
            # requirements to trigger:
 | 
			
		||||
            #  * bytestring (not unicode str)
 | 
			
		||||
            #  * just the drive letter (subfolders are ok)
 | 
			
		||||
            #  * networked drive (regular disks and vmhgfs are ok)
 | 
			
		||||
            #  * on an enterprise network (idk, cannot repro with samba)
 | 
			
		||||
            #
 | 
			
		||||
            # hits the following exceptions in succession:
 | 
			
		||||
            #  * access denied at L601: "path = _getfinalpathname(path)"
 | 
			
		||||
            #  * "cant concat str to bytes" at L621: "return path + tail"
 | 
			
		||||
            #
 | 
			
		||||
            return os.path.realpath(rp)
 | 
			
		||||
 | 
			
		||||
    def ls(self, rem, uname, scandir, lstat=False):
 | 
			
		||||
        """return user-readable [fsdir,real,virt] items at vpath"""
 | 
			
		||||
@@ -233,12 +253,6 @@ class AuthSrv(object):
 | 
			
		||||
    def log(self, msg, c=0):
 | 
			
		||||
        self.log_func("auth", msg, c)
 | 
			
		||||
 | 
			
		||||
    def invert(self, orig):
 | 
			
		||||
        if PY2:
 | 
			
		||||
            return {v: k for k, v in orig.iteritems()}
 | 
			
		||||
        else:
 | 
			
		||||
            return {v: k for k, v in orig.items()}
 | 
			
		||||
 | 
			
		||||
    def laggy_iter(self, iterable):
 | 
			
		||||
        """returns [value,isFinalValue]"""
 | 
			
		||||
        it = iter(iterable)
 | 
			
		||||
@@ -495,7 +509,7 @@ class AuthSrv(object):
 | 
			
		||||
        with self.mutex:
 | 
			
		||||
            self.vfs = vfs
 | 
			
		||||
            self.user = user
 | 
			
		||||
            self.iuser = self.invert(user)
 | 
			
		||||
            self.iuser = {v: k for k, v in user.items()}
 | 
			
		||||
 | 
			
		||||
        # import pprint
 | 
			
		||||
        # pprint.pprint({"usr": user, "rd": mread, "wr": mwrite, "mnt": mount})
 | 
			
		||||
 
 | 
			
		||||
@@ -13,7 +13,7 @@ import ctypes
 | 
			
		||||
from datetime import datetime
 | 
			
		||||
import calendar
 | 
			
		||||
 | 
			
		||||
from .__init__ import E, PY2, WINDOWS
 | 
			
		||||
from .__init__ import E, PY2, WINDOWS, ANYWIN
 | 
			
		||||
from .util import *  # noqa  # pylint: disable=unused-wildcard-import
 | 
			
		||||
from .szip import StreamZip
 | 
			
		||||
from .star import StreamTar
 | 
			
		||||
@@ -74,7 +74,7 @@ class HttpCli(object):
 | 
			
		||||
                headerlines.pop(0)
 | 
			
		||||
 | 
			
		||||
            try:
 | 
			
		||||
                self.mode, self.req, _ = headerlines[0].split(" ")
 | 
			
		||||
                self.mode, self.req, self.http_ver = headerlines[0].split(" ")
 | 
			
		||||
            except:
 | 
			
		||||
                raise Pebkac(400, "bad headers:\n" + "\n".join(headerlines))
 | 
			
		||||
 | 
			
		||||
@@ -93,30 +93,13 @@ class HttpCli(object):
 | 
			
		||||
            self.headers[k.lower()] = v.strip()
 | 
			
		||||
 | 
			
		||||
        v = self.headers.get("connection", "").lower()
 | 
			
		||||
        self.keepalive = not v.startswith("close")
 | 
			
		||||
        self.keepalive = not v.startswith("close") and self.http_ver != "HTTP/1.0"
 | 
			
		||||
 | 
			
		||||
        v = self.headers.get("x-forwarded-for", None)
 | 
			
		||||
        if v is not None and self.conn.addr[0] in ["127.0.0.1", "::1"]:
 | 
			
		||||
            self.ip = v.split(",")[0]
 | 
			
		||||
            self.log_src = self.conn.set_rproxy(self.ip)
 | 
			
		||||
 | 
			
		||||
        self.uname = "*"
 | 
			
		||||
        if "cookie" in self.headers:
 | 
			
		||||
            cookies = self.headers["cookie"].split(";")
 | 
			
		||||
            for k, v in [x.split("=", 1) for x in cookies]:
 | 
			
		||||
                if k.strip() != "cppwd":
 | 
			
		||||
                    continue
 | 
			
		||||
 | 
			
		||||
                v = unescape_cookie(v)
 | 
			
		||||
                if v in self.auth.iuser:
 | 
			
		||||
                    self.uname = self.auth.iuser[v]
 | 
			
		||||
 | 
			
		||||
                break
 | 
			
		||||
 | 
			
		||||
        if self.uname:
 | 
			
		||||
            self.rvol = self.auth.vfs.user_tree(self.uname, readable=True)
 | 
			
		||||
            self.wvol = self.auth.vfs.user_tree(self.uname, writable=True)
 | 
			
		||||
 | 
			
		||||
        # split req into vpath + uparam
 | 
			
		||||
        uparam = {}
 | 
			
		||||
        if "?" not in self.req:
 | 
			
		||||
@@ -140,6 +123,22 @@ class HttpCli(object):
 | 
			
		||||
        self.uparam = uparam
 | 
			
		||||
        self.vpath = unquotep(vpath)
 | 
			
		||||
 | 
			
		||||
        pwd = None
 | 
			
		||||
        if "cookie" in self.headers:
 | 
			
		||||
            cookies = self.headers["cookie"].split(";")
 | 
			
		||||
            for k, v in [x.split("=", 1) for x in cookies]:
 | 
			
		||||
                if k.strip() != "cppwd":
 | 
			
		||||
                    continue
 | 
			
		||||
 | 
			
		||||
                pwd = unescape_cookie(v)
 | 
			
		||||
                break
 | 
			
		||||
 | 
			
		||||
        pwd = uparam.get("pw", pwd)
 | 
			
		||||
        self.uname = self.auth.iuser.get(pwd, "*")
 | 
			
		||||
        if self.uname:
 | 
			
		||||
            self.rvol = self.auth.vfs.user_tree(self.uname, readable=True)
 | 
			
		||||
            self.wvol = self.auth.vfs.user_tree(self.uname, writable=True)
 | 
			
		||||
 | 
			
		||||
        ua = self.headers.get("user-agent", "")
 | 
			
		||||
        if ua.startswith("rclone/"):
 | 
			
		||||
            uparam["raw"] = False
 | 
			
		||||
@@ -160,7 +159,9 @@ class HttpCli(object):
 | 
			
		||||
        except Pebkac as ex:
 | 
			
		||||
            try:
 | 
			
		||||
                # self.log("pebkac at httpcli.run #2: " + repr(ex))
 | 
			
		||||
                self.keepalive = self._check_nonfatal(ex)
 | 
			
		||||
                if not self._check_nonfatal(ex):
 | 
			
		||||
                    self.keepalive = False
 | 
			
		||||
 | 
			
		||||
                self.log("{}\033[0m, {}".format(str(ex), self.vpath), 3)
 | 
			
		||||
                msg = "<pre>{}\r\nURL: {}\r\n".format(str(ex), self.vpath)
 | 
			
		||||
                self.reply(msg.encode("utf-8", "replace"), status=ex.code)
 | 
			
		||||
@@ -169,7 +170,7 @@ class HttpCli(object):
 | 
			
		||||
                return False
 | 
			
		||||
 | 
			
		||||
    def send_headers(self, length, status=200, mime=None, headers={}):
 | 
			
		||||
        response = ["HTTP/1.1 {} {}".format(status, HTTPCODE[status])]
 | 
			
		||||
        response = ["{} {} {}".format(self.http_ver, status, HTTPCODE[status])]
 | 
			
		||||
 | 
			
		||||
        if length is not None:
 | 
			
		||||
            response.append("Content-Length: " + unicode(length))
 | 
			
		||||
@@ -213,6 +214,20 @@ class HttpCli(object):
 | 
			
		||||
        self.log(body.rstrip())
 | 
			
		||||
        self.reply(b"<pre>" + body.encode("utf-8") + b"\r\n", *list(args), **kwargs)
 | 
			
		||||
 | 
			
		||||
    def urlq(self, add={}, rm=[]):
 | 
			
		||||
        """
 | 
			
		||||
        generates url query based on uparam (b, pw, all others)
 | 
			
		||||
        removing anything in rm, adding pairs in add
 | 
			
		||||
        """
 | 
			
		||||
 | 
			
		||||
        kv = {k: v for k, v in self.uparam.items() if k not in rm}
 | 
			
		||||
        kv.update(add)
 | 
			
		||||
        if not kv:
 | 
			
		||||
            return ""
 | 
			
		||||
 | 
			
		||||
        r = ["{}={}".format(k, quotep(v)) if v else k for k, v in kv.items()]
 | 
			
		||||
        return "?" + "&".join(r)
 | 
			
		||||
 | 
			
		||||
    def handle_get(self):
 | 
			
		||||
        logmsg = "{:4} {}".format(self.mode, self.req)
 | 
			
		||||
 | 
			
		||||
@@ -246,12 +261,14 @@ class HttpCli(object):
 | 
			
		||||
 | 
			
		||||
                self.absolute_urls = True
 | 
			
		||||
 | 
			
		||||
        # go home if verboten
 | 
			
		||||
        self.readable, self.writable = self.conn.auth.vfs.can_access(
 | 
			
		||||
            self.vpath, self.uname
 | 
			
		||||
        )
 | 
			
		||||
        if not self.readable and not self.writable:
 | 
			
		||||
            self.log("inaccessible: [{}]".format(self.vpath))
 | 
			
		||||
            if self.vpath:
 | 
			
		||||
                self.log("inaccessible: [{}]".format(self.vpath))
 | 
			
		||||
                raise Pebkac(404)
 | 
			
		||||
 | 
			
		||||
            self.uparam = {"h": False}
 | 
			
		||||
 | 
			
		||||
        if "h" in self.uparam:
 | 
			
		||||
@@ -611,7 +628,7 @@ class HttpCli(object):
 | 
			
		||||
            self.loud_reply(x, status=500)
 | 
			
		||||
            return False
 | 
			
		||||
 | 
			
		||||
        if not WINDOWS and num_left == 0:
 | 
			
		||||
        if not ANYWIN and num_left == 0:
 | 
			
		||||
            times = (int(time.time()), int(lastmod))
 | 
			
		||||
            self.log("no more chunks, setting times {}".format(times))
 | 
			
		||||
            try:
 | 
			
		||||
@@ -665,7 +682,7 @@ class HttpCli(object):
 | 
			
		||||
                raise Pebkac(500, "mkdir failed, check the logs")
 | 
			
		||||
 | 
			
		||||
        vpath = "{}/{}".format(self.vpath, sanitized).lstrip("/")
 | 
			
		||||
        esc_paths = [quotep(vpath), html_escape(vpath)]
 | 
			
		||||
        esc_paths = [quotep(vpath), html_escape(vpath, crlf=True)]
 | 
			
		||||
        html = self.j2(
 | 
			
		||||
            "msg",
 | 
			
		||||
            h2='<a href="/{}">go to /{}</a>'.format(*esc_paths),
 | 
			
		||||
@@ -1166,17 +1183,16 @@ class HttpCli(object):
 | 
			
		||||
        template = self.j2(tpl)
 | 
			
		||||
 | 
			
		||||
        st = os.stat(fsenc(fs_path))
 | 
			
		||||
        # sz_md = st.st_size
 | 
			
		||||
        ts_md = st.st_mtime
 | 
			
		||||
 | 
			
		||||
        st = os.stat(fsenc(html_path))
 | 
			
		||||
        ts_html = st.st_mtime
 | 
			
		||||
 | 
			
		||||
        # TODO dont load into memory ;_;
 | 
			
		||||
        #   (trivial fix, count the &'s)
 | 
			
		||||
        with open(fsenc(fs_path), "rb") as f:
 | 
			
		||||
            md = f.read().replace(b"&", b"&")
 | 
			
		||||
            sz_md = len(md)
 | 
			
		||||
        sz_md = 0
 | 
			
		||||
        for buf in yieldfile(fs_path):
 | 
			
		||||
            sz_md += len(buf)
 | 
			
		||||
            for c, v in [[b"&", 4], [b"<", 3], [b">", 3]]:
 | 
			
		||||
                sz_md += (len(buf) - len(buf.replace(c, b""))) * v
 | 
			
		||||
 | 
			
		||||
        file_ts = max(ts_md, ts_html)
 | 
			
		||||
        file_lastmod, do_send = self._chk_lastmod(file_ts)
 | 
			
		||||
@@ -1184,27 +1200,34 @@ class HttpCli(object):
 | 
			
		||||
        self.out_headers["Cache-Control"] = "no-cache"
 | 
			
		||||
        status = 200 if do_send else 304
 | 
			
		||||
 | 
			
		||||
        boundary = "\roll\tide"
 | 
			
		||||
        targs = {
 | 
			
		||||
            "edit": "edit" in self.uparam,
 | 
			
		||||
            "title": html_escape(self.vpath),
 | 
			
		||||
            "title": html_escape(self.vpath, crlf=True),
 | 
			
		||||
            "lastmod": int(ts_md * 1000),
 | 
			
		||||
            "md_plug": "true" if self.args.emp else "false",
 | 
			
		||||
            "md_chk_rate": self.args.mcr,
 | 
			
		||||
            "md": "",
 | 
			
		||||
            "md": boundary,
 | 
			
		||||
        }
 | 
			
		||||
        sz_html = len(template.render(**targs).encode("utf-8"))
 | 
			
		||||
        self.send_headers(sz_html + sz_md, status)
 | 
			
		||||
        html = template.render(**targs).encode("utf-8")
 | 
			
		||||
        html = html.split(boundary.encode("utf-8"))
 | 
			
		||||
        if len(html) != 2:
 | 
			
		||||
            raise Exception("boundary appears in " + html_path)
 | 
			
		||||
 | 
			
		||||
        self.send_headers(sz_md + len(html[0]) + len(html[1]), status)
 | 
			
		||||
 | 
			
		||||
        logmsg += unicode(status)
 | 
			
		||||
        if self.mode == "HEAD" or not do_send:
 | 
			
		||||
            self.log(logmsg)
 | 
			
		||||
            return True
 | 
			
		||||
 | 
			
		||||
        # TODO jinja2 can stream this right?
 | 
			
		||||
        targs["md"] = md.decode("utf-8", "replace")
 | 
			
		||||
        html = template.render(**targs).encode("utf-8")
 | 
			
		||||
        try:
 | 
			
		||||
            self.s.sendall(html)
 | 
			
		||||
            self.s.sendall(html[0])
 | 
			
		||||
            for buf in yieldfile(fs_path):
 | 
			
		||||
                self.s.sendall(html_bescape(buf))
 | 
			
		||||
 | 
			
		||||
            self.s.sendall(html[1])
 | 
			
		||||
 | 
			
		||||
        except:
 | 
			
		||||
            self.log(logmsg + " \033[31md/c\033[0m")
 | 
			
		||||
            return False
 | 
			
		||||
@@ -1213,9 +1236,10 @@ class HttpCli(object):
 | 
			
		||||
        return True
 | 
			
		||||
 | 
			
		||||
    def tx_mounts(self):
 | 
			
		||||
        suf = self.urlq(rm=["h"])
 | 
			
		||||
        rvol = [x + "/" if x else x for x in self.rvol]
 | 
			
		||||
        wvol = [x + "/" if x else x for x in self.wvol]
 | 
			
		||||
        html = self.j2("splash", this=self, rvol=rvol, wvol=wvol)
 | 
			
		||||
        html = self.j2("splash", this=self, rvol=rvol, wvol=wvol, url_suf=suf)
 | 
			
		||||
        self.reply(html.encode("utf-8"))
 | 
			
		||||
        return True
 | 
			
		||||
 | 
			
		||||
@@ -1284,7 +1308,7 @@ class HttpCli(object):
 | 
			
		||||
                else:
 | 
			
		||||
                    vpath += "/" + node
 | 
			
		||||
 | 
			
		||||
                vpnodes.append([quotep(vpath) + "/", html_escape(node)])
 | 
			
		||||
                vpnodes.append([quotep(vpath) + "/", html_escape(node, crlf=True)])
 | 
			
		||||
 | 
			
		||||
        vn, rem = self.auth.vfs.get(
 | 
			
		||||
            self.vpath, self.uname, self.readable, self.writable
 | 
			
		||||
@@ -1345,6 +1369,8 @@ class HttpCli(object):
 | 
			
		||||
            idx = self.conn.get_u2idx()
 | 
			
		||||
            icur = idx.get_cur(vn.realpath)
 | 
			
		||||
 | 
			
		||||
        url_suf = self.urlq()
 | 
			
		||||
 | 
			
		||||
        dirs = []
 | 
			
		||||
        files = []
 | 
			
		||||
        for fn in vfs_ls:
 | 
			
		||||
@@ -1376,7 +1402,7 @@ class HttpCli(object):
 | 
			
		||||
                    margin = '<a href="{}?zip">zip</a>'.format(quotep(href))
 | 
			
		||||
            elif fn in hist:
 | 
			
		||||
                margin = '<a href="{}.hist/{}">#{}</a>'.format(
 | 
			
		||||
                    base, html_escape(hist[fn][2], quote=True), hist[fn][0]
 | 
			
		||||
                    base, html_escape(hist[fn][2], quote=True, crlf=True), hist[fn][0]
 | 
			
		||||
                )
 | 
			
		||||
            else:
 | 
			
		||||
                margin = "-"
 | 
			
		||||
@@ -1497,8 +1523,12 @@ class HttpCli(object):
 | 
			
		||||
 | 
			
		||||
        dirs.extend(files)
 | 
			
		||||
 | 
			
		||||
        tpl = "browser"
 | 
			
		||||
        if "b" in self.uparam:
 | 
			
		||||
            tpl = "browser2"
 | 
			
		||||
 | 
			
		||||
        html = self.j2(
 | 
			
		||||
            "browser",
 | 
			
		||||
            tpl,
 | 
			
		||||
            vdir=quotep(self.vpath),
 | 
			
		||||
            vpnodes=vpnodes,
 | 
			
		||||
            files=dirs,
 | 
			
		||||
@@ -1511,8 +1541,10 @@ class HttpCli(object):
 | 
			
		||||
            have_up2k_idx=("e2d" in vn.flags),
 | 
			
		||||
            have_tags_idx=("e2t" in vn.flags),
 | 
			
		||||
            have_zip=(not self.args.no_zip),
 | 
			
		||||
            have_b_u=(self.writable and self.uparam.get("b") == "u"),
 | 
			
		||||
            url_suf=url_suf,
 | 
			
		||||
            logues=logues,
 | 
			
		||||
            title=html_escape(self.vpath),
 | 
			
		||||
            title=html_escape(self.vpath, crlf=True),
 | 
			
		||||
            srv_info=srv_info,
 | 
			
		||||
        )
 | 
			
		||||
        self.reply(html.encode("utf-8", "replace"))
 | 
			
		||||
 
 | 
			
		||||
@@ -52,7 +52,7 @@ class HttpSrv(object):
 | 
			
		||||
        env.loader = jinja2.FileSystemLoader(os.path.join(E.mod, "web"))
 | 
			
		||||
        self.j2 = {
 | 
			
		||||
            x: env.get_template(x + ".html")
 | 
			
		||||
            for x in ["splash", "browser", "msg", "md", "mde"]
 | 
			
		||||
            for x in ["splash", "browser", "browser2", "msg", "md", "mde"]
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        cert_path = os.path.join(E.cfg, "cert.pem")
 | 
			
		||||
 
 | 
			
		||||
@@ -16,7 +16,7 @@ import traceback
 | 
			
		||||
import subprocess as sp
 | 
			
		||||
from copy import deepcopy
 | 
			
		||||
 | 
			
		||||
from .__init__ import WINDOWS
 | 
			
		||||
from .__init__ import WINDOWS, ANYWIN
 | 
			
		||||
from .util import (
 | 
			
		||||
    Pebkac,
 | 
			
		||||
    Queue,
 | 
			
		||||
@@ -79,7 +79,7 @@ class Up2k(object):
 | 
			
		||||
            if self.sqlite_ver < (3, 9):
 | 
			
		||||
                self.no_expr_idx = True
 | 
			
		||||
 | 
			
		||||
        if WINDOWS:
 | 
			
		||||
        if ANYWIN:
 | 
			
		||||
            # usually fails to set lastmod too quickly
 | 
			
		||||
            self.lastmod_q = Queue()
 | 
			
		||||
            thr = threading.Thread(target=self._lastmodder)
 | 
			
		||||
@@ -101,17 +101,18 @@ class Up2k(object):
 | 
			
		||||
            thr.daemon = True
 | 
			
		||||
            thr.start()
 | 
			
		||||
 | 
			
		||||
            thr = threading.Thread(target=self._tagger)
 | 
			
		||||
            thr.daemon = True
 | 
			
		||||
            thr.start()
 | 
			
		||||
 | 
			
		||||
            thr = threading.Thread(target=self._hasher)
 | 
			
		||||
            thr.daemon = True
 | 
			
		||||
            thr.start()
 | 
			
		||||
 | 
			
		||||
            thr = threading.Thread(target=self._run_all_mtp)
 | 
			
		||||
            thr.daemon = True
 | 
			
		||||
            thr.start()
 | 
			
		||||
            if self.mtag:
 | 
			
		||||
                thr = threading.Thread(target=self._tagger)
 | 
			
		||||
                thr.daemon = True
 | 
			
		||||
                thr.start()
 | 
			
		||||
 | 
			
		||||
                thr = threading.Thread(target=self._run_all_mtp)
 | 
			
		||||
                thr.daemon = True
 | 
			
		||||
                thr.start()
 | 
			
		||||
 | 
			
		||||
    def log(self, msg, c=0):
 | 
			
		||||
        self.log_func("up2k", msg + "\033[K", c)
 | 
			
		||||
@@ -667,12 +668,6 @@ class Up2k(object):
 | 
			
		||||
            cur.close()
 | 
			
		||||
 | 
			
		||||
    def _start_mpool(self):
 | 
			
		||||
        if WINDOWS and False:
 | 
			
		||||
            nah = open(os.devnull, "wb")
 | 
			
		||||
            wmic = "processid={}".format(os.getpid())
 | 
			
		||||
            wmic = ["wmic", "process", "where", wmic, "call", "setpriority"]
 | 
			
		||||
            sp.call(wmic + ["below normal"], stdout=nah, stderr=nah)
 | 
			
		||||
 | 
			
		||||
        # mp.pool.ThreadPool and concurrent.futures.ThreadPoolExecutor
 | 
			
		||||
        # both do crazy runahead so lets reinvent another wheel
 | 
			
		||||
        nw = os.cpu_count() if hasattr(os, "cpu_count") else 4
 | 
			
		||||
@@ -697,12 +692,6 @@ class Up2k(object):
 | 
			
		||||
 | 
			
		||||
        mpool.join()
 | 
			
		||||
        done = self._flush_mpool(wcur)
 | 
			
		||||
        if WINDOWS and False:
 | 
			
		||||
            nah = open(os.devnull, "wb")
 | 
			
		||||
            wmic = "processid={}".format(os.getpid())
 | 
			
		||||
            wmic = ["wmic", "process", "where", wmic, "call", "setpriority"]
 | 
			
		||||
            sp.call(wmic + ["below normal"], stdout=nah, stderr=nah)
 | 
			
		||||
 | 
			
		||||
        return done
 | 
			
		||||
 | 
			
		||||
    def _tag_thr(self, q):
 | 
			
		||||
@@ -1068,6 +1057,8 @@ class Up2k(object):
 | 
			
		||||
        with self.mutex:
 | 
			
		||||
            job = self.registry[ptop].get(wark, None)
 | 
			
		||||
            if not job:
 | 
			
		||||
                known = " ".join([x for x in self.registry[ptop].keys()])
 | 
			
		||||
                self.log("unknown wark [{}], known: {}".format(wark, known))
 | 
			
		||||
                raise Pebkac(400, "unknown wark")
 | 
			
		||||
 | 
			
		||||
            if chash not in job["need"]:
 | 
			
		||||
@@ -1107,8 +1098,9 @@ class Up2k(object):
 | 
			
		||||
 | 
			
		||||
            atomic_move(src, dst)
 | 
			
		||||
 | 
			
		||||
            if WINDOWS:
 | 
			
		||||
                self.lastmod_q.put([dst, (int(time.time()), int(job["lmod"]))])
 | 
			
		||||
            if ANYWIN:
 | 
			
		||||
                a = [dst, job["size"], (int(time.time()), int(job["lmod"]))]
 | 
			
		||||
                self.lastmod_q.put(a)
 | 
			
		||||
 | 
			
		||||
            # legit api sware 2 me mum
 | 
			
		||||
            if self.idx_wark(
 | 
			
		||||
@@ -1209,6 +1201,17 @@ class Up2k(object):
 | 
			
		||||
        suffix = ".{:.6f}-{}".format(job["t0"], job["addr"])
 | 
			
		||||
        with ren_open(tnam, "wb", fdir=pdir, suffix=suffix) as f:
 | 
			
		||||
            f, job["tnam"] = f["orz"]
 | 
			
		||||
            if (
 | 
			
		||||
                ANYWIN
 | 
			
		||||
                and self.args.sparse
 | 
			
		||||
                and self.args.sparse * 1024 * 1024 <= job["size"]
 | 
			
		||||
            ):
 | 
			
		||||
                fp = os.path.join(pdir, job["tnam"])
 | 
			
		||||
                try:
 | 
			
		||||
                    sp.check_call(["fsutil", "sparse", "setflag", fp])
 | 
			
		||||
                except:
 | 
			
		||||
                    self.log("could not sparse [{}]".format(fp), 3)
 | 
			
		||||
 | 
			
		||||
            f.seek(job["size"] - 1)
 | 
			
		||||
            f.write(b"e")
 | 
			
		||||
 | 
			
		||||
@@ -1220,13 +1223,19 @@ class Up2k(object):
 | 
			
		||||
 | 
			
		||||
            # self.log("lmod: got {}".format(len(ready)))
 | 
			
		||||
            time.sleep(5)
 | 
			
		||||
            for path, times in ready:
 | 
			
		||||
            for path, sz, times in ready:
 | 
			
		||||
                self.log("lmod: setting times {} on {}".format(times, path))
 | 
			
		||||
                try:
 | 
			
		||||
                    os.utime(fsenc(path), times)
 | 
			
		||||
                except:
 | 
			
		||||
                    self.log("lmod: failed to utime ({}, {})".format(path, times))
 | 
			
		||||
 | 
			
		||||
                if self.args.sparse and self.args.sparse * 1024 * 1024 <= sz:
 | 
			
		||||
                    try:
 | 
			
		||||
                        sp.check_call(["fsutil", "sparse", "setflag", path, "0"])
 | 
			
		||||
                    except:
 | 
			
		||||
                        self.log("could not unsparse [{}]".format(path), 3)
 | 
			
		||||
 | 
			
		||||
    def _snapshot(self):
 | 
			
		||||
        persist_interval = 30  # persist unfinished uploads index every 30 sec
 | 
			
		||||
        discard_interval = 21600  # drop unfinished uploads after 6 hours inactivity
 | 
			
		||||
 
 | 
			
		||||
@@ -16,7 +16,7 @@ import mimetypes
 | 
			
		||||
import contextlib
 | 
			
		||||
import subprocess as sp  # nosec
 | 
			
		||||
 | 
			
		||||
from .__init__ import PY2, WINDOWS
 | 
			
		||||
from .__init__ import PY2, WINDOWS, ANYWIN
 | 
			
		||||
from .stolen import surrogateescape
 | 
			
		||||
 | 
			
		||||
FAKE_MP = False
 | 
			
		||||
@@ -580,8 +580,8 @@ def sanitize_fn(fn, ok=""):
 | 
			
		||||
    if "/" not in ok:
 | 
			
		||||
        fn = fn.replace("\\", "/").split("/")[-1]
 | 
			
		||||
 | 
			
		||||
    if WINDOWS:
 | 
			
		||||
        for bad, good in [x for x in [
 | 
			
		||||
    if ANYWIN:
 | 
			
		||||
        remap = [
 | 
			
		||||
            ["<", "<"],
 | 
			
		||||
            [">", ">"],
 | 
			
		||||
            [":", ":"],
 | 
			
		||||
@@ -591,7 +591,8 @@ def sanitize_fn(fn, ok=""):
 | 
			
		||||
            ["|", "|"],
 | 
			
		||||
            ["?", "?"],
 | 
			
		||||
            ["*", "*"],
 | 
			
		||||
        ] if x[0] not in ok]:
 | 
			
		||||
        ]
 | 
			
		||||
        for bad, good in [x for x in remap if x[0] not in ok]:
 | 
			
		||||
            fn = fn.replace(bad, good)
 | 
			
		||||
 | 
			
		||||
        bad = ["con", "prn", "aux", "nul"]
 | 
			
		||||
@@ -615,17 +616,24 @@ def exclude_dotfiles(filepaths):
 | 
			
		||||
    return [x for x in filepaths if not x.split("/")[-1].startswith(".")]
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def html_escape(s, quote=False):
 | 
			
		||||
def html_escape(s, quote=False, crlf=False):
 | 
			
		||||
    """html.escape but also newlines"""
 | 
			
		||||
    s = (
 | 
			
		||||
        s.replace("&", "&")
 | 
			
		||||
        .replace("<", "<")
 | 
			
		||||
        .replace(">", ">")
 | 
			
		||||
        .replace("\r", "
")
 | 
			
		||||
        .replace("\n", "
")
 | 
			
		||||
    )
 | 
			
		||||
    s = s.replace("&", "&").replace("<", "<").replace(">", ">")
 | 
			
		||||
    if quote:
 | 
			
		||||
        s = s.replace('"', """).replace("'", "'")
 | 
			
		||||
    if crlf:
 | 
			
		||||
        s = s.replace("\r", "
").replace("\n", "
")
 | 
			
		||||
 | 
			
		||||
    return s
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def html_bescape(s, quote=False, crlf=False):
 | 
			
		||||
    """html.escape but bytestrings"""
 | 
			
		||||
    s = s.replace(b"&", b"&").replace(b"<", b"<").replace(b">", b">")
 | 
			
		||||
    if quote:
 | 
			
		||||
        s = s.replace(b'"', b""").replace(b"'", b"'")
 | 
			
		||||
    if crlf:
 | 
			
		||||
        s = s.replace(b"\r", b"
").replace(b"\n", b"
")
 | 
			
		||||
 | 
			
		||||
    return s
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -183,9 +183,9 @@ a, #files tbody div a:last-child {
 | 
			
		||||
	text-shadow: 0 0 .3em #b80;
 | 
			
		||||
}
 | 
			
		||||
#files tbody tr.sel td {
 | 
			
		||||
	background: #80b;
 | 
			
		||||
	color: #fff;
 | 
			
		||||
	border-color: #a3d;
 | 
			
		||||
	background: #925;
 | 
			
		||||
	border-color: #c37;
 | 
			
		||||
}
 | 
			
		||||
#blocked {
 | 
			
		||||
	position: fixed;
 | 
			
		||||
@@ -243,7 +243,7 @@ a, #files tbody div a:last-child {
 | 
			
		||||
	height: 100%;
 | 
			
		||||
	background: #3c3c3c;
 | 
			
		||||
}
 | 
			
		||||
#wtoggle {
 | 
			
		||||
#wtico {
 | 
			
		||||
	cursor: url(/.cpr/dd/1.png), pointer;
 | 
			
		||||
	animation: cursor 500ms infinite;
 | 
			
		||||
}
 | 
			
		||||
@@ -273,24 +273,32 @@ a, #files tbody div a:last-child {
 | 
			
		||||
	padding: .2em 0 0 .07em;
 | 
			
		||||
	color: #fff;
 | 
			
		||||
}
 | 
			
		||||
#wtoggle>span {
 | 
			
		||||
#wzip {
 | 
			
		||||
	display: none;
 | 
			
		||||
	margin-right: .3em;
 | 
			
		||||
	padding-right: .3em;
 | 
			
		||||
	border-right: .1em solid #555;
 | 
			
		||||
}
 | 
			
		||||
#wtoggle,
 | 
			
		||||
#wtoggle * {
 | 
			
		||||
	line-height: 1em;
 | 
			
		||||
}
 | 
			
		||||
#wtoggle.sel {
 | 
			
		||||
	width: 4.27em;
 | 
			
		||||
	width: 6.4em;
 | 
			
		||||
}
 | 
			
		||||
#wtoggle.sel>span {
 | 
			
		||||
#wtoggle.sel #wzip {
 | 
			
		||||
	display: inline-block;
 | 
			
		||||
	line-height: 0;
 | 
			
		||||
}
 | 
			
		||||
#wtoggle.sel>span a {
 | 
			
		||||
#wtoggle.sel #wzip a {
 | 
			
		||||
	font-size: .4em;
 | 
			
		||||
	margin: -.3em 0;
 | 
			
		||||
	padding: 0 .3em;
 | 
			
		||||
	margin: -.3em .2em;
 | 
			
		||||
	position: relative;
 | 
			
		||||
	display: inline-block;
 | 
			
		||||
}
 | 
			
		||||
#wtoggle.sel>span #selzip {
 | 
			
		||||
#wtoggle.sel #wzip #selzip {
 | 
			
		||||
	top: -.6em;
 | 
			
		||||
	padding: .4em .3em;
 | 
			
		||||
}
 | 
			
		||||
#barpos,
 | 
			
		||||
#barbuf {
 | 
			
		||||
@@ -487,7 +495,7 @@ input[type="checkbox"]:checked+label {
 | 
			
		||||
}
 | 
			
		||||
#tree {
 | 
			
		||||
	display: none;
 | 
			
		||||
	position: fixed;
 | 
			
		||||
	position: absolute;
 | 
			
		||||
	left: 0;
 | 
			
		||||
	bottom: 0;
 | 
			
		||||
	top: 7em;
 | 
			
		||||
@@ -677,3 +685,173 @@ input[type="checkbox"]:checked+label {
 | 
			
		||||
	font-family: monospace, monospace;
 | 
			
		||||
	line-height: 2em;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
html.light {
 | 
			
		||||
	color: #333;
 | 
			
		||||
	background: #eee;
 | 
			
		||||
	text-shadow: none;
 | 
			
		||||
}
 | 
			
		||||
html.light #ops,
 | 
			
		||||
html.light .opbox,
 | 
			
		||||
html.light #srch_form {
 | 
			
		||||
	background: #f7f7f7;
 | 
			
		||||
	box-shadow: 0 0 .3em #ddd;
 | 
			
		||||
	border-color: #f7f7f7;
 | 
			
		||||
}
 | 
			
		||||
html.light #ops a.act {
 | 
			
		||||
	box-shadow: 0 .2em .2em #ccc;
 | 
			
		||||
	background: #f7f7f7;
 | 
			
		||||
	border-color: #07a;
 | 
			
		||||
	padding-top: .4em;
 | 
			
		||||
}
 | 
			
		||||
html.light #op_cfg h3 {
 | 
			
		||||
	border-color: #ccc;
 | 
			
		||||
}
 | 
			
		||||
html.light .tglbtn,
 | 
			
		||||
html.light #tree > a + a {
 | 
			
		||||
	color: #666;
 | 
			
		||||
	background: #ddd;
 | 
			
		||||
	box-shadow: none;
 | 
			
		||||
}
 | 
			
		||||
html.light .tglbtn:hover,
 | 
			
		||||
html.light #tree > a + a:hover {
 | 
			
		||||
	background: #caf;
 | 
			
		||||
}
 | 
			
		||||
html.light .tglbtn.on,
 | 
			
		||||
html.light #tree > a + a.on {
 | 
			
		||||
	background: #4a0;
 | 
			
		||||
	color: #fff;
 | 
			
		||||
}
 | 
			
		||||
html.light #srv_info {
 | 
			
		||||
	color: #c83;
 | 
			
		||||
	text-shadow: 1px 1px 0 #fff;
 | 
			
		||||
}
 | 
			
		||||
html.light #srv_info span {
 | 
			
		||||
	color: #000;
 | 
			
		||||
}
 | 
			
		||||
html.light #treeul a+a {
 | 
			
		||||
	background: inherit;
 | 
			
		||||
	color: #06a;
 | 
			
		||||
}
 | 
			
		||||
html.light #treeul a.hl {
 | 
			
		||||
	background: #07a;
 | 
			
		||||
	color: #fff;
 | 
			
		||||
}
 | 
			
		||||
html.light #tree li {
 | 
			
		||||
	border-color: #ddd #fff #f7f7f7 #fff;
 | 
			
		||||
}
 | 
			
		||||
html.light #tree ul {
 | 
			
		||||
	border-color: #ccc;
 | 
			
		||||
}
 | 
			
		||||
html.light a,
 | 
			
		||||
html.light #ops a,
 | 
			
		||||
html.light #files tbody div a:last-child {
 | 
			
		||||
	color: #06a;
 | 
			
		||||
}
 | 
			
		||||
html.light #files tbody {
 | 
			
		||||
	background: #f7f7f7;
 | 
			
		||||
}
 | 
			
		||||
html.light #files {
 | 
			
		||||
	box-shadow: 0 0 .3em #ccc;
 | 
			
		||||
}
 | 
			
		||||
html.light #files thead th {
 | 
			
		||||
	background: #eee;
 | 
			
		||||
}
 | 
			
		||||
html.light #files tr+tr td {
 | 
			
		||||
	border-top: 1px solid #ddd;
 | 
			
		||||
}
 | 
			
		||||
html.light #files td {
 | 
			
		||||
	border-bottom: 1px solid #f7f7f7;
 | 
			
		||||
}
 | 
			
		||||
html.light #files tbody tr:last-child td {
 | 
			
		||||
	border-bottom: .2em solid #ccc;
 | 
			
		||||
}
 | 
			
		||||
html.light #files td:nth-child(2n) {
 | 
			
		||||
	color: #d38;
 | 
			
		||||
}
 | 
			
		||||
html.light #files tr:hover td {
 | 
			
		||||
	background: #fff;
 | 
			
		||||
}
 | 
			
		||||
html.light #files tbody a.play {
 | 
			
		||||
	color: #c0f;
 | 
			
		||||
}
 | 
			
		||||
html.light tr.play td {
 | 
			
		||||
	background: #fc5;
 | 
			
		||||
}
 | 
			
		||||
html.light tr.play a {
 | 
			
		||||
	color: #406;
 | 
			
		||||
}
 | 
			
		||||
html.light #files > thead > tr > th.min span {
 | 
			
		||||
	background: linear-gradient(90deg, rgba(68,68,68,0), rgba(68,68,68,0.2) 70%, rgba(68,68,68,0.5));
 | 
			
		||||
}
 | 
			
		||||
html.light #blocked {
 | 
			
		||||
	background: #eee;
 | 
			
		||||
}
 | 
			
		||||
html.light #blk_play a,
 | 
			
		||||
html.light #blk_abrt a {
 | 
			
		||||
	background: #fff;
 | 
			
		||||
	box-shadow: 0 .2em .4em #ddd;
 | 
			
		||||
}
 | 
			
		||||
html.light #widget a {
 | 
			
		||||
	color: #fc5;
 | 
			
		||||
}
 | 
			
		||||
html.light #files tr.sel:hover td {
 | 
			
		||||
	background: #c37;
 | 
			
		||||
}
 | 
			
		||||
html.light #files tr.sel td {
 | 
			
		||||
	color: #fff;
 | 
			
		||||
}
 | 
			
		||||
html.light #files tr.sel a {
 | 
			
		||||
	color: #fff;
 | 
			
		||||
}
 | 
			
		||||
html.light input[type="checkbox"] + label {
 | 
			
		||||
	color: #333;
 | 
			
		||||
}
 | 
			
		||||
html.light .opview input[type="text"] {
 | 
			
		||||
	background: #fff;
 | 
			
		||||
	color: #333;
 | 
			
		||||
	box-shadow: 0 0 2px #888;
 | 
			
		||||
	border-color: #38d;
 | 
			
		||||
}
 | 
			
		||||
html.light #ops:hover #opdesc {
 | 
			
		||||
	background: #fff;
 | 
			
		||||
	box-shadow: 0 .3em 1em #ccc;
 | 
			
		||||
}
 | 
			
		||||
html.light #opdesc code {
 | 
			
		||||
	background: #060;
 | 
			
		||||
	color: #fff;
 | 
			
		||||
}
 | 
			
		||||
html.light #u2tab a>span,
 | 
			
		||||
html.light #files td div span {
 | 
			
		||||
	color: #000;
 | 
			
		||||
}
 | 
			
		||||
html.light #path {
 | 
			
		||||
	background: #f7f7f7;
 | 
			
		||||
	text-shadow: none;
 | 
			
		||||
	box-shadow: 0 0 .3em #bbb;
 | 
			
		||||
}
 | 
			
		||||
html.light #path a {
 | 
			
		||||
	color: #333;
 | 
			
		||||
}
 | 
			
		||||
html.light #path a:not(:last-child)::after {
 | 
			
		||||
	border-color: #ccc;
 | 
			
		||||
	background: none;
 | 
			
		||||
	border-width: .1em .1em 0 0;
 | 
			
		||||
	margin: -.2em .3em -.2em -.3em;
 | 
			
		||||
}
 | 
			
		||||
html.light #path a:hover {
 | 
			
		||||
	background: none;
 | 
			
		||||
	color: #60a;
 | 
			
		||||
}
 | 
			
		||||
html.light #files tbody div a {
 | 
			
		||||
	color: #d38;
 | 
			
		||||
}
 | 
			
		||||
html.light #files a:hover,
 | 
			
		||||
html.light #files tr.sel a:hover {
 | 
			
		||||
	color: #000;
 | 
			
		||||
	background: #fff;
 | 
			
		||||
}
 | 
			
		||||
@@ -13,8 +13,8 @@
 | 
			
		||||
<body>
 | 
			
		||||
    <div id="ops">
 | 
			
		||||
        <a href="#" data-dest="" data-desc="close submenu">---</a>
 | 
			
		||||
        <a href="#" data-perm="read" data-dest="search" data-desc="search for files by attributes, path/name, music tags, or any combination of those.<br /><br /><code>foo bar</code> = must contain both foo and bar,<br /><code>foo -bar</code> = must contain foo but not bar,<br /><code>^yana .opus$</code> = must start with yana and have the opus extension">🔎</a>
 | 
			
		||||
        {%- if have_up2k_idx %}
 | 
			
		||||
        <a href="#" data-perm="read" data-dest="search" data-desc="search for files by attributes, path/name, music tags, or any combination of those.<br /><br /><code>foo bar</code> = must contain both foo and bar,<br /><code>foo -bar</code> = must contain foo but not bar,<br /><code>^yana .opus$</code> = must start with yana and have the opus extension">🔎</a>
 | 
			
		||||
        <a href="#" data-dest="up2k" data-desc="up2k: upload files (if you have write-access) or toggle into the search-mode and drag files onto the search button to see if they exist somewhere on the server">🚀</a>
 | 
			
		||||
        {%- else %}
 | 
			
		||||
        <a href="#" data-perm="write" data-dest="up2k" data-desc="up2k: upload files with resume support (close your browser and drop the same files in later)">🚀</a>
 | 
			
		||||
@@ -39,14 +39,17 @@
 | 
			
		||||
    {%- include 'upload.html' %}
 | 
			
		||||
 | 
			
		||||
    <div id="op_cfg" class="opview opbox">
 | 
			
		||||
        <h3>key notation</h3>
 | 
			
		||||
        <div id="key_notation"></div>
 | 
			
		||||
        <h3>switches</h3>
 | 
			
		||||
        <div>
 | 
			
		||||
            <a id="tooltips" class="tglbtn" href="#">tooltips</a>
 | 
			
		||||
            <a id="lightmode" class="tglbtn" href="#">lightmode</a>
 | 
			
		||||
        </div>
 | 
			
		||||
        {%- if have_zip %}
 | 
			
		||||
        <h3>folder download</h3>
 | 
			
		||||
        <div id="arc_fmt"></div>
 | 
			
		||||
        {%- endif %}
 | 
			
		||||
        <h3>tooltips</h3>
 | 
			
		||||
        <div><a id="tooltips" class="tglbtn" href="#">enable</a></div>
 | 
			
		||||
        <h3>key notation</h3>
 | 
			
		||||
        <div id="key_notation"></div>
 | 
			
		||||
    </div>
 | 
			
		||||
    
 | 
			
		||||
    <h1 id="path">
 | 
			
		||||
@@ -113,12 +116,12 @@
 | 
			
		||||
 | 
			
		||||
    <div id="widget">
 | 
			
		||||
        <div id="wtoggle">
 | 
			
		||||
            <span>
 | 
			
		||||
            <span id="wzip">
 | 
			
		||||
                <a href="#" id="selall">sel.<br />all</a>
 | 
			
		||||
                <a href="#" id="selinv">sel.<br />inv.</a>
 | 
			
		||||
                <a href="#" id="selzip">zip</a>
 | 
			
		||||
            </span>
 | 
			
		||||
            ♫
 | 
			
		||||
            </span><a
 | 
			
		||||
                href="#" id="wtico">♫</a>
 | 
			
		||||
        </div>
 | 
			
		||||
        <div id="widgeti">
 | 
			
		||||
            <div id="pctl"><a href="#" id="bprev">⏮</a><a href="#" id="bplay">▶</a><a href="#" id="bnext">⏭</a></div>
 | 
			
		||||
 
 | 
			
		||||
@@ -75,7 +75,7 @@ makeSortable(ebi('files'), mp.read_order.bind(mp));
 | 
			
		||||
var widget = (function () {
 | 
			
		||||
	var ret = {};
 | 
			
		||||
	var widget = ebi('widget');
 | 
			
		||||
	var wtoggle = ebi('wtoggle');
 | 
			
		||||
	var wtico = ebi('wtico');
 | 
			
		||||
	var touchmode = false;
 | 
			
		||||
	var side_open = false;
 | 
			
		||||
	var was_paused = true;
 | 
			
		||||
@@ -113,14 +113,7 @@ var widget = (function () {
 | 
			
		||||
 | 
			
		||||
		return false;
 | 
			
		||||
	};
 | 
			
		||||
	if (window.Touch) {
 | 
			
		||||
		var touch_handler = function (e) {
 | 
			
		||||
			touchmode = true;
 | 
			
		||||
			return ret.toggle(e);
 | 
			
		||||
		};
 | 
			
		||||
		wtoggle.addEventListener('touchstart', touch_handler, false);
 | 
			
		||||
	}
 | 
			
		||||
	wtoggle.onclick = click_handler;
 | 
			
		||||
	wtico.onclick = click_handler;
 | 
			
		||||
	return ret;
 | 
			
		||||
})();
 | 
			
		||||
 | 
			
		||||
@@ -321,10 +314,10 @@ function seek_au_sec(seek) {
 | 
			
		||||
 | 
			
		||||
	mp.au.currentTime = seek;
 | 
			
		||||
 | 
			
		||||
	// ogv.js breaks on .play() during playback
 | 
			
		||||
	if (mp.au === mp.au_native)
 | 
			
		||||
		// hack: ogv.js breaks on .play() during playback
 | 
			
		||||
		mp.au.play();
 | 
			
		||||
};
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
function song_skip(n) {
 | 
			
		||||
@@ -336,7 +329,7 @@ function song_skip(n) {
 | 
			
		||||
		play(mp.order.indexOf(tid) + n);
 | 
			
		||||
	else
 | 
			
		||||
		play(mp.order[0]);
 | 
			
		||||
};
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
// hook up the widget buttons
 | 
			
		||||
@@ -434,7 +427,7 @@ catch (ex) { }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
// plays the tid'th audio file on the page
 | 
			
		||||
function play(tid, call_depth) {
 | 
			
		||||
function play(tid, seek, call_depth) {
 | 
			
		||||
	if (mp.order.length == 0)
 | 
			
		||||
		return alert('no audio found wait what');
 | 
			
		||||
 | 
			
		||||
@@ -456,7 +449,7 @@ function play(tid, call_depth) {
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// ogv.js breaks on .play() unless directly user-triggered
 | 
			
		||||
	var hack_attempt_play = true;
 | 
			
		||||
	var attempt_play = true;
 | 
			
		||||
 | 
			
		||||
	var url = mp.tracks[tid];
 | 
			
		||||
	if (need_ogv && /\.(ogg|opus)$/i.test(url)) {
 | 
			
		||||
@@ -465,7 +458,7 @@ function play(tid, call_depth) {
 | 
			
		||||
		}
 | 
			
		||||
		else if (window['OGVPlayer']) {
 | 
			
		||||
			mp.au = mp.au_ogvjs = new OGVPlayer();
 | 
			
		||||
			hack_attempt_play = false;
 | 
			
		||||
			attempt_play = false;
 | 
			
		||||
			mp.au.addEventListener('error', evau_error, true);
 | 
			
		||||
			mp.au.addEventListener('progress', pbar.drawpos, false);
 | 
			
		||||
			widget.open();
 | 
			
		||||
@@ -477,7 +470,7 @@ function play(tid, call_depth) {
 | 
			
		||||
			show_modal('<h1>loading ogv.js</h1><h2>thanks apple</h2>');
 | 
			
		||||
 | 
			
		||||
			import_js('/.cpr/deps/ogv.js', function () {
 | 
			
		||||
				play(tid, 1);
 | 
			
		||||
				play(tid, seek, 1);
 | 
			
		||||
			});
 | 
			
		||||
 | 
			
		||||
			return;
 | 
			
		||||
@@ -505,21 +498,26 @@ function play(tid, call_depth) {
 | 
			
		||||
	ebi(oid).parentElement.parentElement.className += ' play';
 | 
			
		||||
 | 
			
		||||
	try {
 | 
			
		||||
		if (hack_attempt_play)
 | 
			
		||||
		if (attempt_play)
 | 
			
		||||
			mp.au.play();
 | 
			
		||||
 | 
			
		||||
		if (mp.au.paused)
 | 
			
		||||
			autoplay_blocked();
 | 
			
		||||
			autoplay_blocked(seek);
 | 
			
		||||
		else if (seek) {
 | 
			
		||||
			seek_au_sec(seek);
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		var o = ebi(oid);
 | 
			
		||||
		o.setAttribute('id', 'thx_js');
 | 
			
		||||
		if (window.history && history.replaceState) {
 | 
			
		||||
			hist_replace(document.location.pathname + '#' + oid);
 | 
			
		||||
		if (!seek) {
 | 
			
		||||
			var o = ebi(oid);
 | 
			
		||||
			o.setAttribute('id', 'thx_js');
 | 
			
		||||
			if (window.history && history.replaceState) {
 | 
			
		||||
				hist_replace(document.location.pathname + '#' + oid);
 | 
			
		||||
			}
 | 
			
		||||
			else {
 | 
			
		||||
				document.location.hash = oid;
 | 
			
		||||
			}
 | 
			
		||||
			o.setAttribute('id', oid);
 | 
			
		||||
		}
 | 
			
		||||
		else {
 | 
			
		||||
			document.location.hash = oid;
 | 
			
		||||
		}
 | 
			
		||||
		o.setAttribute('id', oid);
 | 
			
		||||
 | 
			
		||||
		pbar.drawbuf();
 | 
			
		||||
		return true;
 | 
			
		||||
@@ -583,7 +581,7 @@ function unblocked() {
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
// show ui to manually start playback of a linked song
 | 
			
		||||
function autoplay_blocked() {
 | 
			
		||||
function autoplay_blocked(seek) {
 | 
			
		||||
	show_modal(
 | 
			
		||||
		'<div id="blk_play"><a href="#" id="blk_go"></a></div>' +
 | 
			
		||||
		'<div id="blk_abrt"><a href="#" id="blk_na">Cancel<br />(show file list)</a></div>');
 | 
			
		||||
@@ -599,6 +597,8 @@ function autoplay_blocked() {
 | 
			
		||||
		if (e) e.preventDefault();
 | 
			
		||||
		unblocked();
 | 
			
		||||
		mp.au.play();
 | 
			
		||||
		if (seek)
 | 
			
		||||
			seek_au_sec(seek);
 | 
			
		||||
	};
 | 
			
		||||
	na.onclick = unblocked;
 | 
			
		||||
}
 | 
			
		||||
@@ -607,8 +607,20 @@ function autoplay_blocked() {
 | 
			
		||||
// autoplay linked track
 | 
			
		||||
(function () {
 | 
			
		||||
	var v = location.hash;
 | 
			
		||||
	if (v && v.length == 12 && v.indexOf('#af-') === 0)
 | 
			
		||||
		play(v.slice(2));
 | 
			
		||||
	if (v && v.indexOf('#af-') === 0) {
 | 
			
		||||
		var id = v.slice(2).split('&');
 | 
			
		||||
		if (id[0].length != 10)
 | 
			
		||||
			return;
 | 
			
		||||
 | 
			
		||||
		if (id.length == 1)
 | 
			
		||||
			return play(id[0]);
 | 
			
		||||
 | 
			
		||||
		var m = /^[Tt=0]*([0-9]+[Mm:])?0*([0-9]+)[Ss]?$/.exec(id[1]);
 | 
			
		||||
		if (!m)
 | 
			
		||||
			return play(id[0]);
 | 
			
		||||
 | 
			
		||||
		return play(id[0], parseInt(m[1] || 0) * 60 + parseInt(m[2] || 0));
 | 
			
		||||
	}
 | 
			
		||||
})();
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@@ -625,13 +637,16 @@ function tree_neigh(n) {
 | 
			
		||||
			break;
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	a += n;
 | 
			
		||||
	if (a < 0)
 | 
			
		||||
		a = links.length - 1;
 | 
			
		||||
	if (a >= links.length)
 | 
			
		||||
		a = 0;
 | 
			
		||||
	if (act == -1)
 | 
			
		||||
		return;
 | 
			
		||||
 | 
			
		||||
	links[a].click();
 | 
			
		||||
	act += n;
 | 
			
		||||
	if (act < 0)
 | 
			
		||||
		act = links.length - 1;
 | 
			
		||||
	if (act >= links.length)
 | 
			
		||||
		act = 0;
 | 
			
		||||
 | 
			
		||||
	links[act].click();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@@ -652,6 +667,9 @@ 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)
 | 
			
		||||
		return;
 | 
			
		||||
 | 
			
		||||
	var k = (e.code + ''), pos = -1;
 | 
			
		||||
	if (k.indexOf('Digit') === 0)
 | 
			
		||||
		pos = parseInt(k.slice(-1)) * 0.1;
 | 
			
		||||
@@ -879,12 +897,16 @@ document.onkeydown = function (e) {
 | 
			
		||||
var treectl = (function () {
 | 
			
		||||
	var treectl = {
 | 
			
		||||
		"hidden": false
 | 
			
		||||
	};
 | 
			
		||||
	var dyn = bcfg_get('dyntree', true);
 | 
			
		||||
	var treesz = icfg_get('treesz', 16);
 | 
			
		||||
	},
 | 
			
		||||
		entreed = false,
 | 
			
		||||
		fixedpos = false,
 | 
			
		||||
		prev_atop = null,
 | 
			
		||||
		prev_winh = null,
 | 
			
		||||
		dyn = bcfg_get('dyntree', true),
 | 
			
		||||
		treesz = icfg_get('treesz', 16);
 | 
			
		||||
 | 
			
		||||
	treesz = Math.min(Math.max(treesz, 4), 50);
 | 
			
		||||
	console.log('treesz [' + treesz + ']');
 | 
			
		||||
	var entreed = false;
 | 
			
		||||
 | 
			
		||||
	function entree(e) {
 | 
			
		||||
		ev(e);
 | 
			
		||||
@@ -916,13 +938,43 @@ var treectl = (function () {
 | 
			
		||||
		if (!entreed || treectl.hidden)
 | 
			
		||||
			return;
 | 
			
		||||
 | 
			
		||||
		var top = ebi('wrap').getBoundingClientRect().top;
 | 
			
		||||
		ebi('tree').style.top = Math.max(0, parseInt(top)) + 'px';
 | 
			
		||||
		var tree = ebi('tree'),
 | 
			
		||||
			wrap = ebi('wrap'),
 | 
			
		||||
			atop = wrap.getBoundingClientRect().top,
 | 
			
		||||
			winh = window.innerHeight;
 | 
			
		||||
 | 
			
		||||
		if (atop === prev_atop && winh === prev_winh)
 | 
			
		||||
			return;
 | 
			
		||||
 | 
			
		||||
		prev_atop = atop;
 | 
			
		||||
		prev_winh = winh;
 | 
			
		||||
 | 
			
		||||
		if (fixedpos && atop >= 0) {
 | 
			
		||||
			tree.style.position = 'absolute';
 | 
			
		||||
			tree.style.bottom = '';
 | 
			
		||||
			fixedpos = false;
 | 
			
		||||
		}
 | 
			
		||||
		else if (!fixedpos && atop < 0) {
 | 
			
		||||
			tree.style.position = 'fixed';
 | 
			
		||||
			tree.style.height = 'auto';
 | 
			
		||||
			fixedpos = true;
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		if (fixedpos) {
 | 
			
		||||
			tree.style.top = Math.max(0, parseInt(atop)) + 'px';
 | 
			
		||||
		}
 | 
			
		||||
		else {
 | 
			
		||||
			var top = Math.max(0, parseInt(wrap.offsetTop)),
 | 
			
		||||
				treeh = (winh - atop) - 4;
 | 
			
		||||
 | 
			
		||||
			tree.style.top = top + 'px';
 | 
			
		||||
			tree.style.height = treeh < 10 ? '' : treeh + 'px';
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	function periodic() {
 | 
			
		||||
		onscroll();
 | 
			
		||||
		setTimeout(periodic, document.visibilityState ? 200 : 5000);
 | 
			
		||||
		setTimeout(periodic, document.visibilityState ? 100 : 5000);
 | 
			
		||||
	}
 | 
			
		||||
	periodic();
 | 
			
		||||
 | 
			
		||||
@@ -1516,8 +1568,11 @@ function addcrc() {
 | 
			
		||||
			ebi('unsearch') ? 'div>a:last-child' : 'a'));
 | 
			
		||||
 | 
			
		||||
	for (var a = 0, aa = links.length; a < aa; a++)
 | 
			
		||||
		if (!links[a].getAttribute('id'))
 | 
			
		||||
			links[a].setAttribute('id', 'f-' + crc32(links[a].textContent || links[a].innerText));
 | 
			
		||||
		if (!links[a].getAttribute('id')) {
 | 
			
		||||
			var crc = crc32(links[a].textContent || links[a].innerText);
 | 
			
		||||
			crc = ('00000000' + crc).slice(-8);
 | 
			
		||||
			links[a].setAttribute('id', 'f-' + crc);
 | 
			
		||||
		}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@@ -1544,6 +1599,24 @@ function addcrc() {
 | 
			
		||||
})();
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
(function () {
 | 
			
		||||
	var light = bcfg_get('lightmode', false);
 | 
			
		||||
 | 
			
		||||
	function freshen() {
 | 
			
		||||
		document.documentElement.setAttribute("class", light ? "light" : "");
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	ebi('lightmode').onclick = function (e) {
 | 
			
		||||
		ev(e);
 | 
			
		||||
		light = !light;
 | 
			
		||||
		bcfg_set('lightmode', light);
 | 
			
		||||
		freshen();
 | 
			
		||||
	};
 | 
			
		||||
 | 
			
		||||
	freshen();
 | 
			
		||||
})();
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
var arcfmt = (function () {
 | 
			
		||||
	if (!ebi('arc_fmt'))
 | 
			
		||||
		return { "render": function () { } };
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										55
									
								
								copyparty/web/browser2.html
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										55
									
								
								copyparty/web/browser2.html
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,55 @@
 | 
			
		||||
<!DOCTYPE html>
 | 
			
		||||
<html lang="en">
 | 
			
		||||
 | 
			
		||||
<head>
 | 
			
		||||
    <meta charset="utf-8">
 | 
			
		||||
    <title>{{ title }}</title>
 | 
			
		||||
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
 | 
			
		||||
    <meta name="viewport" content="width=device-width, initial-scale=0.8">
 | 
			
		||||
</head>
 | 
			
		||||
 | 
			
		||||
<body>
 | 
			
		||||
    {%- if srv_info %}
 | 
			
		||||
    <p><span>{{ srv_info }}</span></p>
 | 
			
		||||
    {%- endif %}
 | 
			
		||||
 | 
			
		||||
    {%- if have_b_u %}
 | 
			
		||||
    <form method="post" enctype="multipart/form-data" accept-charset="utf-8" action="{{ url_suf }}">
 | 
			
		||||
        <input type="hidden" name="act" value="bput" />
 | 
			
		||||
        <input type="file" name="f" multiple /><br />
 | 
			
		||||
        <input type="submit" value="start upload" />
 | 
			
		||||
    </form>
 | 
			
		||||
    <br />
 | 
			
		||||
    {%- endif %}
 | 
			
		||||
 | 
			
		||||
    {%- if logues[0] %}
 | 
			
		||||
    <div>{{ logues[0] }}</div><br />
 | 
			
		||||
    {%- endif %}
 | 
			
		||||
 | 
			
		||||
    <table id="files">
 | 
			
		||||
        <thead>
 | 
			
		||||
            <tr>
 | 
			
		||||
                <th name="lead"><span>c</span></th>
 | 
			
		||||
                <th name="href"><span>File Name</span></th>
 | 
			
		||||
                <th name="sz" sort="int"><span>Size</span></th>
 | 
			
		||||
                <th name="ts"><span>Date</span></th>
 | 
			
		||||
            </tr>
 | 
			
		||||
        </thead>
 | 
			
		||||
        <tbody>
 | 
			
		||||
            <tr><td></td><td><a href="../{{ url_suf }}">parent folder</a></td><td>-</td><td>-</td></tr>
 | 
			
		||||
 | 
			
		||||
{%- for f in files %}
 | 
			
		||||
    <tr><td>{{ f.lead }}</td><td><a href="{{ f.href }}{{ url_suf }}">{{ f.name|e }}</a></td><td>{{ f.sz }}</td><td>{{ f.dt }}</td></tr>
 | 
			
		||||
{%- endfor %}
 | 
			
		||||
 | 
			
		||||
        </tbody>
 | 
			
		||||
    </table>
 | 
			
		||||
    
 | 
			
		||||
    {%- if logues[1] %}
 | 
			
		||||
    <div>{{ logues[1] }}</div><br />
 | 
			
		||||
    {%- endif %}
 | 
			
		||||
    
 | 
			
		||||
    <h2><a href="{{ url_suf }}&h">control-panel</a></h2>
 | 
			
		||||
 | 
			
		||||
</body>
 | 
			
		||||
</html>
 | 
			
		||||
@@ -138,10 +138,10 @@ var md_opt = {
 | 
			
		||||
        document.documentElement.setAttribute("class", dark ? "dark" : "");
 | 
			
		||||
        btn.innerHTML = "go " + (dark ? "light" : "dark");
 | 
			
		||||
        if (window.localStorage)
 | 
			
		||||
            localStorage.setItem('darkmode', dark ? 1 : 0);
 | 
			
		||||
            localStorage.setItem('lightmode', dark ? 0 : 1);
 | 
			
		||||
    };
 | 
			
		||||
    btn.onclick = toggle;
 | 
			
		||||
    if (window.localStorage && localStorage.getItem('darkmode') == 1)
 | 
			
		||||
    if (window.localStorage && localStorage.getItem('lightmode') != 1)
 | 
			
		||||
		toggle();
 | 
			
		||||
})();
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -31,12 +31,12 @@ var md_opt = {
 | 
			
		||||
 | 
			
		||||
var lightswitch = (function () {
 | 
			
		||||
	var fun = function () {
 | 
			
		||||
		var dark = !!!document.documentElement.getAttribute("class");
 | 
			
		||||
		var dark = !document.documentElement.getAttribute("class");
 | 
			
		||||
		document.documentElement.setAttribute("class", dark ? "dark" : "");
 | 
			
		||||
		if (window.localStorage)
 | 
			
		||||
			localStorage.setItem('darkmode', dark ? 1 : 0);
 | 
			
		||||
			localStorage.setItem('lightmode', dark ? 0 : 1);
 | 
			
		||||
	};
 | 
			
		||||
	if (window.localStorage && localStorage.getItem('darkmode') == 1)
 | 
			
		||||
	if (window.localStorage && localStorage.getItem('lightmode') != 1)
 | 
			
		||||
		fun();
 | 
			
		||||
	
 | 
			
		||||
	return fun;
 | 
			
		||||
 
 | 
			
		||||
@@ -16,20 +16,20 @@
 | 
			
		||||
        <h1>you can browse these:</h1>
 | 
			
		||||
        <ul>
 | 
			
		||||
            {% for mp in rvol %}
 | 
			
		||||
            <li><a href="/{{ mp }}">/{{ mp }}</a></li>
 | 
			
		||||
            <li><a href="/{{ mp }}{{ url_suf }}">/{{ mp }}</a></li>
 | 
			
		||||
            {% endfor %}
 | 
			
		||||
        </ul>
 | 
			
		||||
 | 
			
		||||
        <h1>you can upload to:</h1>
 | 
			
		||||
        <ul>
 | 
			
		||||
            {% for mp in wvol %}
 | 
			
		||||
            <li><a href="/{{ mp }}">/{{ mp }}</a></li>
 | 
			
		||||
            <li><a href="/{{ mp }}{{ url_suf }}">/{{ mp }}</a></li>
 | 
			
		||||
            {% endfor %}
 | 
			
		||||
        </ul>
 | 
			
		||||
 | 
			
		||||
        <h1>login for more:</h1>
 | 
			
		||||
        <ul>
 | 
			
		||||
            <form method="post" enctype="multipart/form-data" action="/">
 | 
			
		||||
            <form method="post" enctype="multipart/form-data" action="/{{ url_suf }}">
 | 
			
		||||
                <input type="hidden" name="act" value="login" />
 | 
			
		||||
                <input type="password" name="cppwd" />
 | 
			
		||||
                <input type="submit" value="Login" />
 | 
			
		||||
@@ -38,7 +38,7 @@
 | 
			
		||||
    </div>
 | 
			
		||||
    <script>
 | 
			
		||||
 | 
			
		||||
if (window.localStorage && localStorage.getItem('darkmode') == 1)
 | 
			
		||||
if (window.localStorage && localStorage.getItem('lightmode') != 1)
 | 
			
		||||
    document.documentElement.setAttribute("class", "dark");
 | 
			
		||||
 | 
			
		||||
</script>
 | 
			
		||||
 
 | 
			
		||||
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							@@ -47,6 +47,11 @@
 | 
			
		||||
	margin: -1.5em 0;
 | 
			
		||||
	padding: .8em 0;
 | 
			
		||||
	width: 100%;
 | 
			
		||||
	max-width: 12em;
 | 
			
		||||
	display: inline-block;
 | 
			
		||||
}
 | 
			
		||||
#u2conf #u2btn_cw {
 | 
			
		||||
	text-align: right;
 | 
			
		||||
}
 | 
			
		||||
#u2notbtn {
 | 
			
		||||
	display: none;
 | 
			
		||||
@@ -72,6 +77,7 @@
 | 
			
		||||
}
 | 
			
		||||
#u2tab td:nth-child(2) {
 | 
			
		||||
	width: 5em;
 | 
			
		||||
	white-space: nowrap;
 | 
			
		||||
}
 | 
			
		||||
#u2tab td:nth-child(3) {
 | 
			
		||||
	width: 40%;
 | 
			
		||||
@@ -83,6 +89,42 @@
 | 
			
		||||
#u2tab tr+tr:hover td {
 | 
			
		||||
	background: #222;
 | 
			
		||||
}
 | 
			
		||||
#u2cards {
 | 
			
		||||
	padding: 1em 0 .3em 0;
 | 
			
		||||
	margin: 1.5em auto -2.5em auto;
 | 
			
		||||
	text-align: center;
 | 
			
		||||
	overflow: hidden;
 | 
			
		||||
}
 | 
			
		||||
#u2cards.w {
 | 
			
		||||
	width: 45em;
 | 
			
		||||
	text-align: left;
 | 
			
		||||
}
 | 
			
		||||
#u2cards a {
 | 
			
		||||
	padding: .2em 1em;
 | 
			
		||||
	border: 1px solid #777;
 | 
			
		||||
	border-width: 0 0 1px 0;
 | 
			
		||||
	background: linear-gradient(to bottom, #333, #222);
 | 
			
		||||
}
 | 
			
		||||
#u2cards a:first-child {
 | 
			
		||||
	border-radius: .4em 0 0 0;
 | 
			
		||||
}
 | 
			
		||||
#u2cards a:last-child {
 | 
			
		||||
	border-radius: 0 .4em 0 0;
 | 
			
		||||
}
 | 
			
		||||
#u2cards a.act {
 | 
			
		||||
	padding-bottom: .5em;
 | 
			
		||||
	border-width: 1px 1px .1em 1px;
 | 
			
		||||
	border-radius: .3em .3em 0 0;
 | 
			
		||||
	margin-left: -1px;
 | 
			
		||||
	background: linear-gradient(to bottom, #464, #333 80%);
 | 
			
		||||
	box-shadow: 0 -.17em .67em #280;
 | 
			
		||||
	border-color: #7c5 #583 #333 #583;
 | 
			
		||||
	position: relative;
 | 
			
		||||
	color: #fd7;
 | 
			
		||||
}
 | 
			
		||||
#u2cards span {
 | 
			
		||||
	color: #fff;
 | 
			
		||||
}
 | 
			
		||||
#u2conf {
 | 
			
		||||
	margin: 1em auto;
 | 
			
		||||
	width: 30em;
 | 
			
		||||
@@ -99,12 +141,16 @@
 | 
			
		||||
	outline: none;
 | 
			
		||||
}
 | 
			
		||||
#u2conf .txtbox {
 | 
			
		||||
	width: 4em;
 | 
			
		||||
	width: 3em;
 | 
			
		||||
	color: #fff;
 | 
			
		||||
	background: #444;
 | 
			
		||||
	border: 1px solid #777;
 | 
			
		||||
	font-size: 1.2em;
 | 
			
		||||
	padding: .15em 0;
 | 
			
		||||
	height: 1.05em;
 | 
			
		||||
}
 | 
			
		||||
#u2conf .txtbox.err {
 | 
			
		||||
	background: #922;
 | 
			
		||||
}
 | 
			
		||||
#u2conf a {
 | 
			
		||||
	color: #fff;
 | 
			
		||||
@@ -113,13 +159,12 @@
 | 
			
		||||
	border-radius: .1em;
 | 
			
		||||
	font-size: 1.5em;
 | 
			
		||||
	padding: .1em 0;
 | 
			
		||||
	margin: 0 -.25em;
 | 
			
		||||
	margin: 0 -1px;
 | 
			
		||||
	width: 1.5em;
 | 
			
		||||
	height: 1em;
 | 
			
		||||
	display: inline-block;
 | 
			
		||||
	position: relative;
 | 
			
		||||
	line-height: 1em;
 | 
			
		||||
	bottom: -.08em;
 | 
			
		||||
	bottom: -0.08em;
 | 
			
		||||
}
 | 
			
		||||
#u2conf input+a {
 | 
			
		||||
	background: #d80;
 | 
			
		||||
@@ -170,12 +215,13 @@
 | 
			
		||||
	text-align: center;
 | 
			
		||||
	overflow: hidden;
 | 
			
		||||
	margin: 0 -2em;
 | 
			
		||||
	height: 0;
 | 
			
		||||
	padding: 0 1em;
 | 
			
		||||
	height: 0;
 | 
			
		||||
	opacity: .1;
 | 
			
		||||
    transition: all 0.14s ease-in-out;
 | 
			
		||||
	border-radius: .4em;
 | 
			
		||||
	box-shadow: 0 .2em .5em #222;
 | 
			
		||||
	border-radius: .4em;
 | 
			
		||||
	z-index: 1;
 | 
			
		||||
}
 | 
			
		||||
#u2cdesc.show {
 | 
			
		||||
	padding: 1em;
 | 
			
		||||
@@ -193,24 +239,6 @@
 | 
			
		||||
.prog {
 | 
			
		||||
	font-family: monospace;
 | 
			
		||||
}
 | 
			
		||||
.prog>div {
 | 
			
		||||
	display: inline-block;
 | 
			
		||||
	position: relative;
 | 
			
		||||
	overflow: hidden;
 | 
			
		||||
	margin: 0;
 | 
			
		||||
	padding: 0;
 | 
			
		||||
	height: 1.1em;
 | 
			
		||||
	margin-bottom: -.15em;
 | 
			
		||||
	box-shadow: -1px -1px 0 inset rgba(255,255,255,0.1);
 | 
			
		||||
}
 | 
			
		||||
.prog>div>div {
 | 
			
		||||
	width: 0%;
 | 
			
		||||
	position: absolute;
 | 
			
		||||
	left: 0;
 | 
			
		||||
	top: 0;
 | 
			
		||||
	bottom: 0;
 | 
			
		||||
	background: #0a0;
 | 
			
		||||
}
 | 
			
		||||
#u2tab a>span {
 | 
			
		||||
	font-weight: bold;
 | 
			
		||||
	font-style: italic;
 | 
			
		||||
@@ -221,3 +249,38 @@
 | 
			
		||||
	float: right;
 | 
			
		||||
	margin-bottom: -.3em;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
html.light #u2btn {
 | 
			
		||||
	box-shadow: .4em .4em 0 #ccc;
 | 
			
		||||
}
 | 
			
		||||
html.light #u2cards span {
 | 
			
		||||
	color: #000;
 | 
			
		||||
}
 | 
			
		||||
html.light #u2cards a {
 | 
			
		||||
	background: linear-gradient(to bottom, #eee, #fff);
 | 
			
		||||
}
 | 
			
		||||
html.light #u2cards a.act {
 | 
			
		||||
	color: #037;
 | 
			
		||||
	background: inherit;
 | 
			
		||||
	box-shadow: 0 -.17em .67em #0ad;
 | 
			
		||||
	border-color: #09c #05a #eee #05a;
 | 
			
		||||
}
 | 
			
		||||
html.light #u2conf .txtbox {
 | 
			
		||||
	background: #fff;
 | 
			
		||||
	color: #444;
 | 
			
		||||
}
 | 
			
		||||
html.light #u2conf .txtbox.err {
 | 
			
		||||
	background: #f96;
 | 
			
		||||
	color: #300;
 | 
			
		||||
}
 | 
			
		||||
html.light #u2cdesc {
 | 
			
		||||
	background: #fff;
 | 
			
		||||
	border: none;
 | 
			
		||||
}
 | 
			
		||||
html.light #op_up2k.srch #u2btn {
 | 
			
		||||
	border-color: #a80;
 | 
			
		||||
}
 | 
			
		||||
@@ -1,7 +1,7 @@
 | 
			
		||||
 | 
			
		||||
    <div id="op_bup" class="opview opbox act">
 | 
			
		||||
        <div id="u2err"></div>
 | 
			
		||||
        <form method="post" enctype="multipart/form-data" accept-charset="utf-8">
 | 
			
		||||
        <form method="post" enctype="multipart/form-data" accept-charset="utf-8" action="{{ url_suf }}">
 | 
			
		||||
            <input type="hidden" name="act" value="bput" />
 | 
			
		||||
            <input type="file" name="f" multiple><br />
 | 
			
		||||
            <input type="submit" value="start upload">
 | 
			
		||||
@@ -9,7 +9,7 @@
 | 
			
		||||
    </div>
 | 
			
		||||
 | 
			
		||||
    <div id="op_mkdir" class="opview opbox act">
 | 
			
		||||
        <form method="post" enctype="multipart/form-data" accept-charset="utf-8">
 | 
			
		||||
        <form method="post" enctype="multipart/form-data" accept-charset="utf-8" action="{{ url_suf }}">
 | 
			
		||||
            <input type="hidden" name="act" value="mkdir" />
 | 
			
		||||
            <input type="text" name="name" size="30">
 | 
			
		||||
            <input type="submit" value="mkdir">
 | 
			
		||||
@@ -17,7 +17,7 @@
 | 
			
		||||
    </div>
 | 
			
		||||
 | 
			
		||||
    <div id="op_new_md" class="opview opbox">
 | 
			
		||||
        <form method="post" enctype="multipart/form-data" accept-charset="utf-8">
 | 
			
		||||
        <form method="post" enctype="multipart/form-data" accept-charset="utf-8" action="{{ url_suf }}">
 | 
			
		||||
            <input type="hidden" name="act" value="new_md" />
 | 
			
		||||
            <input type="text" name="name" size="30">
 | 
			
		||||
            <input type="submit" value="create doc">
 | 
			
		||||
@@ -25,7 +25,7 @@
 | 
			
		||||
    </div>
 | 
			
		||||
 | 
			
		||||
    <div id="op_msg" class="opview opbox act">
 | 
			
		||||
        <form method="post" enctype="application/x-www-form-urlencoded" accept-charset="utf-8">
 | 
			
		||||
        <form method="post" enctype="application/x-www-form-urlencoded" accept-charset="utf-8" action="{{ url_suf }}">
 | 
			
		||||
            <input type="text" name="msg" size="30">
 | 
			
		||||
            <input type="submit" value="send msg">
 | 
			
		||||
        </form>
 | 
			
		||||
@@ -59,9 +59,9 @@
 | 
			
		||||
                </tr>
 | 
			
		||||
                <tr>
 | 
			
		||||
                    <td>
 | 
			
		||||
                        <a href="#" id="nthread_sub">–</a>
 | 
			
		||||
                        <input class="txtbox" id="nthread" value="2" />
 | 
			
		||||
                        <a href="#" id="nthread_add">+</a>
 | 
			
		||||
                        <a href="#" id="nthread_sub">–</a><input
 | 
			
		||||
                            class="txtbox" id="nthread" value="2"/><a
 | 
			
		||||
                            href="#" id="nthread_add">+</a>
 | 
			
		||||
                    </td>
 | 
			
		||||
                </tr>
 | 
			
		||||
            </table>
 | 
			
		||||
@@ -79,12 +79,23 @@
 | 
			
		||||
                </div>
 | 
			
		||||
            </div>
 | 
			
		||||
 | 
			
		||||
            <div id="u2cards">
 | 
			
		||||
                <a href="#" act="ok">ok <span>0</span></a><a
 | 
			
		||||
                href="#" act="ng">ng <span>0</span></a><a
 | 
			
		||||
                href="#" act="done">done <span>0</span></a><a
 | 
			
		||||
                href="#" act="bz" class="act">busy <span>0</span></a><a
 | 
			
		||||
                href="#" act="q">que <span>0</span></a>
 | 
			
		||||
            </div>
 | 
			
		||||
 | 
			
		||||
            <table id="u2tab">
 | 
			
		||||
                <tr>
 | 
			
		||||
                    <td>filename</td>
 | 
			
		||||
                    <td>status</td>
 | 
			
		||||
                    <td>progress<a href="#" id="u2cleanup">cleanup</a></td>
 | 
			
		||||
                </tr>
 | 
			
		||||
                <thead>
 | 
			
		||||
                    <tr>
 | 
			
		||||
                        <td>filename</td>
 | 
			
		||||
                        <td>status</td>
 | 
			
		||||
                        <td>progress<a href="#" id="u2cleanup">cleanup</a></td>
 | 
			
		||||
                    </tr>
 | 
			
		||||
                </thead>
 | 
			
		||||
                <tbody></tbody>
 | 
			
		||||
            </table>
 | 
			
		||||
 | 
			
		||||
            <p id="u2foot"></p>
 | 
			
		||||
 
 | 
			
		||||
@@ -6,6 +6,10 @@ if (!window['console'])
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
var clickev = window.Touch ? 'touchstart' : 'click',
 | 
			
		||||
    ANDROID = /(android)/i.test(navigator.userAgent);
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
// error handler for mobile devices
 | 
			
		||||
function hcroak(msg) {
 | 
			
		||||
    document.body.innerHTML = msg;
 | 
			
		||||
@@ -116,7 +120,7 @@ function crc32(str) {
 | 
			
		||||
        crc = (crc >>> 8) ^ crctab[(crc ^ str.charCodeAt(i)) & 0xFF];
 | 
			
		||||
    }
 | 
			
		||||
    return ((crc ^ (-1)) >>> 0).toString(16);
 | 
			
		||||
};
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
function clmod(obj, cls, add) {
 | 
			
		||||
 
 | 
			
		||||
@@ -73,6 +73,13 @@ shab64() { sp=$1; f="$2"; v=0; sz=$(stat -c%s "$f"); while true; do w=$((v+sp*10
 | 
			
		||||
command -v gdate && date() { gdate "$@"; }; while true; do t=$(date +%s.%N); (time wget http://127.0.0.1:3923/?ls -qO- | jq -C '.files[]|{sz:.sz,ta:.tags.artist,tb:.tags.".bpm"}|del(.[]|select(.==null))' | awk -F\" '/"/{t[$2]++} END {for (k in t){v=t[k];p=sprintf("%" (v+1) "s",v);gsub(/ /,"#",p);printf "\033[36m%s\033[33m%s   ",k,p}}') 2>&1 | awk -v ts=$t 'NR==1{t1=$0} NR==2{sub(/.*0m/,"");sub(/s$/,"");t2=$0;c=2; if(t2>0.3){c=3} if(t2>0.8){c=1} } END{sub(/[0-9]{6}$/,"",ts);printf "%s   \033[3%dm%s   %s\033[0m\n",ts,c,t2,t1}'; sleep 0.1 || break; done
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
##
 | 
			
		||||
## js oneliners
 | 
			
		||||
 | 
			
		||||
# get all up2k search result URLs
 | 
			
		||||
var t=[]; var b=document.location.href.split('#')[0].slice(0, -1); document.querySelectorAll('#u2tab .prog a').forEach((x) => {t.push(b+encodeURI(x.getAttribute("href")))}); console.log(t.join("\n"));
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
##
 | 
			
		||||
## sqlite3 stuff
 | 
			
		||||
 | 
			
		||||
@@ -139,6 +146,7 @@ dbg.asyncStore.pendingBreakpoints = {}
 | 
			
		||||
# fix firefox phantom breakpoints
 | 
			
		||||
about:config >> devtools.debugger.prefs-schema-version = -1
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
##
 | 
			
		||||
## http 206
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -45,11 +45,13 @@ pybin=$(command -v python3 || command -v python) || {
 | 
			
		||||
	exit 1
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
use_gz=
 | 
			
		||||
do_sh=1
 | 
			
		||||
do_py=1
 | 
			
		||||
while [ ! -z "$1" ]; do
 | 
			
		||||
	[ "$1" = clean  ] && clean=1  && shift && continue
 | 
			
		||||
	[ "$1" = re     ] && repack=1 && shift && continue
 | 
			
		||||
	[ "$1" = gz     ] && use_gz=1 && shift && continue
 | 
			
		||||
	[ "$1" = no-ogv ] && no_ogv=1 && shift && continue
 | 
			
		||||
	[ "$1" = no-cm  ] && no_cm=1  && shift && continue
 | 
			
		||||
	[ "$1" = no-sh  ] && do_sh=   && shift && continue
 | 
			
		||||
@@ -204,16 +206,20 @@ args=(--owner=1000 --group=1000)
 | 
			
		||||
 | 
			
		||||
tar -cf tar "${args[@]}" --numeric-owner copyparty dep-j2
 | 
			
		||||
 | 
			
		||||
pc=bzip2
 | 
			
		||||
pe=bz2
 | 
			
		||||
[ $use_gz ] && pc=gzip && pe=gz
 | 
			
		||||
 | 
			
		||||
echo compressing tar
 | 
			
		||||
# detect best level; bzip2 -7 is usually better than -9
 | 
			
		||||
[ $do_py ] && { for n in {2..9}; do cp tar t.$n; bzip2 -$n t.$n & done; wait; mv -v $(ls -1S t.*.bz2 | tail -n 1) tar.bz2; }
 | 
			
		||||
[ $do_sh ] && { for n in {2..9}; do cp tar t.$n;  xz -ze$n t.$n & done; wait; mv -v $(ls -1S t.*.xz  | tail -n 1) tar.xz; }
 | 
			
		||||
[ $do_py ] && { for n in {2..9}; do cp tar t.$n; $pc  -$n t.$n & done; wait; mv -v $(ls -1S t.*.$pe | tail -n 1) tar.bz2; }
 | 
			
		||||
[ $do_sh ] && { for n in {2..9}; do cp tar t.$n; xz -ze$n t.$n & done; wait; mv -v $(ls -1S t.*.xz  | tail -n 1) tar.xz; }
 | 
			
		||||
rm t.* || true
 | 
			
		||||
exts=()
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
[ $do_sh ] && {
 | 
			
		||||
exts+=(sh)
 | 
			
		||||
exts+=(.sh)
 | 
			
		||||
echo creating unix sfx
 | 
			
		||||
(
 | 
			
		||||
	sed "s/PACK_TS/$ts/; s/PACK_HTS/$hts/; s/CPP_VER/$ver/" <../scripts/sfx.sh |
 | 
			
		||||
@@ -224,17 +230,30 @@ echo creating unix sfx
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
[ $do_py ] && {
 | 
			
		||||
exts+=(py)
 | 
			
		||||
echo creating generic sfx
 | 
			
		||||
$pybin ../scripts/sfx.py --sfx-make tar.bz2 $ver $ts
 | 
			
		||||
mv sfx.out $sfx_out.py
 | 
			
		||||
chmod 755 $sfx_out.*
 | 
			
		||||
	echo creating generic sfx
 | 
			
		||||
 | 
			
		||||
	py=../scripts/sfx.py
 | 
			
		||||
	suf=
 | 
			
		||||
	[ $use_gz ] && {
 | 
			
		||||
		sed -r 's/"r:bz2"/"r:gz"/' <$py >$py.t
 | 
			
		||||
		py=$py.t
 | 
			
		||||
		suf=-gz
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	$pybin $py --sfx-make tar.bz2 $ver $ts
 | 
			
		||||
	mv sfx.out $sfx_out$suf.py
 | 
			
		||||
	
 | 
			
		||||
	exts+=($suf.py)
 | 
			
		||||
	[ $use_gz ] &&
 | 
			
		||||
		rm $py
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
chmod 755 $sfx_out*
 | 
			
		||||
 | 
			
		||||
printf "done:\n"
 | 
			
		||||
for ext in ${exts[@]}; do
 | 
			
		||||
	printf "  %s\n" "$(realpath $sfx_out)."$ext
 | 
			
		||||
	printf "  %s\n" "$(realpath $sfx_out)"$ext
 | 
			
		||||
done
 | 
			
		||||
 | 
			
		||||
# apk add bash python3 tar xz bzip2
 | 
			
		||||
 
 | 
			
		||||
@@ -2,7 +2,7 @@
 | 
			
		||||
# coding: latin-1
 | 
			
		||||
from __future__ import print_function, unicode_literals
 | 
			
		||||
 | 
			
		||||
import os, sys, time, shutil, runpy, tarfile, hashlib, platform, tempfile, traceback
 | 
			
		||||
import os, sys, time, shutil, threading, tarfile, hashlib, platform, tempfile, traceback
 | 
			
		||||
 | 
			
		||||
"""
 | 
			
		||||
run me with any version of python, i will unpack and run copyparty
 | 
			
		||||
@@ -26,6 +26,7 @@ CKSUM = None
 | 
			
		||||
STAMP = None
 | 
			
		||||
 | 
			
		||||
PY2 = sys.version_info[0] == 2
 | 
			
		||||
WINDOWS = sys.platform in ["win32", "msys"]
 | 
			
		||||
sys.dont_write_bytecode = True
 | 
			
		||||
me = os.path.abspath(os.path.realpath(__file__))
 | 
			
		||||
cpp = None
 | 
			
		||||
@@ -343,6 +344,21 @@ def get_payload():
 | 
			
		||||
                break
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def utime(top):
 | 
			
		||||
    i = 0
 | 
			
		||||
    files = [os.path.join(dp, p) for dp, dd, df in os.walk(top) for p in dd + df]
 | 
			
		||||
    while WINDOWS:
 | 
			
		||||
        t = int(time.time())
 | 
			
		||||
        if i:
 | 
			
		||||
            msg("utime {}, {}".format(i, t))
 | 
			
		||||
 | 
			
		||||
        for f in files:
 | 
			
		||||
            os.utime(f, (t, t))
 | 
			
		||||
 | 
			
		||||
        i += 1
 | 
			
		||||
        time.sleep(78123)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def confirm(rv):
 | 
			
		||||
    msg()
 | 
			
		||||
    msg(traceback.format_exc())
 | 
			
		||||
@@ -362,15 +378,20 @@ def run(tmp, j2ver):
 | 
			
		||||
    msg("sfxdir:", tmp)
 | 
			
		||||
    msg()
 | 
			
		||||
 | 
			
		||||
    # "systemd-tmpfiles-clean.timer"?? HOW do you even come up with this shit
 | 
			
		||||
    # block systemd-tmpfiles-clean.timer
 | 
			
		||||
    try:
 | 
			
		||||
        import fcntl
 | 
			
		||||
 | 
			
		||||
        fd = os.open(tmp, os.O_RDONLY)
 | 
			
		||||
        fcntl.flock(fd, fcntl.LOCK_EX | fcntl.LOCK_NB)
 | 
			
		||||
        tmp = os.readlink(tmp)  # can't flock a symlink, even with O_NOFOLLOW
 | 
			
		||||
    except:
 | 
			
		||||
        pass
 | 
			
		||||
    except Exception as ex:
 | 
			
		||||
        if not WINDOWS:
 | 
			
		||||
            msg("\033[31mflock:", repr(ex))
 | 
			
		||||
 | 
			
		||||
    t = threading.Thread(target=utime, args=(tmp,))
 | 
			
		||||
    t.daemon = True
 | 
			
		||||
    t.start()
 | 
			
		||||
 | 
			
		||||
    ld = [tmp, os.path.join(tmp, "dep-j2")]
 | 
			
		||||
    if j2ver:
 | 
			
		||||
@@ -380,7 +401,10 @@ def run(tmp, j2ver):
 | 
			
		||||
        sys.path.insert(0, x)
 | 
			
		||||
 | 
			
		||||
    try:
 | 
			
		||||
        runpy.run_module(str("copyparty"), run_name=str("__main__"))
 | 
			
		||||
        from copyparty.__main__ import main as copyparty
 | 
			
		||||
 | 
			
		||||
        copyparty()
 | 
			
		||||
 | 
			
		||||
    except SystemExit as ex:
 | 
			
		||||
        if ex.code:
 | 
			
		||||
            confirm(ex.code)
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user