mirror of
				https://github.com/9001/copyparty.git
				synced 2025-10-31 03:53:31 +00:00 
			
		
		
		
	Compare commits
	
		
			49 Commits
		
	
	
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
|  | bca0cdbb62 | ||
|  | 1ee11e04e6 | ||
|  | 6eef44f212 | ||
|  | 8bd94f4a1c | ||
|  | 4bc4701372 | ||
|  | dfd89b503a | ||
|  | 060dc54832 | ||
|  | f7a4ea5793 | ||
|  | 71b478e6e2 | ||
|  | ed8fff8c52 | ||
|  | 95dc78db10 | ||
|  | addeac64c7 | ||
|  | d77ec22007 | ||
|  | 20030c91b7 | ||
|  | 8b366e255c | ||
|  | 6da366fcb0 | ||
|  | 2fa35f851e | ||
|  | e4ca4260bb | ||
|  | b69aace8d8 | ||
|  | 79097bb43c | ||
|  | 806fac1742 | ||
|  | 4f97d7cf8d | ||
|  | 42acc457af | ||
|  | c02920607f | ||
|  | 452885c271 | ||
|  | 5c242a07b6 | ||
|  | 088899d59f | ||
|  | 1faff2a37e | ||
|  | 23c8d3d045 | ||
|  | a033388d2b | ||
|  | 82fe45ac56 | ||
|  | bcb7fcda6b | ||
|  | 726a98100b | ||
|  | 2f021a0c2b | ||
|  | eb05cb6c6e | ||
|  | 7530af95da | ||
|  | 8399e95bda | ||
|  | 3b4dfe326f | ||
|  | 2e787a254e | ||
|  | f888bed1a6 | ||
|  | d865e9f35a | ||
|  | fc7fe70f66 | ||
|  | 5aff39d2b2 | ||
|  | d1be37a04a | ||
|  | b0fd8bf7d4 | ||
|  | b9cf8f3973 | ||
|  | 4588f11613 | ||
|  | 1a618c3c97 | ||
|  | d500a51d97 | 
							
								
								
									
										1
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										1
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							| @@ -9,6 +9,7 @@ buildenv/ | ||||
| build/ | ||||
| dist/ | ||||
| sfx/ | ||||
| py2/ | ||||
| .venv/ | ||||
|  | ||||
| # ide | ||||
|   | ||||
							
								
								
									
										31
									
								
								README.md
									
									
									
									
									
								
							
							
						
						
									
										31
									
								
								README.md
									
									
									
									
									
								
							| @@ -53,6 +53,7 @@ turn your phone or raspi into a portable file server with resumable uploads/down | ||||
|     * [database location](#database-location) - in-volume (`.hist/up2k.db`, default) or somewhere else | ||||
|     * [metadata from audio files](#metadata-from-audio-files) - set `-e2t` to index tags on upload | ||||
|     * [file parser plugins](#file-parser-plugins) - provide custom parsers to index additional tags | ||||
|     * [upload events](#upload-events) - trigger a script/program on each upload | ||||
|     * [complete examples](#complete-examples) | ||||
| * [browser support](#browser-support) - TLDR: yes | ||||
| * [client examples](#client-examples) - interact with copyparty using non-browser clients | ||||
| @@ -595,12 +596,14 @@ note: | ||||
| * `e2tsr` is probably always overkill, since `e2ds`/`e2dsa` would pick up any file modifications and `e2ts` would then reindex those, unless there is a new copyparty version with new parsers and the release note says otherwise | ||||
| * the rescan button in the admin panel has no effect unless the volume has `-e2ds` or higher | ||||
|  | ||||
| to save some time, you can choose to only index filename/path/size/last-modified (and not the hash of the file contents) by setting `--no-hash` or the volume-flag `:c,dhash`, this has the following consequences: | ||||
| to save some time, you can provide a regex pattern for filepaths to only index by filename/path/size/last-modified (and not the hash of the file contents) by setting `--no-hash \.iso$` or the volume-flag `:c,nohash=\.iso$`, this has the following consequences: | ||||
| * initial indexing is way faster, especially when the volume is on a network disk | ||||
| * makes it impossible to [file-search](#file-search) | ||||
| * if someone uploads the same file contents, the upload will not be detected as a dupe, so it will not get symlinked or rejected | ||||
|  | ||||
| if you set `--no-hash`, you can enable hashing for specific volumes using flag `:c,ehash` | ||||
| similarly, you can fully ignore files/folders using `--no-idx [...]` and `:c,noidx=\.iso$` | ||||
|  | ||||
| if you set `--no-hash [...]` globally, you can enable hashing for specific volumes using flag `:c,nohash=` | ||||
|  | ||||
|  | ||||
| ## upload rules | ||||
| @@ -699,6 +702,25 @@ copyparty can invoke external programs to collect additional metadata for files | ||||
| * `-mtp arch,built,ver,orig=an,eexe,edll,~/bin/exe.py` runs `~/bin/exe.py` to get properties about windows-binaries only if file is not audio (`an`) and file extension is exe or dll | ||||
|  | ||||
|  | ||||
| ## upload events | ||||
|  | ||||
| trigger a script/program on each upload  like so: | ||||
|  | ||||
| ``` | ||||
| -v /mnt/inc:inc:w:c,mte=+a1:c,mtp=a1=ad,/usr/bin/notify-send | ||||
| ``` | ||||
|  | ||||
| so filesystem location `/mnt/inc` shared at `/inc`, write-only for everyone, appending `a1` to the list of tags to index, and using `/usr/bin/notify-send` to "provide" that tag | ||||
|  | ||||
| that'll run the command `notify-send` with the path to the uploaded file as the first and only argument (so on linux it'll show a notification on-screen) | ||||
|  | ||||
| note that it will only trigger on new unique files, not dupes | ||||
|  | ||||
| and it will occupy the parsing threads, so fork anything expensive, or if you want to intentionally queue/singlethread you can combine it with `--no-mtag-mt` | ||||
|  | ||||
| if this becomes popular maybe there should be a less janky way to do it actually | ||||
|  | ||||
|  | ||||
| ## complete examples | ||||
|  | ||||
| * read-only music server with bpm and key scanning   | ||||
| @@ -725,7 +747,7 @@ TLDR: yes | ||||
| | zip selection   |  -  | yep  | yep  | yep  |  yep  | yep  | yep | yep  | | ||||
| | file rename     |  -  | yep  | yep  | yep  |  yep  | yep  | yep | yep  | | ||||
| | file cut/paste  |  -  | yep  | yep  | yep  |  yep  | yep  | yep | yep  | | ||||
| | navpane         |  -  | `*2` | yep  | yep  |  yep  | yep  | yep | yep  | | ||||
| | navpane         |  -  | yep  | yep  | yep  |  yep  | yep  | yep | yep  | | ||||
| | image viewer    |  -  | yep  | yep  | yep  |  yep  | yep  | yep | yep  | | ||||
| | video player    |  -  | yep  | yep  | yep  |  yep  | yep  | yep | yep  | | ||||
| | markdown editor |  -  |  -   | yep  | yep  |  yep  | yep  | yep | yep  | | ||||
| @@ -737,7 +759,6 @@ TLDR: yes | ||||
| * internet explorer 6 to 8 behave the same | ||||
| * firefox 52 and chrome 49 are the final winxp versions | ||||
| * `*1` yes, but extremely slow (ie10: `1 MiB/s`, ie11: `270 KiB/s`) | ||||
| * `*2` causes a full-page refresh on each navigation | ||||
| * `*3` using a wasm decoder which consumes a bit more power | ||||
|  | ||||
| quick summary of more eccentric web-browsers trying to view a directory index: | ||||
| @@ -831,7 +852,7 @@ below are some tweaks roughly ordered by usefulness: | ||||
| * `-q` disables logging and can help a bunch, even when combined with `-lo` to redirect logs to file | ||||
| * `--http-only` or `--https-only` (unless you want to support both protocols) will reduce the delay before a new connection is established | ||||
| * `--hist` pointing to a fast location (ssd) will make directory listings and searches faster when `-e2d` or `-e2t` is set | ||||
| * `--no-hash` when indexing a network-disk if you don't care about the actual filehashes and only want the names/tags searchable | ||||
| * `--no-hash .` when indexing a network-disk if you don't care about the actual filehashes and only want the names/tags searchable | ||||
| * `-j` enables multiprocessing (actual multithreading) and can make copyparty perform better in cpu-intensive workloads, for example: | ||||
|   * huge amount of short-lived connections | ||||
|   * really heavy traffic (downloads/uploads) | ||||
|   | ||||
							
								
								
									
										97
									
								
								bin/up2k.py
									
									
									
									
									
								
							
							
						
						
									
										97
									
								
								bin/up2k.py
									
									
									
									
									
								
							| @@ -3,7 +3,7 @@ from __future__ import print_function, unicode_literals | ||||
|  | ||||
| """ | ||||
| up2k.py: upload to copyparty | ||||
| 2021-10-04, v0.7, ed <irc.rizon.net>, MIT-Licensed | ||||
| 2021-10-12, v0.9, ed <irc.rizon.net>, MIT-Licensed | ||||
| https://github.com/9001/copyparty/blob/hovudstraum/bin/up2k.py | ||||
|  | ||||
| - dependencies: requests | ||||
| @@ -33,11 +33,15 @@ import datetime | ||||
| PY2 = sys.version_info[0] == 2 | ||||
| if PY2: | ||||
|     from Queue import Queue | ||||
|     from urllib import unquote | ||||
|     from urllib import quote | ||||
|  | ||||
|     sys.dont_write_bytecode = True | ||||
|     bytes = str | ||||
| else: | ||||
|     from queue import Queue | ||||
|     from urllib.parse import unquote_to_bytes as unquote | ||||
|     from urllib.parse import quote_from_bytes as quote | ||||
|  | ||||
|     unicode = str | ||||
|  | ||||
| @@ -121,17 +125,30 @@ class FileSlice(object): | ||||
|         return ret | ||||
|  | ||||
|  | ||||
| _print = print | ||||
|  | ||||
|  | ||||
| def eprint(*a, **ka): | ||||
|     ka["file"] = sys.stderr | ||||
|     ka["end"] = "" | ||||
|     if not PY2: | ||||
|         ka["flush"] = True | ||||
|  | ||||
|     print(*a, **ka) | ||||
|     if PY2: | ||||
|     _print(*a, **ka) | ||||
|     if PY2 or not VT100: | ||||
|         sys.stderr.flush() | ||||
|  | ||||
|  | ||||
| def flushing_print(*a, **ka): | ||||
|     _print(*a, **ka) | ||||
|     if "flush" not in ka: | ||||
|         sys.stdout.flush() | ||||
|  | ||||
|  | ||||
| if not VT100: | ||||
|     print = flushing_print | ||||
|  | ||||
|  | ||||
| def termsize(): | ||||
|     import os | ||||
|  | ||||
| @@ -231,16 +248,29 @@ def walkdir(top): | ||||
|  | ||||
| def walkdirs(tops): | ||||
|     """recursive statdir for a list of tops, yields [top, relpath, stat]""" | ||||
|     sep = "{0}".format(os.sep).encode("ascii") | ||||
|     for top in tops: | ||||
|         stop = top | ||||
|         if top[-1:] == sep: | ||||
|             stop = os.path.dirname(top.rstrip(sep)) | ||||
|  | ||||
|         if os.path.isdir(top): | ||||
|             for ap, inf in walkdir(top): | ||||
|                 yield top, ap[len(top) + 1 :], inf | ||||
|                 yield stop, ap[len(stop) :].lstrip(sep), inf | ||||
|         else: | ||||
|             sep = "{0}".format(os.sep).encode("ascii") | ||||
|             d, n = top.rsplit(sep, 1) | ||||
|             yield d, n, os.stat(top) | ||||
|  | ||||
|  | ||||
| # mostly from copyparty/util.py | ||||
| def quotep(btxt): | ||||
|     quot1 = quote(btxt, safe=b"/") | ||||
|     if not PY2: | ||||
|         quot1 = quot1.encode("ascii") | ||||
|  | ||||
|     return quot1.replace(b" ", b"+") | ||||
|  | ||||
|  | ||||
| # from copyparty/util.py | ||||
| def humansize(sz, terse=False): | ||||
|     """picks a sensible unit for the given extent""" | ||||
| @@ -334,7 +364,7 @@ def handshake(req_ses, url, file, pw, search): | ||||
|     if file.url: | ||||
|         url = file.url | ||||
|     elif b"/" in file.rel: | ||||
|         url += file.rel.rsplit(b"/", 1)[0].decode("utf-8", "replace") | ||||
|         url += quotep(file.rel.rsplit(b"/", 1)[0]).decode("utf-8", "replace") | ||||
|  | ||||
|     while True: | ||||
|         try: | ||||
| @@ -403,7 +433,9 @@ class Ctl(object): | ||||
|     def __init__(self, ar): | ||||
|         self.ar = ar | ||||
|         ar.files = [ | ||||
|             os.path.abspath(os.path.realpath(x.encode("utf-8"))) for x in ar.files | ||||
|             os.path.abspath(os.path.realpath(x.encode("utf-8"))) | ||||
|             + (x[-1:] if x[-1:] == os.sep else "").encode("utf-8") | ||||
|             for x in ar.files | ||||
|         ] | ||||
|         ar.url = ar.url.rstrip("/") + "/" | ||||
|         if "://" not in ar.url: | ||||
| @@ -442,13 +474,14 @@ class Ctl(object): | ||||
|             print("{0} {1}\n  hash...".format(self.nfiles - nf, upath)) | ||||
|             get_hashlist(file, None) | ||||
|  | ||||
|             burl = self.ar.url[:8] + self.ar.url[8:].split("/")[0] + "/" | ||||
|             while True: | ||||
|                 print("  hs...") | ||||
|                 hs = handshake(req_ses, self.ar.url, file, self.ar.a, search) | ||||
|                 if search: | ||||
|                     if hs: | ||||
|                         for hit in hs: | ||||
|                             print("  found: {0}{1}".format(self.ar.url, hit["rp"])) | ||||
|                             print("  found: {0}{1}".format(burl, hit["rp"])) | ||||
|                     else: | ||||
|                         print("  NOT found") | ||||
|                     break | ||||
| @@ -564,7 +597,36 @@ class Ctl(object): | ||||
|         self.st_hash = [file, ofs] | ||||
|  | ||||
|     def hasher(self): | ||||
|         prd = None | ||||
|         ls = {} | ||||
|         for top, rel, inf in self.filegen: | ||||
|             if self.ar.z: | ||||
|                 rd = os.path.dirname(rel) | ||||
|                 if prd != rd: | ||||
|                     prd = rd | ||||
|                     headers = {} | ||||
|                     if self.ar.a: | ||||
|                         headers["Cookie"] = "=".join(["cppwd", self.ar.a]) | ||||
|  | ||||
|                     ls = {} | ||||
|                     try: | ||||
|                         print("      ls ~{0}".format(rd.decode("utf-8", "replace"))) | ||||
|                         r = req_ses.get( | ||||
|                             self.ar.url.encode("utf-8") + quotep(rd) + b"?ls", | ||||
|                             headers=headers, | ||||
|                         ) | ||||
|                         for f in r.json()["files"]: | ||||
|                             rfn = f["href"].split("?")[0].encode("utf-8", "replace") | ||||
|                             ls[unquote(rfn)] = f | ||||
|                     except: | ||||
|                         print("   mkdir ~{0}".format(rd.decode("utf-8", "replace"))) | ||||
|  | ||||
|                 rf = ls.get(os.path.basename(rel), None) | ||||
|                 if rf and rf["sz"] == inf.st_size and abs(rf["ts"] - inf.st_mtime) <= 1: | ||||
|                     self.nfiles -= 1 | ||||
|                     self.nbytes -= inf.st_size | ||||
|                     continue | ||||
|  | ||||
|             file = File(top, rel, inf.st_size, inf.st_mtime) | ||||
|             while True: | ||||
|                 with self.mutex: | ||||
| @@ -598,6 +660,7 @@ class Ctl(object): | ||||
|     def handshaker(self): | ||||
|         search = self.ar.s | ||||
|         q = self.q_handshake | ||||
|         burl = self.ar.url[:8] + self.ar.url[8:].split("/")[0] + "/" | ||||
|         while True: | ||||
|             file = q.get() | ||||
|             if not file: | ||||
| @@ -627,7 +690,7 @@ class Ctl(object): | ||||
|                 if hs: | ||||
|                     for hit in hs: | ||||
|                         m = "found: {0}\n  {1}{2}\n" | ||||
|                         print(m.format(upath, self.ar.url, hit["rp"]), end="") | ||||
|                         print(m.format(upath, burl, hit["rp"]), end="") | ||||
|                 else: | ||||
|                     print("NOT found: {0}\n".format(upath), end="") | ||||
|  | ||||
| @@ -659,7 +722,8 @@ class Ctl(object): | ||||
|                 self.handshaker_busy -= 1 | ||||
|  | ||||
|             if not hs: | ||||
|                 print("uploaded {0}".format(upath)) | ||||
|                 kw = "uploaded" if file.up_b else "   found" | ||||
|                 print("{0} {1}".format(kw, upath)) | ||||
|             for cid in hs: | ||||
|                 self.q_upload.put([file, cid]) | ||||
|  | ||||
| @@ -696,13 +760,23 @@ class Ctl(object): | ||||
|                 self.uploader_busy -= 1 | ||||
|  | ||||
|  | ||||
| class APF(argparse.ArgumentDefaultsHelpFormatter, argparse.RawDescriptionHelpFormatter): | ||||
|     pass | ||||
|  | ||||
|  | ||||
| def main(): | ||||
|     time.strptime("19970815", "%Y%m%d")  # python#7980 | ||||
|     if not VT100: | ||||
|         os.system("rem")  # enables colors | ||||
|  | ||||
|     # fmt: off | ||||
|     ap = app = argparse.ArgumentParser(formatter_class=argparse.ArgumentDefaultsHelpFormatter) | ||||
|     ap = app = argparse.ArgumentParser(formatter_class=APF, epilog=""" | ||||
| NOTE: | ||||
| source file/folder selection uses rsync syntax, meaning that: | ||||
|   "foo" uploads the entire folder to URL/foo/ | ||||
|   "foo/" uploads the CONTENTS of the folder into URL/ | ||||
| """) | ||||
|  | ||||
|     ap.add_argument("url", type=unicode, help="server url, including destination folder") | ||||
|     ap.add_argument("files", type=unicode, nargs="+", help="files and/or folders to process") | ||||
|     ap.add_argument("-a", metavar="PASSWORD", help="password") | ||||
| @@ -711,6 +785,7 @@ def main(): | ||||
|     ap.add_argument("-j", type=int, metavar="THREADS", default=4, help="parallel connections") | ||||
|     ap.add_argument("-nh", action="store_true", help="disable hashing while uploading") | ||||
|     ap.add_argument("--safe", action="store_true", help="use simple fallback approach") | ||||
|     ap.add_argument("-z", action="store_true", help="ZOOMIN' (skip uploading files if they exist at the destination with the ~same last-modified timestamp, so same as yolo / turbo with date-chk but even faster)") | ||||
|     ap = app.add_argument_group("tls") | ||||
|     ap.add_argument("-te", metavar="PEM_FILE", help="certificate to expect/verify") | ||||
|     ap.add_argument("-td", action="store_true", help="disable certificate check") | ||||
|   | ||||
| @@ -276,7 +276,8 @@ def run_argparse(argv, formatter): | ||||
|               \033[36me2d\033[35m sets -e2d (all -e2* args can be set using ce2* volflags) | ||||
|               \033[36md2t\033[35m disables metadata collection, overrides -e2t* | ||||
|               \033[36md2d\033[35m disables all database stuff, overrides -e2* | ||||
|               \033[36mdhash\033[35m disables file hashing on initial scans, also ehash | ||||
|               \033[36mnohash=\\.iso$\033[35m skips hashing file contents if path matches *.iso | ||||
|               \033[36mnoidx=\\.iso$\033[35m fully ignores the contents at paths matching *.iso | ||||
|               \033[36mhist=/tmp/cdb\033[35m puts thumbnails and indexes at that location | ||||
|               \033[36mscan=60\033[35m scan for new files every 60sec, same as --re-maxage | ||||
|              | ||||
| @@ -380,6 +381,10 @@ def run_argparse(argv, formatter): | ||||
|     ap2.add_argument("--no-readme", action="store_true", help="disable rendering readme.md into directory listings") | ||||
|     ap2.add_argument("--vague-403", action="store_true", help="send 404 instead of 403 (security through ambiguity, very enterprise)") | ||||
|  | ||||
|     ap2 = ap.add_argument_group('yolo options') | ||||
|     ap2.add_argument("--ign-ebind", action="store_true", help="continue running even if it's impossible to listen on some of the requested endpoints") | ||||
|     ap2.add_argument("--ign-ebind-all", action="store_true", help="continue running even if it's impossible to receive connections at all") | ||||
|  | ||||
|     ap2 = ap.add_argument_group('logging options') | ||||
|     ap2.add_argument("-q", action="store_true", help="quiet") | ||||
|     ap2.add_argument("-lo", metavar="PATH", type=u, help="logfile, example: cpp-%%Y-%%m%%d-%%H%%M%%S.txt.xz") | ||||
| @@ -412,7 +417,8 @@ def run_argparse(argv, formatter): | ||||
|     ap2.add_argument("-e2ds", action="store_true", help="enable up2k db-scanner, sets -e2d") | ||||
|     ap2.add_argument("-e2dsa", action="store_true", help="scan all folders (for search), sets -e2ds") | ||||
|     ap2.add_argument("--hist", metavar="PATH", type=u, help="where to store volume data (db, thumbs)") | ||||
|     ap2.add_argument("--no-hash", action="store_true", help="disable hashing during e2ds folder scans") | ||||
|     ap2.add_argument("--no-hash", metavar="PTN", type=u, help="regex: disable hashing of matching paths during e2ds folder scans") | ||||
|     ap2.add_argument("--no-idx", metavar="PTN", type=u, help="regex: disable indexing of matching paths during e2ds folder scans") | ||||
|     ap2.add_argument("--re-int", metavar="SEC", type=int, default=30, help="disk rescan check interval") | ||||
|     ap2.add_argument("--re-maxage", metavar="SEC", type=int, default=0, help="disk rescan volume interval, 0=off, can be set per-volume with the 'scan' volflag") | ||||
|     ap2.add_argument("--srch-time", metavar="SEC", type=int, default=30, help="search deadline") | ||||
| @@ -431,7 +437,8 @@ def run_argparse(argv, formatter): | ||||
|         default=".vq,.aq,vc,ac,res,.fps") | ||||
|     ap2.add_argument("-mtp", metavar="M=[f,]bin", type=u, action="append", help="read tag M using bin") | ||||
|  | ||||
|     ap2 = ap.add_argument_group('appearance options') | ||||
|     ap2 = ap.add_argument_group('ui options') | ||||
|     ap2.add_argument("--js-browser", metavar="L", type=u, help="URL to additional JS to include") | ||||
|     ap2.add_argument("--css-browser", metavar="L", type=u, help="URL to additional CSS to include") | ||||
|  | ||||
|     ap2 = ap.add_argument_group('debug options') | ||||
|   | ||||
| @@ -1,8 +1,8 @@ | ||||
| # coding: utf-8 | ||||
|  | ||||
| VERSION = (1, 0, 8) | ||||
| VERSION = (1, 0, 13) | ||||
| CODENAME = "sufficient" | ||||
| BUILD_DT = (2021, 10, 4) | ||||
| BUILD_DT = (2021, 10, 24) | ||||
|  | ||||
| S_VERSION = ".".join(map(str, VERSION)) | ||||
| S_BUILD_DT = "{0:04d}-{1:02d}-{2:02d}".format(*BUILD_DT) | ||||
|   | ||||
| @@ -726,6 +726,7 @@ class AuthSrv(object): | ||||
|                     axs = getattr(vol.axs, axs_key) | ||||
|                     if usr in axs or "*" in axs: | ||||
|                         umap[usr].append(mp) | ||||
|                 umap[usr].sort() | ||||
|             setattr(vfs, "a" + perm, umap) | ||||
|  | ||||
|         all_users = {} | ||||
| @@ -865,9 +866,14 @@ class AuthSrv(object): | ||||
|             if self.args.e2d or "e2ds" in vol.flags: | ||||
|                 vol.flags["e2d"] = True | ||||
|  | ||||
|             if self.args.no_hash: | ||||
|                 if "ehash" not in vol.flags: | ||||
|                     vol.flags["dhash"] = True | ||||
|             for ga, vf in [["no_hash", "nohash"], ["no_idx", "noidx"]]: | ||||
|                 if vf in vol.flags: | ||||
|                     ptn = vol.flags.pop(vf) | ||||
|                 else: | ||||
|                     ptn = getattr(self.args, ga) | ||||
|  | ||||
|                 if ptn: | ||||
|                     vol.flags[vf] = re.compile(ptn) | ||||
|  | ||||
|             for k in ["e2t", "e2ts", "e2tsr"]: | ||||
|                 if getattr(self.args, k): | ||||
|   | ||||
| @@ -25,14 +25,14 @@ def lstat(p): | ||||
| def makedirs(name, mode=0o755, exist_ok=True): | ||||
|     bname = fsenc(name) | ||||
|     try: | ||||
|         os.makedirs(bname, mode=mode) | ||||
|         os.makedirs(bname, mode) | ||||
|     except: | ||||
|         if not exist_ok or not os.path.isdir(bname): | ||||
|             raise | ||||
|  | ||||
|  | ||||
| def mkdir(p, mode=0o755): | ||||
|     return os.mkdir(fsenc(p), mode=mode) | ||||
|     return os.mkdir(fsenc(p), mode) | ||||
|  | ||||
|  | ||||
| def rename(src, dst): | ||||
|   | ||||
| @@ -10,7 +10,6 @@ import json | ||||
| import base64 | ||||
| import string | ||||
| import socket | ||||
| import ctypes | ||||
| from datetime import datetime | ||||
| from operator import itemgetter | ||||
| import calendar | ||||
| @@ -20,6 +19,11 @@ try: | ||||
| except: | ||||
|     pass | ||||
|  | ||||
| try: | ||||
|     import ctypes | ||||
| except: | ||||
|     pass | ||||
|  | ||||
| from .__init__ import E, PY2, WINDOWS, ANYWIN, unicode | ||||
| from .util import *  # noqa  # pylint: disable=unused-wildcard-import | ||||
| from .bos import bos | ||||
| @@ -55,7 +59,7 @@ class HttpCli(object): | ||||
|  | ||||
|         self.bufsz = 1024 * 32 | ||||
|         self.hint = None | ||||
|         self.absolute_urls = False | ||||
|         self.trailing_slash = True | ||||
|         self.out_headers = { | ||||
|             "Access-Control-Allow-Origin": "*", | ||||
|             "Cache-Control": "no-store; max-age=0", | ||||
| @@ -94,6 +98,7 @@ class HttpCli(object): | ||||
|     def run(self): | ||||
|         """returns true if connection can be reused""" | ||||
|         self.keepalive = False | ||||
|         self.is_https = False | ||||
|         self.headers = {} | ||||
|         self.hint = None | ||||
|         try: | ||||
| @@ -131,6 +136,7 @@ class HttpCli(object): | ||||
|  | ||||
|         v = self.headers.get("connection", "").lower() | ||||
|         self.keepalive = not v.startswith("close") and self.http_ver != "HTTP/1.0" | ||||
|         self.is_https = (self.headers.get("x-forwarded-proto", "").lower() == "https" or self.tls) | ||||
|  | ||||
|         n = self.args.rproxy | ||||
|         if n: | ||||
| @@ -148,6 +154,8 @@ class HttpCli(object): | ||||
|  | ||||
|                 self.log_src = self.conn.set_rproxy(self.ip) | ||||
|  | ||||
|         self.dip = self.ip.replace(":", ".") | ||||
|  | ||||
|         if self.args.ihead: | ||||
|             keys = self.args.ihead | ||||
|             if "*" in keys: | ||||
| @@ -164,15 +172,11 @@ class HttpCli(object): | ||||
|         # split req into vpath + uparam | ||||
|         uparam = {} | ||||
|         if "?" not in self.req: | ||||
|             if not self.req.endswith("/"): | ||||
|                 self.absolute_urls = True | ||||
|  | ||||
|             self.trailing_slash = self.req.endswith("/") | ||||
|             vpath = undot(self.req) | ||||
|         else: | ||||
|             vpath, arglist = self.req.split("?", 1) | ||||
|             if not vpath.endswith("/"): | ||||
|                 self.absolute_urls = True | ||||
|  | ||||
|             self.trailing_slash = vpath.endswith("/") | ||||
|             vpath = undot(vpath) | ||||
|             for k in arglist.split("&"): | ||||
|                 if "=" in k: | ||||
| @@ -270,6 +274,15 @@ class HttpCli(object): | ||||
|             except Pebkac: | ||||
|                 return False | ||||
|  | ||||
|     def permit_caching(self): | ||||
|         cache = self.uparam.get("cache") | ||||
|         if cache is None: | ||||
|             self.out_headers.update(NO_CACHE) | ||||
|             return | ||||
|  | ||||
|         n = "604800" if cache == "i" else cache or "69" | ||||
|         self.out_headers["Cache-Control"] = "max-age=" + n | ||||
|  | ||||
|     def send_headers(self, length, status=200, mime=None, headers=None): | ||||
|         response = ["{} {} {}".format(self.http_ver, status, HTTPCODE[status])] | ||||
|  | ||||
| @@ -466,13 +479,13 @@ class HttpCli(object): | ||||
|             except: | ||||
|                 raise Pebkac(400, "client d/c before 100 continue") | ||||
|  | ||||
|         if "raw" in self.uparam: | ||||
|             return self.handle_stash() | ||||
|  | ||||
|         ctype = self.headers.get("content-type", "").lower() | ||||
|         if not ctype: | ||||
|             raise Pebkac(400, "you can't post without a content-type header") | ||||
|  | ||||
|         if "raw" in self.uparam: | ||||
|             return self.handle_stash() | ||||
|  | ||||
|         if "multipart/form-data" in ctype: | ||||
|             return self.handle_post_multipart() | ||||
|  | ||||
| @@ -533,17 +546,16 @@ class HttpCli(object): | ||||
|         fdir = os.path.join(vfs.realpath, rem) | ||||
|         if lim: | ||||
|             fdir, rem = lim.all(self.ip, rem, remains, fdir) | ||||
|             bos.makedirs(fdir) | ||||
|  | ||||
|         addr = self.ip.replace(":", ".") | ||||
|         fn = "put-{:.6f}-{}.bin".format(time.time(), addr) | ||||
|         path = os.path.join(fdir, fn) | ||||
|         if self.args.nw: | ||||
|             path = os.devnull | ||||
|         fn = None | ||||
|         if rem and not self.trailing_slash and not bos.path.isdir(fdir): | ||||
|             fdir, fn = os.path.split(fdir) | ||||
|             rem, _ = vsplit(rem) | ||||
|  | ||||
|         open_f = open | ||||
|         open_a = [fsenc(path), "wb", 512 * 1024] | ||||
|         open_ka = {} | ||||
|         bos.makedirs(fdir) | ||||
|  | ||||
|         open_ka = {"fun": open} | ||||
|         open_a = ["wb", 512 * 1024] | ||||
|  | ||||
|         # user-request || config-force | ||||
|         if ("gz" in vfs.flags or "xz" in vfs.flags) and ( | ||||
| @@ -584,16 +596,28 @@ class HttpCli(object): | ||||
|  | ||||
|             self.log("compressing with {} level {}".format(alg, lv.get(alg))) | ||||
|             if alg == "gz": | ||||
|                 open_f = gzip.GzipFile | ||||
|                 open_a = [fsenc(path), "wb", lv[alg], None, 0x5FEE6600]  # 2021-01-01 | ||||
|                 open_ka["fun"] = gzip.GzipFile | ||||
|                 open_a = ["wb", lv[alg], None, 0x5FEE6600]  # 2021-01-01 | ||||
|             elif alg == "xz": | ||||
|                 open_f = lzma.open | ||||
|                 open_a = [fsenc(path), "wb"] | ||||
|                 open_ka = {"preset": lv[alg]} | ||||
|                 open_ka = {"fun": lzma.open, "preset": lv[alg]} | ||||
|                 open_a = ["wb"] | ||||
|             else: | ||||
|                 self.log("fallthrough? thats a bug", 1) | ||||
|  | ||||
|         with open_f(*open_a, **open_ka) as f: | ||||
|         suffix = "-{:.6f}-{}".format(time.time(), self.dip) | ||||
|         params = {"suffix": suffix, "fdir": fdir} | ||||
|         if self.args.nw: | ||||
|             params = {} | ||||
|             fn = os.devnull | ||||
|  | ||||
|         params.update(open_ka) | ||||
|  | ||||
|         if not fn: | ||||
|             fn = "put" + suffix | ||||
|  | ||||
|         with ren_open(fn, *open_a, **params) as f: | ||||
|             f, fn = f["orz"] | ||||
|             path = os.path.join(fdir, fn) | ||||
|             post_sz, _, sha_b64 = hashcopy(reader, f) | ||||
|  | ||||
|         if lim: | ||||
| @@ -1030,7 +1054,7 @@ class HttpCli(object): | ||||
|                     if not bos.path.isdir(fdir): | ||||
|                         raise Pebkac(404, "that folder does not exist") | ||||
|  | ||||
|                     suffix = ".{:.6f}-{}".format(time.time(), self.ip) | ||||
|                     suffix = "-{:.6f}-{}".format(time.time(), self.dip) | ||||
|                     open_args = {"fdir": fdir, "suffix": suffix} | ||||
|                 else: | ||||
|                     open_args = {} | ||||
| @@ -1129,7 +1153,7 @@ class HttpCli(object): | ||||
|             # using SHA-512/224, optionally SHA-512/256 = :64 | ||||
|             jpart = { | ||||
|                 "url": "{}://{}/{}".format( | ||||
|                     "https" if self.tls else "http", | ||||
|                     "https" if self.is_https else "http", | ||||
|                     self.headers.get("host", "copyparty"), | ||||
|                     vpath + vsuf, | ||||
|                 ), | ||||
| @@ -1434,10 +1458,8 @@ class HttpCli(object): | ||||
|  | ||||
|         if is_compressed: | ||||
|             self.out_headers["Cache-Control"] = "max-age=573" | ||||
|         elif "cache" in self.uparam: | ||||
|             self.out_headers["Cache-Control"] = "max-age=69" | ||||
|         else: | ||||
|             self.out_headers.update(NO_CACHE) | ||||
|             self.permit_caching() | ||||
|  | ||||
|         self.out_headers["Accept-Ranges"] = "bytes" | ||||
|         self.send_headers( | ||||
| @@ -1533,6 +1555,7 @@ class HttpCli(object): | ||||
|         return True | ||||
|  | ||||
|     def tx_ico(self, ext, exact=False): | ||||
|         self.permit_caching() | ||||
|         if ext.endswith("/"): | ||||
|             ext = "folder" | ||||
|             exact = True | ||||
| @@ -1915,11 +1938,14 @@ class HttpCli(object): | ||||
|             # some fuses misbehave | ||||
|             if not self.args.nid: | ||||
|                 if WINDOWS: | ||||
|                     bfree = ctypes.c_ulonglong(0) | ||||
|                     ctypes.windll.kernel32.GetDiskFreeSpaceExW( | ||||
|                         ctypes.c_wchar_p(abspath), None, None, ctypes.pointer(bfree) | ||||
|                     ) | ||||
|                     srv_info.append(humansize(bfree.value) + " free") | ||||
|                     try: | ||||
|                         bfree = ctypes.c_ulonglong(0) | ||||
|                         ctypes.windll.kernel32.GetDiskFreeSpaceExW( | ||||
|                             ctypes.c_wchar_p(abspath), None, None, ctypes.pointer(bfree) | ||||
|                         ) | ||||
|                         srv_info.append(humansize(bfree.value) + " free") | ||||
|                     except: | ||||
|                         pass | ||||
|                 else: | ||||
|                     sv = os.statvfs(fsenc(abspath)) | ||||
|                     free = humansize(sv.f_frsize * sv.f_bfree, True) | ||||
| @@ -2063,7 +2089,7 @@ class HttpCli(object): | ||||
|         for fn in vfs_ls: | ||||
|             base = "" | ||||
|             href = fn | ||||
|             if not is_ls and self.absolute_urls and vpath: | ||||
|             if not is_ls and not self.trailing_slash and vpath: | ||||
|                 base = "/" + vpath + "/" | ||||
|                 href = base + fn | ||||
|  | ||||
| @@ -2198,6 +2224,9 @@ class HttpCli(object): | ||||
|         if "mth" in vn.flags: | ||||
|             j2a["def_hcols"] = vn.flags["mth"].split(",") | ||||
|  | ||||
|         if self.args.js_browser: | ||||
|             j2a["js"] = self.args.js_browser | ||||
|  | ||||
|         if self.args.css_browser: | ||||
|             j2a["css"] = self.args.css_browser | ||||
|  | ||||
|   | ||||
| @@ -471,7 +471,10 @@ class MTag(object): | ||||
|         ret = {} | ||||
|         for tagname, mp in parsers.items(): | ||||
|             try: | ||||
|                 cmd = [sys.executable, mp.bin, abspath] | ||||
|                 cmd = [mp.bin, abspath] | ||||
|                 if mp.bin.endswith(".py"): | ||||
|                     cmd = [sys.executable] + cmd | ||||
|  | ||||
|                 args = {"env": env, "timeout": mp.timeout} | ||||
|  | ||||
|                 if WINDOWS: | ||||
|   | ||||
| @@ -38,6 +38,7 @@ class SvcHub(object): | ||||
|         self.stop_req = False | ||||
|         self.stopping = False | ||||
|         self.stop_cond = threading.Condition() | ||||
|         self.retcode = 0 | ||||
|         self.httpsrv_up = 0 | ||||
|  | ||||
|         self.log_mutex = threading.Lock() | ||||
| @@ -59,9 +60,9 @@ class SvcHub(object): | ||||
|         if not args.no_fpool and args.j != 1: | ||||
|             m = "WARNING: --use-fpool combined with multithreading is untested and can probably cause undefined behavior" | ||||
|             if ANYWIN: | ||||
|                 m = "windows cannot do multithreading without --no-fpool, so enabling that -- note that upload performance will suffer if you have microsoft defender \"real-time protection\" enabled, so you probably want to use -j 1 instead" | ||||
|                 m = 'windows cannot do multithreading without --no-fpool, so enabling that -- note that upload performance will suffer if you have microsoft defender "real-time protection" enabled, so you probably want to use -j 1 instead' | ||||
|                 args.no_fpool = True | ||||
|              | ||||
|  | ||||
|             self.log("root", m, c=3) | ||||
|  | ||||
|         # initiate all services to manage | ||||
| @@ -98,14 +99,23 @@ class SvcHub(object): | ||||
|  | ||||
|     def thr_httpsrv_up(self): | ||||
|         time.sleep(5) | ||||
|         failed = self.broker.num_workers - self.httpsrv_up | ||||
|         expected = self.broker.num_workers * self.tcpsrv.nsrv | ||||
|         failed = expected - self.httpsrv_up | ||||
|         if not failed: | ||||
|             return | ||||
|  | ||||
|         m = "{}/{} workers failed to start" | ||||
|         m = m.format(failed, self.broker.num_workers) | ||||
|         m = m.format(failed, expected) | ||||
|         self.log("root", m, 1) | ||||
|         os._exit(1) | ||||
|  | ||||
|         if self.args.ign_ebind_all: | ||||
|             return | ||||
|  | ||||
|         if self.args.ign_ebind and self.tcpsrv.srv: | ||||
|             return | ||||
|  | ||||
|         self.retcode = 1 | ||||
|         os.kill(os.getpid(), signal.SIGTERM) | ||||
|  | ||||
|     def cb_httpsrv_up(self): | ||||
|         self.httpsrv_up += 1 | ||||
| @@ -242,7 +252,7 @@ class SvcHub(object): | ||||
|                         print("waiting for thumbsrv (10sec)...") | ||||
|  | ||||
|             print("nailed it", end="") | ||||
|             ret = 0 | ||||
|             ret = self.retcode | ||||
|         finally: | ||||
|             print("\033[0m") | ||||
|             if self.logf: | ||||
|   | ||||
| @@ -42,9 +42,21 @@ class TcpSrv(object): | ||||
|                 self.log("tcpsrv", m) | ||||
|  | ||||
|         self.srv = [] | ||||
|         self.nsrv = 0 | ||||
|         for ip in self.args.i: | ||||
|             for port in self.args.p: | ||||
|                 self.srv.append(self._listen(ip, port)) | ||||
|                 self.nsrv += 1 | ||||
|                 try: | ||||
|                     self._listen(ip, port) | ||||
|                 except Exception as ex: | ||||
|                     if self.args.ign_ebind or self.args.ign_ebind_all: | ||||
|                         m = "could not listen on {}:{}: {}" | ||||
|                         self.log("tcpsrv", m.format(ip, port, ex), c=1) | ||||
|                     else: | ||||
|                         raise | ||||
|  | ||||
|         if not self.srv and not self.args.ign_ebind_all: | ||||
|             raise Exception("could not listen on any of the given interfaces") | ||||
|  | ||||
|     def _listen(self, ip, port): | ||||
|         srv = socket.socket(socket.AF_INET, socket.SOCK_STREAM) | ||||
| @@ -52,7 +64,7 @@ class TcpSrv(object): | ||||
|         srv.setsockopt(socket.IPPROTO_TCP, socket.TCP_NODELAY, 1) | ||||
|         try: | ||||
|             srv.bind((ip, port)) | ||||
|             return srv | ||||
|             self.srv.append(srv) | ||||
|         except (OSError, socket.error) as ex: | ||||
|             if ex.errno in [98, 48]: | ||||
|                 e = "\033[1;31mport {} is busy on interface {}\033[0m".format(port, ip) | ||||
|   | ||||
| @@ -29,6 +29,8 @@ from .util import ( | ||||
|     atomic_move, | ||||
|     quotep, | ||||
|     vsplit, | ||||
|     w8b64enc, | ||||
|     w8b64dec, | ||||
|     s3enc, | ||||
|     s3dec, | ||||
|     rmdirs, | ||||
| @@ -464,7 +466,8 @@ class Up2k(object): | ||||
|     def _build_file_index(self, vol, all_vols): | ||||
|         do_vac = False | ||||
|         top = vol.realpath | ||||
|         nohash = "dhash" in vol.flags | ||||
|         rei = vol.flags.get("noidx") | ||||
|         reh = vol.flags.get("nohash") | ||||
|         with self.mutex: | ||||
|             cur, _ = self.register_vpath(top, vol.flags) | ||||
|  | ||||
| @@ -479,38 +482,55 @@ class Up2k(object): | ||||
|             if WINDOWS: | ||||
|                 excl = [x.replace("/", "\\") for x in excl] | ||||
|  | ||||
|             n_add = self._build_dir(dbw, top, set(excl), top, nohash, []) | ||||
|             n_rm = self._drop_lost(dbw[0], top) | ||||
|             n_add = n_rm = 0 | ||||
|             try: | ||||
|                 n_add = self._build_dir(dbw, top, set(excl), top, rei, reh, []) | ||||
|                 n_rm = self._drop_lost(dbw[0], top) | ||||
|             except: | ||||
|                 m = "failed to index volume [{}]:\n{}" | ||||
|                 self.log(m.format(top, min_ex()), c=1) | ||||
|  | ||||
|             if dbw[1]: | ||||
|                 self.log("commit {} new files".format(dbw[1])) | ||||
|                 dbw[0].connection.commit() | ||||
|  | ||||
|             dbw[0].connection.commit() | ||||
|  | ||||
|             return True, n_add or n_rm or do_vac | ||||
|  | ||||
|     def _build_dir(self, dbw, top, excl, cdir, nohash, seen): | ||||
|     def _build_dir(self, dbw, top, excl, cdir, rei, reh, seen): | ||||
|         rcdir = absreal(cdir)  # a bit expensive but worth | ||||
|         if rcdir in seen: | ||||
|             m = "bailing from symlink loop,\n  prev: {}\n  curr: {}\n  from: {}" | ||||
|             self.log(m.format(seen[-1], rcdir, cdir), 3) | ||||
|             return 0 | ||||
|  | ||||
|         seen = seen + [cdir] | ||||
|         seen = seen + [rcdir] | ||||
|         self.pp.msg = "a{} {}".format(self.pp.n, cdir) | ||||
|         histpath = self.asrv.vfs.histtab[top] | ||||
|         ret = 0 | ||||
|         seen_files = {} | ||||
|         g = statdir(self.log_func, not self.args.no_scandir, False, cdir) | ||||
|         for iname, inf in sorted(g): | ||||
|             abspath = os.path.join(cdir, iname) | ||||
|             if rei and rei.search(abspath): | ||||
|                 continue | ||||
|  | ||||
|             nohash = reh.search(abspath) if reh else False | ||||
|             lmod = int(inf.st_mtime) | ||||
|             sz = inf.st_size | ||||
|             if stat.S_ISDIR(inf.st_mode): | ||||
|                 if abspath in excl or abspath == histpath: | ||||
|                     continue | ||||
|                 # self.log(" dir: {}".format(abspath)) | ||||
|                 ret += self._build_dir(dbw, top, excl, abspath, nohash, seen) | ||||
|                 try: | ||||
|                     ret += self._build_dir(dbw, top, excl, abspath, rei, reh, seen) | ||||
|                 except: | ||||
|                     m = "failed to index subdir [{}]:\n{}" | ||||
|                     self.log(m.format(abspath, min_ex()), c=1) | ||||
|             else: | ||||
|                 # self.log("file: {}".format(abspath)) | ||||
|                 rp = abspath[len(top) + 1 :] | ||||
|                 seen_files[iname] = 1 | ||||
|                 rp = abspath[len(top) :].lstrip("/") | ||||
|                 if WINDOWS: | ||||
|                     rp = rp.replace("\\", "/").strip("/") | ||||
|  | ||||
| @@ -568,34 +588,65 @@ class Up2k(object): | ||||
|                     dbw[0].connection.commit() | ||||
|                     dbw[1] = 0 | ||||
|                     dbw[2] = time.time() | ||||
|  | ||||
|         # drop missing files | ||||
|         rd = cdir[len(top) + 1 :].strip("/") | ||||
|         if WINDOWS: | ||||
|             rd = rd.replace("\\", "/").strip("/") | ||||
|  | ||||
|         q = "select fn from up where rd = ?" | ||||
|         try: | ||||
|             c = dbw[0].execute(q, (rd,)) | ||||
|         except: | ||||
|             c = dbw[0].execute(q, ("//" + w8b64enc(rd),)) | ||||
|  | ||||
|         hits = [w8b64dec(x[2:]) if x.startswith("//") else x for (x,) in c] | ||||
|         rm_files = [x for x in hits if x not in seen_files] | ||||
|         n_rm = len(rm_files) | ||||
|         for fn in rm_files: | ||||
|             self.db_rm(dbw[0], rd, fn) | ||||
|  | ||||
|         if n_rm: | ||||
|             self.log("forgot {} deleted files".format(n_rm)) | ||||
|  | ||||
|         return ret | ||||
|  | ||||
|     def _drop_lost(self, cur, top): | ||||
|         rm = [] | ||||
|         n_rm = 0 | ||||
|         nchecked = 0 | ||||
|         nfiles = next(cur.execute("select count(w) from up"))[0] | ||||
|         c = cur.execute("select rd, fn from up") | ||||
|         for drd, dfn in c: | ||||
|         # `_build_dir` did all the files, now do dirs | ||||
|         ndirs = next(cur.execute("select count(distinct rd) from up"))[0] | ||||
|         c = cur.execute("select distinct rd from up order by rd desc") | ||||
|         for (drd,) in c: | ||||
|             nchecked += 1 | ||||
|             if drd.startswith("//") or dfn.startswith("//"): | ||||
|                 drd, dfn = s3dec(drd, dfn) | ||||
|             if drd.startswith("//"): | ||||
|                 rd = w8b64dec(drd[2:]) | ||||
|             else: | ||||
|                 rd = drd | ||||
|  | ||||
|             abspath = os.path.join(top, drd, dfn) | ||||
|             # almost zero overhead dw | ||||
|             self.pp.msg = "b{} {}".format(nfiles - nchecked, abspath) | ||||
|             abspath = os.path.join(top, rd) | ||||
|             self.pp.msg = "b{} {}".format(ndirs - nchecked, abspath) | ||||
|             try: | ||||
|                 if not bos.path.exists(abspath): | ||||
|                     rm.append([drd, dfn]) | ||||
|             except Exception as ex: | ||||
|                 self.log("stat-rm: {} @ [{}]".format(repr(ex), abspath)) | ||||
|                 if os.path.isdir(abspath): | ||||
|                     continue | ||||
|             except: | ||||
|                 pass | ||||
|  | ||||
|         if rm: | ||||
|             self.log("forgetting {} deleted files".format(len(rm))) | ||||
|             for rd, fn in rm: | ||||
|                 # self.log("{} / {}".format(rd, fn)) | ||||
|                 self.db_rm(cur, rd, fn) | ||||
|             rm.append(drd) | ||||
|  | ||||
|         return len(rm) | ||||
|         if not rm: | ||||
|             return 0 | ||||
|  | ||||
|         q = "select count(w) from up where rd = ?" | ||||
|         for rd in rm: | ||||
|             n_rm += next(cur.execute(q, (rd,)))[0] | ||||
|  | ||||
|         self.log("forgetting {} deleted dirs, {} files".format(len(rm), n_rm)) | ||||
|         for rd in rm: | ||||
|             cur.execute("delete from up where rd = ?", (rd,)) | ||||
|  | ||||
|         return n_rm | ||||
|  | ||||
|     def _build_tags_index(self, vol): | ||||
|         ptop = vol.realpath | ||||
| @@ -1267,7 +1318,7 @@ class Up2k(object): | ||||
|  | ||||
|         # TODO broker which avoid this race and | ||||
|         # provides a new filename if taken (same as bup) | ||||
|         suffix = ".{:.6f}-{}".format(ts, ip) | ||||
|         suffix = "-{:.6f}-{}".format(ts, ip.replace(":", ".")) | ||||
|         with ren_open(fname, "wb", fdir=fdir, suffix=suffix) as f: | ||||
|             return f["orz"][1] | ||||
|  | ||||
| @@ -1467,6 +1518,7 @@ class Up2k(object): | ||||
|         try: | ||||
|             permsets = [[True, False, False, True]] | ||||
|             vn, rem = self.asrv.vfs.get(vpath, uname, *permsets[0]) | ||||
|             vn, rem = vn.get_dbv(rem) | ||||
|             unpost = False | ||||
|         except: | ||||
|             # unpost with missing permissions? try read+write and verify with db | ||||
| @@ -1476,6 +1528,7 @@ class Up2k(object): | ||||
|             unpost = True | ||||
|             permsets = [[True, True]] | ||||
|             vn, rem = self.asrv.vfs.get(vpath, uname, *permsets[0]) | ||||
|             vn, rem = vn.get_dbv(rem) | ||||
|             _, _, _, _, dip, dat = self._find_from_vpath(vn.realpath, rem) | ||||
|  | ||||
|             m = "you cannot delete this: " | ||||
| @@ -1824,7 +1877,8 @@ class Up2k(object): | ||||
|                 del self.registry[job["ptop"]][job["wark"]] | ||||
|             return | ||||
|  | ||||
|         suffix = ".{:.6f}-{}".format(job["t0"], job["addr"]) | ||||
|         dip = job["addr"].replace(":", ".") | ||||
|         suffix = "-{:.6f}-{}".format(job["t0"], dip) | ||||
|         with ren_open(tnam, "wb", fdir=pdir, suffix=suffix) as f: | ||||
|             f, job["tnam"] = f["orz"] | ||||
|             if ( | ||||
|   | ||||
| @@ -478,11 +478,12 @@ def min_ex(): | ||||
|  | ||||
| @contextlib.contextmanager | ||||
| def ren_open(fname, *args, **kwargs): | ||||
|     fun = kwargs.pop("fun", open) | ||||
|     fdir = kwargs.pop("fdir", None) | ||||
|     suffix = kwargs.pop("suffix", None) | ||||
|  | ||||
|     if fname == os.devnull: | ||||
|         with open(fname, *args, **kwargs) as f: | ||||
|         with fun(fname, *args, **kwargs) as f: | ||||
|             yield {"orz": [f, fname]} | ||||
|             return | ||||
|  | ||||
| @@ -516,7 +517,7 @@ def ren_open(fname, *args, **kwargs): | ||||
|                 fname += suffix | ||||
|                 ext += suffix | ||||
|  | ||||
|             with open(fsenc(fpath), *args, **kwargs) as f: | ||||
|             with fun(fsenc(fpath), *args, **kwargs) as f: | ||||
|                 if b64: | ||||
|                     fp2 = "fn-trunc.{}.txt".format(b64) | ||||
|                     fp2 = os.path.join(fdir, fp2) | ||||
| @@ -1190,6 +1191,9 @@ def sendfile_kern(lower, upper, f, s): | ||||
|  | ||||
|  | ||||
| def statdir(logger, scandir, lstat, top): | ||||
|     if lstat and ANYWIN: | ||||
|         lstat = False | ||||
|  | ||||
|     if lstat and not os.supports_follow_symlinks: | ||||
|         scandir = False | ||||
|  | ||||
|   | ||||
| @@ -16,7 +16,6 @@ html,body,tr,th,td,#files,a { | ||||
| } | ||||
| html { | ||||
| 	color: #ccc; | ||||
| 	background: #333; | ||||
| 	font-family: sans-serif; | ||||
| 	text-shadow: 1px 1px 0px #000; | ||||
| } | ||||
| @@ -36,11 +35,9 @@ pre, code, tt { | ||||
| 	text-shadow: 1px 1px 0 #000; | ||||
| 	font-variant: small-caps; | ||||
| 	font-weight: normal; | ||||
| 	background: #4c4c4c; | ||||
| 	display: inline-block; | ||||
| 	padding: .35em .5em .2em .5em; | ||||
| 	border-radius: 0 .3em .3em 0; | ||||
| 	box-shadow: .1em .1em .4em #222; | ||||
| 	margin: 1.3em 0 0 0; | ||||
| 	font-size: 1.4em; | ||||
| } | ||||
| @@ -71,7 +68,7 @@ a, #files tbody div a:last-child { | ||||
| } | ||||
| #files a:hover { | ||||
| 	color: #fff; | ||||
| 	background: #161616; | ||||
| 	background: #111; | ||||
| 	text-decoration: underline; | ||||
| } | ||||
| #files thead { | ||||
| @@ -82,38 +79,23 @@ a, #files tbody div a:last-child { | ||||
| 	color: #999; | ||||
| 	font-weight: normal; | ||||
| } | ||||
| #files tr:hover td { | ||||
| #files tbody tr:hover td { | ||||
| 	background: #1c1c1c; | ||||
| } | ||||
| #files thead th { | ||||
| 	padding: .5em .3em .3em .3em; | ||||
| 	border-right: 2px solid #3c3c3c; | ||||
| 	border-bottom: 2px solid #444; | ||||
| 	background: #333; | ||||
| 	padding: 0 .3em .3em .3em; | ||||
| 	border-bottom: 1px solid #444; | ||||
| 	cursor: pointer; | ||||
| } | ||||
| #files thead th+th { | ||||
| 	border-left: 2px solid #2a2a2a; | ||||
| } | ||||
| #files thead th:last-child { | ||||
| 	border-right: none; | ||||
| } | ||||
| #files tbody { | ||||
| 	background: #222; | ||||
| } | ||||
| #files td { | ||||
| 	margin: 0; | ||||
| 	padding: 0 .5em; | ||||
| 	border-bottom: 1px solid #111; | ||||
| 	border-left: 1px solid #2c2c2c; | ||||
| 	padding: .1em .5em; | ||||
| 	border-left: 1px solid #3c3c3c; | ||||
| } | ||||
| #files td+td+td { | ||||
| 	max-width: 30em; | ||||
| 	overflow: hidden; | ||||
| } | ||||
| #files tr+tr td { | ||||
| 	border-top: 1px solid #383838; | ||||
| } | ||||
| #files tbody td:nth-child(3) { | ||||
| 	font-family: 'scp', monospace, monospace; | ||||
| 	text-align: right; | ||||
| @@ -121,18 +103,15 @@ a, #files tbody div a:last-child { | ||||
| 	white-space: nowrap; | ||||
| } | ||||
| #files tbody td:first-child { | ||||
| 	padding-left: 1.5em; | ||||
| 	color: #888; | ||||
| } | ||||
| #files tbody tr:first-child td { | ||||
| 	padding-top: .9em; | ||||
| 	text-align: center; | ||||
| } | ||||
| #files tbody tr:last-child td { | ||||
| 	padding-bottom: 1.3em; | ||||
| 	border-bottom: .5em solid #444; | ||||
| 	border-bottom: 1px solid #444; | ||||
| } | ||||
| #files tbody tr td:last-child { | ||||
| 	white-space: nowrap; | ||||
| 	border-right: 1px solid #3c3c3c; | ||||
| } | ||||
| #files thead th[style] { | ||||
| 	width: auto !important; | ||||
| @@ -163,7 +142,7 @@ a, #files tbody div a:last-child { | ||||
| 	background: linear-gradient(90deg, rgba(0,0,0,0), rgba(0,0,0,0.2), rgba(0,0,0,0)); | ||||
| } | ||||
| .logue { | ||||
| 	padding: .2em 1.5em; | ||||
| 	padding: .2em 0; | ||||
| } | ||||
| .logue.hidden, | ||||
| .logue:empty { | ||||
| @@ -175,6 +154,21 @@ a, #files tbody div a:last-child { | ||||
| #epi.logue { | ||||
| 	margin: .8em 0; | ||||
| } | ||||
| #epi.logue.mdo:before { | ||||
| 	content: 'README.md'; | ||||
| 	text-align: center; | ||||
| 	display: block; | ||||
| 	margin-top: -1.5em; | ||||
| } | ||||
| #epi.logue.mdo { | ||||
| 	border-top: 1px solid #555; | ||||
| 	margin-top: 2.5em; | ||||
| } | ||||
| .mdo>h1:first-child, | ||||
| .mdo>h2:first-child, | ||||
| .mdo>h3:first-child { | ||||
| 	margin-top: 1.5rem; | ||||
| } | ||||
| .mdo { | ||||
| 	max-width: 52em; | ||||
| } | ||||
| @@ -184,7 +178,6 @@ a, #files tbody div a:last-child { | ||||
| } | ||||
| #srv_info { | ||||
| 	color: #a73; | ||||
| 	background: #333; | ||||
| 	position: absolute; | ||||
| 	font-size: .8em; | ||||
|  	top: .5em; | ||||
| @@ -286,43 +279,6 @@ html.light #ggrid>a.sel { | ||||
| #files tr:focus td:first-child { | ||||
| 	box-shadow: -.2em .2em 0 #fc0, -.2em -.2em 0 #fc0; | ||||
| } | ||||
| #files tr:focus+tr td { | ||||
| 	border-top: 1px solid transparent; | ||||
| } | ||||
| #blocked { | ||||
| 	position: fixed; | ||||
| 	top: 0; | ||||
| 	left: 0; | ||||
| 	width: 100%; | ||||
| 	height: 100%; | ||||
| 	background: #333; | ||||
| 	font-size: 2.5em; | ||||
| 	z-index: 99; | ||||
| } | ||||
| #blk_play, | ||||
| #blk_abrt { | ||||
| 	position: fixed; | ||||
| 	display: table; | ||||
| 	width: 80%; | ||||
| } | ||||
| #blk_play { | ||||
| 	height: 60%; | ||||
| 	left: 10%; | ||||
| 	top: 5%; | ||||
| } | ||||
| #blk_abrt { | ||||
| 	height: 25%; | ||||
| 	left: 10%; | ||||
| 	bottom: 5%; | ||||
| } | ||||
| #blk_play a, | ||||
| #blk_abrt a { | ||||
| 	display: table-cell; | ||||
| 	vertical-align: middle; | ||||
| 	text-align: center; | ||||
| 	background: #444; | ||||
| 	border-radius: 2em; | ||||
| } | ||||
| #widget { | ||||
| 	position: fixed; | ||||
| 	font-size: 1.4em; | ||||
| @@ -344,7 +300,6 @@ html.light #ggrid>a.sel { | ||||
| 	z-index: 10; | ||||
| 	width: 100%; | ||||
| 	height: 100%; | ||||
| 	background: #3c3c3c; | ||||
| } | ||||
| #wtgrid, | ||||
| #wtico { | ||||
| @@ -385,7 +340,6 @@ html.light #ggrid>a.sel { | ||||
| 	line-height: 1em; | ||||
| 	text-align: center; | ||||
| 	text-shadow: none; | ||||
| 	background: #3c3c3c; | ||||
| 	box-shadow: 0 0 .5em #222; | ||||
| 	border-radius: .3em 0 0 0; | ||||
| 	padding: 0 0 0 .1em; | ||||
| @@ -397,7 +351,7 @@ html.light #ggrid>a.sel { | ||||
| #wzip, #wnp { | ||||
| 	margin-right: .2em; | ||||
| 	padding-right: .2em; | ||||
| 	border: 1px solid #555; | ||||
| 	border: 1px solid #444; | ||||
| 	border-width: 0 .1em 0 0; | ||||
| } | ||||
| #wfm.act+#wzip, | ||||
| @@ -553,36 +507,28 @@ html.light #wfm a:not(.en) { | ||||
| 	box-shadow: 0 -.15em .2em #000 inset; | ||||
| 	padding-bottom: .3em; | ||||
| } | ||||
| #ops, | ||||
| .opbox, | ||||
| #u2etas { | ||||
| 	border: 1px solid #3a3a3a; | ||||
| 	box-shadow: 0 0 1em #222 inset; | ||||
| } | ||||
| #ops { | ||||
| 	background: #333; | ||||
| 	margin: 1.7em 1.5em 0 1.5em; | ||||
| 	padding: .3em .6em; | ||||
| 	border-radius: .3em; | ||||
| 	border-width: .15em 0; | ||||
| 	border-width: 1px 0; | ||||
| 	white-space: nowrap; | ||||
| } | ||||
| .opbox { | ||||
| 	background: #2d2d2d; | ||||
| 	margin: 1.5em 0 0 0; | ||||
| 	padding: .5em; | ||||
| 	border-radius: 0 1em 1em 0; | ||||
| 	border-width: .15em .3em .3em 0; | ||||
| 	border-radius: 0 .3em .3em 0; | ||||
| 	border-width: 1px 1px 1px 0; | ||||
| 	max-width: 41em; | ||||
| 	max-width: min(41em, calc(100% - 2.6em)); | ||||
| } | ||||
| .opbox input { | ||||
| 	margin: .5em; | ||||
| } | ||||
| .opview input[type=text] { | ||||
| 	background: #383838; | ||||
| 	color: #fff; | ||||
| 	border: none; | ||||
| 	box-shadow: 0 0 .3em #222; | ||||
| 	box-shadow: 0 0 .3em #181818; | ||||
| 	border-bottom: 1px solid #fc5; | ||||
| 	border-radius: .2em; | ||||
| 	padding: .2em .3em; | ||||
| @@ -599,14 +545,12 @@ html.light .opview input[type="text"].err { | ||||
| input[type="checkbox"]+label { | ||||
| 	color: #f5a; | ||||
| } | ||||
| input[type="radio"]:checked+label, | ||||
| input[type="checkbox"]:checked+label { | ||||
| 	color: #fc5; | ||||
| } | ||||
| input[type="radio"]:checked+label { | ||||
| 	color: #fc0; | ||||
| } | ||||
| html.light input[type="radio"]:checked+label { | ||||
| 	color: #07c; | ||||
| .opview input.i { | ||||
| 	width: calc(100% - 16.2em); | ||||
| } | ||||
| input.eq_gain { | ||||
| 	width: 3em; | ||||
| @@ -629,15 +573,13 @@ input.eq_gain { | ||||
| 	margin-top: .5em; | ||||
| 	padding: 1.3em .3em; | ||||
| } | ||||
| #ico1 { | ||||
| 	cursor: pointer; | ||||
| } | ||||
|  | ||||
|  | ||||
|  | ||||
| #srch_form { | ||||
| 	border: 1px solid #3a3a3a; | ||||
| 	box-shadow: 0 0 1em #222 inset; | ||||
| 	background: #2d2d2d; | ||||
| 	border-radius: .4em; | ||||
| 	margin: 1.4em; | ||||
| 	margin-bottom: 0; | ||||
| 	padding: 0 .5em .5em 0; | ||||
| } | ||||
| @@ -694,8 +636,8 @@ input.eq_gain { | ||||
| 	width: 100%; | ||||
| } | ||||
| #wrap { | ||||
| 	margin-top: 2em; | ||||
| 	min-height: 90vh; | ||||
| 	margin: 1.8em 1.5em 0 1.5em; | ||||
| 	min-height: 70vh; | ||||
| 	padding-bottom: 5em; | ||||
| } | ||||
| #tree { | ||||
| @@ -709,19 +651,23 @@ input.eq_gain { | ||||
| 	-ms-scroll-chaining: none; | ||||
| 	overscroll-behavior-y: none; | ||||
| 	scrollbar-color: #eb0 #333; | ||||
| 	border: 1px solid #333; | ||||
| 	box-shadow: 0 0 1em #181818; | ||||
| } | ||||
| #treeh { | ||||
| 	background: #333; | ||||
| 	position: sticky; | ||||
| 	z-index: 1; | ||||
| 	top: 0; | ||||
| 	height: 2.2em; | ||||
| 	line-height: 2.2em; | ||||
| 	border-bottom: 1px solid #555; | ||||
| 	border-bottom: 1px solid #111; | ||||
| 	overflow: hidden; | ||||
| } | ||||
| #thx_ff { | ||||
| 	padding: 5em 0; | ||||
| #tree, #treeh { | ||||
| 	border-radius: 0 .3em 0 0; | ||||
| } | ||||
| .np_open #thx_ff { | ||||
| 	padding: 4.5em 0; | ||||
| 	/* widget */ | ||||
| } | ||||
| #tree::-webkit-scrollbar-track, | ||||
| @@ -742,8 +688,6 @@ input.eq_gain { | ||||
| .btn { | ||||
| 	padding: .2em .4em; | ||||
| 	font-size: 1.2em; | ||||
| 	background: #2a2a2a; | ||||
| 	box-shadow: 0 .1em .2em #222 inset; | ||||
| 	border-radius: .3em; | ||||
| 	margin: .2em; | ||||
| 	white-space: pre; | ||||
| @@ -772,13 +716,13 @@ input.eq_gain { | ||||
| 	margin: 0; | ||||
| } | ||||
| #tree ul { | ||||
| 	border-left: .2em solid #555; | ||||
| 	border-left: .2em solid #444; | ||||
| } | ||||
| #tree li { | ||||
| 	margin-left: 1em; | ||||
| 	list-style: none; | ||||
| 	border-top: 1px solid #4c4c4c; | ||||
| 	border-bottom: 1px solid #222; | ||||
| 	border-top: 1px solid #444; | ||||
| 	border-bottom: 1px solid #111; | ||||
| } | ||||
| #tree li:last-child { | ||||
| 	border-bottom: none; | ||||
| @@ -801,7 +745,7 @@ input.eq_gain { | ||||
| 	white-space: nowrap; | ||||
| } | ||||
| #tree.nowrap #treeul a+a:hover { | ||||
| 	background: rgba(34, 34, 34, 0.67); | ||||
| 	background: rgba(16, 16, 16, 0.67); | ||||
| 	min-width: calc(var(--nav-sz) - 2em); | ||||
| 	width: auto; | ||||
| } | ||||
| @@ -810,7 +754,7 @@ html.light #tree.nowrap #treeul a+a:hover { | ||||
| 	color: #000; | ||||
| } | ||||
| #treeul a+a:hover { | ||||
| 	background: #222; | ||||
| 	background: #181818; | ||||
| 	color: #fff; | ||||
| } | ||||
| #treeul a:first-child { | ||||
| @@ -849,30 +793,31 @@ html.light #tree.nowrap #treeul a+a:hover { | ||||
| #files td:nth-child(2n) { | ||||
| 	color: #f5a; | ||||
| } | ||||
| #files tr.play td, | ||||
| #files tr.play div a { | ||||
| #files tbody tr.play td, | ||||
| #files tbody tr.play div a { | ||||
| 	background: #fc4; | ||||
| 	border-color: transparent; | ||||
| 	color: #400; | ||||
| 	text-shadow: none; | ||||
| } | ||||
| #files tr.play a { | ||||
| #files tbody tr.play a { | ||||
| 	color: inherit; | ||||
| } | ||||
| #files tr.play a:hover { | ||||
| #files tbody tr.play a:hover { | ||||
| 	color: #300; | ||||
| 	background: #fea; | ||||
| } | ||||
| .opwide, | ||||
| #op_unpost { | ||||
| #op_unpost, | ||||
| #srch_form { | ||||
| 	max-width: none; | ||||
| 	margin-right: 1.5em; | ||||
| } | ||||
| .opwide>div { | ||||
| 	display: inline-block; | ||||
| 	vertical-align: top; | ||||
| 	border-left: .2em solid #4c4c4c; | ||||
| 	margin-left: .5em; | ||||
| 	border-left: .4em solid #4c4c4c; | ||||
| 	margin: .7em 0 .7em .5em; | ||||
| 	padding-left: .5em; | ||||
| } | ||||
| .opwide>div.fill { | ||||
| @@ -881,6 +826,10 @@ html.light #tree.nowrap #treeul a+a:hover { | ||||
| .opwide>div>div>a { | ||||
| 	line-height: 2em; | ||||
| } | ||||
| .opwide>div>h3 { | ||||
| 	margin: 0 .4em; | ||||
| 	padding: 0; | ||||
| } | ||||
| #op_cfg>div>div>span { | ||||
| 	display: inline-block; | ||||
| 	padding: .2em .4em; | ||||
| @@ -904,12 +853,10 @@ html.light #tree.nowrap #treeul a+a:hover { | ||||
| 	display: none; | ||||
| } | ||||
| #ghead { | ||||
| 	background: #3c3c3c; | ||||
| 	border: 1px solid #444; | ||||
| 	border-radius: .3em; | ||||
| 	padding: .2em .5em; | ||||
| 	line-height: 2.3em; | ||||
| 	margin: 0 1.5em 1em .4em; | ||||
| 	margin-bottom: 1em; | ||||
| 	position: sticky; | ||||
| 	top: -.3em; | ||||
| 	z-index: 1; | ||||
| @@ -928,6 +875,7 @@ html.light #ghead { | ||||
| } | ||||
| #ggrid { | ||||
| 	padding-top: .5em; | ||||
| 	margin: 0 -.5em; | ||||
| } | ||||
| #ggrid>a>span { | ||||
| 	overflow: hidden; | ||||
| @@ -943,17 +891,10 @@ html.light #ghead { | ||||
| 	width: var(--grid-sz); | ||||
| 	vertical-align: top; | ||||
| 	overflow-wrap: break-word; | ||||
| 	background: #383838; | ||||
| 	border: 1px solid #444; | ||||
| 	border-top: 1px solid #555; | ||||
| 	box-shadow: 0 .1em .2em #222; | ||||
| 	border-radius: .3em; | ||||
| 	padding: .3em; | ||||
| 	margin: .5em; | ||||
| } | ||||
| #ggrid>a[tt] { | ||||
| 	background: linear-gradient(135deg, #383838 95%, #555 95%); | ||||
| } | ||||
| #ggrid>a img { | ||||
| 	border-radius: .2em; | ||||
| 	max-width: 10em; | ||||
| @@ -976,25 +917,6 @@ html.light #ghead { | ||||
| 	border-radius: .3em; | ||||
| 	font-size: 2em; | ||||
| } | ||||
| #ggrid>a:hover { | ||||
| 	background: #444; | ||||
| 	border-color: #555; | ||||
| 	color: #fd9; | ||||
| } | ||||
| html.light #ggrid>a { | ||||
| 	background: #f7f7f7; | ||||
| 	border-color: #ddd; | ||||
| 	box-shadow: 0 .1em .2em #ddd; | ||||
| } | ||||
| html.light #ggrid>a[tt] { | ||||
| 	background: linear-gradient(135deg, #f7f7f7 95%, #ccc 95%); | ||||
| } | ||||
| html.light #ggrid>a:hover { | ||||
| 	background: #fff; | ||||
| 	border-color: #ccc; | ||||
| 	color: #015; | ||||
| 	box-shadow: 0 .1em .5em #aaa; | ||||
| } | ||||
| #op_unpost { | ||||
| 	padding: 1em; | ||||
| } | ||||
| @@ -1015,7 +937,6 @@ html.light #ggrid>a:hover { | ||||
| 	max-height: calc(100% - 2em); | ||||
| 	border-bottom: .5em solid #999; | ||||
| 	box-shadow: 0 0 5em rgba(0,0,0,0.8); | ||||
| 	background: #333; | ||||
| 	padding: 1em; | ||||
| 	z-index: 765; | ||||
| } | ||||
| @@ -1072,7 +993,8 @@ a.btn, | ||||
| #rui label, | ||||
| #modal-ok, | ||||
| #modal-ng, | ||||
| #ops { | ||||
| #ops, | ||||
| #ico1 { | ||||
| 	-webkit-user-select: none; | ||||
| 	-moz-user-select: none; | ||||
| 	-ms-user-select: none; | ||||
| @@ -1098,6 +1020,77 @@ a.btn, | ||||
|  | ||||
|  | ||||
|  | ||||
|  | ||||
|  | ||||
|  | ||||
|  | ||||
|  | ||||
| html, | ||||
| #rui, | ||||
| #files td, | ||||
| #files thead th, | ||||
| #bbox-halp, | ||||
| #u2notbtn, | ||||
| #srv_info { | ||||
| 	background: #222; | ||||
| } | ||||
| #ops, | ||||
| .opbox, | ||||
| #path, | ||||
| #srch_form, | ||||
| #ghead { | ||||
| 	background: #2b2b2b; | ||||
| 	border: 1px solid #333; | ||||
| 	box-shadow: 0 0 .3em #111; | ||||
| } | ||||
| #files tr:nth-child(2n+1) td { | ||||
| 	background: #282828; | ||||
| } | ||||
| #tree, | ||||
| #treeh { | ||||
| 	background: #2b2b2b; | ||||
| } | ||||
| #wtoggle, | ||||
| #widgeti { | ||||
| 	background: #333; | ||||
| } | ||||
| .btn, | ||||
| .opview input[type=text] { | ||||
| 	background: #383838; | ||||
| } | ||||
|  | ||||
|  | ||||
|  | ||||
|  | ||||
|  | ||||
| #ggrid>a { | ||||
| 	background: #2c2c2c; | ||||
| 	border: 1px solid #383838; | ||||
| 	border-top: 1px solid #444; | ||||
| 	box-shadow: 0 .1em .2em #181818; | ||||
| } | ||||
| #ggrid>a[tt] { | ||||
| 	background: linear-gradient(135deg, #2c2c2c 95%, #444 95%); | ||||
| } | ||||
| #ggrid>a:hover { | ||||
| 	background: #383838; | ||||
| 	border-color: #555; | ||||
| 	color: #fd9; | ||||
| } | ||||
| html.light #ggrid>a { | ||||
| 	background: #f7f7f7; | ||||
| 	border-color: #ddd; | ||||
| 	box-shadow: 0 .1em .2em #ddd; | ||||
| } | ||||
| html.light #ggrid>a[tt] { | ||||
| 	background: linear-gradient(135deg, #f7f7f7 95%, #ccc 95%); | ||||
| } | ||||
| html.light #ggrid>a:hover { | ||||
| 	background: #fff; | ||||
| 	border-color: #ccc; | ||||
| 	color: #015; | ||||
| 	box-shadow: 0 .1em .5em #aaa; | ||||
| } | ||||
|  | ||||
|  | ||||
|  | ||||
| @@ -1105,15 +1098,17 @@ a.btn, | ||||
|  | ||||
| html.light { | ||||
| 	color: #333; | ||||
| 	background: #eee; | ||||
| 	background: #eaeaea; | ||||
| 	text-shadow: none; | ||||
| } | ||||
| html.light #ops, | ||||
| html.light .opbox, | ||||
| html.light #path, | ||||
| html.light #srch_form, | ||||
| html.light #ghead, | ||||
| html.light #u2etas { | ||||
| 	background: #f7f7f7; | ||||
| 	box-shadow: 0 0 .3em #ddd; | ||||
| 	box-shadow: 0 0 .3em #ccc; | ||||
| 	border-color: #f7f7f7; | ||||
| } | ||||
| html.light #ops a.act { | ||||
| @@ -1176,25 +1171,19 @@ html.light #ops a, | ||||
| html.light #files tbody div a:last-child { | ||||
| 	color: #06a; | ||||
| } | ||||
| html.light #files tbody { | ||||
| html.light #files thead th { | ||||
| 	background: #eaeaea; | ||||
| 	border-color: #ccc; | ||||
| } | ||||
| html.light #files tbody td { | ||||
| 	background: #eee; | ||||
| 	border-color: #ccc; | ||||
| } | ||||
| html.light #files tr:nth-child(2n+1) td { | ||||
| 	background: #f7f7f7; | ||||
| } | ||||
| html.light #files { | ||||
| 	box-shadow: 0 0 .3em #ccc; | ||||
| } | ||||
| html.light #files thead th { | ||||
| 	background: #eee; | ||||
| 	border: 1px solid #ccc; | ||||
| 	border-top: none; | ||||
| } | ||||
| html.light #files thead th+th { | ||||
| 	border-left: 1px solid #f7f7f7; | ||||
| } | ||||
| html.light #files td { | ||||
| 	border-color: #fff #fff #ddd #ddd; | ||||
| } | ||||
| html.light #files tbody tr:last-child td { | ||||
| 	border-bottom: .2em solid #ccc; | ||||
| 	border-bottom: 1px solid #ccc; | ||||
| } | ||||
| html.light #files tr:focus td { | ||||
| 	background: #fff; | ||||
| @@ -1232,14 +1221,6 @@ html.light tr.play a { | ||||
| html.light #files th:hover .cfg { | ||||
| 	background: #ccc; | ||||
| } | ||||
| 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: #06a; | ||||
| } | ||||
| @@ -1276,6 +1257,10 @@ html.light #files tr.sel a.play.act { | ||||
| html.light input[type="checkbox"] + label { | ||||
| 	color: #333; | ||||
| } | ||||
| html.light input[type="radio"]:checked + label, | ||||
| html.light input[type="checkbox"]:checked + label { | ||||
| 	color: #07c;  | ||||
| } | ||||
| html.light .opwide>div { | ||||
| 	border-color: #ccc; | ||||
| } | ||||
| @@ -1311,20 +1296,24 @@ html.light #files a:hover, | ||||
| html.light #files tr.sel a:hover { | ||||
| 	color: #000; | ||||
| 	background: #fff; | ||||
| 	text-decoration: underline; | ||||
| } | ||||
| html.light #treeh { | ||||
| 	background: #eee; | ||||
| 	background: #f7f7f7; | ||||
| 	border-color: #ddd; | ||||
| } | ||||
| html.light #tree { | ||||
| 	scrollbar-color: #a70 #ddd; | ||||
| 	border-color: #ddd; | ||||
| 	box-shadow: 0 0 1em #ddd; | ||||
| 	background: #f7f7f7; | ||||
| 	scrollbar-color: #490 #ddd; | ||||
| } | ||||
| html.light #tree::-webkit-scrollbar-track, | ||||
| html.light #tree::-webkit-scrollbar { | ||||
| 	background: #ddd; | ||||
| } | ||||
| #tree::-webkit-scrollbar-thumb { | ||||
| 	background: #da0; | ||||
| html.light #tree::-webkit-scrollbar-thumb { | ||||
| 	background: #490; | ||||
| } | ||||
|  | ||||
|  | ||||
| @@ -1403,7 +1392,7 @@ html.light #tree::-webkit-scrollbar { | ||||
| 	box-shadow: 0 0 8px rgba(0, 0, 0, 0.6); | ||||
| } | ||||
| .full-image video { | ||||
| 	background: #333; | ||||
| 	background: #222; | ||||
| } | ||||
| .full-image figcaption { | ||||
| 	display: block; | ||||
| @@ -1499,7 +1488,6 @@ html.light #bbox-overlay figcaption a { | ||||
| } | ||||
| #bbox-halp { | ||||
| 	color: #fff; | ||||
| 	background: #333; | ||||
| 	position: absolute; | ||||
| 	top: 0; | ||||
| 	left: 0; | ||||
| @@ -1707,6 +1695,10 @@ html.light #u2err.err { | ||||
| 	cursor: pointer; | ||||
| 	box-shadow: .4em .4em 0 #111; | ||||
| } | ||||
| #u2conf.ww #u2btn { | ||||
| 	font-size: 1.3em; | ||||
| 	margin-right: .5em; | ||||
| } | ||||
| #op_up2k.srch #u2btn { | ||||
| 	background: linear-gradient(to bottom, #ca3 0%, #fd8 50%, #fc6 51%, #b92 100%); | ||||
| 	text-shadow: 1px 1px 1px #fc6; | ||||
| @@ -1725,7 +1717,6 @@ html.light #u2err.err { | ||||
| #u2notbtn { | ||||
| 	display: none; | ||||
| 	text-align: center; | ||||
| 	background: #333; | ||||
| 	padding-top: 1em; | ||||
| } | ||||
| #u2notbtn * { | ||||
| @@ -1758,10 +1749,12 @@ html.light #u2err.err { | ||||
| 	width: auto; | ||||
| } | ||||
| #u2tab tbody tr:hover td { | ||||
| 	background: #222; | ||||
| 	background: #333; | ||||
| } | ||||
| #u2etas { | ||||
| 	background: #333; | ||||
| 	background: #1c1c1c; | ||||
| 	border: 1px solid #282828; | ||||
| 	border-width: .1em 0; | ||||
| 	padding: .2em .5em; | ||||
| 	border-radius: .5em; | ||||
| 	border-width: .25em 0; | ||||
| @@ -1800,16 +1793,22 @@ html.light #u2err.err { | ||||
| 	width: 44em; | ||||
| 	text-align: left; | ||||
| } | ||||
| #u2cards.ww { | ||||
| 	display: inline-block; | ||||
| } | ||||
| #u2etaw.w { | ||||
| 	width: 52em; | ||||
| 	text-align: right; | ||||
| 	margin: 3em auto -2.7em auto; | ||||
| } | ||||
| #u2etaw.ww { | ||||
| 	margin: 0 2em 1em 2em; | ||||
| } | ||||
| #u2cards a { | ||||
| 	padding: .2em 1em; | ||||
| 	border: 1px solid #777; | ||||
| 	border-width: 0 0 1px 0; | ||||
| 	background: linear-gradient(to bottom, #333, #222); | ||||
| 	background: linear-gradient(to bottom, #222, #2b2b2b); | ||||
| } | ||||
| #u2cards a:first-child { | ||||
| 	border-radius: .4em 0 0 0; | ||||
| @@ -1822,9 +1821,9 @@ html.light #u2err.err { | ||||
| 	border-width: 1px 1px .1em 1px; | ||||
| 	border-radius: .3em .3em 0 0; | ||||
| 	margin-left: -1px; | ||||
| 	background: linear-gradient(to bottom, #464, #333 80%); | ||||
| 	background: linear-gradient(to bottom, #353, #222 80%); | ||||
| 	box-shadow: 0 -.17em .67em #280; | ||||
| 	border-color: #7c5 #583 #333 #583; | ||||
| 	border-color: #7c5 #583 #222 #583; | ||||
| 	position: relative; | ||||
| 	color: #fd7; | ||||
| } | ||||
| @@ -1835,10 +1834,17 @@ html.light #u2err.err { | ||||
| 	margin: 1em auto; | ||||
| 	width: 30em; | ||||
| } | ||||
| #u2conf.has_btn { | ||||
| #u2conf.w { | ||||
| 	width: 48em; | ||||
| } | ||||
| #u2conf * { | ||||
| #u2conf.ww { | ||||
| 	width: 74em; | ||||
| } | ||||
| #u2conf.ww #u2c3w { | ||||
| 	width: 29em; | ||||
| } | ||||
| #u2conf .c, | ||||
| #u2conf .c * { | ||||
| 	text-align: center; | ||||
| 	line-height: 1em; | ||||
| 	margin: 0; | ||||
| @@ -1858,7 +1864,7 @@ html.light #u2err.err { | ||||
| #u2conf .txtbox.err { | ||||
| 	background: #922; | ||||
| } | ||||
| #u2conf a { | ||||
| #u2conf a.b { | ||||
| 	color: #fff; | ||||
| 	background: #c38; | ||||
| 	text-decoration: none; | ||||
| @@ -1872,10 +1878,10 @@ html.light #u2err.err { | ||||
| 	position: relative; | ||||
| 	bottom: -0.08em; | ||||
| } | ||||
| #u2conf input+a { | ||||
| #u2conf input+a.b { | ||||
| 	background: #d80; | ||||
| } | ||||
| #u2conf label { | ||||
| #u2conf .c label { | ||||
| 	font-size: 1.6em; | ||||
| 	width: 2em; | ||||
| 	height: 1em; | ||||
|   | ||||
| @@ -18,9 +18,9 @@ | ||||
|  | ||||
| 	<div id="op_search" class="opview"> | ||||
| 		{%- if have_tags_idx %} | ||||
| 		<div id="srch_form" class="tags"></div> | ||||
| 		<div id="srch_form" class="tags opbox"></div> | ||||
| 		{%- else %} | ||||
| 		<div id="srch_form"></div> | ||||
| 		<div id="srch_form" class="opbox"></div> | ||||
| 		{%- endif %} | ||||
| 		<div id="srch_q"></div> | ||||
| 	</div> | ||||
| @@ -31,7 +31,7 @@ | ||||
| 		<div id="u2err"></div> | ||||
| 		<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="file" name="f" multiple /><br /> | ||||
| 			<input type="submit" value="start upload"> | ||||
| 		</form> | ||||
| 	</div> | ||||
| @@ -39,7 +39,7 @@ | ||||
| 	<div id="op_mkdir" class="opview opbox act"> | ||||
| 		<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="text" name="name" class="i"> | ||||
| 			<input type="submit" value="make directory"> | ||||
| 		</form> | ||||
| 	</div> | ||||
| @@ -47,15 +47,15 @@ | ||||
| 	<div id="op_new_md" class="opview opbox"> | ||||
| 		<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="text" name="name" class="i"> | ||||
| 			<input type="submit" value="new markdown doc"> | ||||
| 		</form> | ||||
| 	</div> | ||||
|  | ||||
| 	<div id="op_msg" class="opview opbox act"> | ||||
| 		<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 to server log"> | ||||
| 			📟<input type="text" name="msg" class="i"> | ||||
| 			<input type="submit" value="send msg to srv log"> | ||||
| 		</form> | ||||
| 	</div> | ||||
|  | ||||
| @@ -135,10 +135,15 @@ | ||||
| 			have_unpost = {{ have_unpost|tojson }}, | ||||
| 			have_zip = {{ have_zip|tojson }}, | ||||
| 			readme = {{ readme|tojson }}; | ||||
|  | ||||
| 		document.documentElement.setAttribute("class", localStorage.lightmode == 1 ? "light" : "dark"); | ||||
| 	</script> | ||||
| 	<script src="/.cpr/util.js?_={{ ts }}"></script> | ||||
| 	<script src="/.cpr/browser.js?_={{ ts }}"></script> | ||||
| 	<script src="/.cpr/up2k.js?_={{ ts }}"></script> | ||||
| 	{%- if js %} | ||||
| 	<script src="{{ js }}?_={{ ts }}"></script> | ||||
| 	{%- endif %} | ||||
| </body> | ||||
|  | ||||
| </html> | ||||
|   | ||||
| @@ -61,28 +61,29 @@ ebi('op_up2k').innerHTML = ( | ||||
|  | ||||
| 	'<table id="u2conf">\n' + | ||||
| 	'	<tr>\n' + | ||||
| 	'		<td><br />parallel uploads:</td>\n' + | ||||
| 	'		<td rowspan="2">\n' + | ||||
| 	'		<td class="c"><br />parallel uploads:</td>\n' + | ||||
| 	'		<td class="c" rowspan="2">\n' + | ||||
| 	'			<input type="checkbox" id="multitask" />\n' + | ||||
| 	'			<label for="multitask" tt="continue hashing other files while uploading">🏃</label>\n' + | ||||
| 	'		</td>\n' + | ||||
| 	'		<td rowspan="2">\n' + | ||||
| 	'		<td class="c" rowspan="2">\n' + | ||||
| 	'			<input type="checkbox" id="ask_up" />\n' + | ||||
| 	'			<label for="ask_up" tt="ask for confirmation before upload starts">💭</label>\n' + | ||||
| 	'		</td>\n' + | ||||
| 	(have_up2k_idx ? ( | ||||
| 		'		<td data-perm="read" rowspan="2">\n' + | ||||
| 		'		<td class="c" data-perm="read" rowspan="2">\n' + | ||||
| 		'			<input type="checkbox" id="fsearch" />\n' + | ||||
| 		'			<label for="fsearch" tt="don\'t actually upload, instead check if the files already $N exist on the server (will scan all folders you can read)">🔎</label>\n' + | ||||
| 		'		</td>\n' | ||||
| 	) : '') + | ||||
| 	'		<td data-perm="read" rowspan="2" id="u2btn_cw"></td>\n' + | ||||
| 	'		<td data-perm="read" rowspan="2" id="u2c3w"></td>\n' + | ||||
| 	'	</tr>\n' + | ||||
| 	'	<tr>\n' + | ||||
| 	'		<td>\n' + | ||||
| 	'			<a href="#" id="nthread_sub">–</a><input\n' + | ||||
| 	'		<td class="c">\n' + | ||||
| 	'			<a href="#" class="b" id="nthread_sub">–</a><input\n' + | ||||
| 	'				class="txtbox" id="nthread" value="2" tt="pause uploads by setting it to 0"/><a\n' + | ||||
| 	'				href="#" id="nthread_add">+</a><br /> \n' + | ||||
| 	'				href="#" class="b" id="nthread_add">+</a><br /> \n' + | ||||
| 	'		</td>\n' + | ||||
| 	'	</tr>\n' + | ||||
| 	'</table>\n' + | ||||
| @@ -98,6 +99,8 @@ ebi('op_up2k').innerHTML = ( | ||||
| 	'	</div>\n' + | ||||
| 	'</div>\n' + | ||||
|  | ||||
| 	'<div id="u2c3t">\n' + | ||||
|  | ||||
| 	'<div id="u2etaw"><div id="u2etas"><div class="o">\n' + | ||||
| 	'	hash: <span id="u2etah" tt="average <em>hashing</em> speed, and estimated time until finish">(no uploads are queued yet)</span><br />\n' + | ||||
| 	'	send: <span id="u2etau" tt="average <em>upload</em> speed and estimated time until finish">(no uploads are queued yet)</span><br />\n' + | ||||
| @@ -112,6 +115,8 @@ ebi('op_up2k').innerHTML = ( | ||||
| 	'	href="#" act="q" tt="idle, pending">que <span>0</span></a>\n' + | ||||
| 	'</div>\n' + | ||||
|  | ||||
| 	'</div>\n' + | ||||
|  | ||||
| 	'<table id="u2tab">\n' + | ||||
| 	'	<thead>\n' + | ||||
| 	'		<tr>\n' + | ||||
| @@ -168,6 +173,15 @@ ebi('op_cfg').innerHTML = ( | ||||
| 	'		</td>\n' + | ||||
| 	'	</div>\n' + | ||||
| 	'</div>\n' + | ||||
| 	'<div>\n' + | ||||
| 	'	<h3>favicon <span id="ico1">🎉</span></h3>\n' + | ||||
| 	'	<div>\n' + | ||||
| 	'		<input type="text" id="icot" style="width:1.3em" value="" tt="favicon text (blank and refresh to disable)" />' + | ||||
| 	'		<input type="text" id="icof" style="width:2em" value="" tt="foreground color" />' + | ||||
| 	'		<input type="text" id="icob" style="width:2em" value="" tt="background color" />' + | ||||
| 	'		</td>\n' + | ||||
| 	'	</div>\n' + | ||||
| 	'</div>\n' + | ||||
| 	'<div><h3>key notation</h3><div id="key_notation"></div></div>\n' + | ||||
| 	'<div class="fill"><h3>hidden columns</h3><div id="hcols"></div></div>' | ||||
| ); | ||||
| @@ -254,19 +268,42 @@ function goto(dest) { | ||||
| } | ||||
|  | ||||
|  | ||||
| var have_webp = null; | ||||
| var have_webp = sread('have_webp'); | ||||
| (function () { | ||||
| 	if (have_webp !== null) | ||||
| 		return; | ||||
|  | ||||
| 	var img = new Image(); | ||||
| 	img.onload = function () { | ||||
| 		have_webp = img.width > 0 && img.height > 0; | ||||
| 		swrite('have_webp', 'ya'); | ||||
| 	}; | ||||
| 	img.onerror = function () { | ||||
| 		have_webp = false; | ||||
| 		swrite('have_webp', ''); | ||||
| 	}; | ||||
| 	img.src = "data:image/webp;base64,UklGRhoAAABXRUJQVlA4TA0AAAAvAAAAEAcQERGIiP4HAA=="; | ||||
| })(); | ||||
|  | ||||
|  | ||||
| function set_files_html(html) { | ||||
| 	var files = ebi('files'); | ||||
| 	try { | ||||
| 		files.innerHTML = html; | ||||
| 		return files; | ||||
| 	} | ||||
| 	catch (e) { | ||||
| 		var par = files.parentNode; | ||||
| 		par.removeChild(files); | ||||
| 		files = mknod('div'); | ||||
| 		files.innerHTML = '<table id="files">' + html + '</table>'; | ||||
| 		par.insertBefore(files.childNodes[0], ebi('epi')); | ||||
| 		files = ebi('files'); | ||||
| 		return files; | ||||
| 	} | ||||
| } | ||||
|  | ||||
|  | ||||
| var mpl = (function () { | ||||
| 	var have_mctl = 'mediaSession' in navigator && window.MediaMetadata; | ||||
|  | ||||
| @@ -584,6 +621,7 @@ var widget = (function () { | ||||
| 		if (r.is_open) | ||||
| 			return false; | ||||
|  | ||||
| 		clmod(document.documentElement, 'np_open', 1); | ||||
| 		widget.className = 'open'; | ||||
| 		r.is_open = true; | ||||
| 		return true; | ||||
| @@ -592,6 +630,7 @@ var widget = (function () { | ||||
| 		if (!r.is_open) | ||||
| 			return false; | ||||
|  | ||||
| 		clmod(document.documentElement, 'np_open'); | ||||
| 		widget.className = ''; | ||||
| 		r.is_open = false; | ||||
| 		return true; | ||||
| @@ -1032,8 +1071,8 @@ var need_ogv = true; | ||||
| try { | ||||
| 	need_ogv = new Audio().canPlayType('audio/ogg; codecs=opus') !== 'probably'; | ||||
|  | ||||
| 	if (/ Edge\//.exec(navigator.userAgent + '')) | ||||
| 		need_ogv = true; | ||||
| 	if (document.documentMode) | ||||
| 		need_ogv = false;  // ie8-11 | ||||
| } | ||||
| catch (ex) { } | ||||
|  | ||||
| @@ -1430,12 +1469,7 @@ function play(tid, is_ev, seek, call_depth) { | ||||
| 		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; | ||||
| 			} | ||||
| 			sethash(oid); | ||||
| 			o.setAttribute('id', oid); | ||||
| 		} | ||||
|  | ||||
| @@ -1483,44 +1517,14 @@ function evau_error(e) { | ||||
| } | ||||
|  | ||||
|  | ||||
| // show a fullscreen message | ||||
| function show_modal(html) { | ||||
| 	var body = document.body || document.getElementsByTagName('body')[0], | ||||
| 		div = mknod('div'); | ||||
|  | ||||
| 	div.setAttribute('id', 'blocked'); | ||||
| 	div.innerHTML = html; | ||||
| 	unblocked(); | ||||
| 	body.appendChild(div); | ||||
| } | ||||
|  | ||||
|  | ||||
| // hide fullscreen message | ||||
| function unblocked(e) { | ||||
| 	ev(e); | ||||
| 	var dom = ebi('blocked'); | ||||
| 	if (dom) | ||||
| 		dom.parentNode.removeChild(dom); | ||||
| } | ||||
|  | ||||
|  | ||||
| // show ui to manually start playback of a linked song | ||||
| 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>'); | ||||
|  | ||||
| 	var go = ebi('blk_go'), | ||||
| 		na = ebi('blk_na'), | ||||
| 		tid = mp.au.tid, | ||||
| 	var tid = mp.au.tid, | ||||
| 		fn = mp.tracks[tid].split(/\//).pop(); | ||||
|  | ||||
| 	fn = uricom_dec(fn.replace(/\+/g, ' '))[0]; | ||||
|  | ||||
| 	go.textContent = 'Play "' + fn + '"'; | ||||
| 	go.onclick = function (e) { | ||||
| 		unblocked(e); | ||||
| 		toast.hide(); | ||||
| 	modal.confirm('<h6>play this audio file?</h6>\n«' + esc(fn) + '»', function () { | ||||
| 		if (mp.au !== mp.au_ogvjs) | ||||
| 			// chrome 91 may permanently taint on a failed play() | ||||
| 			// depending on win10 settings or something? idk | ||||
| @@ -1533,14 +1537,16 @@ function autoplay_blocked(seek) { | ||||
|  | ||||
| 		play(tid, true, seek); | ||||
| 		mp.fade_in(); | ||||
| 	}; | ||||
| 	na.onclick = unblocked; | ||||
| 	}, null); | ||||
| } | ||||
|  | ||||
|  | ||||
| function play_linked() { | ||||
| function eval_hash() { | ||||
| 	var v = location.hash; | ||||
| 	if (v && v.indexOf('#af-') === 0) { | ||||
| 	if (!v) | ||||
| 		return; | ||||
|  | ||||
| 	if (v.indexOf('#af-') === 0) { | ||||
| 		var id = v.slice(2).split('&'); | ||||
| 		if (id[0].length != 10) | ||||
| 			return; | ||||
| @@ -1554,6 +1560,13 @@ function play_linked() { | ||||
|  | ||||
| 		return play(id[0], false, parseInt(m[1] || 0) * 60 + parseInt(m[2] || 0)); | ||||
| 	} | ||||
|  | ||||
| 	if (v.indexOf('#q=') === 0) { | ||||
| 		goto('search'); | ||||
| 		var i = ebi('q_raw'); | ||||
| 		i.value = uricom_dec(v.slice(3))[0]; | ||||
| 		return i.oninput(); | ||||
| 	} | ||||
| }; | ||||
|  | ||||
|  | ||||
| @@ -2304,15 +2317,6 @@ var thegrid = (function () { | ||||
| 	for (var a = 0; a < links.length; a++) | ||||
| 		links[a].onclick = btnclick; | ||||
|  | ||||
| 	bcfg_bind(r, 'thumbs', 'thumbs', true, r.setdirty); | ||||
| 	bcfg_bind(r, 'sel', 'gridsel', false, r.loadsel); | ||||
| 	bcfg_bind(r, 'en', 'griden', false, function (v) { | ||||
| 		v ? loadgrid() : ungrid(); | ||||
| 		pbar.onresize(); | ||||
| 		vbar.onresize(); | ||||
| 	}); | ||||
| 	ebi('wtgrid').onclick = ebi('griden').onclick; | ||||
|  | ||||
| 	r.setvis = function (vis) { | ||||
| 		(r.en ? gfiles : lfiles).style.display = vis ? '' : 'none'; | ||||
| 	}; | ||||
| @@ -2365,21 +2369,12 @@ var thegrid = (function () { | ||||
| 			td = oth.closest('td').nextSibling, | ||||
| 			tr = td.parentNode; | ||||
|  | ||||
| 		if (href.endsWith('/')) { | ||||
| 			var ta = QSA('#treeul a.hl+ul>li>a+a'), | ||||
| 				txt = oth.textContent.slice(0, -1); | ||||
|  | ||||
| 			for (var a = 0, aa = ta.length; a < aa; a++) { | ||||
| 				if (ta[a].textContent == txt) { | ||||
| 					in_tree = ta[a]; | ||||
| 					break; | ||||
| 				} | ||||
| 			} | ||||
| 		} | ||||
| 		if (href.endsWith('/')) | ||||
| 			in_tree = treectl.find(oth.textContent.slice(0, -1)); | ||||
|  | ||||
| 		if (r.sel) { | ||||
| 			td.click(); | ||||
| 			this.setAttribute('class', tr.getAttribute('class')); | ||||
| 			clmod(this, 'sel', clgot(tr, 'sel')); | ||||
| 		} | ||||
| 		else if (widget.is_open && aplay) | ||||
| 			aplay.click(); | ||||
| @@ -2502,9 +2497,11 @@ var thegrid = (function () { | ||||
| 				} | ||||
| 				ihref = '/.cpr/ico/' + ihref.slice(0, -1); | ||||
| 			} | ||||
| 			ihref += (ihref.indexOf('?') > 0 ? '&' : '?') + 'cache=i'; | ||||
|  | ||||
| 			html.push('<a href="' + ohref + '" ref="' + ref + | ||||
| 				'"' + ac + ' ttt="' + esc(name) + '"><img src="' + | ||||
| 				'"' + ac + ' ttt="' + esc(name) + '"><img style="height:' + | ||||
| 				(r.sz / 1.25) + 'em" onload="th_onload(this)" src="' + | ||||
| 				ihref + '" /><span' + ac + '>' + ao.innerHTML + '</span></a>'); | ||||
| 		} | ||||
| 		ebi('ggrid').innerHTML = html.join('\n'); | ||||
| @@ -2542,6 +2539,15 @@ var thegrid = (function () { | ||||
| 		})[0]; | ||||
| 	}; | ||||
|  | ||||
| 	bcfg_bind(r, 'thumbs', 'thumbs', true, r.setdirty); | ||||
| 	bcfg_bind(r, 'sel', 'gridsel', false, r.loadsel); | ||||
| 	bcfg_bind(r, 'en', 'griden', false, function (v) { | ||||
| 		v ? loadgrid() : ungrid(); | ||||
| 		pbar.onresize(); | ||||
| 		vbar.onresize(); | ||||
| 	}); | ||||
| 	ebi('wtgrid').onclick = ebi('griden').onclick; | ||||
|  | ||||
| 	setTimeout(function () { | ||||
| 		import_js('/.cpr/baguettebox.js', r.bagit); | ||||
| 	}, 1); | ||||
| @@ -2554,6 +2560,11 @@ var thegrid = (function () { | ||||
| })(); | ||||
|  | ||||
|  | ||||
| function th_onload(el) { | ||||
| 	el.style.height = ''; | ||||
| } | ||||
|  | ||||
|  | ||||
| function tree_scrollto(e) { | ||||
| 	ev(e); | ||||
| 	var act = QS('#treeul a.hl'), | ||||
| @@ -2747,28 +2758,28 @@ document.onkeydown = function (e) { | ||||
| (function () { | ||||
| 	var sconf = [ | ||||
| 		["size", | ||||
| 			["szl", "sz_min", "minimum MiB", ""], | ||||
| 			["szu", "sz_max", "maximum MiB", ""] | ||||
| 			["szl", "sz_min", "minimum MiB", "16"], | ||||
| 			["szu", "sz_max", "maximum MiB", "16"] | ||||
| 		], | ||||
| 		["date", | ||||
| 			["dtl", "dt_min", "min. iso8601", ""], | ||||
| 			["dtu", "dt_max", "max. iso8601", ""] | ||||
| 			["dtl", "dt_min", "min. iso8601", "16"], | ||||
| 			["dtu", "dt_max", "max. iso8601", "16"] | ||||
| 		], | ||||
| 		["path", | ||||
| 			["path", "path", "path contains   (space-separated)", "46"] | ||||
| 			["path", "path", "path contains   (space-separated)", "34"] | ||||
| 		], | ||||
| 		["name", | ||||
| 			["name", "name", "name contains   (negate with -nope)", "46"] | ||||
| 			["name", "name", "name contains   (negate with -nope)", "34"] | ||||
| 		] | ||||
| 	]; | ||||
| 	var oldcfg = []; | ||||
|  | ||||
| 	if (QS('#srch_form.tags')) { | ||||
| 		sconf.push(["tags", | ||||
| 			["tags", "tags", "tags contains   (^=start, end=$)", "46"] | ||||
| 			["tags", "tags", "tags contains   (^=start, end=$)", "34"] | ||||
| 		]); | ||||
| 		sconf.push(["adv.", | ||||
| 			["adv", "adv", "key>=1A  key<=2B  .bpm>165", "46"] | ||||
| 			["adv", "adv", "key>=1A  key<=2B  .bpm>165", "34"] | ||||
| 		]); | ||||
| 	} | ||||
|  | ||||
| @@ -2785,8 +2796,8 @@ document.onkeydown = function (e) { | ||||
| 			html.push( | ||||
| 				'<td colspan="' + csp + '"><input id="' + hn + 'c" type="checkbox">\n' + | ||||
| 				'<label for="' + hn + 'c">' + sconf[a][b][2] + '</label>\n' + | ||||
| 				'<br /><input id="' + hn + 'v" type="text" size="' + sconf[a][b][3] + | ||||
| 				'" name="' + sconf[a][b][1] + '" /></td>'); | ||||
| 				'<br /><input id="' + hn + 'v" type="text" style="width:' + sconf[a][b][3] + | ||||
| 				'em" name="' + sconf[a][b][1] + '" /></td>'); | ||||
| 			if (csp == 2) | ||||
| 				break; | ||||
| 		} | ||||
| @@ -2953,7 +2964,7 @@ document.onkeydown = function (e) { | ||||
|  | ||||
| 		var html = mk_files_header(tagord); | ||||
| 		html.push('<tbody>'); | ||||
| 		html.push('<tr><td>-</td><td colspan="42"><a href="#" id="unsearch">! close search results</a></td></tr>'); | ||||
| 		html.push('<tr><td>-</td><td colspan="42"><a href="#" id="unsearch"><big style="font-weight:bold">[❌] close search results</big></a></td></tr>'); | ||||
| 		for (var a = 0; a < res.hits.length; a++) { | ||||
| 			var r = res.hits[a], | ||||
| 				ts = parseInt(r.ts), | ||||
| @@ -2990,7 +3001,7 @@ document.onkeydown = function (e) { | ||||
| 			orig_url = get_evpath(); | ||||
| 		} | ||||
|  | ||||
| 		ofiles.innerHTML = html.join('\n'); | ||||
| 		ofiles = set_files_html(html.join('\n')); | ||||
| 		ofiles.setAttribute("ts", this.ts); | ||||
| 		ofiles.setAttribute("q_raw", this.q_raw); | ||||
| 		set_vq(); | ||||
| @@ -2998,15 +3009,17 @@ document.onkeydown = function (e) { | ||||
| 		reload_browser(); | ||||
| 		filecols.set_style(['File Name']); | ||||
|  | ||||
| 		sethash('q=' + uricom_enc(this.q_raw)); | ||||
| 		ebi('unsearch').onclick = unsearch; | ||||
| 	} | ||||
|  | ||||
| 	function unsearch(e) { | ||||
| 		ev(e); | ||||
| 		treectl.show(); | ||||
| 		ebi('files').innerHTML = orig_html; | ||||
| 		set_files_html(orig_html); | ||||
| 		ebi('files').removeAttribute('q_raw'); | ||||
| 		orig_html = null; | ||||
| 		sethash(''); | ||||
| 		reload_browser(); | ||||
| 	} | ||||
| })(); | ||||
| @@ -3066,14 +3079,14 @@ var treectl = (function () { | ||||
| 		swrite('entreed', 'na'); | ||||
|  | ||||
| 		treectl.hide(); | ||||
| 		ebi('path').style.display = 'inline-block'; | ||||
| 		ebi('path').style.display = ''; | ||||
| 	} | ||||
|  | ||||
| 	treectl.hide = function () { | ||||
| 		treectl.hidden = true; | ||||
| 		ebi('path').style.display = 'none'; | ||||
| 		ebi('tree').style.display = 'none'; | ||||
| 		ebi('wrap').style.marginLeft = '0'; | ||||
| 		ebi('wrap').style.marginLeft = ''; | ||||
| 		window.removeEventListener('resize', onresize); | ||||
| 		window.removeEventListener('scroll', onscroll); | ||||
| 	} | ||||
| @@ -3124,7 +3137,7 @@ var treectl = (function () { | ||||
| 				treeh = winh - atop; | ||||
|  | ||||
| 			tree.style.top = top + 'px'; | ||||
| 			tree.style.height = treeh < 10 ? '' : treeh + 'px'; | ||||
| 			tree.style.height = treeh < 10 ? '' : Math.floor(treeh - 2) + 'px'; | ||||
| 		} | ||||
| 	} | ||||
| 	timer.add(onscroll2, true); | ||||
| @@ -3142,20 +3155,30 @@ var treectl = (function () { | ||||
| 			if (!QS(q)) | ||||
| 				break; | ||||
| 		} | ||||
| 		var w = (treesz + Math.max(0, nq)) + 'em'; | ||||
| 		var iw = (treesz + Math.max(0, nq)), | ||||
| 			w = iw + 'em', | ||||
| 			w2 = (iw + 2) + 'em'; | ||||
|  | ||||
| 		try { | ||||
| 			document.documentElement.style.setProperty('--nav-sz', w); | ||||
| 		} | ||||
| 		catch (ex) { } | ||||
| 		ebi('tree').style.width = w; | ||||
| 		ebi('wrap').style.marginLeft = w; | ||||
| 		ebi('wrap').style.marginLeft = w2; | ||||
| 		onscroll(); | ||||
| 	} | ||||
|  | ||||
| 	treectl.find = function (txt) { | ||||
| 		var ta = QSA('#treeul a.hl+ul>li>a+a'); | ||||
| 		for (var a = 0, aa = ta.length; a < aa; a++) | ||||
| 			if (ta[a].textContent == txt) | ||||
| 				return ta[a]; | ||||
| 	}; | ||||
|  | ||||
| 	treectl.goto = function (url, push) { | ||||
| 		get_tree("", url, true); | ||||
| 		reqls(url, push); | ||||
| 	} | ||||
| 		reqls(url, push, true); | ||||
| 	}; | ||||
|  | ||||
| 	function get_tree(top, dst, rst) { | ||||
| 		var xhr = new XMLHttpRequest(); | ||||
| @@ -3282,7 +3305,7 @@ var treectl = (function () { | ||||
| 		reqls(this.getAttribute('href'), true); | ||||
| 	} | ||||
|  | ||||
| 	function reqls(url, hpush) { | ||||
| 	function reqls(url, hpush, no_tree) { | ||||
| 		var xhr = new XMLHttpRequest(); | ||||
| 		xhr.top = url; | ||||
| 		xhr.hpush = hpush; | ||||
| @@ -3290,7 +3313,7 @@ var treectl = (function () { | ||||
| 		xhr.open('GET', xhr.top + '?ls' + (treectl.dots ? '&dots' : ''), true); | ||||
| 		xhr.onreadystatechange = recvls; | ||||
| 		xhr.send(); | ||||
| 		if (hpush) | ||||
| 		if (hpush && !no_tree) | ||||
| 			get_tree('.', xhr.top); | ||||
|  | ||||
| 		enspin(thegrid.en ? '#gfiles' : '#files'); | ||||
| @@ -3367,13 +3390,7 @@ var treectl = (function () { | ||||
| 		} | ||||
| 		html.push('</tbody>'); | ||||
| 		html = html.join('\n'); | ||||
| 		try { | ||||
| 			ebi('files').innerHTML = html; | ||||
| 		} | ||||
| 		catch (ex) { //ie9 | ||||
| 			window.location.href = this.top; | ||||
| 			return; | ||||
| 		} | ||||
| 		set_files_html(html); | ||||
|  | ||||
| 		if (this.hpush) | ||||
| 			hist_push(this.top); | ||||
| @@ -3463,10 +3480,7 @@ var treectl = (function () { | ||||
| 		treectl.goto(url.pathname); | ||||
| 	}; | ||||
|  | ||||
| 	if (window.history && history.pushState) { | ||||
| 		hist_replace(get_evpath() + window.location.hash); | ||||
| 	} | ||||
|  | ||||
| 	hist_replace(get_evpath() + window.location.hash); | ||||
| 	treectl.onscroll = onscroll; | ||||
| 	return treectl; | ||||
| })(); | ||||
| @@ -4414,6 +4428,20 @@ function goto_unpost(e) { | ||||
| } | ||||
|  | ||||
|  | ||||
| ebi('files').onclick = function (e) { | ||||
| 	var tgt = e.target.closest('a[id]'); | ||||
| 	if (!tgt || tgt.getAttribute('id').indexOf('f-') !== 0 || !tgt.textContent.endsWith('/')) | ||||
| 		return; | ||||
|  | ||||
| 	var el = treectl.find(tgt.textContent.slice(0, -1)); | ||||
| 	if (!el) | ||||
| 		return; | ||||
|  | ||||
| 	ev(e); | ||||
| 	el.click(); | ||||
| } | ||||
|  | ||||
|  | ||||
| function reload_mp() { | ||||
| 	if (mp && mp.au) { | ||||
| 		mp.au.pause(); | ||||
| @@ -4469,4 +4497,4 @@ function reload_browser(not_mp) { | ||||
| } | ||||
| reload_browser(true); | ||||
| mukey.render(); | ||||
| play_linked(); | ||||
| setTimeout(eval_hash, 1); | ||||
|   | ||||
| @@ -135,13 +135,13 @@ var md_opt = { | ||||
|  | ||||
| (function () { | ||||
|     var l = localStorage, | ||||
| 		drk = l.getItem('lightmode') != 1, | ||||
| 		drk = l.lightmode != 1, | ||||
| 		btn = document.getElementById("lightswitch"), | ||||
| 		f = function (e) { | ||||
| if (e) { e.preventDefault(); drk = !drk; } | ||||
| document.documentElement.setAttribute("class", drk? "dark":"light"); | ||||
| btn.innerHTML = "go " + (drk ? "light":"dark"); | ||||
| l.setItem('lightmode', drk? 0:1); | ||||
| l.lightmode = drk? 0:1; | ||||
|     	}; | ||||
| 	 | ||||
| 	btn.onclick = f; | ||||
|   | ||||
| @@ -33,11 +33,11 @@ var md_opt = { | ||||
|  | ||||
| var lightswitch = (function () { | ||||
| 	var l = localStorage, | ||||
| 		drk = l.getItem('lightmode') != 1, | ||||
| 		drk = l.lightmode != 1, | ||||
| 		f = function (e) { | ||||
| if (e) drk = !drk; | ||||
| document.documentElement.setAttribute("class", drk? "dark":"light"); | ||||
| l.setItem('lightmode', drk? 0:1); | ||||
| l.lightmode = drk? 0:1; | ||||
| 		}; | ||||
| 	f(); | ||||
| 	return f; | ||||
|   | ||||
| @@ -80,7 +80,7 @@ | ||||
| 	<a href="#" id="repl">π</a> | ||||
|     <script> | ||||
|  | ||||
| if (localStorage.getItem('lightmode') != 1) | ||||
| if (localStorage.lightmode != 1) | ||||
|     document.documentElement.setAttribute("class", "dark"); | ||||
|  | ||||
| </script> | ||||
|   | ||||
| @@ -11,9 +11,9 @@ html { | ||||
| 	max-width: 34em; | ||||
| 	max-width: min(34em, 90%); | ||||
| 	max-width: min(34em, calc(100% - 7em)); | ||||
| 	background: #222; | ||||
| 	background: #333; | ||||
| 	border: 0 solid #777; | ||||
| 	box-shadow: 0 .2em .5em #222; | ||||
| 	box-shadow: 0 .2em .5em #111; | ||||
| 	border-radius: .4em; | ||||
| 	z-index: 9001; | ||||
| } | ||||
|   | ||||
| @@ -246,7 +246,7 @@ function U2pvis(act, btns) { | ||||
|  | ||||
|         obj.innerHTML = fo.hp; | ||||
|         obj.style.color = '#fff'; | ||||
|         obj.style.background = 'linear-gradient(90deg, #025, #06a ' + o1 + '%, #09d ' + o2 + '%, #333 ' + o3 + '%, #333 99%, #777)'; | ||||
|         obj.style.background = 'linear-gradient(90deg, #025, #06a ' + o1 + '%, #09d ' + o2 + '%, #222 ' + o3 + '%, #222 99%, #555)'; | ||||
|     }; | ||||
|  | ||||
|     r.prog = function (fobj, nchunk, cbd) { | ||||
| @@ -303,7 +303,7 @@ function U2pvis(act, btns) { | ||||
|  | ||||
|         obj.innerHTML = fo.hp; | ||||
|         obj.style.color = '#fff'; | ||||
|         obj.style.background = 'linear-gradient(90deg, #050, #270 ' + o1 + '%, #4b0 ' + o2 + '%, #333 ' + o3 + '%, #333 99%, #777)'; | ||||
|         obj.style.background = 'linear-gradient(90deg, #050, #270 ' + o1 + '%, #4b0 ' + o2 + '%, #222 ' + o3 + '%, #222 99%, #555)'; | ||||
|     }; | ||||
|  | ||||
|     r.move = function (nfile, newcat) { | ||||
| @@ -1162,6 +1162,11 @@ function up2k_init(subtle) { | ||||
|                         } | ||||
|                     } | ||||
|                     is_busy = st.todo.handshake.length; | ||||
|                     try { | ||||
|                         if (!is_busy && !uc.fsearch && !msel.getsel().length && (!mp.au || mp.au.paused)) | ||||
|                             treectl.goto(get_evpath()); | ||||
|                     } | ||||
|                     catch (ex) { } | ||||
|                 } | ||||
|  | ||||
|                 if (was_busy != is_busy) { | ||||
| @@ -1822,16 +1827,28 @@ function up2k_init(subtle) { | ||||
|             wpx = window.innerWidth, | ||||
|             fpx = parseInt(getComputedStyle(bar)['font-size']), | ||||
|             wem = wpx * 1.0 / fpx, | ||||
|             wide = wem > 54, | ||||
|             parent = ebi(wide && has(perms, 'write') ? 'u2btn_cw' : 'u2btn_ct'), | ||||
|             wide = wem > 54 ? 'w' : '', | ||||
|             write = has(perms, 'write'), | ||||
|             parent = ebi(wide && write ? 'u2btn_cw' : 'u2btn_ct'), | ||||
|             btn = ebi('u2btn'); | ||||
|  | ||||
|         //console.log([wpx, fpx, wem]); | ||||
|         if (btn.parentNode !== parent) { | ||||
|             parent.appendChild(btn); | ||||
|             ebi('u2conf').setAttribute('class', wide ? 'has_btn' : ''); | ||||
|             ebi('u2cards').setAttribute('class', wide ? 'w' : ''); | ||||
|             ebi('u2etaw').setAttribute('class', wide ? 'w' : ''); | ||||
|             ebi('u2conf').setAttribute('class', wide); | ||||
|             ebi('u2cards').setAttribute('class', wide); | ||||
|             ebi('u2etaw').setAttribute('class', wide); | ||||
|         } | ||||
|  | ||||
|         wide = wem > 78 ? 'ww' : wide; | ||||
|         parent = ebi(wide == 'ww' && write ? 'u2c3w' : 'u2c3t'); | ||||
|         var its = [ebi('u2etaw'), ebi('u2cards')]; | ||||
|         if (its[0].parentNode !== parent) { | ||||
|             ebi('u2conf').setAttribute('class', wide); | ||||
|             for (var a = 0; a < 2; a++) { | ||||
|                 parent.appendChild(its[a]); | ||||
|                 its[a].setAttribute('class', wide); | ||||
|             } | ||||
|         } | ||||
|     } | ||||
|     window.addEventListener('resize', onresize); | ||||
| @@ -1844,7 +1861,7 @@ function up2k_init(subtle) { | ||||
|         setTimeout(onresize, 500); | ||||
|     } | ||||
|  | ||||
|     var o = QSA('#u2conf *[tt]'); | ||||
|     var o = QSA('#u2conf .c *[tt]'); | ||||
|     for (var a = o.length - 1; a >= 0; a--) { | ||||
|         o[a].parentNode.getElementsByTagName('input')[0].setAttribute('tt', o[a].getAttribute('tt')); | ||||
|     } | ||||
| @@ -2012,6 +2029,15 @@ function warn_uploader_busy(e) { | ||||
|  | ||||
|  | ||||
| tt.init(); | ||||
| favico.init(); | ||||
| ebi('ico1').onclick = function () { | ||||
|     var a = favico.txt == this.textContent; | ||||
|     swrite('icot', a ? 'c' : this.textContent); | ||||
|     swrite('icof', a ? null : '000'); | ||||
|     swrite('icob', a ? null : ''); | ||||
|     favico.init(); | ||||
| }; | ||||
|  | ||||
|  | ||||
| if (QS('#op_up2k.act')) | ||||
|     goto_up2k(); | ||||
|   | ||||
| @@ -146,7 +146,7 @@ function vis_exh(msg, url, lineNo, columnNo, error) { | ||||
|  | ||||
|             var s = mknod('style'); | ||||
|             s.innerHTML = ( | ||||
|                 '#exbox{background:#333;color:#ddd;font-family:sans-serif;font-size:0.8em;padding:0 1em 1em 1em;z-index:80386;position:fixed;top:0;left:0;right:0;bottom:0;width:100%;height:100%;overflow:auto;width:calc(100% - 2em)} ' + | ||||
|                 '#exbox{background:#222;color:#ddd;font-family:sans-serif;font-size:0.8em;padding:0 1em 1em 1em;z-index:80386;position:fixed;top:0;left:0;right:0;bottom:0;width:100%;height:100%;overflow:auto;width:calc(100% - 2em)} ' + | ||||
|                 '#exbox,#exbox *{line-height:1.5em;overflow-wrap:break-word} ' + | ||||
|                 '#exbox code{color:#bf7;background:#222;padding:.1em;margin:.2em;font-size:1.1em;font-family:monospace,monospace} ' + | ||||
|                 '#exbox a{text-decoration:underline;color:#fc0} ' + | ||||
| @@ -583,14 +583,22 @@ function jcp(obj) { | ||||
|  | ||||
|  | ||||
| function sread(key) { | ||||
|     return localStorage.getItem(key); | ||||
|     try { | ||||
|         return localStorage.getItem(key); | ||||
|     } | ||||
|     catch (e) { | ||||
|         return null; | ||||
|     } | ||||
| } | ||||
|  | ||||
| function swrite(key, val) { | ||||
|     if (val === undefined || val === null) | ||||
|         localStorage.removeItem(key); | ||||
|     else | ||||
|         localStorage.setItem(key, val); | ||||
|     try { | ||||
|         if (val === undefined || val === null) | ||||
|             localStorage.removeItem(key); | ||||
|         else | ||||
|             localStorage.setItem(key, val); | ||||
|     } | ||||
|     catch (e) { } | ||||
| } | ||||
|  | ||||
| function jread(key, fb) { | ||||
| @@ -613,9 +621,9 @@ function icfg_get(name, defval) { | ||||
| } | ||||
|  | ||||
| function fcfg_get(name, defval) { | ||||
|     var o = ebi(name); | ||||
|     var o = ebi(name), | ||||
|         val = parseFloat(sread(name)); | ||||
|  | ||||
|     var val = parseFloat(sread(name)); | ||||
|     if (isNaN(val)) | ||||
|         return parseFloat(o ? o.value : defval); | ||||
|  | ||||
| @@ -625,6 +633,19 @@ function fcfg_get(name, defval) { | ||||
|     return val; | ||||
| } | ||||
|  | ||||
| function scfg_get(name, defval) { | ||||
|     var o = ebi(name), | ||||
|         val = sread(name); | ||||
|  | ||||
|     if (val === null) | ||||
|         val = defval; | ||||
|  | ||||
|     if (o) | ||||
|         o.value = val; | ||||
|  | ||||
|     return val; | ||||
| } | ||||
|  | ||||
| function bcfg_get(name, defval) { | ||||
|     var o = ebi(name); | ||||
|     if (!o) | ||||
| @@ -676,15 +697,41 @@ function bcfg_bind(obj, oname, cname, defval, cb, un_ev) { | ||||
|     return v; | ||||
| } | ||||
|  | ||||
| function scfg_bind(obj, oname, cname, defval, cb) { | ||||
|     var v = scfg_get(cname, defval), | ||||
|         el = ebi(cname); | ||||
|  | ||||
|     obj[oname] = v; | ||||
|     if (el) | ||||
|         el.oninput = function (e) { | ||||
|             swrite(cname, obj[oname] = this.value); | ||||
|             if (cb) | ||||
|                 cb(obj[oname]); | ||||
|         }; | ||||
|  | ||||
|     return v; | ||||
| } | ||||
|  | ||||
|  | ||||
| function hist_push(url) { | ||||
|     console.log("h-push " + url); | ||||
|     history.pushState(url, url, url); | ||||
|     if (window.history && history.pushState) | ||||
|         history.pushState(url, url, url); | ||||
| } | ||||
|  | ||||
| function hist_replace(url) { | ||||
|     console.log("h-repl " + url); | ||||
|     history.replaceState(url, url, url); | ||||
|     if (window.history && history.replaceState) | ||||
|         history.replaceState(url, url, url); | ||||
| } | ||||
|  | ||||
| function sethash(hv) { | ||||
|     if (window.history && history.replaceState) { | ||||
|         hist_replace(document.location.pathname + '#' + hv); | ||||
|     } | ||||
|     else { | ||||
|         document.location.hash = hv; | ||||
|     } | ||||
| } | ||||
|  | ||||
|  | ||||
| @@ -841,16 +888,7 @@ var tt = (function () { | ||||
|     } | ||||
|  | ||||
|     r.init = function () { | ||||
|         var ttb = ebi('tooltips'); | ||||
|         if (ttb) { | ||||
|             ttb.onclick = function (e) { | ||||
|                 ev(e); | ||||
|                 r.en = !r.en; | ||||
|                 bcfg_set('tooltips', r.en); | ||||
|                 r.init(); | ||||
|             }; | ||||
|             r.en = bcfg_get('tooltips', true) | ||||
|         } | ||||
|         bcfg_bind(r, 'en', 'tooltips', r.en, r.init); | ||||
|         r.att(document); | ||||
|     }; | ||||
|  | ||||
| @@ -1173,3 +1211,54 @@ function repl(e) { | ||||
| } | ||||
| if (ebi('repl')) | ||||
|     ebi('repl').onclick = repl; | ||||
|  | ||||
|  | ||||
| var favico = (function () { | ||||
|     var r = {}; | ||||
|     r.en = true; | ||||
|  | ||||
|     function gx(txt) { | ||||
|         return ( | ||||
|             '<?xml version="1.0" encoding="UTF-8"?>\n' + | ||||
|             '<svg version="1.1" viewBox="0 0 64 64" xmlns="http://www.w3.org/2000/svg"><g>\n' + | ||||
|             (r.bg ? '<rect width="100%" height="100%" rx="16" fill="#' + r.bg + '" />\n' : '') + | ||||
|             '<text x="50%" y="55%" dominant-baseline="middle" text-anchor="middle"' + | ||||
|             ' font-family="sans-serif" font-weight="bold" font-size="64px"' + | ||||
|             ' fill="#' + r.fg + '">' + txt + '</text></g></svg>' | ||||
|         ); | ||||
|     } | ||||
|  | ||||
|     r.upd = function () { | ||||
|         var i = QS('link[rel="icon"]'), b64; | ||||
|         if (!r.txt) | ||||
|             return; | ||||
|  | ||||
|         try { | ||||
|             b64 = btoa(gx(r.txt)); | ||||
|         } | ||||
|         catch (ex) { | ||||
|             b64 = encodeURIComponent(r.txt).replace(/%([0-9A-F]{2})/g, | ||||
|                 function x(m, v) { return String.fromCharCode('0x' + v); }); | ||||
|  | ||||
|             b64 = btoa(gx(unescape(encodeURIComponent(r.txt)))); | ||||
|         } | ||||
|  | ||||
|         if (!i) { | ||||
|             i = mknod('link'); | ||||
|             i.rel = 'icon'; | ||||
|             document.head.appendChild(i); | ||||
|         } | ||||
|         i.href = 'data:image/svg+xml;base64,' + b64; | ||||
|     }; | ||||
|  | ||||
|     r.init = function () { | ||||
|         clearTimeout(r.to); | ||||
|         scfg_bind(r, 'txt', 'icot', '', r.upd); | ||||
|         scfg_bind(r, 'fg', 'icof', 'fc5', r.upd); | ||||
|         scfg_bind(r, 'bg', 'icob', '222', r.upd); | ||||
|         r.upd(); | ||||
|     }; | ||||
|  | ||||
|     r.to = setTimeout(r.init, 100); | ||||
|     return r; | ||||
| })(); | ||||
|   | ||||
| @@ -1,11 +1,11 @@ | ||||
| html { | ||||
|     background: #333 url('/wp/wallhaven-mdjrqy.jpg') center / cover no-repeat fixed; | ||||
|     background: #222 url('/wp/wallhaven-mdjrqy.jpg') center / cover no-repeat fixed; | ||||
| } | ||||
| #files th { | ||||
|     background: rgba(32, 32, 32, 0.9) !important; | ||||
| } | ||||
| #ops, | ||||
| #treeul, | ||||
| #tree, | ||||
| #files td { | ||||
|     background: rgba(32, 32, 32, 0.3) !important; | ||||
| } | ||||
| @@ -19,7 +19,7 @@ html.light #files th { | ||||
| } | ||||
| html.light .logue, | ||||
| html.light #ops, | ||||
| html.light #treeul, | ||||
| html.light #tree, | ||||
| html.light #files td { | ||||
|     background: rgba(248, 248, 248, 0.8) !important; | ||||
| } | ||||
|   | ||||
| @@ -27,7 +27,7 @@ | ||||
|     #u2conf #u2btn, #u2btn {padding:1.5em 0} | ||||
|  | ||||
|     /* adjust the button area a bit */ | ||||
|     #u2conf.has_btn {width: 35em !important; margin: 5em auto} | ||||
|     #u2conf.w, #u2conf.ww {width: 35em !important; margin: 5em auto} | ||||
|  | ||||
|     /* a */ | ||||
|     #op_up2k {min-height: 0} | ||||
|   | ||||
| @@ -238,7 +238,7 @@ rm have | ||||
| 	rm -rf copyparty/web/dd | ||||
| 	f=copyparty/web/browser.css | ||||
| 	gzip -d "$f.gz" || true | ||||
| 	sed -r 's/(cursor: ?)url\([^)]+\), ?(pointer)/\1\2/; /[0-9]+% \{cursor:/d; /animation: ?cursor/d' <$f >t | ||||
| 	sed -r 's/(cursor: ?)url\([^)]+\), ?(pointer)/\1\2/; s/[0-9]+% \{cursor:[^}]+\}//; s/animation: ?cursor[^};]+//' <$f >t | ||||
| 	tmv "$f" | ||||
| } | ||||
|  | ||||
| @@ -271,7 +271,7 @@ find | grep -E '\.css$' | while IFS= read -r f; do | ||||
| 	} | ||||
| 	!/\}$/ {printf "%s",$0;next} | ||||
| 	1 | ||||
| 	' <$f | sed 's/;\}$/}/' >t | ||||
| 	' <$f | sed -r 's/;\}$/}/; /\{\}$/d' >t | ||||
| 	tmv "$f" | ||||
| done | ||||
| unexpand -h 2>/dev/null && | ||||
|   | ||||
| @@ -9,7 +9,7 @@ import subprocess as sp | ||||
| to edit this file, use HxD or "vim -b" | ||||
|   (there is compressed stuff at the end) | ||||
|  | ||||
| run me with any version of python, i will unpack and run copyparty | ||||
| run me with python 2.7 or 3.3+ to unpack and run copyparty | ||||
|  | ||||
| there's zero binaries! just plaintext python scripts all the way down | ||||
|   so you can easily unpack the archive and inspect it for shady stuff | ||||
|   | ||||
| @@ -48,7 +48,9 @@ class Cfg(Namespace): | ||||
|             mte="a", | ||||
|             mth="", | ||||
|             hist=None, | ||||
|             no_hash=False, | ||||
|             no_idx=None, | ||||
|             no_hash=None, | ||||
|             js_browser=None, | ||||
|             css_browser=None, | ||||
|             **{k: False for k in "e2d e2ds e2dsa e2t e2ts e2tsr".split()} | ||||
|         ) | ||||
|   | ||||
| @@ -23,7 +23,9 @@ class Cfg(Namespace): | ||||
|             "mte": "a", | ||||
|             "mth": "", | ||||
|             "hist": None, | ||||
|             "no_hash": False, | ||||
|             "no_idx": None, | ||||
|             "no_hash": None, | ||||
|             "js_browser": None, | ||||
|             "css_browser": None, | ||||
|             "no_voldump": True, | ||||
|             "no_logues": False, | ||||
|   | ||||
		Reference in New Issue
	
	Block a user