diff --git a/README.md b/README.md index 4b03903b..75f74a55 100644 --- a/README.md +++ b/README.md @@ -1238,11 +1238,15 @@ if you want thumbnails, `apt -y install ffmpeg` ideas for context to include in bug reports +in general, commandline arguments (and config file if any) + if something broke during an upload (replacing FILENAME with a part of the filename that broke): ``` journalctl -aS '48 hour ago' -u copyparty | grep -C10 FILENAME | tee bug.log ``` +if there's a wall of base64 in the log (thread stacks) then please include that, especially if you run into something freezing up or getting stuck, for example `OperationalError('database is locked')` -- alternatively you can visit `/?stack` to see the stacks live, so http://127.0.0.1:3923/?stack for example + # building diff --git a/copyparty/authsrv.py b/copyparty/authsrv.py index a21700de..8d0f3b97 100644 --- a/copyparty/authsrv.py +++ b/copyparty/authsrv.py @@ -1107,6 +1107,7 @@ class AuthSrv(object): vfs.bubble_flags() + e2vs = [] t = "volumes and permissions:\n" for zv in vfs.all_vols.values(): if not self.warn_anonwrite: @@ -1124,8 +1125,16 @@ class AuthSrv(object): u = ", ".join("\033[35meverybody\033[0m" if x == "*" else x for x in u) u = u if u else "\033[36m--none--\033[0m" t += "\n| {}: {}".format(txt, u) + + if "e2v" in zv.flags and zv.axs.uwrite: + e2vs.append(zv.vpath or "/") + t += "\n" + if e2vs: + t += "\n\033[33me2v enabled for the following volumes;\nuploads will be blocked until scan has finished:\n \033[0m" + t += " ".join(e2vs) + "\n" + if self.warn_anonwrite and not self.args.no_voldump: self.log(t) @@ -1133,7 +1142,7 @@ class AuthSrv(object): zv, _ = vfs.get("/", "*", False, True) if self.warn_anonwrite and os.getcwd() == zv.realpath: self.warn_anonwrite = False - t = "anyone can read/write the current directory: {}\n" + t = "anyone can write to the current directory: {}\n" self.log(t.format(zv.realpath), c=1) except Pebkac: self.warn_anonwrite = True diff --git a/copyparty/httpcli.py b/copyparty/httpcli.py index 9480b66c..8dc27462 100644 --- a/copyparty/httpcli.py +++ b/copyparty/httpcli.py @@ -380,13 +380,18 @@ class HttpCli(object): if not self._check_nonfatal(pex, post): self.keepalive = False - msg = str(ex) if pex == ex else min_ex() + em = str(ex) + msg = em if pex == ex else min_ex() self.log("{}\033[0m, {}".format(msg, self.vpath), 3) - msg = "{}\r\nURL: {}\r\n".format(str(ex), self.vpath) + msg = "{}\r\nURL: {}\r\n".format(em, self.vpath) if self.hint: msg += "hint: {}\r\n".format(self.hint) + if "database is locked" in em: + self.conn.hsrv.broker.say("log_stacks") + msg += "hint: important info in the server log\r\n" + msg = "
" + html_escape(msg) self.reply(msg.encode("utf-8", "replace"), status=pex.code, volsan=True) return self.keepalive diff --git a/copyparty/svchub.py b/copyparty/svchub.py index 48e1c130..287dc672 100644 --- a/copyparty/svchub.py +++ b/copyparty/svchub.py @@ -2,7 +2,9 @@ from __future__ import print_function, unicode_literals import argparse +import base64 import calendar +import gzip import os import shlex import signal @@ -27,7 +29,7 @@ from .mtag import HAVE_FFMPEG, HAVE_FFPROBE from .tcpsrv import TcpSrv from .th_srv import HAVE_PIL, HAVE_VIPS, HAVE_WEBP, ThumbSrv from .up2k import Up2k -from .util import ansi_re, min_ex, mp, start_log_thrs, start_stackmon +from .util import ansi_re, min_ex, mp, start_log_thrs, start_stackmon, alltrace class SvcHub(object): @@ -56,6 +58,7 @@ class SvcHub(object): self.log_mutex = threading.Lock() self.next_day = 0 + self.tstack = 0.0 if args.sss or args.s >= 3: args.ss = True @@ -500,3 +503,15 @@ class SvcHub(object): sck.sendall(b"READY=1") except: self.log("sd_notify", min_ex()) + + def log_stacks(self) -> None: + td = time.time() - self.tstack + if td < 300: + self.log("stacks", "cooldown {}".format(td)) + return + + self.tstack = time.time() + zb = alltrace().encode("utf-8", "replace") + zb = gzip.compress(zb) + zs = base64.b64encode(zb).decode("ascii") + self.log("stacks", zs) diff --git a/copyparty/up2k.py b/copyparty/up2k.py index 0b45895f..ccddcd19 100644 --- a/copyparty/up2k.py +++ b/copyparty/up2k.py @@ -98,6 +98,7 @@ class Up2k(object): self.gid = 0 self.stop = False self.mutex = threading.Lock() + self.blocked: Optional[str] = None self.pp: Optional[ProgressPrinter] = None self.rescan_cond = threading.Condition() self.need_rescan: set[str] = set() @@ -193,6 +194,14 @@ class Up2k(object): def log(self, msg: str, c: Union[int, str] = 0) -> None: self.log_func("up2k", msg + "\033[K", c) + def _block(self, why: str) -> None: + self.blocked = why + self.log("uploads are temporarily blocked due to " + why, 3) + + def _unblock(self) -> None: + self.blocked = None + self.log("uploads are now possible", 2) + def get_state(self) -> str: mtpq: Union[int, str] = 0 q = "select count(w) from mt where k = 't:mtp'" @@ -416,6 +425,9 @@ class Up2k(object): self.mtag = None # e2ds(a) volumes first + if next((zv for zv in vols if "e2ds" in zv.flags), None): + self._block("indexing") + for vol in vols: if self.stop: break @@ -445,6 +457,9 @@ class Up2k(object): self.volstate[vol.vpath] = t # file contents verification + if next((zv for zv in vols if "e2v" in zv.flags), None): + self._block("integrity verification") + for vol in vols: if self.stop: break @@ -468,6 +483,9 @@ class Up2k(object): self.volstate[vol.vpath] = t + if self.blocked: + self._unblock() + # open the rest + do any e2ts(a) needed_mutagen = False for vol in vols: @@ -1485,11 +1503,24 @@ class Up2k(object): cur.connection.commit() + def _job_volchk(self, cj: dict[str, Any]) -> None: + if not self.register_vpath(cj["ptop"], cj["vcfg"]): + if cj["ptop"] not in self.registry: + raise Pebkac(410, "location unavailable") + def handle_json(self, cj: dict[str, Any]) -> dict[str, Any]: - with self.mutex: - if not self.register_vpath(cj["ptop"], cj["vcfg"]): - if cj["ptop"] not in self.registry: - raise Pebkac(410, "location unavailable") + try: + # bit expensive; 3.9=10x 3.11=2x + if self.mutex.acquire(timeout=10): + self._job_volchk(cj) + self.mutex.release() + else: + t = "cannot receive uploads right now;\nserver busy with {}.\nPlease wait; the client will retry..." + raise Pebkac(503, t.format(self.blocked or "[unknown]")) + except TypeError: + # py2 + with self.mutex: + self._job_volchk(cj) cj["name"] = sanitize_fn(cj["name"], "", [".prologue.html", ".epilogue.html"]) cj["poke"] = time.time() diff --git a/copyparty/web/browser.css b/copyparty/web/browser.css index 34477de2..c32d9ad7 100644 --- a/copyparty/web/browser.css +++ b/copyparty/web/browser.css @@ -2244,6 +2244,7 @@ html.y #bbox-overlay figcaption a { max-width: none; } #u2tab td { + word-wrap: break-word; border: 1px solid rgba(128,128,128,0.8); border-width: 0 0px 1px 0; padding: .2em .3em; @@ -2258,7 +2259,19 @@ html.y #bbox-overlay figcaption a { #u2tab.up.ok td:nth-child(3), #u2tab.up.bz td:nth-child(3), #u2tab.up.q td:nth-child(3) { - width: 19em; + width: 18em; +} +@media (max-width: 65em) { + #u2tab { + font-size: .9em; + } +} +@media (max-width: 50em) { + #u2tab.up.ok td:nth-child(3), + #u2tab.up.bz td:nth-child(3), + #u2tab.up.q td:nth-child(3) { + width: 16em; + } } #op_up2k.srch td.prog { font-family: sans-serif;