allow setting lifetimes from up2k ui

This commit is contained in:
ed 2022-09-19 23:49:07 +02:00
parent 1882afb8b6
commit 0b87a4a810
11 changed files with 214 additions and 26 deletions

View file

@ -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 * [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 * [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 * [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) * [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 * [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 * [markdown viewer](#markdown-viewer) - and there are *two* editors
@ -168,6 +169,7 @@ feature summary
* ☑ [up2k](#uploading): js, resumable, multithreaded * ☑ [up2k](#uploading): js, resumable, multithreaded
* ☑ stash: simple PUT filedropper * ☑ stash: simple PUT filedropper
* ☑ [unpost](#unpost): undo/delete accidental uploads * ☑ [unpost](#unpost): undo/delete accidental uploads
* ☑ [self-destruct](#self-destruct) (specified server-side or client-side)
* ☑ symlink/discard existing files (content-matching) * ☑ symlink/discard existing files (content-matching)
* download * download
* ☑ single files in browser * ☑ 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` 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 ## file manager
cut/paste, rename, and delete files/folders (if you have permission) cut/paste, rename, and delete files/folders (if you have permission)

View file

@ -1121,6 +1121,16 @@ class AuthSrv(object):
vol.flags = {k: v for k, v in vol.flags.items() if not k.startswith(rm)} 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 # verify tags mentioned by -mt[mp] are used by -mte
local_mtp = {} local_mtp = {}
local_only_mtp = {} local_only_mtp = {}

View file

@ -1313,7 +1313,7 @@ class HttpCli(object):
want_url = ac == "url" want_url = ac == "url"
zs = self.uparam.get("life", self.headers.get("life", "")) zs = self.uparam.get("life", self.headers.get("life", ""))
if zs: if zs:
vlife = int(vfs.flags.get("lifetime") or 0) vlife = vfs.flags.get("lifetime") or 0
lifetime = max(0, int(vlife - int(zs))) lifetime = max(0, int(vlife - int(zs)))
else: else:
lifetime = 0 lifetime = 0
@ -2495,6 +2495,7 @@ class HttpCli(object):
"srvinf": srv_infot, "srvinf": srv_infot,
"acct": self.uname, "acct": self.uname,
"idx": ("e2d" in vn.flags), "idx": ("e2d" in vn.flags),
"lifetime": vn.flags.get("lifetime") or 0,
"perms": perms, "perms": perms,
"logues": logues, "logues": logues,
"readme": readme, "readme": readme,
@ -2506,6 +2507,7 @@ class HttpCli(object):
"ls0": None, "ls0": None,
"acct": self.uname, "acct": self.uname,
"perms": json.dumps(perms), "perms": json.dumps(perms),
"lifetime": ls_ret["lifetime"],
"taglist": [], "taglist": [],
"def_hcols": [], "def_hcols": [],
"have_emp": self.args.emp, "have_emp": self.args.emp,
@ -2515,7 +2517,7 @@ class HttpCli(object):
"have_mv": (not self.args.no_mv), "have_mv": (not self.args.no_mv),
"have_del": (not self.args.no_del), "have_del": (not self.args.no_del),
"have_zip": (not self.args.no_zip), "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"), "have_b_u": (self.can_write and self.uparam.get("b") == "u"),
"url_suf": url_suf, "url_suf": url_suf,
"logues": logues, "logues": logues,

View file

@ -351,11 +351,9 @@ class Up2k(object):
if not cur: if not cur:
continue continue
lifetime = int(lifetime)
timeout = min(timeout, now + lifetime)
nrm = 0 nrm = 0
deadline = time.time() - lifetime deadline = time.time() - lifetime
timeout = min(timeout, now + lifetime)
q = "select rd, fn from up where at > 0 and at < ? limit 100" q = "select rd, fn from up where at > 0 and at < ? limit 100"
while True: while True:
with self.mutex: with self.mutex:
@ -1960,6 +1958,7 @@ class Up2k(object):
"sprs": sprs, # dontcare; finished anyways "sprs": sprs, # dontcare; finished anyways
"size": dsize, "size": dsize,
"lmod": dtime, "lmod": dtime,
"life": cj.get("life"),
"addr": ip, "addr": ip,
"at": at, "at": at,
"hash": [], "hash": [],
@ -2072,6 +2071,7 @@ class Up2k(object):
"name", "name",
"size", "size",
"lmod", "lmod",
"life",
"poke", "poke",
]: ]:
job[k] = cj[k] job[k] = cj[k]
@ -2293,12 +2293,30 @@ class Up2k(object):
pass pass
z2 = [job[x] for x in "ptop wark prel name lmod size addr".split()] 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): if self.idx_wark(*z2):
del self.registry[ptop][wark] del self.registry[ptop][wark]
else: else:
self.regdrop(ptop, wark) self.regdrop(ptop, wark)
if wake_sr:
with self.rescan_cond:
self.rescan_cond.notify_all()
dupes = self.dupesched.pop(dst, []) dupes = self.dupesched.pop(dst, [])
if not dupes: if not dupes:
return return

View file

@ -2498,13 +2498,34 @@ html.b #u2conf a.b:hover {
text-align: center; text-align: center;
border: .2em dashed rgba(128, 128, 128, 0.3); border: .2em dashed rgba(128, 128, 128, 0.3);
} }
#u2foot { #u2foot,
#u2life {
color: var(--fg-max); color: var(--fg-max);
font-style: italic;
text-align: center; text-align: center;
font-size: 1.2em;
margin: .8em 0; 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 { #u2foot .warn {
font-size: 1.2em; font-size: 1.2em;
padding: .5em .8em; padding: .5em .8em;
@ -2523,6 +2544,10 @@ html.b #u2conf a.b:hover {
#u2foot>*+* { #u2foot>*+* {
margin-top: 1.5em; margin-top: 1.5em;
} }
#u2life input {
width: 4em;
text-align: right;
}
.prog { .prog {
font-family: 'scp', monospace, monospace; font-family: 'scp', monospace, monospace;
} }

View file

@ -146,8 +146,9 @@
have_acode = {{ have_acode|tojson }}, have_acode = {{ have_acode|tojson }},
have_mv = {{ have_mv|tojson }}, have_mv = {{ have_mv|tojson }},
have_del = {{ have_del|tojson }}, have_del = {{ have_del|tojson }},
have_unpost = {{ have_unpost|tojson }}, have_unpost = {{ have_unpost }},
have_zip = {{ have_zip|tojson }}, have_zip = {{ have_zip|tojson }},
lifetime = {{ lifetime }},
turbolvl = {{ turbolvl }}, turbolvl = {{ turbolvl }},
u2sort = "{{ u2sort }}", u2sort = "{{ u2sort }}",
have_emp = {{ have_emp|tojson }}, have_emp = {{ have_emp|tojson }},

View file

@ -25,6 +25,12 @@ var Ls = {
"hz": "sample rate" "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", "goh": "control-panel",
"logout": "Logout ", "logout": "Logout ",
"access": " access", "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_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_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_just1": '\nMaybe it works better if you select just one file',
"u_ff_many": "This amount of files <em>may</em> 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 <em>may</em> 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 <code>{1}</code>', "u_asku": 'upload these {0} files to <code>{1}</code>',
"u_unpt": "you can undo / delete this upload using the top-left 🧯", "u_unpt": "you can undo / delete this upload using the top-left 🧯",
"u_etadone": 'Done ({0}, {1} files)', "u_etadone": 'Done ({0}, {1} files)',
@ -338,6 +345,11 @@ var Ls = {
"u_expl": "explain", "u_expl": "explain",
"u_tu": '<p class="warn">WARNING: turbo enabled, <span>&nbsp;client may not detect and resume incomplete uploads; see turbo-button tooltip</span></p>', "u_tu": '<p class="warn">WARNING: turbo enabled, <span>&nbsp;client may not detect and resume incomplete uploads; see turbo-button tooltip</span></p>',
"u_ts": '<p class="warn">WARNING: turbo enabled, <span>&nbsp;search results can be incorrect; see turbo-button tooltip</span></p>', "u_ts": '<p class="warn">WARNING: turbo enabled, <span>&nbsp;search results can be incorrect; see turbo-button tooltip</span></p>',
"u_life_cfg": 'autodelete after <input id="lifem" p="60" /> min (or <input id="lifeh" p="3600" /> hours)',
"u_life_est": 'upload will be deleted <span id="lifew" tt="local time">---</span>',
"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_ro": 'your access to this folder is Read-Only\n\n',
"ue_nl": 'you are currently not logged in', "ue_nl": 'you are currently not logged in',
"ue_la": 'you are currently logged in as "{0}"', "ue_la": 'you are currently logged in as "{0}"',
@ -379,6 +391,12 @@ var Ls = {
"hz": "lyd-oppløsning" "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", "goh": "kontrollpanel",
"logout": "Logg ut ", "logout": "Logg ut ",
"access": " tilgang", "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_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_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_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 <code>{1}</code>', "u_asku": 'Laste opp disse {0} filene til <code>{1}</code>',
"u_unpt": "Du kan angre / slette opplastningen med 🧯 oppe til venstre", "u_unpt": "Du kan angre / slette opplastningen med 🧯 oppe til venstre",
"u_etadone": 'Ferdig ({0}, {1} filer)', "u_etadone": 'Ferdig ({0}, {1} filer)',
@ -692,6 +711,11 @@ var Ls = {
"u_expl": "forklar", "u_expl": "forklar",
"u_tu": '<p class="warn">ADVARSEL: turbo er på, <span>&nbsp;avbrutte opplastninger vil muligens ikke oppdages og gjenopptas; hold musepekeren over turbo-knappen for mer info</span></p>', "u_tu": '<p class="warn">ADVARSEL: turbo er på, <span>&nbsp;avbrutte opplastninger vil muligens ikke oppdages og gjenopptas; hold musepekeren over turbo-knappen for mer info</span></p>',
"u_ts": '<p class="warn">ADVARSEL: turbo er på, <span>&nbsp;søkeresultater kan være feil; hold musepekeren over turbo-knappen for mer info</span></p>', "u_ts": '<p class="warn">ADVARSEL: turbo er på, <span>&nbsp;søkeresultater kan være feil; hold musepekeren over turbo-knappen for mer info</span></p>',
"u_life_cfg": 'slett opplastning etter <input id="lifem" p="60" /> min (eller <input id="lifeh" p="3600" /> timer)',
"u_life_est": 'opplastningen slettes <span id="lifew" tt="lokal tid">---</span>',
"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_ro": 'du har ikke skrivetilgang i denne mappen\n\n',
"ue_nl": 'du er ikke logget inn', "ue_nl": 'du er ikke logget inn',
"ue_la": 'du er logget inn som "{0}"', "ue_la": 'du er logget inn som "{0}"',
@ -837,6 +861,7 @@ ebi('op_up2k').innerHTML = (
'</table><div id="u2mu"></div></div>\n' + '</table><div id="u2mu"></div></div>\n' +
'<p id="u2flagblock"><b>' + L.ul_flagblk + '</p>\n' + '<p id="u2flagblock"><b>' + L.ul_flagblk + '</p>\n' +
'<div id="u2life"></div>' +
'<div id="u2foot"></div>' '<div id="u2foot"></div>'
); );
@ -4646,6 +4671,7 @@ var treectl = (function () {
r.hide(); r.hide();
ebi('path').style.display = ''; ebi('path').style.display = '';
ebi('treeul').innerHTML = '';
} }
r.hide = function () { r.hide = function () {
@ -4865,10 +4891,18 @@ var treectl = (function () {
} }
} }
} }
QS('#treeul>li>a+a').textContent = '[root]';
despin('#tree'); 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; var fun = r.dir_cb;
if (fun) { if (fun) {
r.dir_cb = null; r.dir_cb = null;
@ -5042,7 +5076,7 @@ var treectl = (function () {
if (this.hpush && !showfile.active()) if (this.hpush && !showfile.active())
hist_push(this.top); hist_push(this.top);
if (!this.back && treectl.entreed) { if (!this.back && !treectl.hidden) {
var dirs = []; var dirs = [];
for (var a = 0; a < res.dirs.length; a++) for (var a = 0; a < res.dirs.length; a++)
dirs.push(res.dirs[a].href.split('/')[0].split('?')[0]); dirs.push(res.dirs[a].href.split('/')[0].split('?')[0]);
@ -5159,6 +5193,7 @@ var treectl = (function () {
if (res.acct) { if (res.acct) {
acct = res.acct; acct = res.acct;
have_up2k_idx = res.idx; have_up2k_idx = res.idx;
lifetime = res.lifetime;
apply_perms(res.perms); apply_perms(res.perms);
fileman.render(); fileman.render();
} }
@ -6348,7 +6383,7 @@ var unpost = (function () {
for (var a = n; a < n2; a++) for (var a = n; a < n2; a++)
if (QS('#op_unpost a.n' + 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); var links = QSA('#op_unpost a.n' + n);
for (var a = 0, aa = links.length; a < aa; a++) { for (var a = 0, aa = links.length; a < aa; a++) {

View file

@ -1179,8 +1179,11 @@ function up2k_init(subtle) {
var msg = []; var msg = [];
if (lifetime)
msg.push('<b>' + L.u_up_life.format(lhumantime(st.lifetime || lifetime)) + '</b>\n\n');
if (FIREFOX && good_files.length > 3000) 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())) + '<ul>'); msg.push(L.u_asku.format(good_files.length, esc(get_vpath())) + '<ul>');
for (var a = 0, aa = Math.min(20, good_files.length); a < aa; a++) for (var a = 0, aa = Math.min(20, good_files.length); a < aa; a++)
@ -2226,6 +2229,7 @@ function up2k_init(subtle) {
"name": t.name, "name": t.name,
"size": t.size, "size": t.size,
"lmod": t.lmod, "lmod": t.lmod,
"life": st.lifetime,
"hash": t.hash "hash": t.hash
}; };
if (t.srch) if (t.srch)
@ -2459,6 +2463,69 @@ function up2k_init(subtle) {
} }
draw_turbo(); draw_turbo();
function draw_life() {
var el = ebi('u2life');
if (!lifetime) {
el.style.display = 'none';
el.innerHTML = '';
st.lifetime = 0;
return;
}
el.style.display = uc.fsearch ? 'none' : '';
el.innerHTML = '<div>' + L.u_life_cfg + '</div><div>' + L.u_life_est + '</div><div id="undor"></div>';
set_life(Math.min(lifetime, icfg_get('lifetime', lifetime)));
ebi('lifem').oninput = ebi('lifeh').oninput = mod_life;
tt.att(ebi('u2life'));
}
draw_life();
function mod_life(e) {
var el = e.target,
pow = parseInt(el.getAttribute('p')),
v = parseInt(el.value);
if (!isNum(v))
return;
if (toast.tag == mod_life)
toast.hide();
v *= pow;
if (v > lifetime) {
v = lifetime;
toast.warn(20, L.u_life_max.format(lhumantime(lifetime)), mod_life);
}
swrite('lifetime', v);
set_life(v);
}
function set_life(v) {
//ebi('lifes').value = v;
ebi('lifem').value = parseInt(v / 60);
ebi('lifeh').value = parseInt(v / 3600);
var undo = have_unpost - (v || lifetime);
ebi('undor').innerHTML = undo <= 0 ?
L.u_unp_ng : L.u_unp_ok.format(lhumantime(undo));
st.lifetime = v;
rel_life();
}
function rel_life() {
if (!lifetime)
return;
try {
ebi('lifew').innerHTML = unix2iso((st.lifetime || lifetime) +
Date.now() / 1000 - new Date().getTimezoneOffset() * 60
).replace(' ', ', ').slice(0, -3);
}
catch (ex) { }
}
setInterval(rel_life, 9000);
function set_potato() { function set_potato() {
pvis.potato(); pvis.potato();
set_fsearch(); set_fsearch();
@ -2509,6 +2576,7 @@ function up2k_init(subtle) {
ebi('u2mu').style.display = potato ? '' : 'none'; ebi('u2mu').style.display = potato ? '' : 'none';
draw_turbo(); draw_turbo();
draw_life();
onresize(); onresize();
} }

View file

@ -626,7 +626,7 @@ function uricom_sdec(txt) {
function uricom_adec(arr, li) { function uricom_adec(arr, li) {
var ret = []; var ret = [];
for (var a = 0; a < arr.length; a++) { for (var a = 0; a < arr.length; a++) {
var txt = uricom_dec(arr[a])[0]; var txt = uricom_dec(arr[a]);
ret.push(li ? '<li>' + esc(txt) + '</li>' : txt); ret.push(li ? '<li>' + esc(txt) + '</li>' : txt);
} }
@ -682,7 +682,7 @@ var isNum = function (v) {
var n = parseFloat(v); var n = parseFloat(v);
return !isNaN(v - n) && n === v; return !isNaN(v - n) && n === v;
}; };
if (window.Number) if (window.Number && Number.isFinite)
isNum = Number.isFinite; isNum = Number.isFinite;
@ -717,7 +717,7 @@ function humantime(v) {
} }
function shumantime(v) { function shumantime(v, long) {
if (v < 10) if (v < 10)
return f2f(v, 2) + 's'; return f2f(v, 2) + 's';
if (v < 60) if (v < 60)
@ -737,11 +737,27 @@ function shumantime(v) {
var v1 = parseInt(v / m1), var v1 = parseInt(v / m1),
v2 = ('0' + parseInt((v % m1) / m2)).slice(-2); v2 = ('0' + parseInt((v % m1) / m2)).slice(-2);
return v1 + ch + (v1 >= 10 ? '' : v2); return v1 + ch + (v1 >= 10 || v2 == '00' ? '' : v2 + (
long && a < st.length - 1 ? st[a + 1][2] : ''));
} }
} }
function lhumantime(v) {
var t = shumantime(v, 1),
tp = t.replace(/([a-z])/g, " $1 ").split(/ /g).slice(0, -1);
if (!window.L || tp.length < 2 || tp[1].indexOf('$') + 1)
return t;
var ret = '';
for (var a = 0; a < tp.length; a += 2)
ret += tp[a] + ' ' + L['ht_' + tp[a + 1]].replace(tp[a] == 1 ? /!.*/ : /!/, '') + L.ht_and;
return ret.slice(0, -L.ht_and.length);
}
function clamp(v, a, b) { function clamp(v, a, b) {
return Math.min(Math.max(v, a), b); return Math.min(Math.max(v, a), b);
} }
@ -812,7 +828,7 @@ function fcfg_get(name, defval) {
var o = ebi(name), var o = ebi(name),
val = parseFloat(sread(name)); val = parseFloat(sread(name));
if (isNaN(val)) if (!isNum(val))
return parseFloat(o ? o.value : defval); return parseFloat(o ? o.value : defval);
if (o) if (o)
@ -1068,7 +1084,7 @@ var tt = (function () {
}; };
r.hide = function (e) { r.hide = function (e) {
ev(e); //ev(e); // eats checkbox-label clicks
clearTimeout(tev); clearTimeout(tev);
window.removeEventListener('scroll', r.hide); window.removeEventListener('scroll', r.hide);

View file

@ -2,7 +2,7 @@
set -e set -e
curl http://192.168.123.1:3923/cpp/scripts/pyinstaller/build.sh | curl http://192.168.123.1:3923/cpp/scripts/pyinstaller/build.sh |
tee build2.sh | cmp build.sh || { tee build2.sh | cmp build.sh && rm build2.sh || {
echo "new build script; upgrade y/n:" echo "new build script; upgrade y/n:"
while true; do read -u1 -n1 -r r; [[ $r =~ [yYnN] ]] && break; done while true; do read -u1 -n1 -r r; [[ $r =~ [yYnN] ]] && break; done
[[ $r =~ [yY] ]] && mv build{2,}.sh && exec ./build.sh [[ $r =~ [yY] ]] && mv build{2,}.sh && exec ./build.sh

View file

@ -6,11 +6,11 @@ this is the EXE edition of copyparty, compatible with Windows7-SP1
and later. To make this possible, the EXE was compiled with Python and later. To make this possible, the EXE was compiled with Python
3.7.9, which is EOL and does not receive security patches anymore. 3.7.9, which is EOL and does not receive security patches anymore.
it is strongly recommended to use the python sfx instead: if possible, for performance and security reasons, please use this instead:
https://github.com/9001/copyparty/releases/latest/download/copyparty-sfx.py https://github.com/9001/copyparty/releases/latest/download/copyparty-sfx.py
""" """
print(v.replace("\n", "\n")[1:] + "\n") print(v.replace("\n", "\n")[1:] + "\n")
import re import re