mirror of
				https://github.com/9001/copyparty.git
				synced 2025-10-31 03:53:31 +00:00 
			
		
		
		
	Compare commits
	
		
			18 Commits
		
	
	
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
|  | 9cde2352f3 | ||
|  | 482dd7a938 | ||
|  | bddcc69438 | ||
|  | 19d4540630 | ||
|  | 4f5f6c81f5 | ||
|  | 7e4c1238ba | ||
|  | f7196ac773 | ||
|  | 7a7c832000 | ||
|  | 2b4ccdbebb | ||
|  | 0d16b49489 | ||
|  | 768405b691 | ||
|  | da01413b7b | ||
|  | 914e22c53e | ||
|  | 43a23bf733 | ||
|  | 92bb00c6d2 | ||
|  | b0b97a2648 | ||
|  | 2c452fe323 | ||
|  | ad73d0c77d | 
							
								
								
									
										12
									
								
								README.md
									
									
									
									
									
								
							
							
						
						
									
										12
									
								
								README.md
									
									
									
									
									
								
							| @@ -59,7 +59,7 @@ you may also want these, especially on servers: | |||||||
| * server indexing | * server indexing | ||||||
|   * ☑ locate files by contents |   * ☑ locate files by contents | ||||||
|   * ☑ search by name/path/date/size |   * ☑ search by name/path/date/size | ||||||
|   * ✖ search by ID3-tags etc. |   * ☑ search by ID3-tags etc. | ||||||
| * markdown | * markdown | ||||||
|   * ☑ viewer |   * ☑ viewer | ||||||
|   * ☑ editor (sure why not) |   * ☑ editor (sure why not) | ||||||
| @@ -82,6 +82,8 @@ path/name queries are space-separated, AND'ed together, and words are negated wi | |||||||
| * path: `shibayan -bossa` finds all files where one of the folders contain `shibayan` but filters out any results where `bossa` exists somewhere in the path | * path: `shibayan -bossa` finds all files where one of the folders contain `shibayan` but filters out any results where `bossa` exists somewhere in the path | ||||||
| * name: `demetori styx` gives you [good stuff](https://www.youtube.com/watch?v=zGh0g14ZJ8I&list=PL3A147BD151EE5218&index=9) | * name: `demetori styx` gives you [good stuff](https://www.youtube.com/watch?v=zGh0g14ZJ8I&list=PL3A147BD151EE5218&index=9) | ||||||
|  |  | ||||||
|  | add `-e2ts` to also scan/index tags from music files: | ||||||
|  |  | ||||||
|  |  | ||||||
| ## search configuration | ## search configuration | ||||||
|  |  | ||||||
| @@ -144,9 +146,13 @@ copyparty returns a truncated sha512sum of your PUT/POST as base64; you can gene | |||||||
|  |  | ||||||
| # dependencies | # dependencies | ||||||
|  |  | ||||||
| * `jinja2` | * `jinja2` (is built into the SFX) | ||||||
|  |  | ||||||
| optional, will eventually enable thumbnails: | **optional,** enables music tags: | ||||||
|  | * either `mutagen` (fast, pure-python, skips a few tags, makes copyparty GPL? idk) | ||||||
|  | * or `FFprobe` (20x slower, more accurate, possibly dangerous depending on your distro and users) | ||||||
|  |  | ||||||
|  | **optional,** will eventually enable thumbnails: | ||||||
| * `Pillow` (requires py2.7 or py3.5+) | * `Pillow` (requires py2.7 or py3.5+) | ||||||
|  |  | ||||||
|  |  | ||||||
|   | |||||||
| @@ -243,7 +243,8 @@ def main(): | |||||||
|     ap.add_argument("-nw", action="store_true", help="disable writes (benchmark)") |     ap.add_argument("-nw", action="store_true", help="disable writes (benchmark)") | ||||||
|     ap.add_argument("-nih", action="store_true", help="no info hostname") |     ap.add_argument("-nih", action="store_true", help="no info hostname") | ||||||
|     ap.add_argument("-nid", action="store_true", help="no info disk-usage") |     ap.add_argument("-nid", action="store_true", help="no info disk-usage") | ||||||
|     ap.add_argument("--no-sendfile", action="store_true", help="disable sendfile") |     ap.add_argument("--no-sendfile", action="store_true", help="disable sendfile (for debugging)") | ||||||
|  |     ap.add_argument("--no-scandir", action="store_true", help="disable scandir (for debugging)") | ||||||
|     ap.add_argument("--urlform", type=str, default="print,get", help="how to handle url-forms") |     ap.add_argument("--urlform", type=str, default="print,get", help="how to handle url-forms") | ||||||
|     ap.add_argument("--salt", type=str, default="hunter2", help="up2k file-hash salt") |     ap.add_argument("--salt", type=str, default="hunter2", help="up2k file-hash salt") | ||||||
|  |  | ||||||
| @@ -255,6 +256,7 @@ def main(): | |||||||
|     ap2.add_argument("-e2ts", action="store_true", help="enable metadata scanner, sets -e2t") |     ap2.add_argument("-e2ts", action="store_true", help="enable metadata scanner, sets -e2t") | ||||||
|     ap2.add_argument("-e2tsr", action="store_true", help="rescan all metadata, sets -e2ts") |     ap2.add_argument("-e2tsr", action="store_true", help="rescan all metadata, sets -e2ts") | ||||||
|     ap2.add_argument("--no-mutagen", action="store_true", help="use ffprobe for tags instead") |     ap2.add_argument("--no-mutagen", action="store_true", help="use ffprobe for tags instead") | ||||||
|  |     ap2.add_argument("--no-mtag-mt", action="store_true", help="disable tag-read parallelism") | ||||||
|     ap2.add_argument("-mtm", metavar="M=t,t,t", action="append", type=str, help="add/replace metadata mapping") |     ap2.add_argument("-mtm", metavar="M=t,t,t", action="append", type=str, help="add/replace metadata mapping") | ||||||
|     ap2.add_argument("-mte", metavar="M,M,M", type=str, help="tags to index/display (comma-sep.)", |     ap2.add_argument("-mte", metavar="M,M,M", type=str, help="tags to index/display (comma-sep.)", | ||||||
|         default="circle,album,.tn,artist,title,.bpm,key,.dur,.q") |         default="circle,album,.tn,artist,title,.bpm,key,.dur,.q") | ||||||
|   | |||||||
| @@ -1,8 +1,8 @@ | |||||||
| # coding: utf-8 | # coding: utf-8 | ||||||
|  |  | ||||||
| VERSION = (0, 9, 0) | VERSION = (0, 9, 4) | ||||||
| CODENAME = "the strongest music server" | CODENAME = "the strongest music server" | ||||||
| BUILD_DT = (2021, 3, 2) | BUILD_DT = (2021, 3, 5) | ||||||
|  |  | ||||||
| S_VERSION = ".".join(map(str, VERSION)) | S_VERSION = ".".join(map(str, VERSION)) | ||||||
| S_BUILD_DT = "{0:04d}-{1:02d}-{2:02d}".format(*BUILD_DT) | S_BUILD_DT = "{0:04d}-{1:02d}-{2:02d}".format(*BUILD_DT) | ||||||
|   | |||||||
| @@ -6,7 +6,7 @@ import re | |||||||
| import threading | import threading | ||||||
|  |  | ||||||
| from .__init__ import PY2, WINDOWS | from .__init__ import PY2, WINDOWS | ||||||
| from .util import undot, Pebkac, fsdec, fsenc | from .util import undot, Pebkac, fsdec, fsenc, statdir | ||||||
|  |  | ||||||
|  |  | ||||||
| class VFS(object): | class VFS(object): | ||||||
| @@ -102,12 +102,11 @@ class VFS(object): | |||||||
|  |  | ||||||
|         return fsdec(os.path.realpath(fsenc(rp))) |         return fsdec(os.path.realpath(fsenc(rp))) | ||||||
|  |  | ||||||
|     def ls(self, rem, uname): |     def ls(self, rem, uname, scandir, lstat=False): | ||||||
|         """return user-readable [fsdir,real,virt] items at vpath""" |         """return user-readable [fsdir,real,virt] items at vpath""" | ||||||
|         virt_vis = {}  # nodes readable by user |         virt_vis = {}  # nodes readable by user | ||||||
|         abspath = self.canonical(rem) |         abspath = self.canonical(rem) | ||||||
|         items = os.listdir(fsenc(abspath)) |         real = list(statdir(print, scandir, lstat, abspath)) | ||||||
|         real = [fsdec(x) for x in items] |  | ||||||
|         real.sort() |         real.sort() | ||||||
|         if not rem: |         if not rem: | ||||||
|             for name, vn2 in sorted(self.nodes.items()): |             for name, vn2 in sorted(self.nodes.items()): | ||||||
| @@ -115,7 +114,7 @@ class VFS(object): | |||||||
|                     virt_vis[name] = vn2 |                     virt_vis[name] = vn2 | ||||||
|  |  | ||||||
|             # no vfs nodes in the list of real inodes |             # no vfs nodes in the list of real inodes | ||||||
|             real = [x for x in real if x not in self.nodes] |             real = [x for x in real if x[0] not in self.nodes] | ||||||
|  |  | ||||||
|         return [abspath, real, virt_vis] |         return [abspath, real, virt_vis] | ||||||
|  |  | ||||||
| @@ -315,7 +314,7 @@ class AuthSrv(object): | |||||||
|             if (self.args.e2ds and vol.uwrite) or self.args.e2dsa: |             if (self.args.e2ds and vol.uwrite) or self.args.e2dsa: | ||||||
|                 vol.flags["e2ds"] = True |                 vol.flags["e2ds"] = True | ||||||
|  |  | ||||||
|             if self.args.e2d: |             if self.args.e2d or "e2ds" in vol.flags: | ||||||
|                 vol.flags["e2d"] = True |                 vol.flags["e2d"] = True | ||||||
|  |  | ||||||
|             for k in ["e2t", "e2ts", "e2tsr"]: |             for k in ["e2t", "e2ts", "e2tsr"]: | ||||||
|   | |||||||
| @@ -345,6 +345,10 @@ class HttpCli(object): | |||||||
|         with open(path, "wb", 512 * 1024) as f: |         with open(path, "wb", 512 * 1024) as f: | ||||||
|             post_sz, _, sha_b64 = hashcopy(self.conn, reader, f) |             post_sz, _, sha_b64 = hashcopy(self.conn, reader, f) | ||||||
|  |  | ||||||
|  |         self.conn.hsrv.broker.put( | ||||||
|  |             False, "up2k.hash_file", vfs.realpath, vfs.flags, rem, fn | ||||||
|  |         ) | ||||||
|  |  | ||||||
|         return post_sz, sha_b64, remains, path |         return post_sz, sha_b64, remains, path | ||||||
|  |  | ||||||
|     def handle_stash(self): |     def handle_stash(self): | ||||||
| @@ -428,6 +432,7 @@ class HttpCli(object): | |||||||
|         body["ptop"] = vfs.realpath |         body["ptop"] = vfs.realpath | ||||||
|         body["prel"] = rem |         body["prel"] = rem | ||||||
|         body["addr"] = self.ip |         body["addr"] = self.ip | ||||||
|  |         body["vcfg"] = vfs.flags | ||||||
|  |  | ||||||
|         x = self.conn.hsrv.broker.put(True, "up2k.handle_json", body) |         x = self.conn.hsrv.broker.put(True, "up2k.handle_json", body) | ||||||
|         response = x.get() |         response = x.get() | ||||||
| @@ -674,6 +679,9 @@ class HttpCli(object): | |||||||
|                             raise Pebkac(400, "empty files in post") |                             raise Pebkac(400, "empty files in post") | ||||||
|  |  | ||||||
|                         files.append([sz, sha512_hex]) |                         files.append([sz, sha512_hex]) | ||||||
|  |                         self.conn.hsrv.broker.put( | ||||||
|  |                             False, "up2k.hash_file", vfs.realpath, vfs.flags, rem, fname | ||||||
|  |                         ) | ||||||
|                         self.conn.nbyte += sz |                         self.conn.nbyte += sz | ||||||
|  |  | ||||||
|                 except Pebkac: |                 except Pebkac: | ||||||
| @@ -1111,7 +1119,7 @@ class HttpCli(object): | |||||||
|  |  | ||||||
|         try: |         try: | ||||||
|             vn, rem = self.auth.vfs.get(top, self.uname, True, False) |             vn, rem = self.auth.vfs.get(top, self.uname, True, False) | ||||||
|             fsroot, vfs_ls, vfs_virt = vn.ls(rem, self.uname) |             fsroot, vfs_ls, vfs_virt = vn.ls(rem, self.uname, not self.args.no_scandir) | ||||||
|         except: |         except: | ||||||
|             vfs_ls = [] |             vfs_ls = [] | ||||||
|             vfs_virt = {} |             vfs_virt = {} | ||||||
| @@ -1122,13 +1130,13 @@ class HttpCli(object): | |||||||
|  |  | ||||||
|         dirs = [] |         dirs = [] | ||||||
|  |  | ||||||
|  |         vfs_ls = [x[0] for x in vfs_ls if stat.S_ISDIR(x[1].st_mode)] | ||||||
|  |  | ||||||
|         if not self.args.ed or "dots" not in self.uparam: |         if not self.args.ed or "dots" not in self.uparam: | ||||||
|             vfs_ls = exclude_dotfiles(vfs_ls) |             vfs_ls = exclude_dotfiles(vfs_ls) | ||||||
|  |  | ||||||
|         for fn in [x for x in vfs_ls if x != excl]: |         for fn in [x for x in vfs_ls if x != excl]: | ||||||
|             abspath = os.path.join(fsroot, fn) |             dirs.append(fn) | ||||||
|             if os.path.isdir(abspath): |  | ||||||
|                 dirs.append(fn) |  | ||||||
|  |  | ||||||
|         for x in vfs_virt.keys(): |         for x in vfs_virt.keys(): | ||||||
|             if x != excl: |             if x != excl: | ||||||
| @@ -1167,7 +1175,9 @@ class HttpCli(object): | |||||||
|  |  | ||||||
|             return self.tx_file(abspath) |             return self.tx_file(abspath) | ||||||
|  |  | ||||||
|         fsroot, vfs_ls, vfs_virt = vn.ls(rem, self.uname) |         fsroot, vfs_ls, vfs_virt = vn.ls(rem, self.uname, not self.args.no_scandir) | ||||||
|  |         stats = {k: v for k, v in vfs_ls} | ||||||
|  |         vfs_ls = [x[0] for x in vfs_ls] | ||||||
|         vfs_ls.extend(vfs_virt.keys()) |         vfs_ls.extend(vfs_virt.keys()) | ||||||
|  |  | ||||||
|         # check for old versions of files, |         # check for old versions of files, | ||||||
| @@ -1218,7 +1228,7 @@ class HttpCli(object): | |||||||
|                 fspath = fsroot + "/" + fn |                 fspath = fsroot + "/" + fn | ||||||
|  |  | ||||||
|             try: |             try: | ||||||
|                 inf = os.stat(fsenc(fspath)) |                 inf = stats.get(fn) or os.stat(fsenc(fspath)) | ||||||
|             except: |             except: | ||||||
|                 self.log("broken symlink: {}".format(repr(fspath))) |                 self.log("broken symlink: {}".format(repr(fspath))) | ||||||
|                 continue |                 continue | ||||||
| @@ -1250,7 +1260,7 @@ class HttpCli(object): | |||||||
|                 "sz": sz, |                 "sz": sz, | ||||||
|                 "ext": ext, |                 "ext": ext, | ||||||
|                 "dt": dt, |                 "dt": dt, | ||||||
|                 "ts": inf.st_mtime, |                 "ts": int(inf.st_mtime), | ||||||
|             } |             } | ||||||
|             if is_dir: |             if is_dir: | ||||||
|                 dirs.append(item) |                 dirs.append(item) | ||||||
| @@ -1271,7 +1281,8 @@ class HttpCli(object): | |||||||
|  |  | ||||||
|                 w = r[0][:16] |                 w = r[0][:16] | ||||||
|                 tags = {} |                 tags = {} | ||||||
|                 for k, v in icur.execute("select k, v from mt where w = ?", (w,)): |                 q = "select k, v from mt where w = ? and k != 'x'" | ||||||
|  |                 for k, v in icur.execute(q, (w,)): | ||||||
|                     taglist[k] = True |                     taglist[k] = True | ||||||
|                     tags[k] = v |                     tags[k] = v | ||||||
|  |  | ||||||
|   | |||||||
| @@ -1,6 +1,5 @@ | |||||||
| # coding: utf-8 | # coding: utf-8 | ||||||
| from __future__ import print_function, unicode_literals | from __future__ import print_function, unicode_literals | ||||||
| from math import fabs |  | ||||||
|  |  | ||||||
| import re | import re | ||||||
| import os | import os | ||||||
| @@ -16,19 +15,21 @@ class MTag(object): | |||||||
|     def __init__(self, log_func, args): |     def __init__(self, log_func, args): | ||||||
|         self.log_func = log_func |         self.log_func = log_func | ||||||
|         self.usable = True |         self.usable = True | ||||||
|  |         self.prefer_mt = False | ||||||
|         mappings = args.mtm |         mappings = args.mtm | ||||||
|         backend = "ffprobe" if args.no_mutagen else "mutagen" |         self.backend = "ffprobe" if args.no_mutagen else "mutagen" | ||||||
|  |  | ||||||
|         if backend == "mutagen": |         if self.backend == "mutagen": | ||||||
|             self.get = self.get_mutagen |             self.get = self.get_mutagen | ||||||
|             try: |             try: | ||||||
|                 import mutagen |                 import mutagen | ||||||
|             except: |             except: | ||||||
|                 self.log("\033[33mcould not load mutagen, trying ffprobe instead") |                 self.log("\033[33mcould not load mutagen, trying ffprobe instead") | ||||||
|                 backend = "ffprobe" |                 self.backend = "ffprobe" | ||||||
|  |  | ||||||
|         if backend == "ffprobe": |         if self.backend == "ffprobe": | ||||||
|             self.get = self.get_ffprobe |             self.get = self.get_ffprobe | ||||||
|  |             self.prefer_mt = True | ||||||
|             # about 20x slower |             # about 20x slower | ||||||
|             if PY2: |             if PY2: | ||||||
|                 cmd = ["ffprobe", "-version"] |                 cmd = ["ffprobe", "-version"] | ||||||
|   | |||||||
| @@ -37,7 +37,7 @@ class U2idx(object): | |||||||
|         fsize = body["size"] |         fsize = body["size"] | ||||||
|         fhash = body["hash"] |         fhash = body["hash"] | ||||||
|         wark = up2k_wark_from_hashlist(self.args.salt, fsize, fhash) |         wark = up2k_wark_from_hashlist(self.args.salt, fsize, fhash) | ||||||
|         return self.run_query(vols, "w = ?", [wark], "", []) |         return self.run_query(vols, "w = ?", [wark], "", [])[0] | ||||||
|  |  | ||||||
|     def get_cur(self, ptop): |     def get_cur(self, ptop): | ||||||
|         cur = self.cur.get(ptop) |         cur = self.cur.get(ptop) | ||||||
| @@ -115,7 +115,7 @@ class U2idx(object): | |||||||
|                 w = hit["w"] |                 w = hit["w"] | ||||||
|                 del hit["w"] |                 del hit["w"] | ||||||
|                 tags = {} |                 tags = {} | ||||||
|                 q = "select k, v from mt where w = ?" |                 q = "select k, v from mt where w = ? and k != 'x'" | ||||||
|                 for k, v in cur.execute(q, (w,)): |                 for k, v in cur.execute(q, (w,)): | ||||||
|                     taglist[k] = True |                     taglist[k] = True | ||||||
|                     tags[k] = v |                     tags[k] = v | ||||||
| @@ -124,7 +124,7 @@ class U2idx(object): | |||||||
|  |  | ||||||
|             ret.extend(sret) |             ret.extend(sret) | ||||||
|  |  | ||||||
|         return ret, taglist.keys() |         return ret, list(taglist.keys()) | ||||||
|  |  | ||||||
|  |  | ||||||
| def _open(ptop): | def _open(ptop): | ||||||
|   | |||||||
| @@ -3,7 +3,6 @@ from __future__ import print_function, unicode_literals | |||||||
|  |  | ||||||
| import re | import re | ||||||
| import os | import os | ||||||
| import sys |  | ||||||
| import time | import time | ||||||
| import math | import math | ||||||
| import json | import json | ||||||
| @@ -28,6 +27,7 @@ from .util import ( | |||||||
|     atomic_move, |     atomic_move, | ||||||
|     w8b64enc, |     w8b64enc, | ||||||
|     w8b64dec, |     w8b64dec, | ||||||
|  |     statdir, | ||||||
| ) | ) | ||||||
| from .mtag import MTag | from .mtag import MTag | ||||||
| from .authsrv import AuthSrv | from .authsrv import AuthSrv | ||||||
| @@ -51,17 +51,21 @@ class Up2k(object): | |||||||
|         self.broker = broker |         self.broker = broker | ||||||
|         self.args = broker.args |         self.args = broker.args | ||||||
|         self.log_func = broker.log |         self.log_func = broker.log | ||||||
|         self.persist = self.args.e2d |  | ||||||
|  |  | ||||||
|         # config |         # config | ||||||
|         self.salt = broker.args.salt |         self.salt = broker.args.salt | ||||||
|  |  | ||||||
|         # state |         # state | ||||||
|         self.mutex = threading.Lock() |         self.mutex = threading.Lock() | ||||||
|  |         self.hashq = Queue() | ||||||
|  |         self.tagq = Queue() | ||||||
|         self.registry = {} |         self.registry = {} | ||||||
|         self.entags = {} |         self.entags = {} | ||||||
|         self.flags = {} |         self.flags = {} | ||||||
|         self.cur = {} |         self.cur = {} | ||||||
|  |         self.mtag = None | ||||||
|  |         self.n_mtag_thr_alive = 0 | ||||||
|  |         self.n_mtag_tags_added = 0 | ||||||
|  |  | ||||||
|         self.mem_cur = None |         self.mem_cur = None | ||||||
|         if HAVE_SQLITE3: |         if HAVE_SQLITE3: | ||||||
| @@ -76,25 +80,29 @@ class Up2k(object): | |||||||
|             thr.daemon = True |             thr.daemon = True | ||||||
|             thr.start() |             thr.start() | ||||||
|  |  | ||||||
|         self.mtag = MTag(self.log_func, self.args) |  | ||||||
|         if not self.mtag.usable: |  | ||||||
|             self.mtag = None |  | ||||||
|  |  | ||||||
|         # static |         # static | ||||||
|         self.r_hash = re.compile("^[0-9a-zA-Z_-]{43}$") |         self.r_hash = re.compile("^[0-9a-zA-Z_-]{43}$") | ||||||
|  |  | ||||||
|         if self.persist and not HAVE_SQLITE3: |         if not HAVE_SQLITE3: | ||||||
|             self.log("could not initialize sqlite3, will use in-memory registry only") |             self.log("could not initialize sqlite3, will use in-memory registry only") | ||||||
|  |  | ||||||
|         # this is kinda jank |         # this is kinda jank | ||||||
|         auth = AuthSrv(self.args, self.log, False) |         auth = AuthSrv(self.args, self.log, False) | ||||||
|         self.init_indexes(auth) |         have_e2d = self.init_indexes(auth) | ||||||
|  |  | ||||||
|         if self.persist: |         if have_e2d: | ||||||
|             thr = threading.Thread(target=self._snapshot) |             thr = threading.Thread(target=self._snapshot) | ||||||
|             thr.daemon = True |             thr.daemon = True | ||||||
|             thr.start() |             thr.start() | ||||||
|  |  | ||||||
|  |             thr = threading.Thread(target=self._tagger) | ||||||
|  |             thr.daemon = True | ||||||
|  |             thr.start() | ||||||
|  |  | ||||||
|  |             thr = threading.Thread(target=self._hasher) | ||||||
|  |             thr.daemon = True | ||||||
|  |             thr.start() | ||||||
|  |  | ||||||
|     def log(self, msg): |     def log(self, msg): | ||||||
|         self.log_func("up2k", msg + "\033[K") |         self.log_func("up2k", msg + "\033[K") | ||||||
|  |  | ||||||
| @@ -137,6 +145,27 @@ class Up2k(object): | |||||||
|         self.pp = ProgressPrinter() |         self.pp = ProgressPrinter() | ||||||
|         vols = auth.vfs.all_vols.values() |         vols = auth.vfs.all_vols.values() | ||||||
|         t0 = time.time() |         t0 = time.time() | ||||||
|  |         have_e2d = False | ||||||
|  |  | ||||||
|  |         live_vols = [] | ||||||
|  |         for vol in vols: | ||||||
|  |             try: | ||||||
|  |                 os.listdir(vol.realpath) | ||||||
|  |                 live_vols.append(vol) | ||||||
|  |             except: | ||||||
|  |                 self.log("\033[31mcannot access " + vol.realpath) | ||||||
|  |  | ||||||
|  |         vols = live_vols | ||||||
|  |  | ||||||
|  |         need_mtag = False | ||||||
|  |         for vol in auth.vfs.all_vols.values(): | ||||||
|  |             if "e2t" in vol.flags: | ||||||
|  |                 need_mtag = True | ||||||
|  |  | ||||||
|  |         if need_mtag: | ||||||
|  |             self.mtag = MTag(self.log_func, self.args) | ||||||
|  |             if not self.mtag.usable: | ||||||
|  |                 self.mtag = None | ||||||
|  |  | ||||||
|         # e2ds(a) volumes first, |         # e2ds(a) volumes first, | ||||||
|         # also covers tags where e2ts is set |         # also covers tags where e2ts is set | ||||||
| @@ -147,6 +176,9 @@ class Up2k(object): | |||||||
|  |  | ||||||
|             self.entags[vol.realpath] = en |             self.entags[vol.realpath] = en | ||||||
|  |  | ||||||
|  |             if "e2d" in vol.flags: | ||||||
|  |                 have_e2d = True | ||||||
|  |  | ||||||
|             if "e2ds" in vol.flags: |             if "e2ds" in vol.flags: | ||||||
|                 r = self._build_file_index(vol, vols) |                 r = self._build_file_index(vol, vols) | ||||||
|                 if not r: |                 if not r: | ||||||
| @@ -175,6 +207,8 @@ class Up2k(object): | |||||||
|             msg = "\033[31mcould not read tags because no backends are available (mutagen or ffprobe)\033[0m" |             msg = "\033[31mcould not read tags because no backends are available (mutagen or ffprobe)\033[0m" | ||||||
|             self.log(msg) |             self.log(msg) | ||||||
|  |  | ||||||
|  |         return have_e2d | ||||||
|  |  | ||||||
|     def register_vpath(self, ptop, flags): |     def register_vpath(self, ptop, flags): | ||||||
|         with self.mutex: |         with self.mutex: | ||||||
|             if ptop in self.registry: |             if ptop in self.registry: | ||||||
| @@ -182,7 +216,7 @@ class Up2k(object): | |||||||
|  |  | ||||||
|             reg = {} |             reg = {} | ||||||
|             path = os.path.join(ptop, ".hist", "up2k.snap") |             path = os.path.join(ptop, ".hist", "up2k.snap") | ||||||
|             if self.persist and os.path.exists(path): |             if "e2d" in flags and os.path.exists(path): | ||||||
|                 with gzip.GzipFile(path, "rb") as f: |                 with gzip.GzipFile(path, "rb") as f: | ||||||
|                     j = f.read().decode("utf-8") |                     j = f.read().decode("utf-8") | ||||||
|  |  | ||||||
| @@ -196,7 +230,7 @@ class Up2k(object): | |||||||
|  |  | ||||||
|             self.flags[ptop] = flags |             self.flags[ptop] = flags | ||||||
|             self.registry[ptop] = reg |             self.registry[ptop] = reg | ||||||
|             if not self.persist or not HAVE_SQLITE3 or "d2d" in flags: |             if not HAVE_SQLITE3 or "e2d" not in flags or "d2d" in flags: | ||||||
|                 return None |                 return None | ||||||
|  |  | ||||||
|             try: |             try: | ||||||
| @@ -259,23 +293,12 @@ class Up2k(object): | |||||||
|         self.log(msg) |         self.log(msg) | ||||||
|  |  | ||||||
|     def _build_dir(self, dbw, top, excl, cdir): |     def _build_dir(self, dbw, top, excl, cdir): | ||||||
|         try: |  | ||||||
|             inodes = [fsdec(x) for x in os.listdir(fsenc(cdir))] |  | ||||||
|         except Exception as ex: |  | ||||||
|             self.log("listdir: {} @ [{}]".format(repr(ex), cdir)) |  | ||||||
|             return 0 |  | ||||||
|  |  | ||||||
|         self.pp.msg = "a{} {}".format(self.pp.n, cdir) |         self.pp.msg = "a{} {}".format(self.pp.n, cdir) | ||||||
|         histdir = os.path.join(top, ".hist") |         histdir = os.path.join(top, ".hist") | ||||||
|         ret = 0 |         ret = 0 | ||||||
|         for inode in inodes: |         for iname, inf in statdir(self.log, not self.args.no_scandir, False, cdir): | ||||||
|             abspath = os.path.join(cdir, inode) |             abspath = os.path.join(cdir, iname) | ||||||
|             try: |             lmod = int(inf.st_mtime) | ||||||
|                 inf = os.stat(fsenc(abspath)) |  | ||||||
|             except Exception as ex: |  | ||||||
|                 self.log("stat: {} @ [{}]".format(repr(ex), abspath)) |  | ||||||
|                 continue |  | ||||||
|  |  | ||||||
|             if stat.S_ISDIR(inf.st_mode): |             if stat.S_ISDIR(inf.st_mode): | ||||||
|                 if abspath in excl or abspath == histdir: |                 if abspath in excl or abspath == histdir: | ||||||
|                     continue |                     continue | ||||||
| @@ -301,11 +324,11 @@ class Up2k(object): | |||||||
|                         self.log(m.format(top, rp, len(in_db), rep_db)) |                         self.log(m.format(top, rp, len(in_db), rep_db)) | ||||||
|                         dts = -1 |                         dts = -1 | ||||||
|  |  | ||||||
|                     if dts == inf.st_mtime and dsz == inf.st_size: |                     if dts == lmod and dsz == inf.st_size: | ||||||
|                         continue |                         continue | ||||||
|  |  | ||||||
|                     m = "reindex [{}] => [{}] ({}/{}) ({}/{})".format( |                     m = "reindex [{}] => [{}] ({}/{}) ({}/{})".format( | ||||||
|                         top, rp, dts, inf.st_mtime, dsz, inf.st_size |                         top, rp, dts, lmod, dsz, inf.st_size | ||||||
|                     ) |                     ) | ||||||
|                     self.log(m) |                     self.log(m) | ||||||
|                     self.db_rm(dbw[0], rd, fn) |                     self.db_rm(dbw[0], rd, fn) | ||||||
| @@ -324,7 +347,7 @@ class Up2k(object): | |||||||
|                     continue |                     continue | ||||||
|  |  | ||||||
|                 wark = up2k_wark_from_hashlist(self.salt, inf.st_size, hashes) |                 wark = up2k_wark_from_hashlist(self.salt, inf.st_size, hashes) | ||||||
|                 self.db_add(dbw[0], wark, rd, fn, inf.st_mtime, inf.st_size) |                 self.db_add(dbw[0], wark, rd, fn, lmod, inf.st_size) | ||||||
|                 dbw[1] += 1 |                 dbw[1] += 1 | ||||||
|                 ret += 1 |                 ret += 1 | ||||||
|                 td = time.time() - dbw[2] |                 td = time.time() - dbw[2] | ||||||
| @@ -405,7 +428,24 @@ class Up2k(object): | |||||||
|             if not self.mtag: |             if not self.mtag: | ||||||
|                 return n_add, n_rm, False |                 return n_add, n_rm, False | ||||||
|  |  | ||||||
|  |             mpool = False | ||||||
|  |             if self.mtag.prefer_mt and not self.args.no_mtag_mt: | ||||||
|  |                 # mp.pool.ThreadPool and concurrent.futures.ThreadPoolExecutor | ||||||
|  |                 # both do crazy runahead so lets reinvent another wheel | ||||||
|  |                 nw = os.cpu_count() | ||||||
|  |                 if not self.n_mtag_thr_alive: | ||||||
|  |                     msg = 'using {} cores for tag reader "{}"' | ||||||
|  |                     self.log(msg.format(nw, self.mtag.backend)) | ||||||
|  |  | ||||||
|  |                 self.n_mtag_thr_alive = nw | ||||||
|  |                 mpool = Queue(nw) | ||||||
|  |                 for _ in range(nw): | ||||||
|  |                     thr = threading.Thread(target=self._tag_thr, args=(mpool,)) | ||||||
|  |                     thr.daemon = True | ||||||
|  |                     thr.start() | ||||||
|  |  | ||||||
|             c2 = cur.connection.cursor() |             c2 = cur.connection.cursor() | ||||||
|  |             c3 = cur.connection.cursor() | ||||||
|             n_left = cur.execute("select count(w) from up").fetchone()[0] |             n_left = cur.execute("select count(w) from up").fetchone()[0] | ||||||
|             for w, rd, fn in cur.execute("select w, rd, fn from up"): |             for w, rd, fn in cur.execute("select w, rd, fn from up"): | ||||||
|                 n_left -= 1 |                 n_left -= 1 | ||||||
| @@ -415,17 +455,17 @@ class Up2k(object): | |||||||
|  |  | ||||||
|                 abspath = os.path.join(ptop, rd, fn) |                 abspath = os.path.join(ptop, rd, fn) | ||||||
|                 self.pp.msg = "c{} {}".format(n_left, abspath) |                 self.pp.msg = "c{} {}".format(n_left, abspath) | ||||||
|                 tags = self.mtag.get(abspath) |                 args = c3, entags, w, abspath | ||||||
|                 tags = {k: v for k, v in tags.items() if k in entags} |                 if not mpool: | ||||||
|                 if not tags: |                     n_tags = self._tag_file(*args) | ||||||
|                     # indicate scanned without tags |                 else: | ||||||
|                     tags = {"x": 0} |                     mpool.put(args) | ||||||
|  |                     with self.mutex: | ||||||
|  |                         n_tags = self.n_mtag_tags_added | ||||||
|  |                         self.n_mtag_tags_added = 0 | ||||||
|  |  | ||||||
|                 for k, v in tags.items(): |                 n_add += n_tags | ||||||
|                     q = "insert into mt values (?,?,?)" |                 n_buf += n_tags | ||||||
|                     c2.execute(q, (w[:16], k, v)) |  | ||||||
|                     n_add += 1 |  | ||||||
|                     n_buf += 1 |  | ||||||
|  |  | ||||||
|                 td = time.time() - last_write |                 td = time.time() - last_write | ||||||
|                 if n_buf >= 4096 or td >= 60: |                 if n_buf >= 4096 or td >= 60: | ||||||
| @@ -434,22 +474,59 @@ class Up2k(object): | |||||||
|                     last_write = time.time() |                     last_write = time.time() | ||||||
|                     n_buf = 0 |                     n_buf = 0 | ||||||
|  |  | ||||||
|  |             if self.n_mtag_thr_alive: | ||||||
|  |                 mpool.join() | ||||||
|  |                 for _ in range(self.n_mtag_thr_alive): | ||||||
|  |                     mpool.put(None) | ||||||
|  |  | ||||||
|  |             c3.close() | ||||||
|             c2.close() |             c2.close() | ||||||
|  |  | ||||||
|         return n_add, n_rm, True |         return n_add, n_rm, True | ||||||
|  |  | ||||||
|  |     def _tag_thr(self, q): | ||||||
|  |         while True: | ||||||
|  |             task = q.get() | ||||||
|  |             if not task: | ||||||
|  |                 break | ||||||
|  |  | ||||||
|  |             try: | ||||||
|  |                 write_cur, entags, wark, abspath = task | ||||||
|  |                 tags = self.mtag.get(abspath) | ||||||
|  |                 with self.mutex: | ||||||
|  |                     n = self._tag_file(write_cur, entags, wark, abspath, tags) | ||||||
|  |                     self.n_mtag_tags_added += n | ||||||
|  |             except: | ||||||
|  |                 with self.mutex: | ||||||
|  |                     self.n_mtag_thr_alive -= 1 | ||||||
|  |                 raise | ||||||
|  |             finally: | ||||||
|  |                 q.task_done() | ||||||
|  |  | ||||||
|  |     def _tag_file(self, write_cur, entags, wark, abspath, tags=None): | ||||||
|  |         tags = tags or self.mtag.get(abspath) | ||||||
|  |         tags = {k: v for k, v in tags.items() if k in entags} | ||||||
|  |         if not tags: | ||||||
|  |             # indicate scanned without tags | ||||||
|  |             tags = {"x": 0} | ||||||
|  |  | ||||||
|  |         ret = 0 | ||||||
|  |         for k, v in tags.items(): | ||||||
|  |             q = "insert into mt values (?,?,?)" | ||||||
|  |             write_cur.execute(q, (wark[:16], k, v)) | ||||||
|  |             ret += 1 | ||||||
|  |  | ||||||
|  |         return ret | ||||||
|  |  | ||||||
|     def _orz(self, db_path): |     def _orz(self, db_path): | ||||||
|         return sqlite3.connect(db_path, check_same_thread=False).cursor() |         return sqlite3.connect(db_path, check_same_thread=False).cursor() | ||||||
|  |  | ||||||
|     def _open_db(self, db_path): |     def _open_db(self, db_path): | ||||||
|         existed = os.path.exists(db_path) |         existed = os.path.exists(db_path) | ||||||
|         cur = self._orz(db_path) |         cur = self._orz(db_path) | ||||||
|         try: |         ver = self._read_ver(cur) | ||||||
|             ver = self._read_ver(cur) |         if not existed and ver is None: | ||||||
|         except: |             return self._create_db(db_path, cur) | ||||||
|             ver = None |  | ||||||
|             if not existed: |  | ||||||
|                 return self._create_db(db_path, cur) |  | ||||||
|  |  | ||||||
|         orig_ver = ver |         orig_ver = ver | ||||||
|         if not ver or ver < 3: |         if not ver or ver < 3: | ||||||
| @@ -571,6 +648,10 @@ class Up2k(object): | |||||||
|         return self._orz(db_path) |         return self._orz(db_path) | ||||||
|  |  | ||||||
|     def handle_json(self, cj): |     def handle_json(self, cj): | ||||||
|  |         if not self.register_vpath(cj["ptop"], cj["vcfg"]): | ||||||
|  |             if cj["ptop"] not in self.registry: | ||||||
|  |                 raise Pebkac(410, "location unavailable") | ||||||
|  |  | ||||||
|         cj["name"] = sanitize_fn(cj["name"]) |         cj["name"] = sanitize_fn(cj["name"]) | ||||||
|         cj["poke"] = time.time() |         cj["poke"] = time.time() | ||||||
|         wark = self._get_wark(cj) |         wark = self._get_wark(cj) | ||||||
| @@ -580,10 +661,9 @@ class Up2k(object): | |||||||
|             cur = self.cur.get(cj["ptop"], None) |             cur = self.cur.get(cj["ptop"], None) | ||||||
|             reg = self.registry[cj["ptop"]] |             reg = self.registry[cj["ptop"]] | ||||||
|             if cur: |             if cur: | ||||||
|                 cur = cur.execute( |                 q = r"select * from up where substr(w,1,16) = ? and w = ?" | ||||||
|                     r"select * from up where substr(w,1,16) = ? and w = ?", |                 argv = (wark[:16], wark) | ||||||
|                     (wark[:16], wark,), |                 cur = cur.execute(q, argv) | ||||||
|                 ) |  | ||||||
|                 for _, dtime, dsize, dp_dir, dp_fn in cur: |                 for _, dtime, dsize, dp_dir, dp_fn in cur: | ||||||
|                     if dp_dir.startswith("//") or dp_fn.startswith("//"): |                     if dp_dir.startswith("//") or dp_fn.startswith("//"): | ||||||
|                         dp_dir, dp_fn = self.w8dec(dp_dir, dp_fn) |                         dp_dir, dp_fn = self.w8dec(dp_dir, dp_fn) | ||||||
| @@ -769,17 +849,33 @@ class Up2k(object): | |||||||
|             if WINDOWS: |             if WINDOWS: | ||||||
|                 self.lastmod_q.put([dst, (int(time.time()), int(job["lmod"]))]) |                 self.lastmod_q.put([dst, (int(time.time()), int(job["lmod"]))]) | ||||||
|  |  | ||||||
|             cur = self.cur.get(job["ptop"], None) |             # legit api sware 2 me mum | ||||||
|             if cur: |             if self.idx_wark( | ||||||
|                 j = job |                 job["ptop"], | ||||||
|                 self.db_rm(cur, j["prel"], j["name"]) |                 job["wark"], | ||||||
|                 self.db_add(cur, j["wark"], j["prel"], j["name"], j["lmod"], j["size"]) |                 job["prel"], | ||||||
|                 cur.connection.commit() |                 job["name"], | ||||||
|  |                 job["lmod"], | ||||||
|  |                 job["size"], | ||||||
|  |             ): | ||||||
|                 del self.registry[ptop][wark] |                 del self.registry[ptop][wark] | ||||||
|                 # in-memory registry is reserved for unfinished uploads |                 # in-memory registry is reserved for unfinished uploads | ||||||
|  |  | ||||||
|             return ret, dst |         return ret, dst | ||||||
|  |  | ||||||
|  |     def idx_wark(self, ptop, wark, rd, fn, lmod, sz): | ||||||
|  |         cur = self.cur.get(ptop, None) | ||||||
|  |         if not cur: | ||||||
|  |             return False | ||||||
|  |  | ||||||
|  |         self.db_rm(cur, rd, fn) | ||||||
|  |         self.db_add(cur, wark, rd, fn, int(lmod), sz) | ||||||
|  |         cur.connection.commit() | ||||||
|  |  | ||||||
|  |         if "e2t" in self.flags[ptop]: | ||||||
|  |             self.tagq.put([ptop, wark, rd, fn]) | ||||||
|  |  | ||||||
|  |         return True | ||||||
|  |  | ||||||
|     def db_rm(self, db, rd, fn): |     def db_rm(self, db, rd, fn): | ||||||
|         sql = "delete from up where rd = ? and fn = ?" |         sql = "delete from up where rd = ? and fn = ?" | ||||||
| @@ -930,6 +1026,45 @@ class Up2k(object): | |||||||
|         self.log("snap: {} |{}|".format(path, len(reg.keys()))) |         self.log("snap: {} |{}|".format(path, len(reg.keys()))) | ||||||
|         prev[k] = etag |         prev[k] = etag | ||||||
|  |  | ||||||
|  |     def _tagger(self): | ||||||
|  |         while True: | ||||||
|  |             ptop, wark, rd, fn = self.tagq.get() | ||||||
|  |             abspath = os.path.join(ptop, rd, fn) | ||||||
|  |             self.log("tagging " + abspath) | ||||||
|  |             with self.mutex: | ||||||
|  |                 cur = self.cur[ptop] | ||||||
|  |                 if not cur: | ||||||
|  |                     self.log("\033[31mno cursor to write tags with??") | ||||||
|  |                     continue | ||||||
|  |  | ||||||
|  |                 entags = self.entags[ptop] | ||||||
|  |                 if not entags: | ||||||
|  |                     self.log("\033[33mno entags okay.jpg") | ||||||
|  |                     continue | ||||||
|  |  | ||||||
|  |                 if "e2t" in self.flags[ptop]: | ||||||
|  |                     self._tag_file(cur, entags, wark, abspath) | ||||||
|  |  | ||||||
|  |                 cur.connection.commit() | ||||||
|  |  | ||||||
|  |     def _hasher(self): | ||||||
|  |         while True: | ||||||
|  |             ptop, rd, fn = self.hashq.get() | ||||||
|  |             if "e2d" not in self.flags[ptop]: | ||||||
|  |                 continue | ||||||
|  |  | ||||||
|  |             abspath = os.path.join(ptop, rd, fn) | ||||||
|  |             self.log("hashing " + abspath) | ||||||
|  |             inf = os.stat(fsenc(abspath)) | ||||||
|  |             hashes = self._hashlist_from_file(abspath) | ||||||
|  |             wark = up2k_wark_from_hashlist(self.salt, inf.st_size, hashes) | ||||||
|  |             with self.mutex: | ||||||
|  |                 self.idx_wark(ptop, wark, rd, fn, inf.st_mtime, inf.st_size) | ||||||
|  |  | ||||||
|  |     def hash_file(self, ptop, flags, rd, fn): | ||||||
|  |         self.register_vpath(ptop, flags) | ||||||
|  |         self.hashq.put([ptop, rd, fn]) | ||||||
|  |  | ||||||
|  |  | ||||||
| def up2k_chunksize(filesize): | def up2k_chunksize(filesize): | ||||||
|     chunksize = 1024 * 1024 |     chunksize = 1024 * 1024 | ||||||
|   | |||||||
| @@ -521,9 +521,7 @@ def u8safe(txt): | |||||||
|  |  | ||||||
|  |  | ||||||
| def exclude_dotfiles(filepaths): | def exclude_dotfiles(filepaths): | ||||||
|     for fpath in filepaths: |     return [x for x in filepaths if not x.split("/")[-1].startswith(".")] | ||||||
|         if not fpath.split("/")[-1].startswith("."): |  | ||||||
|             yield fpath |  | ||||||
|  |  | ||||||
|  |  | ||||||
| def html_escape(s, quote=False): | def html_escape(s, quote=False): | ||||||
| @@ -726,6 +724,30 @@ def sendfile_kern(lower, upper, f, s): | |||||||
|     return 0 |     return 0 | ||||||
|  |  | ||||||
|  |  | ||||||
|  | def statdir(logger, scandir, lstat, top): | ||||||
|  |     try: | ||||||
|  |         btop = fsenc(top) | ||||||
|  |         if scandir and hasattr(os, "scandir"): | ||||||
|  |             src = "scandir" | ||||||
|  |             with os.scandir(btop) as dh: | ||||||
|  |                 for fh in dh: | ||||||
|  |                     try: | ||||||
|  |                         yield [fsdec(fh.name), fh.stat(follow_symlinks=not lstat)] | ||||||
|  |                     except Exception as ex: | ||||||
|  |                         logger("scan-stat: {} @ {}".format(repr(ex), fsdec(fh.path))) | ||||||
|  |         else: | ||||||
|  |             src = "listdir" | ||||||
|  |             fun = os.lstat if lstat else os.stat | ||||||
|  |             for name in os.listdir(btop): | ||||||
|  |                 abspath = os.path.join(btop, name) | ||||||
|  |                 try: | ||||||
|  |                     yield [fsdec(name), fun(abspath)] | ||||||
|  |                 except Exception as ex: | ||||||
|  |                     logger("list-stat: {} @ {}".format(repr(ex), fsdec(abspath))) | ||||||
|  |     except Exception as ex: | ||||||
|  |         logger("{}: {} @ {}".format(src, repr(ex), top)) | ||||||
|  |  | ||||||
|  |  | ||||||
| def unescape_cookie(orig): | def unescape_cookie(orig): | ||||||
|     # mw=idk; doot=qwe%2Crty%3Basd+fgh%2Bjkl%25zxc%26vbn  # qwe,rty;asd fgh+jkl%zxc&vbn |     # mw=idk; doot=qwe%2Crty%3Basd+fgh%2Bjkl%25zxc%26vbn  # qwe,rty;asd fgh+jkl%zxc&vbn | ||||||
|     ret = "" |     ret = "" | ||||||
|   | |||||||
| @@ -67,16 +67,18 @@ a, | |||||||
| #files a:hover { | #files a:hover { | ||||||
| 	color: #fff; | 	color: #fff; | ||||||
| 	background: #161616; | 	background: #161616; | ||||||
|  | 	text-decoration: underline; | ||||||
| } | } | ||||||
| #files thead a { | #files thead a { | ||||||
| 	color: #999; | 	color: #999; | ||||||
| 	font-weight: normal; | 	font-weight: normal; | ||||||
| } | } | ||||||
| #files tr:hover { | #files tr+tr:hover { | ||||||
| 	background: #1c1c1c; | 	background: #1c1c1c; | ||||||
| } | } | ||||||
| #files thead th { | #files thead th { | ||||||
| 	padding: .5em 1.3em .3em 1.3em; | 	padding: .5em 1.3em .3em 1.3em; | ||||||
|  | 	cursor: pointer; | ||||||
| } | } | ||||||
| #files thead th:last-child { | #files thead th:last-child { | ||||||
| 	background: #444; | 	background: #444; | ||||||
| @@ -121,6 +123,8 @@ a, | |||||||
| #files tbody tr:last-child td { | #files tbody tr:last-child td { | ||||||
| 	padding-bottom: 1.3em; | 	padding-bottom: 1.3em; | ||||||
| 	border-bottom: .5em solid #444; | 	border-bottom: .5em solid #444; | ||||||
|  | } | ||||||
|  | #files tbody tr td:last-child { | ||||||
| 	white-space: nowrap; | 	white-space: nowrap; | ||||||
| } | } | ||||||
| #files thead th[style] { | #files thead th[style] { | ||||||
| @@ -303,11 +307,11 @@ a, | |||||||
| 	width: calc(100% - 10.5em); | 	width: calc(100% - 10.5em); | ||||||
| 	background: rgba(0,0,0,0.2); | 	background: rgba(0,0,0,0.2); | ||||||
| } | } | ||||||
| @media (min-width: 100em) { | @media (min-width: 90em) { | ||||||
| 	#barpos, | 	#barpos, | ||||||
| 	#barbuf { | 	#barbuf { | ||||||
| 		width: calc(100% - 24em); | 		width: calc(100% - 24em); | ||||||
| 		left: 10em; | 		left: 9.8em; | ||||||
| 		top: .7em; | 		top: .7em; | ||||||
| 		height: 1.6em; | 		height: 1.6em; | ||||||
| 		bottom: auto; | 		bottom: auto; | ||||||
| @@ -446,12 +450,27 @@ input[type="checkbox"]:checked+label { | |||||||
| #tree { | #tree { | ||||||
| 	padding-top: 2em; | 	padding-top: 2em; | ||||||
| } | } | ||||||
|  | #tree>a+a { | ||||||
|  | 	padding: .2em .4em; | ||||||
|  | 	font-size: 1.2em; | ||||||
|  | 	background: #2a2a2a; | ||||||
|  | 	box-shadow: 0 .1em .2em #222 inset; | ||||||
|  | 	border-radius: .3em; | ||||||
|  | 	margin: .2em; | ||||||
|  | 	position: relative; | ||||||
|  | 	top: -.2em; | ||||||
|  | } | ||||||
|  | #tree>a+a:hover { | ||||||
|  | 	background: #805; | ||||||
|  | } | ||||||
|  | #tree>a+a.on { | ||||||
|  | 	background: #fc4; | ||||||
|  | 	color: #400; | ||||||
|  | 	text-shadow: none; | ||||||
|  | } | ||||||
| #detree { | #detree { | ||||||
| 	padding: .3em .5em; | 	padding: .3em .5em; | ||||||
| 	font-size: 1.5em; | 	font-size: 1.5em; | ||||||
| 	display: inline-block; |  | ||||||
| 	min-width: 12em; |  | ||||||
| 	width: 100%; |  | ||||||
| } | } | ||||||
| #treefiles #files tbody { | #treefiles #files tbody { | ||||||
| 	border-radius: 0 .7em 0 .7em; | 	border-radius: 0 .7em 0 .7em; | ||||||
| @@ -472,20 +491,20 @@ input[type="checkbox"]:checked+label { | |||||||
| 	list-style: none; | 	list-style: none; | ||||||
| 	white-space: nowrap; | 	white-space: nowrap; | ||||||
| } | } | ||||||
| #tree a.hl { | #treeul a.hl { | ||||||
| 	color: #400; | 	color: #400; | ||||||
| 	background: #fc4; | 	background: #fc4; | ||||||
| 	border-radius: .3em; | 	border-radius: .3em; | ||||||
| 	text-shadow: none; | 	text-shadow: none; | ||||||
| } | } | ||||||
| #tree a { | #treeul a { | ||||||
| 	display: inline-block; | 	display: inline-block; | ||||||
| } | } | ||||||
| #tree a+a { | #treeul a+a { | ||||||
| 	width: calc(100% - 2em); | 	width: calc(100% - 2em); | ||||||
| 	background: #333; | 	background: #333; | ||||||
| } | } | ||||||
| #tree a+a:hover { | #treeul a+a:hover { | ||||||
| 	background: #222; | 	background: #222; | ||||||
| 	color: #fff; | 	color: #fff; | ||||||
| } | } | ||||||
| @@ -533,7 +552,7 @@ input[type="checkbox"]:checked+label { | |||||||
| #files>thead>tr>th.min span { | #files>thead>tr>th.min span { | ||||||
| 	position: absolute; | 	position: absolute; | ||||||
| 	transform: rotate(270deg); | 	transform: rotate(270deg); | ||||||
| 	background: linear-gradient(90deg, #222, #444); | 	background: linear-gradient(90deg, rgba(68,68,68,0), rgba(68,68,68,0.5) 70%, #444); | ||||||
| 	margin-left: -4.6em; | 	margin-left: -4.6em; | ||||||
| 	padding: .4em; | 	padding: .4em; | ||||||
| 	top: 5.4em; | 	top: 5.4em; | ||||||
| @@ -552,4 +571,11 @@ input[type="checkbox"]:checked+label { | |||||||
| 	border-color: transparent; | 	border-color: transparent; | ||||||
| 	color: #400; | 	color: #400; | ||||||
| 	text-shadow: none; | 	text-shadow: none; | ||||||
| } | } | ||||||
|  | #files tr.play a { | ||||||
|  | 	color: inherit; | ||||||
|  | } | ||||||
|  | #files tr.play a:hover { | ||||||
|  | 	color: #300; | ||||||
|  | 	background: #fea; | ||||||
|  | } | ||||||
|   | |||||||
| @@ -48,6 +48,9 @@ | |||||||
|         <tr> |         <tr> | ||||||
|             <td id="tree"> |             <td id="tree"> | ||||||
|                 <a href="#" id="detree">🍞...</a> |                 <a href="#" id="detree">🍞...</a> | ||||||
|  |                 <a href="#" step="2" id="twobytwo">+</a> | ||||||
|  |                 <a href="#" step="-2" id="twig">–</a> | ||||||
|  |                 <a href="#" id="dyntree">a</a> | ||||||
|                 <ul id="treeul"></ul> |                 <ul id="treeul"></ul> | ||||||
|             </td> |             </td> | ||||||
|             <td id="treefiles"></td> |             <td id="treefiles"></td> | ||||||
|   | |||||||
| @@ -138,6 +138,9 @@ var pbar = (function () { | |||||||
| 	var grad = null; | 	var grad = null; | ||||||
|  |  | ||||||
| 	r.drawbuf = function () { | 	r.drawbuf = function () { | ||||||
|  | 		if (!mp.au) | ||||||
|  | 			return; | ||||||
|  |  | ||||||
| 		var cs = getComputedStyle(r.bcan); | 		var cs = getComputedStyle(r.bcan); | ||||||
| 		var sw = parseInt(cs['width']); | 		var sw = parseInt(cs['width']); | ||||||
| 		var sh = parseInt(cs['height']); | 		var sh = parseInt(cs['height']); | ||||||
| @@ -164,6 +167,9 @@ var pbar = (function () { | |||||||
| 		} | 		} | ||||||
| 	}; | 	}; | ||||||
| 	r.drawpos = function () { | 	r.drawpos = function () { | ||||||
|  | 		if (!mp.au) | ||||||
|  | 			return; | ||||||
|  |  | ||||||
| 		var cs = getComputedStyle(r.bcan); | 		var cs = getComputedStyle(r.bcan); | ||||||
| 		var sw = parseInt(cs['width']); | 		var sw = parseInt(cs['width']); | ||||||
| 		var sh = parseInt(cs['height']); | 		var sh = parseInt(cs['height']); | ||||||
| @@ -462,7 +468,7 @@ function play(tid, call_depth) { | |||||||
| 		o.setAttribute('id', 'thx_js'); | 		o.setAttribute('id', 'thx_js'); | ||||||
| 		if (window.history && history.replaceState) { | 		if (window.history && history.replaceState) { | ||||||
| 			var nurl = (document.location + '').split('#')[0] + '#' + oid; | 			var nurl = (document.location + '').split('#')[0] + '#' + oid; | ||||||
| 			history.replaceState(ebi('files').innerHTML, nurl, nurl); | 			hist_replace(ebi('files').innerHTML, nurl); | ||||||
| 		} | 		} | ||||||
| 		else { | 		else { | ||||||
| 			document.location.hash = oid; | 			document.location.hash = oid; | ||||||
| @@ -606,7 +612,7 @@ function autoplay_blocked() { | |||||||
| 	} | 	} | ||||||
| 	ebi('srch_form').innerHTML = html.join('\n'); | 	ebi('srch_form').innerHTML = html.join('\n'); | ||||||
|  |  | ||||||
| 	var o = document.querySelectorAll('#op_search input[type="text"]'); | 	var o = document.querySelectorAll('#op_search input'); | ||||||
| 	for (var a = 0; a < o.length; a++) { | 	for (var a = 0; a < o.length; a++) { | ||||||
| 		o[a].oninput = ev_search_input; | 		o[a].oninput = ev_search_input; | ||||||
| 	} | 	} | ||||||
| @@ -615,8 +621,11 @@ function autoplay_blocked() { | |||||||
|  |  | ||||||
| 	function ev_search_input() { | 	function ev_search_input() { | ||||||
| 		var v = this.value; | 		var v = this.value; | ||||||
| 		var chk = ebi(this.getAttribute('id').slice(0, -1) + 'c'); | 		var id = this.getAttribute('id'); | ||||||
| 		chk.checked = ((v + '').length > 0); | 		if (id.slice(-1) == 'v') { | ||||||
|  | 			var chk = ebi(id.slice(0, -1) + 'c'); | ||||||
|  | 			chk.checked = ((v + '').length > 0); | ||||||
|  | 		} | ||||||
| 		clearTimeout(search_timeout); | 		clearTimeout(search_timeout); | ||||||
| 		search_timeout = setTimeout(do_search, 100); | 		search_timeout = setTimeout(do_search, 100); | ||||||
| 	} | 	} | ||||||
| @@ -718,6 +727,10 @@ function autoplay_blocked() { | |||||||
| // tree | // tree | ||||||
| (function () { | (function () { | ||||||
| 	var treedata = null; | 	var treedata = null; | ||||||
|  | 	var dyn = bcfg_get('dyntree', true); | ||||||
|  | 	var treesz = icfg_get('treesz', 16); | ||||||
|  | 	treesz = isNaN(treesz) ? 16 : Math.min(Math.max(treesz, 4), 50); | ||||||
|  | 	console.log('treesz [' + treesz + ']'); | ||||||
|  |  | ||||||
| 	function entree(e) { | 	function entree(e) { | ||||||
| 		ev(e); | 		ev(e); | ||||||
| @@ -776,7 +789,7 @@ function autoplay_blocked() { | |||||||
| 				esc(top) + '">' + esc(name) + | 				esc(top) + '">' + esc(name) + | ||||||
| 				"</a>\n<ul>\n" + html + "</ul>"; | 				"</a>\n<ul>\n" + html + "</ul>"; | ||||||
|  |  | ||||||
| 			var links = document.querySelectorAll('#tree a+a'); | 			var links = document.querySelectorAll('#treeul a+a'); | ||||||
| 			for (var a = 0, aa = links.length; a < aa; a++) { | 			for (var a = 0, aa = links.length; a < aa; a++) { | ||||||
| 				if (links[a].getAttribute('href') == top) { | 				if (links[a].getAttribute('href') == top) { | ||||||
| 					var o = links[a].parentNode; | 					var o = links[a].parentNode; | ||||||
| @@ -790,7 +803,10 @@ function autoplay_blocked() { | |||||||
| 		document.querySelector('#treeul>li>a+a').textContent = '[root]'; | 		document.querySelector('#treeul>li>a+a').textContent = '[root]'; | ||||||
| 		despin('#tree'); | 		despin('#tree'); | ||||||
| 		reload_tree(); | 		reload_tree(); | ||||||
|  | 		rescale_tree(); | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	function rescale_tree() { | ||||||
| 		var q = '#tree'; | 		var q = '#tree'; | ||||||
| 		var nq = 0; | 		var nq = 0; | ||||||
| 		while (true) { | 		while (true) { | ||||||
| @@ -799,18 +815,19 @@ function autoplay_blocked() { | |||||||
| 			if (!document.querySelector(q)) | 			if (!document.querySelector(q)) | ||||||
| 				break; | 				break; | ||||||
| 		} | 		} | ||||||
| 		ebi('treeul').style.width = (24 + nq) + 'em'; | 		var w = treesz + (dyn ? nq : 0); | ||||||
|  | 		ebi('treeul').style.width = w + 'em'; | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	function reload_tree() { | 	function reload_tree() { | ||||||
| 		var cdir = get_vpath(); | 		var cdir = get_vpath(); | ||||||
| 		var links = document.querySelectorAll('#tree a+a'); | 		var links = document.querySelectorAll('#treeul a+a'); | ||||||
| 		for (var a = 0, aa = links.length; a < aa; a++) { | 		for (var a = 0, aa = links.length; a < aa; a++) { | ||||||
| 			var href = links[a].getAttribute('href'); | 			var href = links[a].getAttribute('href'); | ||||||
| 			links[a].setAttribute('class', href == cdir ? 'hl' : ''); | 			links[a].setAttribute('class', href == cdir ? 'hl' : ''); | ||||||
| 			links[a].onclick = treego; | 			links[a].onclick = treego; | ||||||
| 		} | 		} | ||||||
| 		links = document.querySelectorAll('#tree li>a:first-child'); | 		links = document.querySelectorAll('#treeul li>a:first-child'); | ||||||
| 		for (var a = 0, aa = links.length; a < aa; a++) { | 		for (var a = 0, aa = links.length; a < aa; a++) { | ||||||
| 			links[a].setAttribute('dst', links[a].nextSibling.getAttribute('href')); | 			links[a].setAttribute('dst', links[a].nextSibling.getAttribute('href')); | ||||||
| 			links[a].onclick = treegrow; | 			links[a].onclick = treegrow; | ||||||
| @@ -841,6 +858,7 @@ function autoplay_blocked() { | |||||||
| 				rm.parentNode.removeChild(rm); | 				rm.parentNode.removeChild(rm); | ||||||
| 			} | 			} | ||||||
| 			this.textContent = '+'; | 			this.textContent = '+'; | ||||||
|  | 			rescale_tree(); | ||||||
| 			return; | 			return; | ||||||
| 		} | 		} | ||||||
| 		var dst = this.getAttribute('dst'); | 		var dst = this.getAttribute('dst'); | ||||||
| @@ -895,7 +913,7 @@ function autoplay_blocked() { | |||||||
| 		html = html.join('\n'); | 		html = html.join('\n'); | ||||||
| 		ebi('files').innerHTML = html; | 		ebi('files').innerHTML = html; | ||||||
|  |  | ||||||
| 		history.pushState(html, this.top, this.top); | 		hist_push(html, this.top); | ||||||
| 		apply_perms(res.perms); | 		apply_perms(res.perms); | ||||||
| 		despin('#files'); | 		despin('#files'); | ||||||
|  |  | ||||||
| @@ -950,23 +968,45 @@ function autoplay_blocked() { | |||||||
| 		swrite('entreed', 'na'); | 		swrite('entreed', 'na'); | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
|  | 	function dyntree(e) { | ||||||
|  | 		ev(e); | ||||||
|  | 		dyn = !dyn; | ||||||
|  | 		bcfg_set('dyntree', dyn); | ||||||
|  | 		rescale_tree(); | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	function scaletree(e) { | ||||||
|  | 		ev(e); | ||||||
|  | 		treesz += parseInt(this.getAttribute("step")); | ||||||
|  | 		if (isNaN(treesz)) | ||||||
|  | 			treesz = 16; | ||||||
|  |  | ||||||
|  | 		swrite('treesz', treesz); | ||||||
|  | 		rescale_tree(); | ||||||
|  | 	} | ||||||
|  |  | ||||||
| 	ebi('entree').onclick = entree; | 	ebi('entree').onclick = entree; | ||||||
| 	ebi('detree').onclick = detree; | 	ebi('detree').onclick = detree; | ||||||
|  | 	ebi('dyntree').onclick = dyntree; | ||||||
|  | 	ebi('twig').onclick = scaletree; | ||||||
|  | 	ebi('twobytwo').onclick = scaletree; | ||||||
| 	if (sread('entreed') == 'tree') | 	if (sread('entreed') == 'tree') | ||||||
| 		entree(); | 		entree(); | ||||||
|  |  | ||||||
| 	window.onpopstate = function (e) { | 	window.onpopstate = function (e) { | ||||||
| 		console.log(e.url + ' ,, ' + ((e.state + '').slice(0, 64))); | 		console.log(e.url + ' ,, ' + ((e.state + '').slice(0, 64))); | ||||||
| 		if (e.state) { | 		var html = sessionStorage.getItem(e.state || 1); | ||||||
| 			ebi('files').innerHTML = e.state; | 		if (!html) | ||||||
| 			reload_tree(); | 			return; | ||||||
| 			reload_browser(); |  | ||||||
| 		} | 		ebi('files').innerHTML = html; | ||||||
|  | 		reload_tree(); | ||||||
|  | 		reload_browser(); | ||||||
| 	}; | 	}; | ||||||
|  |  | ||||||
| 	if (window.history && history.pushState) { | 	if (window.history && history.pushState) { | ||||||
| 		var u = get_vpath() + window.location.hash; | 		var u = get_vpath() + window.location.hash; | ||||||
| 		history.replaceState(ebi('files').innerHTML, u, u); | 		hist_replace(ebi('files').innerHTML, u); | ||||||
| 	} | 	} | ||||||
| })(); | })(); | ||||||
|  |  | ||||||
| @@ -1158,7 +1198,6 @@ function reload_browser(not_mp) { | |||||||
| 			hsz = sz.replace(/\B(?=(\d{3})+(?!\d))/g, " "); | 			hsz = sz.replace(/\B(?=(\d{3})+(?!\d))/g, " "); | ||||||
|  |  | ||||||
| 		oo[a].textContent = hsz; | 		oo[a].textContent = hsz; | ||||||
| 		oo[a].setAttribute("sortv", sz); |  | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	if (!not_mp) { | 	if (!not_mp) { | ||||||
|   | |||||||
| @@ -209,41 +209,7 @@ function up2k_init(have_crypto) { | |||||||
|         }; |         }; | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     function cfg_get(name) { |     var parallel_uploads = icfg_get('nthread'); | ||||||
|         var val = sread(name); |  | ||||||
|         if (val === null) |  | ||||||
|             return parseInt(ebi(name).value); |  | ||||||
|  |  | ||||||
|         ebi(name).value = val; |  | ||||||
|         return val; |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     function bcfg_get(name, defval) { |  | ||||||
|         var o = ebi(name); |  | ||||||
|         if (!o) |  | ||||||
|             return defval; |  | ||||||
|  |  | ||||||
|         var val = sread(name); |  | ||||||
|         if (val === null) |  | ||||||
|             val = defval; |  | ||||||
|         else |  | ||||||
|             val = (val == '1'); |  | ||||||
|  |  | ||||||
|         o.checked = val; |  | ||||||
|         return val; |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     function bcfg_set(name, val) { |  | ||||||
|         swrite(name, val ? '1' : '0'); |  | ||||||
|  |  | ||||||
|         var o = ebi(name); |  | ||||||
|         if (o) |  | ||||||
|             o.checked = val; |  | ||||||
|  |  | ||||||
|         return val; |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     var parallel_uploads = cfg_get('nthread'); |  | ||||||
|     var multitask = bcfg_get('multitask', true); |     var multitask = bcfg_get('multitask', true); | ||||||
|     var ask_up = bcfg_get('ask_up', true); |     var ask_up = bcfg_get('ask_up', true); | ||||||
|     var flag_en = bcfg_get('flag_en', false); |     var flag_en = bcfg_get('flag_en', false); | ||||||
|   | |||||||
| @@ -99,22 +99,33 @@ function sortTable(table, col) { | |||||||
|         th[a].className = th[a].className.replace(/ *sort-?1 */, " "); |         th[a].className = th[a].className.replace(/ *sort-?1 */, " "); | ||||||
|     th[col].className += ' sort' + reverse; |     th[col].className += ' sort' + reverse; | ||||||
|     var stype = th[col].getAttribute('sort'); |     var stype = th[col].getAttribute('sort'); | ||||||
|     tr = tr.sort(function (a, b) { |     var vl = []; | ||||||
|         if (!a.cells[col]) |     for (var a = 0; a < tr.length; a++) { | ||||||
|  |         var cell = tr[a].cells[col]; | ||||||
|  |         if (!cell) { | ||||||
|  |             vl.push([null, a]); | ||||||
|  |             continue; | ||||||
|  |         } | ||||||
|  |         var v = cell.getAttribute('sortv') || cell.textContent.trim(); | ||||||
|  |         if (stype == 'int') { | ||||||
|  |             v = parseInt(v.replace(/[, ]/g, '')) || 0; | ||||||
|  |         } | ||||||
|  |         vl.push([v, a]); | ||||||
|  |     } | ||||||
|  |     vl.sort(function (a, b) { | ||||||
|  |         a = a[0]; | ||||||
|  |         b = b[0]; | ||||||
|  |         if (a === null) | ||||||
|             return -1; |             return -1; | ||||||
|         if (!b.cells[col]) |         if (b === null) | ||||||
|             return 1; |             return 1; | ||||||
|  |  | ||||||
|         var v1 = a.cells[col].getAttribute('sortv') || a.cells[col].textContent.trim(); |  | ||||||
|         var v2 = b.cells[col].getAttribute('sortv') || b.cells[col].textContent.trim(); |  | ||||||
|         if (stype == 'int') { |         if (stype == 'int') { | ||||||
|             v1 = parseInt(v1.replace(/,/g, '')) || 0; |             return reverse * (a - b); | ||||||
|             v2 = parseInt(v2.replace(/,/g, '')) || 0; |  | ||||||
|             return reverse * (v1 - v2); |  | ||||||
|         } |         } | ||||||
|         return reverse * (v1.localeCompare(v2)); |         return reverse * (a.localeCompare(b)); | ||||||
|     }); |     }); | ||||||
|     for (i = 0; i < tr.length; ++i) tb.appendChild(tr[i]); |     for (i = 0; i < tr.length; ++i) tb.appendChild(tr[vl[i][1]]); | ||||||
| } | } | ||||||
| function makeSortable(table) { | function makeSortable(table) { | ||||||
|     var th = table.tHead, i; |     var th = table.tHead, i; | ||||||
| @@ -162,10 +173,6 @@ function goto(dest) { | |||||||
|     for (var a = obj.length - 1; a >= 0; a--) |     for (var a = obj.length - 1; a >= 0; a--) | ||||||
|         obj[a].classList.remove('act'); |         obj[a].classList.remove('act'); | ||||||
|  |  | ||||||
|     var others = ['path', 'files', 'widget']; |  | ||||||
|     for (var a = 0; a < others.length; a++) |  | ||||||
|         ebi(others[a]).classList.remove('hidden'); |  | ||||||
|  |  | ||||||
|     if (dest) { |     if (dest) { | ||||||
|         var ui = ebi('op_' + dest); |         var ui = ebi('op_' + dest); | ||||||
|         ui.classList.add('act'); |         ui.classList.add('act'); | ||||||
| @@ -281,3 +288,61 @@ function jwrite(key, val) { | |||||||
|     else |     else | ||||||
|         swrite(key, JSON.stringify(val)); |         swrite(key, JSON.stringify(val)); | ||||||
| } | } | ||||||
|  |  | ||||||
|  | function icfg_get(name, defval) { | ||||||
|  |     var o = ebi(name); | ||||||
|  |  | ||||||
|  |     var val = parseInt(sread(name)); | ||||||
|  |     if (val === null) | ||||||
|  |         return parseInt(o ? o.value : defval); | ||||||
|  |  | ||||||
|  |     if (o) | ||||||
|  |         o.value = val; | ||||||
|  |  | ||||||
|  |     return val; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | function bcfg_get(name, defval) { | ||||||
|  |     var o = ebi(name); | ||||||
|  |     if (!o) | ||||||
|  |         return defval; | ||||||
|  |  | ||||||
|  |     var val = sread(name); | ||||||
|  |     if (val === null) | ||||||
|  |         val = defval; | ||||||
|  |     else | ||||||
|  |         val = (val == '1'); | ||||||
|  |  | ||||||
|  |     bcfg_upd_ui(name, val); | ||||||
|  |     return val; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | function bcfg_set(name, val) { | ||||||
|  |     swrite(name, val ? '1' : '0'); | ||||||
|  |     bcfg_upd_ui(name, val); | ||||||
|  |     return val; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | function bcfg_upd_ui(name, val) { | ||||||
|  |     var o = ebi(name); | ||||||
|  |     if (!o) | ||||||
|  |         return; | ||||||
|  |  | ||||||
|  |     if (o.getAttribute('type') == 'checkbox') | ||||||
|  |         o.checked = val; | ||||||
|  |     else if (o) | ||||||
|  |         o.setAttribute('class', val ? 'on' : ''); | ||||||
|  | } | ||||||
|  |  | ||||||
|  |  | ||||||
|  | function hist_push(html, url) { | ||||||
|  |     var key = new Date().getTime(); | ||||||
|  |     sessionStorage.setItem(key, html); | ||||||
|  |     history.pushState(key, url, url); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | function hist_replace(html, url) { | ||||||
|  |     var key = new Date().getTime(); | ||||||
|  |     sessionStorage.setItem(key, html); | ||||||
|  |     history.replaceState(key, url, url); | ||||||
|  | } | ||||||
|   | |||||||
| @@ -122,7 +122,7 @@ git describe --tags >/dev/null 2>/dev/null && { | |||||||
| 		exit 1 | 		exit 1 | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	dt="$(git log -1 --format=%cd --date=format:'%Y,%m,%d' | sed -E 's/,0?/, /g')" | 	dt="$(git log -1 --format=%cd --date=short | sed -E 's/-0?/, /g')" | ||||||
| 	printf 'git %3s: \033[36m%s\033[0m\n' ver "$ver" dt "$dt" | 	printf 'git %3s: \033[36m%s\033[0m\n' ver "$ver" dt "$dt" | ||||||
| 	sed -ri ' | 	sed -ri ' | ||||||
| 		s/^(VERSION =)(.*)/#\1\2\n\1 ('"$t_ver"')/; | 		s/^(VERSION =)(.*)/#\1\2\n\1 ('"$t_ver"')/; | ||||||
|   | |||||||
| @@ -16,6 +16,12 @@ from copyparty.authsrv import AuthSrv | |||||||
| from copyparty import util | from copyparty import util | ||||||
|  |  | ||||||
|  |  | ||||||
|  | class Cfg(Namespace): | ||||||
|  |     def __init__(self, a=[], v=[], c=None): | ||||||
|  |         ex = {k: False for k in "e2d e2ds e2dsa e2t e2ts e2tsr mte".split()} | ||||||
|  |         super(Cfg, self).__init__(a=a, v=v, c=c, **ex) | ||||||
|  |  | ||||||
|  |  | ||||||
| class TestVFS(unittest.TestCase): | class TestVFS(unittest.TestCase): | ||||||
|     def dump(self, vfs): |     def dump(self, vfs): | ||||||
|         print(json.dumps(vfs, indent=4, sort_keys=True, default=lambda o: o.__dict__)) |         print(json.dumps(vfs, indent=4, sort_keys=True, default=lambda o: o.__dict__)) | ||||||
| @@ -35,7 +41,13 @@ class TestVFS(unittest.TestCase): | |||||||
|     def ls(self, vfs, vpath, uname): |     def ls(self, vfs, vpath, uname): | ||||||
|         """helper for resolving and listing a folder""" |         """helper for resolving and listing a folder""" | ||||||
|         vn, rem = vfs.get(vpath, uname, True, False) |         vn, rem = vfs.get(vpath, uname, True, False) | ||||||
|         return vn.ls(rem, uname) |         r1 = vn.ls(rem, uname, False) | ||||||
|  |         r2 = vn.ls(rem, uname, False) | ||||||
|  |         self.assertEqual(r1, r2) | ||||||
|  |  | ||||||
|  |         fsdir, real, virt = r1 | ||||||
|  |         real = [x[0] for x in real] | ||||||
|  |         return fsdir, real, virt | ||||||
|  |  | ||||||
|     def runcmd(self, *argv): |     def runcmd(self, *argv): | ||||||
|         p = sp.Popen(argv, stdout=sp.PIPE, stderr=sp.PIPE) |         p = sp.Popen(argv, stdout=sp.PIPE, stderr=sp.PIPE) | ||||||
| @@ -102,7 +114,7 @@ class TestVFS(unittest.TestCase): | |||||||
|                             f.write(fn) |                             f.write(fn) | ||||||
|  |  | ||||||
|         # defaults |         # defaults | ||||||
|         vfs = AuthSrv(Namespace(c=None, a=[], v=[]), self.log).vfs |         vfs = AuthSrv(Cfg(), self.log).vfs | ||||||
|         self.assertEqual(vfs.nodes, {}) |         self.assertEqual(vfs.nodes, {}) | ||||||
|         self.assertEqual(vfs.vpath, "") |         self.assertEqual(vfs.vpath, "") | ||||||
|         self.assertEqual(vfs.realpath, td) |         self.assertEqual(vfs.realpath, td) | ||||||
| @@ -110,7 +122,7 @@ class TestVFS(unittest.TestCase): | |||||||
|         self.assertEqual(vfs.uwrite, ["*"]) |         self.assertEqual(vfs.uwrite, ["*"]) | ||||||
|  |  | ||||||
|         # single read-only rootfs (relative path) |         # single read-only rootfs (relative path) | ||||||
|         vfs = AuthSrv(Namespace(c=None, a=[], v=["a/ab/::r"]), self.log).vfs |         vfs = AuthSrv(Cfg(v=["a/ab/::r"]), self.log).vfs | ||||||
|         self.assertEqual(vfs.nodes, {}) |         self.assertEqual(vfs.nodes, {}) | ||||||
|         self.assertEqual(vfs.vpath, "") |         self.assertEqual(vfs.vpath, "") | ||||||
|         self.assertEqual(vfs.realpath, os.path.join(td, "a", "ab")) |         self.assertEqual(vfs.realpath, os.path.join(td, "a", "ab")) | ||||||
| @@ -118,9 +130,7 @@ class TestVFS(unittest.TestCase): | |||||||
|         self.assertEqual(vfs.uwrite, []) |         self.assertEqual(vfs.uwrite, []) | ||||||
|  |  | ||||||
|         # single read-only rootfs (absolute path) |         # single read-only rootfs (absolute path) | ||||||
|         vfs = AuthSrv( |         vfs = AuthSrv(Cfg(v=[td + "//a/ac/../aa//::r"]), self.log).vfs | ||||||
|             Namespace(c=None, a=[], v=[td + "//a/ac/../aa//::r"]), self.log |  | ||||||
|         ).vfs |  | ||||||
|         self.assertEqual(vfs.nodes, {}) |         self.assertEqual(vfs.nodes, {}) | ||||||
|         self.assertEqual(vfs.vpath, "") |         self.assertEqual(vfs.vpath, "") | ||||||
|         self.assertEqual(vfs.realpath, os.path.join(td, "a", "aa")) |         self.assertEqual(vfs.realpath, os.path.join(td, "a", "aa")) | ||||||
| @@ -129,7 +139,7 @@ class TestVFS(unittest.TestCase): | |||||||
|  |  | ||||||
|         # read-only rootfs with write-only subdirectory (read-write for k) |         # read-only rootfs with write-only subdirectory (read-write for k) | ||||||
|         vfs = AuthSrv( |         vfs = AuthSrv( | ||||||
|             Namespace(c=None, a=["k:k"], v=[".::r:ak", "a/ac/acb:a/ac/acb:w:ak"]), |             Cfg(a=["k:k"], v=[".::r:ak", "a/ac/acb:a/ac/acb:w:ak"]), | ||||||
|             self.log, |             self.log, | ||||||
|         ).vfs |         ).vfs | ||||||
|         self.assertEqual(len(vfs.nodes), 1) |         self.assertEqual(len(vfs.nodes), 1) | ||||||
| @@ -192,7 +202,10 @@ class TestVFS(unittest.TestCase): | |||||||
|         self.assertEqual(list(virt), []) |         self.assertEqual(list(virt), []) | ||||||
|  |  | ||||||
|         # admin-only rootfs with all-read-only subfolder |         # admin-only rootfs with all-read-only subfolder | ||||||
|         vfs = AuthSrv(Namespace(c=None, a=["k:k"], v=[".::ak", "a:a:r"]), self.log,).vfs |         vfs = AuthSrv( | ||||||
|  |             Cfg(a=["k:k"], v=[".::ak", "a:a:r"]), | ||||||
|  |             self.log, | ||||||
|  |         ).vfs | ||||||
|         self.assertEqual(len(vfs.nodes), 1) |         self.assertEqual(len(vfs.nodes), 1) | ||||||
|         self.assertEqual(vfs.vpath, "") |         self.assertEqual(vfs.vpath, "") | ||||||
|         self.assertEqual(vfs.realpath, td) |         self.assertEqual(vfs.realpath, td) | ||||||
| @@ -211,9 +224,7 @@ class TestVFS(unittest.TestCase): | |||||||
|  |  | ||||||
|         # breadth-first construction |         # breadth-first construction | ||||||
|         vfs = AuthSrv( |         vfs = AuthSrv( | ||||||
|             Namespace( |             Cfg( | ||||||
|                 c=None, |  | ||||||
|                 a=[], |  | ||||||
|                 v=[ |                 v=[ | ||||||
|                     "a/ac/acb:a/ac/acb:w", |                     "a/ac/acb:a/ac/acb:w", | ||||||
|                     "a:a:w", |                     "a:a:w", | ||||||
| @@ -234,7 +245,7 @@ class TestVFS(unittest.TestCase): | |||||||
|         self.undot(vfs, "./.././foo/..", "") |         self.undot(vfs, "./.././foo/..", "") | ||||||
|  |  | ||||||
|         # shadowing |         # shadowing | ||||||
|         vfs = AuthSrv(Namespace(c=None, a=[], v=[".::r", "b:a/ac:r"]), self.log).vfs |         vfs = AuthSrv(Cfg(v=[".::r", "b:a/ac:r"]), self.log).vfs | ||||||
|  |  | ||||||
|         fsp, r1, v1 = self.ls(vfs, "", "*") |         fsp, r1, v1 = self.ls(vfs, "", "*") | ||||||
|         self.assertEqual(fsp, td) |         self.assertEqual(fsp, td) | ||||||
| @@ -271,7 +282,7 @@ class TestVFS(unittest.TestCase): | |||||||
|                 ).encode("utf-8") |                 ).encode("utf-8") | ||||||
|             ) |             ) | ||||||
|  |  | ||||||
|         au = AuthSrv(Namespace(c=[cfg_path], a=[], v=[]), self.log) |         au = AuthSrv(Cfg(c=[cfg_path]), self.log) | ||||||
|         self.assertEqual(au.user["a"], "123") |         self.assertEqual(au.user["a"], "123") | ||||||
|         self.assertEqual(au.user["asd"], "fgh:jkl") |         self.assertEqual(au.user["asd"], "fgh:jkl") | ||||||
|         n = au.vfs |         n = au.vfs | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user