u2cli: server is allowed to reject dupes

This commit is contained in:
ed 2022-11-29 22:09:32 +00:00
parent 29c212a60e
commit 3a800585bc
3 changed files with 47 additions and 31 deletions

View file

@ -3,7 +3,7 @@ from __future__ import print_function, unicode_literals
""" """
up2k.py: upload to copyparty up2k.py: upload to copyparty
2022-11-04, v0.21, ed <irc.rizon.net>, MIT-Licensed 2022-11-29, v0.22, ed <irc.rizon.net>, MIT-Licensed
https://github.com/9001/copyparty/blob/hovudstraum/bin/up2k.py https://github.com/9001/copyparty/blob/hovudstraum/bin/up2k.py
- dependencies: requests - dependencies: requests
@ -94,6 +94,7 @@ class File(object):
self.kchunks = {} # type: dict[str, tuple[int, int]] # hash: [ ofs, sz ] self.kchunks = {} # type: dict[str, tuple[int, int]] # hash: [ ofs, sz ]
# set by handshake # set by handshake
self.recheck = False # duplicate; redo handshake after all files done
self.ucids = [] # type: list[str] # chunks which need to be uploaded self.ucids = [] # type: list[str] # chunks which need to be uploaded
self.wark = None # type: str self.wark = None # type: str
self.url = None # type: str self.url = None # type: str
@ -472,8 +473,8 @@ def get_hashlist(file, pcb, mth):
file.kchunks[k] = [v1, v2] file.kchunks[k] = [v1, v2]
def handshake(req_ses, url, file, pw, search): def handshake(url, file, pw, search):
# type: (requests.Session, str, File, any, bool) -> list[str] # type: (str, File, Any, bool) -> tuple[list[str], bool]
""" """
performs a handshake with the server; reply is: performs a handshake with the server; reply is:
if search, a list of search results if search, a list of search results
@ -507,6 +508,17 @@ def handshake(req_ses, url, file, pw, search):
eprint("handshake failed, retrying: {0}\n {1}\n\n".format(file.name, em)) eprint("handshake failed, retrying: {0}\n {1}\n\n".format(file.name, em))
time.sleep(1) time.sleep(1)
sc = r.status_code
if sc >= 400:
txt = r.text
if sc == 422 or "<pre>partial upload exists at a different" in txt:
file.recheck = True
return [], False
elif sc == 409 or "<pre>upload rejected, file already exists" in txt:
return [], False
raise Exception("http {0}: {1}".format(sc, txt))
try: try:
r = r.json() r = r.json()
except: except:
@ -528,8 +540,8 @@ def handshake(req_ses, url, file, pw, search):
return r["hash"], r["sprs"] return r["hash"], r["sprs"]
def upload(req_ses, file, cid, pw): def upload(file, cid, pw):
# type: (requests.Session, File, str, any) -> None # type: (File, str, Any) -> None
"""upload one specific chunk, `cid` (a chunk-hash)""" """upload one specific chunk, `cid` (a chunk-hash)"""
headers = { headers = {
@ -626,8 +638,8 @@ class Ctl(object):
self.mutex = threading.Lock() self.mutex = threading.Lock()
self.q_handshake = Queue() # type: Queue[File] self.q_handshake = Queue() # type: Queue[File]
self.q_recheck = Queue() # type: Queue[File] # partial upload exists [...]
self.q_upload = Queue() # type: Queue[tuple[File, str]] self.q_upload = Queue() # type: Queue[tuple[File, str]]
self.recheck = [] # type: list[File]
self.st_hash = [None, "(idle, starting...)"] # type: tuple[File, int] self.st_hash = [None, "(idle, starting...)"] # type: tuple[File, int]
self.st_up = [None, "(idle, starting...)"] # type: tuple[File, int] self.st_up = [None, "(idle, starting...)"] # type: tuple[File, int]
@ -649,7 +661,7 @@ class Ctl(object):
burl = self.ar.url[:12] + self.ar.url[8:].split("/")[0] + "/" burl = self.ar.url[:12] + self.ar.url[8:].split("/")[0] + "/"
while True: while True:
print(" hs...") print(" hs...")
hs, _ = handshake(req_ses, self.ar.url, file, self.ar.a, search) hs, _ = handshake(self.ar.url, file, self.ar.a, search)
if search: if search:
if hs: if hs:
for hit in hs: for hit in hs:
@ -666,9 +678,18 @@ class Ctl(object):
ncs = len(hs) ncs = len(hs)
for nc, cid in enumerate(hs): for nc, cid in enumerate(hs):
print(" {0} up {1}".format(ncs - nc, cid)) print(" {0} up {1}".format(ncs - nc, cid))
upload(req_ses, file, cid, self.ar.a) upload(file, cid, self.ar.a)
print(" ok!") print(" ok!")
if file.recheck:
self.recheck.append(file)
if not self.recheck:
return
eprint("finalizing {0} duplicate files".format(len(self.recheck)))
for file in self.recheck:
handshake(self.ar.url, file, self.ar.a, search)
def _fancy(self): def _fancy(self):
if VT100: if VT100:
@ -740,6 +761,13 @@ class Ctl(object):
t = "{0} eta @ {1}/s, {2}, {3}# left".format(eta, spd, sleft, nleft) t = "{0} eta @ {1}/s, {2}, {3}# left".format(eta, spd, sleft, nleft)
eprint(txt + "\033]0;{0}\033\\\r{0}{1}".format(t, tail)) eprint(txt + "\033]0;{0}\033\\\r{0}{1}".format(t, tail))
if not self.recheck:
return
eprint("finalizing {0} duplicate files".format(len(self.recheck)))
for file in self.recheck:
handshake(self.ar.url, file, self.ar.a, False)
def cleanup_vt100(self): def cleanup_vt100(self):
ss.scroll_region(None) ss.scroll_region(None)
eprint("\033[J\033]0;\033\\") eprint("\033[J\033]0;\033\\")
@ -812,16 +840,10 @@ class Ctl(object):
def handshaker(self): def handshaker(self):
search = self.ar.s search = self.ar.s
q = self.q_handshake
burl = self.ar.url[:8] + self.ar.url[8:].split("/")[0] + "/" burl = self.ar.url[:8] + self.ar.url[8:].split("/")[0] + "/"
while True: while True:
file = q.get() file = self.q_handshake.get()
if not file: if not file:
if q == self.q_handshake:
q = self.q_recheck
q.put(None)
continue
self.q_upload.put(None) self.q_upload.put(None)
break break
@ -829,16 +851,7 @@ class Ctl(object):
self.handshaker_busy += 1 self.handshaker_busy += 1
upath = file.abs.decode("utf-8", "replace") upath = file.abs.decode("utf-8", "replace")
hs, sprs = handshake(self.ar.url, file, self.ar.a, search)
try:
hs, sprs = handshake(req_ses, self.ar.url, file, self.ar.a, search)
except Exception as ex:
if q == self.q_handshake and "<pre>partial upload exists" in str(ex):
self.q_recheck.put(file)
hs = []
else:
raise
if search: if search:
if hs: if hs:
for hit in hs: for hit in hs:
@ -855,8 +868,11 @@ class Ctl(object):
continue continue
if file.recheck:
self.recheck.append(file)
with self.mutex: with self.mutex:
if not sprs and not self.serialized: if hs and not sprs and not self.serialized:
t = "server filesystem does not support sparse files; serializing uploads\n" t = "server filesystem does not support sparse files; serializing uploads\n"
eprint(t) eprint(t)
self.serialized = True self.serialized = True
@ -899,7 +915,7 @@ class Ctl(object):
file, cid = task file, cid = task
try: try:
upload(req_ses, file, cid, self.ar.a) upload(file, cid, self.ar.a)
except: except:
eprint("upload failed, retrying: {0} #{1}\n".format(file.name, cid[:8])) eprint("upload failed, retrying: {0} #{1}\n".format(file.name, cid[:8]))
pass # handshake will fix it pass # handshake will fix it

View file

@ -2005,13 +2005,13 @@ class Up2k(object):
except: except:
self.dupesched[src] = [dupe] self.dupesched[src] = [dupe]
raise Pebkac(400, err) raise Pebkac(422, err)
elif "nodupe" in self.flags[job["ptop"]]: elif "nodupe" in self.flags[job["ptop"]]:
self.log("dupe-reject:\n {0}\n {1}".format(src, dst)) self.log("dupe-reject:\n {0}\n {1}".format(src, dst))
err = "upload rejected, file already exists:\n" err = "upload rejected, file already exists:\n"
err += "/" + quotep(vsrc) + " " err += "/" + quotep(vsrc) + " "
raise Pebkac(400, err) raise Pebkac(409, err)
else: else:
# symlink to the client-provided name, # symlink to the client-provided name,
# returning the previous upload info # returning the previous upload info

View file

@ -2297,8 +2297,8 @@ function up2k_init(subtle) {
return; return;
} }
var err_pend = rsp.indexOf('partial upload exists') + 1, var err_pend = rsp.indexOf('partial upload exists at a different') + 1,
err_dupe = rsp.indexOf('file already exists') + 1; err_dupe = rsp.indexOf('upload rejected, file already exists') + 1;
if (err_pend || err_dupe) { if (err_pend || err_dupe) {
err = rsp; err = rsp;