From f19ef033ec43ca06ac60e6c8d0afdd416d77a73e Mon Sep 17 00:00:00 2001 From: ed Date: Sat, 15 Aug 2026 09:45:13 +0000 Subject: [PATCH] up2k: u2sz: allow 1 as max + detect http413 --- README.md | 20 +++++++++++++++++++- copyparty/__main__.py | 2 +- copyparty/svchub.py | 16 +++++++++------- copyparty/web/browser.js | 4 ++-- copyparty/web/md2.js | 3 +++ copyparty/web/up2k.js | 20 +++++++++++++------- copyparty/web/util.js | 15 ++++++++++++++- 7 files changed, 61 insertions(+), 19 deletions(-) diff --git a/README.md b/README.md index 76e97266..f73d7c65 100644 --- a/README.md +++ b/README.md @@ -84,6 +84,7 @@ built in Norway 🇳🇴 with contributions from [not-norway](https://github.com * [exclude-patterns](#exclude-patterns) - to save some time * [filesystem guards](#filesystem-guards) - avoid traversing into other filesystems * [periodic rescan](#periodic-rescan) - filesystem monitoring + * [u2sz](#u2sz) - configure upload-chunking * [upload rules](#upload-rules) - set upload rules using volflags * [compress uploads](#compress-uploads) - files can be autocompressed on upload * [chmod and chown](#chmod-and-chown) - per-volume filesystem-permissions and ownership @@ -922,7 +923,7 @@ note that since up2k has to read each file twice, `[🎈] bup` can *theoreticall if you are resuming a massive upload and want to skip hashing the files which already finished, you can enable `turbo` in the `[⚙️] config` tab, but please read the tooltip on that button -if the server is behind a proxy which imposes a request-size limit, you can configure up2k to sneak below the limit with server-option `--u2sz` (the default is 96 MiB to support Cloudflare) +if the server is behind a proxy which imposes a request-size limit, you can configure up2k to sneak below the limit with server-option [--u2sz](#u2sz) if you want to replace existing files on the server with new uploads by default, run with `--u2ow 2` (only works if users have the delete-permission, and can still be disabled with `🛡️` in the UI) @@ -1826,6 +1827,23 @@ config file example: ``` +## u2sz + +configure upload-chunking; the default `64,96` is optimal for nearly every scenario, however: + +* if you are seeing the message `bad server config; see "u2sz" in readme` then you have a proxy/CDN/whatever with a `max-request-body-size` which is too small, and you must either increase the limit in the reverseproxy config to at least 96 MiB (recommended) or reduce the target chunksize in copyparty (continue reading) + +the global-option `--u2sz` is two numbers, `target,max` MiB of each chunk to send, aiming for `target` MiB and allowing up-to-and-including `max` MiB [depending on filesize](./docs/devnotes.md#list-of-chunk-sizes), and if you only specify one number then it sets that for both + +cloudflare max is 96 MiB, but if you have a reverseproxy (or CDN/WAF/whatever) which is more restrictive, then try some of the following: + +* `u2sz: 32` is still acceptable performance in most cases +* `u2sz: 8` is the lowest recommended value; anything lower will reduce performance over long physical distances and/or shitty routes +* `u2sz: 1` is the absolute minimum, and will be slow in many cases + +the setting can also be changed in the web-UI; the textbox next to `date-chk` in the settings tab + + ## upload rules set upload rules using volflags, some examples: diff --git a/copyparty/__main__.py b/copyparty/__main__.py index ac6dd894..e28a12c5 100644 --- a/copyparty/__main__.py +++ b/copyparty/__main__.py @@ -1338,7 +1338,7 @@ def add_upload(ap): ap2.add_argument("--turbo", metavar="LVL", type=int, default=0, help="configure turbo-mode in up2k client; [\033[32m-1\033[0m] = forbidden/always-off, [\033[32m0\033[0m] = default-off and warn if enabled, [\033[32m1\033[0m] = default-off, [\033[32m2\033[0m] = on, [\033[32m3\033[0m] = on and disable datecheck") ap2.add_argument("--nosubtle", metavar="N", type=int, default=0, help="when to use a wasm-hasher instead of the browser's builtin; faster on chrome, but buggy in older chrome versions. [\033[32m0\033[0m] = only when necessary (non-https), [\033[32m1\033[0m] = always (all browsers), [\033[32m2\033[0m] = always on chrome/firefox, [\033[32m3\033[0m] = always on chrome, [\033[32mN\033[0m] = chrome-version N and newer (recommendation: 137)") ap2.add_argument("--u2j", metavar="JOBS", type=int, default=2, help="web-client: number of file chunks to upload in parallel; 1 or 2 is good when latency is low (same-country), 2~4 for android-clients, 2~6 for cross-atlantic. Max is 6 in most browsers. Big values increase network-speed but may reduce HDD-speed") - ap2.add_argument("--u2sz", metavar="N,N,N", type=u, default="1,64,96", help="web-client: default upload chunksize (MiB); sets \033[33mmin,default,max\033[0m in the settings gui. Each HTTP POST will aim for \033[33mdefault\033[0m, and never exceed \033[33mmax\033[0m. Cloudflare max is 96. Big values are good for cross-atlantic but may increase HDD fragmentation on some FS. Disable this optimization with [\033[32m1,1,1\033[0m]") + ap2.add_argument("--u2sz", metavar="N,N", type=u, default="64,96", help="web-client: default upload chunksize in MiB, either \033[33mtarget,max\033[0m or just \033[33mtarget\033[0m (sets both); this is \033[33mtarget\033[0m in the web-UI. Each HTTP POST will aim for \033[33mtarget\033[0m and never exceed \033[33mmax\033[0m. Cloudflare max is 96. Minimum is [\033[32m1\033[0m] and will be slow. Lowest recommended is [\033[32m8\033[0m]. Big values are good for cross-atlantic but may increase HDD fragmentation on some FS. Disable this optimization with [\033[32mno\033[0m]") ap2.add_argument("--u2ow", metavar="NUM", type=int, default=0, help="web-client: default setting for when to replace/overwrite existing files; [\033[32m0\033[0m]=never, [\033[32m1\033[0m]=if-client-newer, [\033[32m2\033[0m]=always (volflag=u2ow)") ap2.add_argument("--u2sort", metavar="TXT", type=u, default="s", help="upload order; [\033[32ms\033[0m]=smallest-first, [\033[32mn\033[0m]=alphabetical, [\033[32mfs\033[0m]=force-s, [\033[32mfn\033[0m]=force-n -- alphabetical is a bit slower on fiber/LAN but makes it easier to eyeball if everything went fine") ap2.add_argument("--write-uplog", action="store_true", help="write POST reports to textfiles in working-directory") diff --git a/copyparty/svchub.py b/copyparty/svchub.py index 092309d0..9c5ecc30 100644 --- a/copyparty/svchub.py +++ b/copyparty/svchub.py @@ -1383,18 +1383,20 @@ class SvcHub(object): self.args.name_html = zs zs = al.u2sz + if zs in ("no", "1,1,1"): + zs = "0" zsl = [x.strip() for x in zs.split(",")] - if len(zsl) not in (1, 3): - t = "invalid --u2sz; must be either one number, or a comma-separated list of three numbers (min,default,max)" + if len(zsl) not in (1, 2): + t = "invalid --u2sz; must be either one number, or a comma-separated list of two numbers (target,max)" raise Exception(t) - if len(zsl) < 3: - zsl = ["1", zs, zs] - zi2 = 1 + if len(zsl) < 2: + zsl = [zs, zs] + zi2 = 0 for zs in zsl: zi = int(zs) # arbitrary constraint (anything above 2 GiB is probably unintended) - if zi < 1 or zi > 2047: - raise Exception("invalid --u2sz; minimum is 1, max is 2047") + if zi < 0 or zi > 2047: + raise Exception("invalid --u2sz; minimum is 0, max is 2047") if zi < zi2: raise Exception("invalid --u2sz; values must be equal or ascending") zi2 = zi diff --git a/copyparty/web/browser.js b/copyparty/web/browser.js index 017c72f8..dccff93d 100644 --- a/copyparty/web/browser.js +++ b/copyparty/web/browser.js @@ -758,7 +758,7 @@ if (window.glang && navigator.languages && !/\bcplng=/.test(document.cookie)) if (window.stop) window.stop(); document.body.innerHTML = 'Loading ' + n; - setck("cplng=" + n, location.reload.bind(location)); + setck("cplng=" + n, relod); crashed = true; throw 1; } @@ -8626,7 +8626,7 @@ var setfszf = (function () { setck('cplng=' + lang); freshen(); var t = L.tt == 'English' ? '' : Ls.eng.lang_set; - modal.confirm(L.lang_set + "\n\n" + t, location.reload.bind(location), null); + modal.confirm(L.lang_set + "\n\n" + t, relod, null); } freshen(); diff --git a/copyparty/web/md2.js b/copyparty/web/md2.js index 246ab6dc..e625146c 100644 --- a/copyparty/web/md2.js +++ b/copyparty/web/md2.js @@ -325,6 +325,9 @@ var modpoll = new Modpoll(); window.onbeforeunload = function (e) { + if (window.gtfo) + return; + if ((ebi("save").className + '').indexOf('disabled') >= 0) return; //nice (todo) diff --git a/copyparty/web/up2k.js b/copyparty/web/up2k.js index 28146593..02025607 100644 --- a/copyparty/web/up2k.js +++ b/copyparty/web/up2k.js @@ -888,9 +888,8 @@ function up2k_init(subtle) { setmsg(suggest_up2k, 'msg'); var u2szs = u2sz.split(','), - u2sz_min = parseInt(u2szs[0]), - u2sz_tgt = parseInt(u2szs[1]), - u2sz_max = parseInt(u2szs[2]); + u2sz_tgt = parseInt(u2szs[0]), + u2sz_max = parseInt(u2szs[1]); var parallel_uploads = ebi('nthread').value = icfg_get('nthread', u2j), stitch_tgt = ebi('u2szg').value = icfg_get('u2sz', u2sz_tgt), @@ -2677,7 +2676,7 @@ function up2k_init(subtle) { ccs += stp; stp *= 2; } ocs = Math.floor(ocs / 1024 / 1024); - t.stitch_sz = Math.min(ocs, stitch_tgt); + t.stitch_sz = Math.min(ocs, stitch_tgt || 1); } for (var a = 0; a < t.postlist.length; a++) { @@ -2879,7 +2878,8 @@ function up2k_init(subtle) { nparts = upt.nparts, pcar = nparts[0], pcdr = nparts[nparts.length - 1], - maxsz = (u2sz_max > 1 ? u2sz_max : 2040) * 1024 * 1024; + maxmsz = stitch_tgt == u2sz_tgt ? u2sz_max : stitch_tgt, + maxsz = (maxmsz > 0 ? maxmsz : 2040) * 1024 * 1024; if (t.done) return console.log('done; skip chunk', t.name, t); @@ -2951,6 +2951,9 @@ function up2k_init(subtle) { st.etac.u++; st.etac.t++; } + else if (xhr.status == 413) { + modal.die('bad server config; see "u2sz" in readme'); + } else if (txt.indexOf('already got that') + 1) { unqueue_up(t); } @@ -3163,7 +3166,7 @@ function up2k_init(subtle) { var el = ebi('u2szg'), n = parseInt(el.value); stitch_tgt = n = ( isNaN(n) ? u2sz_tgt : - n < u2sz_min ? u2sz_min : + n < 0 ? 0 : n > u2sz_max ? u2sz_max : n ); if (n == u2sz_tgt) sdrop('u2sz'); else swrite('u2sz', n); @@ -3400,7 +3403,7 @@ function up2k_init(subtle) { function set_nosubtle(v) { if (!WebAssembly) return toast.err(10, L.u_nowork); - modal.confirm(L.lang_set, location.reload.bind(location), null); + modal.confirm(L.lang_set, relod, null); } function set_upnag(en) { @@ -3471,6 +3474,9 @@ function up2k_init(subtle) { function warn_uploader_busy(e) { + if (window.gtfo) + return; + e.preventDefault(); e.returnValue = ''; return "upload in progress, click abort and use the file-tree to navigate instead"; diff --git a/copyparty/web/util.js b/copyparty/web/util.js index 5b2fad43..27bf24ce 100644 --- a/copyparty/web/util.js +++ b/copyparty/web/util.js @@ -108,6 +108,13 @@ function qsr(sel) { } +function relod() { + crashed = 1; + window.gtfo = 1; + location.href = location.href; +} + + // error handler for mobile devices function esc(txt) { return txt.replace(/[&"<>]/g, function (c) { @@ -289,7 +296,7 @@ function vis_exh(msg, url, lineNo, columnNo, error) { var x = ebi('exh_wipecfg'); if (x) x.onclick = function () { localStorage.clear(); - location.reload(); + relod(); }; x = ebi('exh_ignex'); if (x) x.onclick = ignex; x = ebi('exh_ignexa'); if (x) x.onclick = ignexa; @@ -2021,6 +2028,12 @@ var modal = (function () { q.shift()(); } + r.die = function (html) { + crashed = 1; + if (r.busy) + r.hide(); + _alert(lf2br(html), relod); + }; r.alert = function (html, cb, fun) { q.push(function () { _alert(lf2br(html), cb, fun);