diff --git a/copyparty/authsrv.py b/copyparty/authsrv.py index a683d346..2b096334 100644 --- a/copyparty/authsrv.py +++ b/copyparty/authsrv.py @@ -93,6 +93,13 @@ LEELOO_DALLAS = "leeloo_dallas" ## thanks for coming to my ted talk +UP_MTE_MAP = { # db-order + "w": "substr(w,1,16)", + "up_ip": "ip", + ".up_at": "at", + "up_by": "un", +} + SEE_LOG = "see log for details" SEESLOG = " (see serverlog for details)" SSEELOG = " ({})".format(SEE_LOG) @@ -1058,6 +1065,7 @@ class AuthSrv(object): "tcolor": self.args.tcolor, "du_iwho": self.args.du_iwho, "shr_who": self.args.shr_who if self.args.shr else "no", + "ls_q_m": ("", ""), } self._vf0 = self._vf0b.copy() self._vf0["d2d"] = True @@ -2672,6 +2680,12 @@ class AuthSrv(object): self.log(t.format(vol.vpath, mtp), 1) errors = True + mte = vol.flags.get("mte") or {} + up_m = [x for x in UP_MTE_MAP if x in mte] + up_q = [UP_MTE_MAP[x] for x in up_m] + zs = "select %s from up where rd=? and fn=?" % (", ".join(up_q),) + vol.flags["ls_q_m"] = (zs if up_m else "", up_m) + for vol in vfs.all_nodes.values(): if not vol.flags.get("is_file"): continue diff --git a/copyparty/ftpd.py b/copyparty/ftpd.py index fd5d0349..949a7457 100644 --- a/copyparty/ftpd.py +++ b/copyparty/ftpd.py @@ -174,7 +174,7 @@ class FtpFs(AbstractedFS): t = "Unsupported characters in [{}]" raise FSE(t.format(vpath), 1) - fn = sanitize_fn(fn or "", "") + fn = sanitize_fn(fn or "") vpath = vjoin(rd, fn) vfs, rem = self.hub.asrv.vfs.get(vpath, self.uname, r, w, m, d) if ( diff --git a/copyparty/httpcli.py b/copyparty/httpcli.py index 8281fc27..fb00f21e 100644 --- a/copyparty/httpcli.py +++ b/copyparty/httpcli.py @@ -2419,7 +2419,7 @@ class HttpCli(object): if rnd: fn = rand_name(fdir, fn, rnd) - fn = sanitize_fn(fn or "", "") + fn = sanitize_fn(fn or "") path = os.path.join(fdir, fn) @@ -2857,7 +2857,7 @@ class HttpCli(object): name = name.translate(tl) dbv, vrem = vfs.get_dbv(rem) - name = sanitize_fn(name, "") + name = sanitize_fn(name) if ( not self.can_read and self.can_write @@ -2875,7 +2875,7 @@ class HttpCli(object): body["addr"] = self.ip body["vcfg"] = dbv.flags - if not self.can_delete: + if not self.can_delete and not body.get("replace") == "skip": body.pop("replace", None) if rem: @@ -3353,7 +3353,7 @@ class HttpCli(object): if "nosub" in vfs.flags: raise Pebkac(403, "mkdir is forbidden below this folder") - rem = sanitize_vpath(rem, "/") + rem = sanitize_vpath(rem) fn = vfs.canonical(rem) if not nullwrite: @@ -3397,7 +3397,7 @@ class HttpCli(object): t = "you can only create .md files because you don't have the delete-permission" raise Pebkac(400, t) - sanitized = sanitize_fn(new_file, "") + sanitized = sanitize_fn(new_file) fdir = vfs.canonical(rem) fn = os.path.join(fdir, sanitized) @@ -3549,7 +3549,7 @@ class HttpCli(object): # fallthrough fdir = fdir_base - fname = sanitize_fn(p_file or "", "") + fname = sanitize_fn(p_file or "") abspath = os.path.join(fdir, fname) suffix = "-%.6f-%s" % (time.time(), dip) if p_file and not nullwrite: @@ -7032,18 +7032,8 @@ class HttpCli(object): if self.can_admin: up_q = "select substr(w,1,16), ip, at, un from up where rd=? and fn=?" up_m = ["w", "up_ip", ".up_at", "up_by"] - elif ".up_at" in mte: - if "w" in mte: - up_q = "select substr(w,1,16), at from up where rd=? and fn=?" - up_m = ["w", ".up_at"] - else: - up_q = "select at from up where rd=? and fn=?" - up_m = [".up_at"] - elif "w" in mte: - up_q = "select substr(w,1,16) from up where rd=? and fn=?" - up_m = ["w"] else: - up_q = "" + up_q, up_m = vn.flags["ls_q_m"] mt_q = "select mt.k, mt.v from up inner join mt on mt.w = substr(up.w,1,16) where up.rd = ? and up.fn = ? and +mt.k != 'x'" for fe in files: diff --git a/copyparty/szip.py b/copyparty/szip.py index 91fa2a34..12caf91f 100644 --- a/copyparty/szip.py +++ b/copyparty/szip.py @@ -8,7 +8,7 @@ import time from .authsrv import AuthSrv from .bos import bos from .sutil import StreamArc, errdesc -from .util import min_ex, sanitize_fn, spack, sunpack, yieldfile, zlib +from .util import VPTL_WIN, min_ex, sanitize_to, spack, sunpack, yieldfile, zlib if True: # pylint: disable=using-constant-test from typing import Any, Generator, Optional @@ -104,7 +104,7 @@ def gen_hdr( ret += spack(b" str: return "/".join(ret) -def sanitize_fn(fn: str, ok: str) -> str: - if "/" not in ok: - fn = fn.replace("\\", "/").split("/")[-1] - +def sanitize_fn(fn: str) -> str: + fn = fn.replace("\\", "/").split("/")[-1] if APTL_OS: - fn = fn.translate(APTL_OS) - if ANYWIN: - bad = ["con", "prn", "aux", "nul"] - for n in range(1, 10): - bad += ("com%s lpt%s" % (n, n)).split(" ") - - if fn.lower().split(".")[0] in bad: - fn = "_" + fn - + fn = sanitize_to(fn, APTL_OS) return fn.strip() -def sanitize_vpath(vp: str, ok: str) -> str: +def sanitize_to(fn: str, tl: dict[int, int]) -> str: + fn = fn.translate(tl) + if ANYWIN: + bad = ["con", "prn", "aux", "nul"] + for n in range(1, 10): + bad += ("com%s lpt%s" % (n, n)).split(" ") + + if fn.lower().split(".")[0] in bad: + fn = "_" + fn + return fn + + +def sanitize_vpath(vp: str) -> str: if not FNTL_OS: return vp parts = vp.replace(os.sep, "/").split("/") - ret = [sanitize_fn(x, ok) for x in parts] + ret = [sanitize_to(x, APTL_OS) for x in parts] return "/".join(ret) diff --git a/copyparty/web/baguettebox.js b/copyparty/web/baguettebox.js index 6686e732..5596eb6e 100644 --- a/copyparty/web/baguettebox.js +++ b/copyparty/web/baguettebox.js @@ -1014,7 +1014,9 @@ window.baguetteBox = (function () { if (index >= imagesElements.length) return bounceAnimation(options.readDirRtl ? 'left' : 'right'); + var orot; try { + orot = vidimg().getAttribute('rot'); vid().pause(); } catch (ex) { } @@ -1032,6 +1034,9 @@ window.baguetteBox = (function () { else if (toast.tag == 'bb-ded') toast.hide(); + if (orot && im.getAttribute('rot') === null) + rotn(orot / 90, 1); + if (options.animation == 'none') unvid(vid()); else @@ -1048,7 +1053,7 @@ window.baguetteBox = (function () { } var prev_cw = 0, prev_ch = 0, unrot_timer = null; - function rotn(n) { + function rotn(n, asap) { var el = vidimg(), orot = parseInt(el.getAttribute('rot') || 0), frot = orot + (n || 0) * 90; @@ -1063,6 +1068,8 @@ window.baguetteBox = (function () { if (!n && prev_cw === cw && prev_ch === ch) return; // reflow noop + clmod(el, 'asap', asap); + prev_cw = cw; prev_ch = ch; var rot = frot, @@ -1072,8 +1079,13 @@ window.baguetteBox = (function () { dl = el.closest('div').querySelector('figcaption a'), vw = cw, vh = ch - dl.offsetHeight + magic, - pmag = Math.min(1, Math.min(vw / ih, vh / iw)), - wmag = Math.min(1, Math.min(vw / iw, vh / ih)); + pmag = Math.min(vw / ih, vh / iw), + wmag = Math.min(vw / iw, vh / ih); + + if (!options.bbzoom) { + pmag = Math.min(1, pmag); + wmag = Math.min(1, wmag); + } while (rot < 0) rot += 360; while (rot >= 360) rot -= 360; @@ -1117,7 +1129,7 @@ window.baguetteBox = (function () { return; clmod(el, 'nt', 1); - el.removeAttribute('rot'); + el.setAttribute('rot', 0); el.removeAttribute("style"); rot = el.offsetHeight; clmod(el, 'nt'); diff --git a/copyparty/web/browser.css b/copyparty/web/browser.css index e88ae036..a3ef0868 100644 --- a/copyparty/web/browser.css +++ b/copyparty/web/browser.css @@ -2166,6 +2166,10 @@ html.noscroll .sbar::-webkit-scrollbar { vertical-align: middle; transition: transform .23s, left .23s, top .23s, width .23s, height .23s; } +.full-image img.asap, +.full-image video.asap { + transition: none; +} #bbox-overlay.fill .full-image img, #bbox-overlay.fill .full-image video { width: 100%; diff --git a/copyparty/web/browser.js b/copyparty/web/browser.js index 124ac14d..680629eb 100644 --- a/copyparty/web/browser.js +++ b/copyparty/web/browser.js @@ -158,7 +158,7 @@ if (1) "ul_par": "parallel uploads:", "ut_rand": "randomize filenames", "ut_u2ts": "copy the last-modified timestamp$Nfrom your filesystem to the server\">📅", - "ut_ow": "overwrite existing files on the server?$N🛡️: never (will generate a new filename instead)$N🕒: overwrite if server-file is older than yours$N♻️: always overwrite if the files are different", + "ut_ow": "overwrite existing files on the server?$N🛡️: never (will generate a new filename instead)$N🕒: overwrite if server-file is older than yours$N♻️: always overwrite if the files are different$N⏭️: skip uploading if the server-file exists, regardless of contents", "ut_mt": "continue hashing other files while uploading$N$Nmaybe disable if your CPU or HDD is a bottleneck", "ut_ask": 'ask for confirmation before upload starts">💭', "ut_pot": "improve upload speed on slow devices$Nby making the UI less complex", diff --git a/copyparty/web/tl/deu.js b/copyparty/web/tl/deu.js index 62c4d001..d1052558 100644 --- a/copyparty/web/tl/deu.js +++ b/copyparty/web/tl/deu.js @@ -155,7 +155,7 @@ Ls.deu = { "ul_par": "Parallele Uploads:", "ut_rand": "Zufällige Dateinamen", "ut_u2ts": "Zuletzt geändert-Zeitstempel von$Ndeinem Dateisystem auf den Server übertragen\">📅", - "ut_ow": "Existierende Dateien auf dem Server überschreiben?$N🛡️: Nie (generiert einen neuen Dateinamen)$N🕒: Überschreiben, wenn Server-Datei älter ist als meine$N♻️: Überschreiben, wenn der Dateiinhalt anders ist", + "ut_ow": "Existierende Dateien auf dem Server überschreiben?$N🛡️: Nie (generiert einen neuen Dateinamen)$N🕒: Überschreiben, wenn Server-Datei älter ist als meine$N♻️: Überschreiben, wenn der Dateiinhalt anders ist$N⏭️: Nur hochladen, wenn keine Datei mit dem gleichen Namen existiert", "ut_mt": "Andere Dateien während des Uploads hashen$N$Nsolltest du deaktivieren, falls deine CPU oder Festplatte zum Flaschenhals werden könnte", "ut_ask": 'Vor dem Upload nach Bestätigung fragen">💭', "ut_pot": "Verbessert Upload-Geschwindigkeit$Nindem das UI weniger komplex gemacht wird", diff --git a/copyparty/web/up2k.js b/copyparty/web/up2k.js index f843431a..418ff31b 100644 --- a/copyparty/web/up2k.js +++ b/copyparty/web/up2k.js @@ -900,19 +900,19 @@ function up2k_init(subtle) { bcfg_bind(uc, 'upnag', 'upnag', false, set_upnag); bcfg_bind(uc, 'upsfx', 'upsfx', false, set_upsfx); - uc.ow = parseInt(sread('u2ow', ['0', '1', '2']) || u2ow); - uc.owt = ['🛡️', '🕒', '♻️']; + uc.ow = parseInt(sread('u2ow', ['0', '1', '2', '3']) || u2ow); + uc.owt = ['🛡️', '🕒', '♻️', '⏭️']; function set_ow() { QS('label[for="u2ow"]').innerHTML = uc.owt[uc.ow]; ebi('u2ow').checked = true; //cosmetic } ebi('u2ow').onclick = function (e) { ev(e); - if (++uc.ow > 2) + if (++uc.ow > 3) uc.ow = 0; swrite('u2ow', uc.ow); set_ow(); - if (uc.ow && !has(perms, 'delete')) + if (uc.ow && uc.ow !== 3 && !has(perms, 'delete')) toast.warn(10, L.u_enoow, 'noow'); else if (toast.tag == 'noow') toast.hide(); @@ -2727,9 +2727,10 @@ function up2k_init(subtle) { var err_pend = rsp.indexOf('partial upload exists at a different') + 1, err_srcb = rsp.indexOf('source file busy; please try again') + 1, err_plug = rsp.indexOf('upload blocked by x') + 1, - err_dupe = rsp.indexOf('upload rejected, file already exists') + 1; + err_dupe = rsp.indexOf('upload rejected, file already exists') + 1, + err_exists = rsp.indexOf('upload rejected, a file with that name already exists') + 1; - if (err_pend || err_srcb || err_plug || err_dupe) { + if (err_pend || err_srcb || err_plug || err_dupe || err_exists) { err = rsp; ofs = err.indexOf('\n/'); if (ofs !== -1) { @@ -2793,6 +2794,8 @@ function up2k_init(subtle) { req.replace = 'mt'; if (uc.ow == 2) req.replace = true; + if (uc.ow == 3) + req.replace = 'skip'; } xhr.open('POST', t.purl, true);