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())) + '