mirror of
https://github.com/9001/copyparty.git
synced 2025-08-17 09:02:15 -06:00
up2k: tristate option for overwriting files; closes #139
adds a third possible value for the `replace` property in handshakes: * absent or False: never overwrite an existing file on the server, and instead generate a new filename to avoid collision * True: always overwrite existing files on the server * "mt": only overwrite if client's last-modified is more recent (this is the new option) the new UI button toggles between all three options, defaulting to never-overwrite
This commit is contained in:
parent
6858cb066f
commit
e9f78ea70c
|
@ -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
|
* "parallel uploads" specifies how many chunks to upload at the same time
|
||||||
* `[🏃]` analysis of other files should continue while one is uploading
|
* `[🏃]` analysis of other files should continue while one is uploading
|
||||||
* `[🥔]` shows a simpler UI for faster uploads from slow devices
|
* `[🥔]` 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
|
* `[🎲]` generate random filenames during upload
|
||||||
* `[📅]` preserve last-modified timestamps; server times will match yours
|
|
||||||
* `[🔎]` switch between upload and [file-search](#file-search) mode
|
* `[🔎]` switch between upload and [file-search](#file-search) mode
|
||||||
* ignore `[🔎]` if you add files by dragging them into the browser
|
* ignore `[🔎]` if you add files by dragging them into the browser
|
||||||
|
|
||||||
|
|
|
@ -1,8 +1,8 @@
|
||||||
#!/usr/bin/env python3
|
#!/usr/bin/env python3
|
||||||
from __future__ import print_function, unicode_literals
|
from __future__ import print_function, unicode_literals
|
||||||
|
|
||||||
S_VERSION = "2.9"
|
S_VERSION = "2.10"
|
||||||
S_BUILD_DT = "2025-01-27"
|
S_BUILD_DT = "2025-02-19"
|
||||||
|
|
||||||
"""
|
"""
|
||||||
u2c.py: upload to copyparty
|
u2c.py: upload to copyparty
|
||||||
|
@ -807,7 +807,9 @@ def handshake(ar, file, search):
|
||||||
else:
|
else:
|
||||||
if ar.touch:
|
if ar.touch:
|
||||||
req["umod"] = True
|
req["umod"] = True
|
||||||
if ar.ow:
|
if ar.owo:
|
||||||
|
req["replace"] = "mt"
|
||||||
|
elif ar.ow:
|
||||||
req["replace"] = True
|
req["replace"] = True
|
||||||
|
|
||||||
file.recheck = False
|
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("--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("--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("--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("--spd", action="store_true", help="print speeds for each file")
|
||||||
ap.add_argument("--version", action="store_true", help="show version and exit")
|
ap.add_argument("--version", action="store_true", help="show version and exit")
|
||||||
|
|
||||||
|
|
|
@ -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("--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("--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("--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("--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")
|
ap2.add_argument("--write-uplog", action="store_true", help="write POST reports to textfiles in working-directory")
|
||||||
|
|
||||||
|
|
|
@ -1956,11 +1956,7 @@ class AuthSrv(object):
|
||||||
if vf not in vol.flags:
|
if vf not in vol.flags:
|
||||||
vol.flags[vf] = getattr(self.args, ga)
|
vol.flags[vf] = getattr(self.args, ga)
|
||||||
|
|
||||||
for k in ("nrand",):
|
zs = "forget_ip nrand u2abort u2ow ups_who zip_who"
|
||||||
if k not in vol.flags:
|
|
||||||
vol.flags[k] = getattr(self.args, k)
|
|
||||||
|
|
||||||
zs = "forget_ip nrand u2abort ups_who zip_who"
|
|
||||||
for k in zs.split():
|
for k in zs.split():
|
||||||
if k in vol.flags:
|
if k in vol.flags:
|
||||||
vol.flags[k] = int(vol.flags[k])
|
vol.flags[k] = int(vol.flags[k])
|
||||||
|
@ -2436,6 +2432,7 @@ class AuthSrv(object):
|
||||||
"u2j": self.args.u2j,
|
"u2j": self.args.u2j,
|
||||||
"u2sz": self.args.u2sz,
|
"u2sz": self.args.u2sz,
|
||||||
"u2ts": vf["u2ts"],
|
"u2ts": vf["u2ts"],
|
||||||
|
"u2ow": vf["u2ow"],
|
||||||
"frand": bool(vf.get("rand")),
|
"frand": bool(vf.get("rand")),
|
||||||
"lifetime": vn.js_ls["lifetime"],
|
"lifetime": vn.js_ls["lifetime"],
|
||||||
"u2sort": self.args.u2sort,
|
"u2sort": self.args.u2sort,
|
||||||
|
|
|
@ -82,6 +82,7 @@ def vf_vmap() -> dict[str, str]:
|
||||||
"lg_sba",
|
"lg_sba",
|
||||||
"md_sba",
|
"md_sba",
|
||||||
"nrand",
|
"nrand",
|
||||||
|
"u2ow",
|
||||||
"og_desc",
|
"og_desc",
|
||||||
"og_site",
|
"og_site",
|
||||||
"og_th",
|
"og_th",
|
||||||
|
@ -170,6 +171,7 @@ flagcats = {
|
||||||
"medialinks": "return medialinks for non-up2k uploads (not hotlinks)",
|
"medialinks": "return medialinks for non-up2k uploads (not hotlinks)",
|
||||||
"rand": "force randomized filenames, 9 chars long by default",
|
"rand": "force randomized filenames, 9 chars long by default",
|
||||||
"nrand=N": "randomized filenames are N chars long",
|
"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",
|
"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",
|
"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",
|
"sz=1k-3m": "allow filesizes between 1 KiB and 3MiB",
|
||||||
|
|
|
@ -3373,7 +3373,17 @@ class Up2k(object):
|
||||||
return fname
|
return fname
|
||||||
|
|
||||||
fp = djoin(fdir, 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,))
|
self.log("replacing existing file at %r" % (fp,))
|
||||||
cur = None
|
cur = None
|
||||||
ptop = job["ptop"]
|
ptop = job["ptop"]
|
||||||
|
|
|
@ -151,7 +151,8 @@ var Ls = {
|
||||||
|
|
||||||
"ul_par": "parallel uploads:",
|
"ul_par": "parallel uploads:",
|
||||||
"ut_rand": "randomize filenames",
|
"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_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_ask": 'ask for confirmation before upload starts">💭',
|
||||||
"ut_pot": "improve upload speed on slow devices$Nby making the UI less complex",
|
"ut_pot": "improve upload speed on slow devices$Nby making the UI less complex",
|
||||||
|
@ -751,7 +752,8 @@ var Ls = {
|
||||||
|
|
||||||
"ul_par": "samtidige handl.:",
|
"ul_par": "samtidige handl.:",
|
||||||
"ut_rand": "finn opp nye tilfeldige filnavn",
|
"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_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_ask": 'bekreft filutvalg før opplastning starter">💭',
|
||||||
"ut_pot": "forbedre ytelsen på trege enheter ved å$Nforenkle brukergrensesnittet",
|
"ut_pot": "forbedre ytelsen på trege enheter ved å$Nforenkle brukergrensesnittet",
|
||||||
|
@ -1351,7 +1353,8 @@ var Ls = {
|
||||||
|
|
||||||
"ul_par": "并行上传:",
|
"ul_par": "并行上传:",
|
||||||
"ut_rand": "随机化文件名",
|
"ut_rand": "随机化文件名",
|
||||||
"ut_u2ts": "将最后修改的时间戳$N从你的文件系统复制到服务器",
|
"ut_u2ts": "将最后修改的时间戳$N从你的文件系统复制到服务器\">📅",
|
||||||
|
"ut_ow": "覆盖服务器上的现有文件?$N🛡️: 从不(会生成一个新文件名)$N🕒: 服务器文件较旧则覆盖$N♻️: 总是覆盖,如果文件内容不同", //m
|
||||||
"ut_mt": "在上传时继续哈希其他文件$N$N如果你的 CPU 或硬盘是瓶颈,可能需要禁用",
|
"ut_mt": "在上传时继续哈希其他文件$N$N如果你的 CPU 或硬盘是瓶颈,可能需要禁用",
|
||||||
"ut_ask": '上传开始前询问确认">💭',
|
"ut_ask": '上传开始前询问确认">💭',
|
||||||
"ut_pot": "通过简化 UI 来$N提高慢设备上的上传速度",
|
"ut_pot": "通过简化 UI 来$N提高慢设备上的上传速度",
|
||||||
|
@ -1918,8 +1921,8 @@ ebi('op_up2k').innerHTML = (
|
||||||
' <label for="u2rand" tt="' + L.ut_rand + '">🎲</label>\n' +
|
' <label for="u2rand" tt="' + L.ut_rand + '">🎲</label>\n' +
|
||||||
' </td>\n' +
|
' </td>\n' +
|
||||||
' <td class="c" rowspan="2">\n' +
|
' <td class="c" rowspan="2">\n' +
|
||||||
' <input type="checkbox" id="u2ts" />\n' +
|
' <input type="checkbox" id="u2ow" />\n' +
|
||||||
' <label for="u2ts" tt="' + L.ut_u2ts + '">📅</a>\n' +
|
' <label for="u2ow" tt="' + L.ut_ow + '">?</a>\n' +
|
||||||
' </td>\n' +
|
' </td>\n' +
|
||||||
' <td class="c" data-perm="read" data-dep="idx" rowspan="2">\n' +
|
' <td class="c" data-perm="read" data-dep="idx" rowspan="2">\n' +
|
||||||
' <input type="checkbox" id="fsearch" />\n' +
|
' <input type="checkbox" id="fsearch" />\n' +
|
||||||
|
@ -2037,6 +2040,7 @@ ebi('op_cfg').innerHTML = (
|
||||||
' <h3>' + L.cl_uopts + '</h3>\n' +
|
' <h3>' + L.cl_uopts + '</h3>\n' +
|
||||||
' <div>\n' +
|
' <div>\n' +
|
||||||
' <a id="ask_up" class="tgl btn" href="#" tt="' + L.ut_ask + '</a>\n' +
|
' <a id="ask_up" class="tgl btn" href="#" tt="' + L.ut_ask + '</a>\n' +
|
||||||
|
' <a id="u2ts" class="tgl btn" href="#" tt="' + L.ut_u2ts + '</a>\n' +
|
||||||
' <a id="umod" class="tgl btn" href="#" tt="' + L.cut_umod + '</a>\n' +
|
' <a id="umod" class="tgl btn" href="#" tt="' + L.cut_umod + '</a>\n' +
|
||||||
' <a id="hashw" class="tgl btn" href="#" tt="' + L.cut_mt + '</a>\n' +
|
' <a id="hashw" class="tgl btn" href="#" tt="' + L.cut_mt + '</a>\n' +
|
||||||
' <a id="u2turbo" class="tgl btn ttb" href="#" tt="' + L.cut_turbo + '</a>\n' +
|
' <a id="u2turbo" class="tgl btn ttb" href="#" tt="' + L.cut_turbo + '</a>\n' +
|
||||||
|
|
|
@ -885,6 +885,21 @@ function up2k_init(subtle) {
|
||||||
bcfg_bind(uc, 'upnag', 'upnag', false, set_upnag);
|
bcfg_bind(uc, 'upnag', 'upnag', false, set_upnag);
|
||||||
bcfg_bind(uc, 'upsfx', 'upsfx', false, set_upsfx);
|
bcfg_bind(uc, 'upsfx', 'upsfx', false, set_upsfx);
|
||||||
|
|
||||||
|
uc.ow = parseInt(sread('u2ow', ['0', '1', '2']) || 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)
|
||||||
|
uc.ow = 0;
|
||||||
|
swrite('u2ow', uc.ow);
|
||||||
|
set_ow();
|
||||||
|
};
|
||||||
|
set_ow();
|
||||||
|
|
||||||
var st = {
|
var st = {
|
||||||
"files": [],
|
"files": [],
|
||||||
"nfile": {
|
"nfile": {
|
||||||
|
@ -2634,6 +2649,13 @@ function up2k_init(subtle) {
|
||||||
else if (t.umod)
|
else if (t.umod)
|
||||||
req.umod = true;
|
req.umod = true;
|
||||||
|
|
||||||
|
if (!t.srch) {
|
||||||
|
if (uc.ow == 1)
|
||||||
|
req.replace = 'mt';
|
||||||
|
if (uc.ow == 2)
|
||||||
|
req.replace = true;
|
||||||
|
}
|
||||||
|
|
||||||
xhr.open('POST', t.purl, true);
|
xhr.open('POST', t.purl, true);
|
||||||
xhr.responseType = 'text';
|
xhr.responseType = 'text';
|
||||||
xhr.timeout = 42000 + (t.srch || t.t_uploaded ? 0 :
|
xhr.timeout = 42000 + (t.srch || t.t_uploaded ? 0 :
|
||||||
|
|
|
@ -144,7 +144,7 @@ class Cfg(Namespace):
|
||||||
ex = "au_vol dl_list mtab_age reg_cap s_thead s_tbody th_convt ups_who zip_who"
|
ex = "au_vol dl_list mtab_age reg_cap s_thead s_tbody th_convt ups_who zip_who"
|
||||||
ka.update(**{k: 9 for k in ex.split()})
|
ka.update(**{k: 9 for k in ex.split()})
|
||||||
|
|
||||||
ex = "db_act forget_ip k304 loris no304 re_maxage rproxy rsp_jtr rsp_slp s_wr_slp snap_wri theme themes turbo"
|
ex = "db_act forget_ip k304 loris no304 re_maxage rproxy rsp_jtr rsp_slp s_wr_slp snap_wri theme themes turbo u2ow"
|
||||||
ka.update(**{k: 0 for k in ex.split()})
|
ka.update(**{k: 0 for k in ex.split()})
|
||||||
|
|
||||||
ex = "ah_alg bname chpw_db doctitle df exit favico idp_h_usr ipa html_head lg_sba lg_sbf log_fk md_sba md_sbf name og_desc og_site og_th og_title og_title_a og_title_v og_title_i shr tcolor textfiles unlist vname xff_src R RS SR"
|
ex = "ah_alg bname chpw_db doctitle df exit favico idp_h_usr ipa html_head lg_sba lg_sbf log_fk md_sba md_sbf name og_desc og_site og_th og_title og_title_a og_title_v og_title_i shr tcolor textfiles unlist vname xff_src R RS SR"
|
||||||
|
|
Loading…
Reference in a new issue