From 0b87a4a810d28765edc3399e15742f4a91983c7e Mon Sep 17 00:00:00 2001 From: ed Date: Mon, 19 Sep 2022 23:49:07 +0200 Subject: [PATCH] allow setting lifetimes from up2k ui --- README.md | 13 +++++++ copyparty/authsrv.py | 10 +++++ copyparty/httpcli.py | 6 ++- copyparty/up2k.py | 26 +++++++++++-- copyparty/web/browser.css | 31 ++++++++++++++-- copyparty/web/browser.html | 3 +- copyparty/web/browser.js | 47 ++++++++++++++++++++--- copyparty/web/up2k.js | 70 ++++++++++++++++++++++++++++++++++- copyparty/web/util.js | 28 +++++++++++--- scripts/pyinstaller/build.sh | 2 +- scripts/pyinstaller/loader.py | 4 +- 11 files changed, 214 insertions(+), 26 deletions(-) diff --git a/README.md b/README.md index 5d3bdc1e..56494d92 100644 --- a/README.md +++ b/README.md @@ -49,6 +49,7 @@ try the **[read-only demo server](https://a.ocv.me/pub/demo/)** 👀 running fro * [uploading](#uploading) - drag files/folders into the web-browser to upload * [file-search](#file-search) - dropping files into the browser also lets you see if they exist on the server * [unpost](#unpost) - undo/delete accidental uploads + * [self-destruct](#self-destruct) - uploads can be given a lifetime * [file manager](#file-manager) - cut/paste, rename, and delete files/folders (if you have permission) * [batch rename](#batch-rename) - select some files and press `F2` to bring up the rename UI * [markdown viewer](#markdown-viewer) - and there are *two* editors @@ -168,6 +169,7 @@ feature summary * ☑ [up2k](#uploading): js, resumable, multithreaded * ☑ stash: simple PUT filedropper * ☑ [unpost](#unpost): undo/delete accidental uploads + * ☑ [self-destruct](#self-destruct) (specified server-side or client-side) * ☑ symlink/discard existing files (content-matching) * download * ☑ single files in browser @@ -543,6 +545,17 @@ undo/delete accidental uploads you can unpost even if you don't have regular move/delete access, however only for files uploaded within the past `--unpost` seconds (default 12 hours) and the server must be running with `-e2d` +### self-destruct + +uploads can be given a lifetime, afer which they expire / self-destruct + +the feature must be enabled per-volume with the `lifetime` [upload rule](#upload-rules) which sets the upper limit for how long a file gets to stay on the server + +clients can specify a shorter expiration time using the [up2k ui](#uploading) -- the relevant options become visible upon navigating into a folder with `lifetimes` enabled -- or by using the `life` [upload modifier](#write) + +specifying a custom expiration time client-side will affect the timespan in which unposts are permitted, so keep an eye on the estimates in the up2k ui + + ## file manager cut/paste, rename, and delete files/folders (if you have permission) diff --git a/copyparty/authsrv.py b/copyparty/authsrv.py index e612675a..5a31b38c 100644 --- a/copyparty/authsrv.py +++ b/copyparty/authsrv.py @@ -1121,6 +1121,16 @@ class AuthSrv(object): vol.flags = {k: v for k, v in vol.flags.items() if not k.startswith(rm)} + ints = ["lifetime"] + for k in list(vol.flags): + if k in ints: + vol.flags[k] = int(vol.flags[k]) + + if "lifetime" in vol.flags and "e2d" not in vol.flags: + t = 'removing lifetime config from volume "/{}" because e2d is disabled' + self.log(t.format(vol.vpath), 1) + del vol.flags["lifetime"] + # verify tags mentioned by -mt[mp] are used by -mte local_mtp = {} local_only_mtp = {} diff --git a/copyparty/httpcli.py b/copyparty/httpcli.py index 3517b2ef..38de8184 100644 --- a/copyparty/httpcli.py +++ b/copyparty/httpcli.py @@ -1313,7 +1313,7 @@ class HttpCli(object): want_url = ac == "url" zs = self.uparam.get("life", self.headers.get("life", "")) if zs: - vlife = int(vfs.flags.get("lifetime") or 0) + vlife = vfs.flags.get("lifetime") or 0 lifetime = max(0, int(vlife - int(zs))) else: lifetime = 0 @@ -2495,6 +2495,7 @@ class HttpCli(object): "srvinf": srv_infot, "acct": self.uname, "idx": ("e2d" in vn.flags), + "lifetime": vn.flags.get("lifetime") or 0, "perms": perms, "logues": logues, "readme": readme, @@ -2506,6 +2507,7 @@ class HttpCli(object): "ls0": None, "acct": self.uname, "perms": json.dumps(perms), + "lifetime": ls_ret["lifetime"], "taglist": [], "def_hcols": [], "have_emp": self.args.emp, @@ -2515,7 +2517,7 @@ class HttpCli(object): "have_mv": (not self.args.no_mv), "have_del": (not self.args.no_del), "have_zip": (not self.args.no_zip), - "have_unpost": (self.args.unpost > 0), + "have_unpost": int(self.args.unpost), "have_b_u": (self.can_write and self.uparam.get("b") == "u"), "url_suf": url_suf, "logues": logues, diff --git a/copyparty/up2k.py b/copyparty/up2k.py index e7e72110..17943184 100644 --- a/copyparty/up2k.py +++ b/copyparty/up2k.py @@ -351,11 +351,9 @@ class Up2k(object): if not cur: continue - lifetime = int(lifetime) - timeout = min(timeout, now + lifetime) - nrm = 0 deadline = time.time() - lifetime + timeout = min(timeout, now + lifetime) q = "select rd, fn from up where at > 0 and at < ? limit 100" while True: with self.mutex: @@ -1960,6 +1958,7 @@ class Up2k(object): "sprs": sprs, # dontcare; finished anyways "size": dsize, "lmod": dtime, + "life": cj.get("life"), "addr": ip, "at": at, "hash": [], @@ -2072,6 +2071,7 @@ class Up2k(object): "name", "size", "lmod", + "life", "poke", ]: job[k] = cj[k] @@ -2293,12 +2293,30 @@ class Up2k(object): pass z2 = [job[x] for x in "ptop wark prel name lmod size addr".split()] - z2 += [job.get("at") or time.time()] + upt = job.get("at") or time.time() + wake_sr = False + try: + flt = job["life"] + vfs = self.asrv.vfs.all_vols[job["vtop"]] + vlt = vfs.flags["lifetime"] + if vlt and flt < vlt: + upt -= vlt - flt + wake_sr = True + t = "using client lifetime; at={:.0f} ({}-{})" + self.log(t.format(upt, vlt, flt)) + except: + pass + + z2 += [upt] if self.idx_wark(*z2): del self.registry[ptop][wark] else: self.regdrop(ptop, wark) + if wake_sr: + with self.rescan_cond: + self.rescan_cond.notify_all() + dupes = self.dupesched.pop(dst, []) if not dupes: return diff --git a/copyparty/web/browser.css b/copyparty/web/browser.css index f2eb5d0f..c2e40414 100644 --- a/copyparty/web/browser.css +++ b/copyparty/web/browser.css @@ -2498,13 +2498,34 @@ html.b #u2conf a.b:hover { text-align: center; border: .2em dashed rgba(128, 128, 128, 0.3); } -#u2foot { +#u2foot, +#u2life { color: var(--fg-max); - font-style: italic; text-align: center; - font-size: 1.2em; margin: .8em 0; } +#u2life { + margin: 2.5em 0; + line-height: 1.5em; +} +#u2life div { + display: inline-block; + white-space: nowrap; + margin: 0 2em; +} +#u2life div:first-child { + margin-bottom: .2em; +} +#u2life small { + opacity: .6; +} +#lifew { + border-bottom: 1px dotted var(--fg-max); +} +#u2foot { + font-size: 1.2em; + font-style: italic; +} #u2foot .warn { font-size: 1.2em; padding: .5em .8em; @@ -2523,6 +2544,10 @@ html.b #u2conf a.b:hover { #u2foot>*+* { margin-top: 1.5em; } +#u2life input { + width: 4em; + text-align: right; +} .prog { font-family: 'scp', monospace, monospace; } diff --git a/copyparty/web/browser.html b/copyparty/web/browser.html index 39285bae..27a3d529 100644 --- a/copyparty/web/browser.html +++ b/copyparty/web/browser.html @@ -146,8 +146,9 @@ have_acode = {{ have_acode|tojson }}, have_mv = {{ have_mv|tojson }}, have_del = {{ have_del|tojson }}, - have_unpost = {{ have_unpost|tojson }}, + have_unpost = {{ have_unpost }}, have_zip = {{ have_zip|tojson }}, + lifetime = {{ lifetime }}, turbolvl = {{ turbolvl }}, u2sort = "{{ u2sort }}", have_emp = {{ have_emp|tojson }}, diff --git a/copyparty/web/browser.js b/copyparty/web/browser.js index 27b6a0d2..65d9d475 100644 --- a/copyparty/web/browser.js +++ b/copyparty/web/browser.js @@ -25,6 +25,12 @@ var Ls = { "hz": "sample rate" }, + "ht_s": "second!s", + "ht_m": "minute!s", + "ht_h": "hour!s", + "ht_d": "day!s", + "ht_and": " and ", + "goh": "control-panel", "logout": "Logout ", "access": " access", @@ -311,7 +317,8 @@ var Ls = { "u_badf": 'These {0} files (of {1} total) were skipped, possibly due to filesystem permissions:\n\n', "u_blankf": 'These {0} files (of {1} total) are blank / empty; upload them anyways?\n\n', "u_just1": '\nMaybe it works better if you select just one file', - "u_ff_many": "This amount of files may cause Firefox to skip some files, or crash.\nPlease try again with fewer files (or use Chrome) if that happens.\n\n", + "u_ff_many": "This amount of files may cause Firefox to skip some files, or crash.\nPlease try again with fewer files (or use Chrome) if that happens.", + "u_up_life": "This upload will be deleted from the server\n{0} after it completes", "u_asku": 'upload these {0} files to {1}', "u_unpt": "you can undo / delete this upload using the top-left 🧯", "u_etadone": 'Done ({0}, {1} files)', @@ -338,6 +345,11 @@ var Ls = { "u_expl": "explain", "u_tu": '

WARNING: turbo enabled,  client may not detect and resume incomplete uploads; see turbo-button tooltip

', "u_ts": '

WARNING: turbo enabled,  search results can be incorrect; see turbo-button tooltip

', + "u_life_cfg": 'autodelete after min (or hours)', + "u_life_est": 'upload will be deleted ---', + "u_life_max": 'this folder enforces a\nmax lifetime of {0}', + "u_unp_ok": 'unpost is allowed for {0}', + "u_unp_ng": 'unpost will NOT be allowed', "ue_ro": 'your access to this folder is Read-Only\n\n', "ue_nl": 'you are currently not logged in', "ue_la": 'you are currently logged in as "{0}"', @@ -379,6 +391,12 @@ var Ls = { "hz": "lyd-oppløsning" }, + "ht_s": "sekund!er", + "ht_m": "minutt!er", + "ht_h": "time!r", + "ht_d": "dag!er", + "ht_and": " og ", + "goh": "kontrollpanel", "logout": "Logg ut ", "access": " tilgang", @@ -665,7 +683,8 @@ var Ls = { "u_badf": 'Disse {0} filene (av totalt {1}) kan ikke leses, kanskje pga rettighetsproblemer i filsystemet på datamaskinen din:\n\n', "u_blankf": 'Disse {0} filene (av totalt {1}) er blanke / uten innhold; ønsker du å laste dem opp uansett?\n\n', "u_just1": '\nFunker kanskje bedre hvis du bare tar én fil om gangen', - "u_ff_many": "Det var mange filer! Mulig at Firefox kommer til å krasje, eller\nhoppe over et par av dem. Smart å ha Chrome på lur i tilfelle.\n\n", + "u_ff_many": "Det var mange filer! Mulig at Firefox kommer til å krasje, eller\nhoppe over et par av dem. Smart å ha Chrome på lur i tilfelle.", + "u_up_life": "Filene slettes fra serveren {0}\netter at opplastningen er fullført", "u_asku": 'Laste opp disse {0} filene til {1}', "u_unpt": "Du kan angre / slette opplastningen med 🧯 oppe til venstre", "u_etadone": 'Ferdig ({0}, {1} filer)', @@ -692,6 +711,11 @@ var Ls = { "u_expl": "forklar", "u_tu": '

ADVARSEL: turbo er pÃ¥,  avbrutte opplastninger vil muligens ikke oppdages og gjenopptas; hold musepekeren over turbo-knappen for mer info

', "u_ts": '

ADVARSEL: turbo er pÃ¥,  søkeresultater kan være feil; hold musepekeren over turbo-knappen for mer info

', + "u_life_cfg": 'slett opplastning etter min (eller timer)', + "u_life_est": 'opplastningen slettes ---', + "u_life_max": 'denne mappen tillater ikke å \noppbevare filer i mer enn {0}', + "u_unp_ok": 'opplastning kan angres i {0}', + "u_unp_ng": 'opplastning kan IKKE angres', "ue_ro": 'du har ikke skrivetilgang i denne mappen\n\n', "ue_nl": 'du er ikke logget inn', "ue_la": 'du er logget inn som "{0}"', @@ -837,6 +861,7 @@ ebi('op_up2k').innerHTML = ( '
\n' + '

' + L.ul_flagblk + '

\n' + + '
' + '
' ); @@ -4646,6 +4671,7 @@ var treectl = (function () { r.hide(); ebi('path').style.display = ''; + ebi('treeul').innerHTML = ''; } r.hide = function () { @@ -4865,10 +4891,18 @@ var treectl = (function () { } } } - QS('#treeul>li>a+a').textContent = '[root]'; despin('#tree'); - reload_tree(); + try { + QS('#treeul>li>a+a').textContent = '[root]'; + } + catch (ex) { + console.log('got no root yet'); + r.dir_cb = null; + return; + } + + reload_tree(); var fun = r.dir_cb; if (fun) { r.dir_cb = null; @@ -5042,7 +5076,7 @@ var treectl = (function () { if (this.hpush && !showfile.active()) hist_push(this.top); - if (!this.back && treectl.entreed) { + if (!this.back && !treectl.hidden) { var dirs = []; for (var a = 0; a < res.dirs.length; a++) dirs.push(res.dirs[a].href.split('/')[0].split('?')[0]); @@ -5159,6 +5193,7 @@ var treectl = (function () { if (res.acct) { acct = res.acct; have_up2k_idx = res.idx; + lifetime = res.lifetime; apply_perms(res.perms); fileman.render(); } @@ -6348,7 +6383,7 @@ var unpost = (function () { for (var a = n; a < n2; a++) if (QS('#op_unpost a.n' + a)) - req.push(uricom_dec(r.files[a].vp)[0]); + req.push(uricom_dec(r.files[a].vp)); var links = QSA('#op_unpost a.n' + n); for (var a = 0, aa = links.length; a < aa; a++) { diff --git a/copyparty/web/up2k.js b/copyparty/web/up2k.js index bf52dd98..cf7bf9cd 100644 --- a/copyparty/web/up2k.js +++ b/copyparty/web/up2k.js @@ -1179,8 +1179,11 @@ function up2k_init(subtle) { var msg = []; + if (lifetime) + msg.push('' + L.u_up_life.format(lhumantime(st.lifetime || lifetime)) + '\n\n'); + if (FIREFOX && good_files.length > 3000) - msg.push(L.u_ff_many); + msg.push(L.u_ff_many + "\n\n"); msg.push(L.u_asku.format(good_files.length, esc(get_vpath())) + '