diff --git a/README.md b/README.md index a3345d0a..6feb79d7 100644 --- a/README.md +++ b/README.md @@ -780,8 +780,11 @@ the up2k UI is the epitome of polished intuitive experiences: * "parallel uploads" specifies how many chunks to upload at the same time * `[🏃]` analysis of other files should continue while one is uploading * `[🥔]` shows a simpler UI for faster uploads from slow devices +* `[🛡️]` decides when to overwrite existing files on the server + * `🛡️` = never (generate a new filename instead) + * `🕒` = overwrite if the server-file is older + * `♻️` = always overwrite if the files are different * `[🎲]` generate random filenames during upload -* `[📅]` preserve last-modified timestamps; server times will match yours * `[🔎]` switch between upload and [file-search](#file-search) mode * ignore `[🔎]` if you add files by dragging them into the browser diff --git a/bin/u2c.py b/bin/u2c.py index e3b4dabd..11ab733d 100755 --- a/bin/u2c.py +++ b/bin/u2c.py @@ -1,8 +1,8 @@ #!/usr/bin/env python3 from __future__ import print_function, unicode_literals -S_VERSION = "2.9" -S_BUILD_DT = "2025-01-27" +S_VERSION = "2.10" +S_BUILD_DT = "2025-02-19" """ u2c.py: upload to copyparty @@ -807,7 +807,9 @@ def handshake(ar, file, search): else: if ar.touch: req["umod"] = True - if ar.ow: + if ar.owo: + req["replace"] = "mt" + elif ar.ow: req["replace"] = True file.recheck = False @@ -1538,6 +1540,7 @@ source file/folder selection uses rsync syntax, meaning that: ap.add_argument("--ok", action="store_true", help="continue even if some local files are inaccessible") ap.add_argument("--touch", action="store_true", help="if last-modified timestamps differ, push local to server (need write+delete perms)") ap.add_argument("--ow", action="store_true", help="overwrite existing files instead of autorenaming") + ap.add_argument("--owo", action="store_true", help="overwrite existing files if server-file is older") ap.add_argument("--spd", action="store_true", help="print speeds for each file") ap.add_argument("--version", action="store_true", help="show version and exit") diff --git a/copyparty/__main__.py b/copyparty/__main__.py index 2b242251..7314f9a2 100644 --- a/copyparty/__main__.py +++ b/copyparty/__main__.py @@ -1039,6 +1039,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("--u2j", metavar="JOBS", type=int, default=2, help="web-client: number of file chunks to upload in parallel; 1 or 2 is good for low-latency (same-country) connections, 4-8 for android clients, 16 for cross-atlantic (max=64)") 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("--u2ow", metavar="NUM", type=int, default=0, help="web-client: default setting for when to 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/authsrv.py b/copyparty/authsrv.py index a4b34a71..62724862 100644 --- a/copyparty/authsrv.py +++ b/copyparty/authsrv.py @@ -1956,11 +1956,7 @@ class AuthSrv(object): if vf not in vol.flags: vol.flags[vf] = getattr(self.args, ga) - for k in ("nrand",): - if k not in vol.flags: - vol.flags[k] = getattr(self.args, k) - - zs = "forget_ip nrand u2abort ups_who zip_who" + zs = "forget_ip nrand u2abort u2ow ups_who zip_who" for k in zs.split(): if k in vol.flags: vol.flags[k] = int(vol.flags[k]) @@ -2436,6 +2432,7 @@ class AuthSrv(object): "u2j": self.args.u2j, "u2sz": self.args.u2sz, "u2ts": vf["u2ts"], + "u2ow": vf["u2ow"], "frand": bool(vf.get("rand")), "lifetime": vn.js_ls["lifetime"], "u2sort": self.args.u2sort, diff --git a/copyparty/cfg.py b/copyparty/cfg.py index 2d4a375b..7f0f86c0 100644 --- a/copyparty/cfg.py +++ b/copyparty/cfg.py @@ -82,6 +82,7 @@ def vf_vmap() -> dict[str, str]: "lg_sba", "md_sba", "nrand", + "u2ow", "og_desc", "og_site", "og_th", @@ -170,6 +171,7 @@ flagcats = { "medialinks": "return medialinks for non-up2k uploads (not hotlinks)", "rand": "force randomized filenames, 9 chars long by default", "nrand=N": "randomized filenames are N chars long", + "u2ow=N": "overwrite existing files? 0=no 1=if-older 2=always", "u2ts=fc": "[f]orce [c]lient-last-modified or [u]pload-time", "u2abort=1": "allow aborting unfinished uploads? 0=no 1=strict 2=ip-chk 3=acct-chk", "sz=1k-3m": "allow filesizes between 1 KiB and 3MiB", diff --git a/copyparty/up2k.py b/copyparty/up2k.py index 8b832ef1..f6f6fa20 100644 --- a/copyparty/up2k.py +++ b/copyparty/up2k.py @@ -3373,7 +3373,17 @@ class Up2k(object): return fname fp = djoin(fdir, fname) - if job.get("replace") and bos.path.exists(fp): + + ow = job.get("replace") and bos.path.exists(fp) + if ow and "mt" in str(job["replace"]).lower(): + mts = bos.stat(fp).st_mtime + mtc = job["lmod"] + if mtc < mts: + t = "will not overwrite; server %d sec newer than client; %d > %d %r" + self.log(t % (mts - mtc, mts, mtc, fp)) + ow = False + + if ow: self.log("replacing existing file at %r" % (fp,)) cur = None ptop = job["ptop"] diff --git a/copyparty/web/browser.js b/copyparty/web/browser.js index 07bb0c0e..81adcf05 100644 --- a/copyparty/web/browser.js +++ b/copyparty/web/browser.js @@ -151,7 +151,8 @@ var Ls = { "ul_par": "parallel uploads:", "ut_rand": "randomize filenames", - "ut_u2ts": "copy the last-modified timestamp$Nfrom your filesystem to the server", + "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_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", @@ -751,7 +752,8 @@ var Ls = { "ul_par": "samtidige handl.:", "ut_rand": "finn opp nye tilfeldige filnavn", - "ut_u2ts": "gi filen på serveren samme$Ntidsstempel som lokalt hos deg", + "ut_u2ts": "gi filen på serveren samme$Ntidsstempel som lokalt hos deg\">📅", + "ut_ow": "overskrive eksisterende filer på serveren?$N🛡️: aldri (finner på et nytt filnavn istedenfor)$N🕒: overskriv hvis serverens fil er eldre$N♻️: alltid, gitt at innholdet er forskjellig", "ut_mt": "fortsett å befare køen mens opplastning foregår$N$Nskru denne av dersom du har en$Ntreg prosessor eller harddisk", "ut_ask": 'bekreft filutvalg før opplastning starter">💭', "ut_pot": "forbedre ytelsen på trege enheter ved å$Nforenkle brukergrensesnittet", @@ -1351,7 +1353,8 @@ var Ls = { "ul_par": "并行上传:", "ut_rand": "随机化文件名", - "ut_u2ts": "将最后修改的时间戳$N从你的文件系统复制到服务器", + "ut_u2ts": "将最后修改的时间戳$N从你的文件系统复制到服务器\">📅", + "ut_ow": "覆盖服务器上的现有文件?$N🛡️: 从不(会生成一个新文件名)$N🕒: 服务器文件较旧则覆盖$N♻️: 总是覆盖,如果文件内容不同", //m "ut_mt": "在上传时继续哈希其他文件$N$N如果你的 CPU 或硬盘是瓶颈,可能需要禁用", "ut_ask": '上传开始前询问确认">💭', "ut_pot": "通过简化 UI 来$N提高慢设备上的上传速度", @@ -1918,8 +1921,8 @@ ebi('op_up2k').innerHTML = ( ' \n' + ' \n' + '