diff --git a/.gitignore b/.gitignore index b70c56b7..2f48b149 100644 --- a/.gitignore +++ b/.gitignore @@ -43,6 +43,7 @@ scripts/docker/*.err # nix build output link result +result-* # IDEA config .idea/ diff --git a/README.md b/README.md index 63227835..adcba825 100644 --- a/README.md +++ b/README.md @@ -2924,6 +2924,11 @@ if [cfssl](https://github.com/cloudflare/cfssl/releases/latest) is installed, co * this will be a self-signed certificate so you must install your `ca.pem` into all your browsers/devices * if you want to avoid the hassle of distributing certs manually, please consider using a reverse proxy +to install cfssl on windows: +* [download](https://github.com/cloudflare/cfssl/releases/latest) `cfssl_windows_amd64.exe`, `cfssljson_windows_amd64.exe`, `cfssl-certinfo_windows_amd64.exe` +* rename them to `cfssl.exe`, `cfssljson.exe`, `cfssl-certinfo.exe` +* put them in PATH, for example inside `c:\windows\system32` + # recovering from crashes diff --git a/bin/hooks/usb-eject.py b/bin/hooks/usb-eject.py index 6bfd92a4..a012098b 100644 --- a/bin/hooks/usb-eject.py +++ b/bin/hooks/usb-eject.py @@ -34,7 +34,10 @@ MOUNT_BASE = b"/run/media/egon/" def main(): try: - label = sys.argv[1].split(":usb-eject:")[1].split(":")[0] + msg = sys.argv[1] + if msg.startswith("upload-queue-empty;"): + return + label = msg.split(":usb-eject:")[1].split(":")[0] mp = MOUNT_BASE + unquote(label) # print("ejecting [%s]... " % (mp,), end="") mp = os.path.abspath(os.path.realpath(mp)) diff --git a/bin/hooks/wget-i.py b/bin/hooks/wget-i.py index 6bb2b5da..86855025 100755 --- a/bin/hooks/wget-i.py +++ b/bin/hooks/wget-i.py @@ -62,6 +62,9 @@ def do_stuff(inf): log = inf["log"] url = inf["txt"] + if url.startswith("upload-queue-empty;"): + return + if "://" not in url: url = "https://" + url diff --git a/bin/hooks/wget.py b/bin/hooks/wget.py index 1f5a824b..ab42226a 100755 --- a/bin/hooks/wget.py +++ b/bin/hooks/wget.py @@ -47,6 +47,9 @@ while you're in the /inc folder (or any folder below there) def main(): inf = json.loads(sys.argv[1]) url = inf["txt"] + if url.startswith("upload-queue-empty;"): + return + if "://" not in url: url = "https://" + url diff --git a/contrib/package/nix/copyparty/default.nix b/contrib/package/nix/copyparty/default.nix index 22ae5bcd..702431d6 100644 --- a/contrib/package/nix/copyparty/default.nix +++ b/contrib/package/nix/copyparty/default.nix @@ -67,18 +67,64 @@ # additional dependencies extraPythonPackages ? (_p: [ ]), + # to build stable + unstable with the same file + stable ? true, + + # for commit date, only used when stable = false + copypartyFlake ? null, + + nix-gitignore, }: let pinData = lib.importJSON ./pin.json; runtimeDeps = ([ util-linux ] ++ extraPackages ++ lib.optional withMediaProcessing ffmpeg); + inherit (copypartyFlake) lastModifiedDate; + # ex: "1970" "01" "01" + dateStringsZeroPrefixed = { + year = builtins.substring 0 4 lastModifiedDate; + month = builtins.substring 4 2 lastModifiedDate; + day = builtins.substring 6 2 lastModifiedDate; + }; + # ex: "1970" "1" "1" + dateStringsShort = builtins.mapAttrs (_: val: toString (lib.toIntBase10 val)) dateStringsZeroPrefixed; + unstableVersion = + if copypartyFlake == null then + "${pinData.version}-unstable" + else + with dateStringsZeroPrefixed; "${pinData.version}-unstable-${year}-${month}-${day}" + ; + version = if stable then pinData.version else unstableVersion; + stableSrc = fetchurl { + inherit (pinData) url hash; + }; + root = ../../../..; + unstableSrc = nix-gitignore.gitignoreSource [] root; + src = if stable then stableSrc else unstableSrc; + rev = copypartyFlake.shortRev or copypartyFlake.dirtyShortRev or "unknown"; + unstableCodename = "unstable" + (lib.optionalString (copypartyFlake != null) "-${rev}"); in buildPythonApplication { pname = "copyparty"; - inherit (pinData) version; - src = fetchurl { - inherit (pinData) url hash; - }; + inherit version src; + postPatch = lib.optionalString (!stable) '' + old_src="$(mktemp -d)" + tar -C "$old_src" -xf ${stableSrc} + declare -a folders + folders=("$old_src"/*) + count_folders="''${#folders[@]}" + if [[ $count_folders != 1 ]]; then + declare -p folders + echo "Expected 1 folder, found $count_folders" >&2 + exit 1 + fi + old_src_folder="''${folders[0]}" + cp -r "$old_src_folder"/copyparty/web/deps copyparty/web/deps + sed -i 's/^CODENAME =.*$/CODENAME = "${unstableCodename}"/' copyparty/__version__.py + ${lib.optionalString (copypartyFlake != null) (with dateStringsShort; '' + sed -i 's/^BUILD_DT =.*$/BUILD_DT = (${year}, ${month}, ${day})/' copyparty/__version__.py + '')} + ''; dependencies = [ jinja2 diff --git a/contrib/package/nix/overlay.nix b/contrib/package/nix/overlay.nix index ca8d5c6e..7b951c5b 100644 --- a/contrib/package/nix/overlay.nix +++ b/contrib/package/nix/overlay.nix @@ -1,8 +1,28 @@ -final: prev: { - copyparty = final.python3.pkgs.callPackage ./copyparty { - ffmpeg = final.ffmpeg-full; +final: prev: +let + fullAttrs = { + withHashedPasswords = true; + withCertgen = true; + withThumbnails = true; + withFastThumbnails = true; + withMediaProcessing = true; + withBasicAudioMetadata = true; + withZeroMQ = true; + withFTP = true; + withFTPS = true; + withTFTP = true; + withSMB = true; + withMagic = true; }; + call = attrs: final.python3.pkgs.callPackage ./copyparty ({ ffmpeg = final.ffmpeg-full; } // attrs); +in +{ + copyparty = call { stable = true; }; + copyparty-unstable = call { stable = false; }; + copyparty-full = call (fullAttrs // { stable = true; }); + copyparty-unstable-full = call (fullAttrs // { stable = false; }); + python3 = prev.python3.override { packageOverrides = pyFinal: pyPrev: { partftpy = pyFinal.callPackage ./partftpy { }; diff --git a/copyparty/cert.py b/copyparty/cert.py index 49fa9ea2..18859536 100644 --- a/copyparty/cert.py +++ b/copyparty/cert.py @@ -2,6 +2,7 @@ import calendar import errno import json import os +import shutil import time from .__init__ import ANYWIN @@ -19,6 +20,19 @@ else: VF = {"mv_re_t": 0, "rm_re_t": 0} +def _sp_err(exe, what, rc, so, se, sin): + try: + zs = shutil.which(exe) + except: + zs = ">" + try: + zi = os.path.getsize(zs) + except: + zi = 0 + t = "failed to %s; error %s using %s (%s):\n STDOUT: %s\n STDERR: %s\n STDIN: %s\n" + raise Exception(t % (what, rc, zs, zi, so, se, sin.decode("utf-8"))) + + def ensure_cert(log: "RootLogger", args) -> None: """ the default cert (and the entire TLS support) is only here to enable the @@ -107,13 +121,13 @@ def _gen_ca(log: "RootLogger", args): cmd = "cfssl gencert -initca -" rc, so, se = runcmd(cmd.split(), 30, sin=sin) if rc: - raise Exception("failed to create ca-cert: {}, {}".format(rc, se), 3) + _sp_err("cfssl", "create ca-cert", rc, so, se, sin) cmd = "cfssljson -bare ca" sin = so.encode("utf-8") rc, so, se = runcmd(cmd.split(), 10, sin=sin, cwd=args.crt_dir) if rc: - raise Exception("failed to translate ca-cert: {}, {}".format(rc, se), 3) + _sp_err("cfssljson", "translate ca-cert", rc, so, se, sin) bname = os.path.join(args.crt_dir, "ca") try: @@ -201,13 +215,13 @@ def _gen_srv(log: "RootLogger", args, netdevs: dict[str, Netdev]): acmd = cmd.split() + ["-hostname=" + ",".join(names), "-"] rc, so, se = runcmd(acmd, 30, sin=sin, cwd=args.crt_dir) if rc: - raise Exception("failed to create cert: {}, {}".format(rc, se)) + _sp_err("cfssl", "create cert", rc, so, se, sin) cmd = "cfssljson -bare srv" sin = so.encode("utf-8") rc, so, se = runcmd(cmd.split(), 10, sin=sin, cwd=args.crt_dir) if rc: - raise Exception("failed to translate cert: {}, {}".format(rc, se)) + _sp_err("cfssljson", "translate cert", rc, so, se, sin) bname = os.path.join(args.crt_dir, "srv") try: diff --git a/copyparty/web/browser.js b/copyparty/web/browser.js index 090ae45b..a29016c3 100644 --- a/copyparty/web/browser.js +++ b/copyparty/web/browser.js @@ -2449,7 +2449,7 @@ function mpause(e) { return true; dist *= -1; - mp.setvol(mp.vol + dist / 500); + mp.setvol(Math.round((mp.vol + dist / 500) * 100) / 100 ); vbar.draw(); ev(e); }; @@ -3019,9 +3019,6 @@ function play(tid, is_ev, seek) { } if (tn >= mp.order.length) { - if (mpl.pb_mode == 'stop') - return; - if (mpl.pb_mode == 'loop' || ebi('unsearch')) { tn = 0; } @@ -3029,6 +3026,7 @@ function play(tid, is_ev, seek) { treectl.ls_cb = next_song; return tree_neigh(1); } + else return; } if (tn < 0) { @@ -3039,6 +3037,7 @@ function play(tid, is_ev, seek) { treectl.ls_cb = last_song; return tree_neigh(-1); } + else return; } tid = mp.order[tn]; diff --git a/copyparty/web/up2k.js b/copyparty/web/up2k.js index d4dd375d..f843431a 100644 --- a/copyparty/web/up2k.js +++ b/copyparty/web/up2k.js @@ -2271,11 +2271,17 @@ function up2k_init(subtle) { busy = {}, nbusy = 0, init = 0, + ninit = 0, hashtab = {}, mem = (MOBILE ? 128 : 256) * 1024 * 1024; if (!hws_ok) - init = setTimeout(function() { + init = setInterval(function() { + if (ninit < hws_ok) { + ninit = hws_ok; + return toast.inf(10, 'initializing webworkers ({0}/{1})'.format(hws_ok, hws.length), "iwwt"); + } + clearInterval(init); hws_ng = true; toast.warn(30, 'webworkers failed to start\n\nwill be a bit slower due to\nhashing on main-thread'); apop(st.busy.hash, t); @@ -2325,12 +2331,17 @@ function up2k_init(subtle) { } function onmsg(d) { + if (hws_ng) + return; + d = d.data; var k = d[0]; if (k == "pong") if (++hws_ok == hws.length) { - clearTimeout(init); + clearInterval(init); + if (toast.tag == 'iwwt') + toast.hide(); go_next(); } diff --git a/docs/examples/docker/idp/copyparty.conf b/docs/examples/docker/idp/copyparty.conf index 3e842f02..40ccfba7 100644 --- a/docs/examples/docker/idp/copyparty.conf +++ b/docs/examples/docker/idp/copyparty.conf @@ -50,7 +50,7 @@ [/] # create a volume at "/" (the webroot), which will /w # share /w (the docker data volume) accs: - rw: * # everyone gets read-access, but + r: * # everyone gets read-access, but rwmda: @su # the group "su" gets read-write-move-delete-admin diff --git a/flake.nix b/flake.nix index 336642ee..d797ca9c 100644 --- a/flake.nix +++ b/flake.nix @@ -12,7 +12,8 @@ }: { nixosModules.default = ./contrib/nixos/modules/copyparty.nix; - overlays.default = import ./contrib/package/nix/overlay.nix; + overlays.default = final: prev: + (import ./contrib/package/nix/overlay.nix final prev) // { copypartyFlake = self; }; } // flake-utils.lib.eachDefaultSystem ( system: @@ -22,26 +23,26 @@ config = { allowAliases = false; }; - overlays = [ self.overlays.default ]; + overlays = [ + self.overlays.default + ]; }; in { # check that copyparty builds with all optionals turned on - checks.copyparty-full = self.packages.${system}.copyparty.override { - withHashedPasswords = true; - withCertgen = true; - withThumbnails = true; - withFastThumbnails = true; - withMediaProcessing = true; - withBasicAudioMetadata = true; - withZeroMQ = true; - withFTPS = true; - withSMB = true; + checks = { + inherit (pkgs) + copyparty-full + copyparty-unstable-full + ; }; packages = { inherit (pkgs) copyparty + copyparty-full + copyparty-unstable + copyparty-unstable-full ; default = self.packages.${system}.copyparty; }; diff --git a/scripts/genhelp.sh b/scripts/genhelp.sh index 0ecb6603..e9d8412e 100755 --- a/scripts/genhelp.sh +++ b/scripts/genhelp.sh @@ -1,6 +1,12 @@ #!/bin/bash set -e +command -v gfind >/dev/null && +command -v gsed >/dev/null && +command -v gsort >/dev/null && { + sed() { gsed "$@"; } +} + [ -e make-sfx.sh ] || cd scripts [ -e make-sfx.sh ] && [ -e deps-docker ] || { echo cd into the scripts folder first diff --git a/scripts/help2html.py b/scripts/help2html.py index f39332ed..2ddb88be 100755 --- a/scripts/help2html.py +++ b/scripts/help2html.py @@ -37,7 +37,12 @@ def cnv(src): hostname = str(socket.gethostname()).split(".")[0] yield '' - yield '
' + yield '' skip_sfx = False in_sfx = 0 in_salt = 0 @@ -104,6 +109,15 @@ def cnv(src): ln = re.sub(r">[0-9]{1,2}\.[0-9]<", ">dynamic<", ln) if t != ln: in_th_ram_max = 0 + m = re.search(r"^# (.* help page)(.*)", ln) + if m: + zs1, zs2 = m.groups() + zs3 = zs1.replace(" ", "-") + ln = '