u2cli: add eta, errorhandling, better windows support

This commit is contained in:
ed 2021-10-01 22:31:24 +02:00
parent 9e10af6885
commit e5b67d2b3a
2 changed files with 67 additions and 48 deletions

View file

@ -3,7 +3,7 @@ from __future__ import print_function, unicode_literals
""" """
up2k.py: upload to copyparty up2k.py: upload to copyparty
2021-09-30, v0.5, ed <irc.rizon.net>, MIT-Licensed 2021-09-30, v0.6, 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
@ -26,6 +26,7 @@ import argparse
import platform import platform
import threading import threading
import requests import requests
import datetime
# from copyparty/__init__.py # from copyparty/__init__.py
@ -40,12 +41,7 @@ else:
unicode = str unicode = str
WINDOWS = False VT100 = platform.system() != "Windows"
if platform.system() == "Windows":
WINDOWS = [int(x) for x in platform.version().split(".")]
VT100 = not WINDOWS or WINDOWS >= [10, 0, 14393]
# introduced in anniversary update
req_ses = requests.Session() req_ses = requests.Session()
@ -76,7 +72,7 @@ class File(object):
self.up_b = 0 # type: int self.up_b = 0 # type: int
self.up_c = 0 # type: int self.up_c = 0 # type: int
# m = "size({}) lmod({}) top({}) rel({}) abs({}) name({})" # m = "size({}) lmod({}) top({}) rel({}) abs({}) name({})\n"
# eprint(m.format(self.size, self.lmod, self.top, self.rel, self.abs, self.name)) # eprint(m.format(self.size, self.lmod, self.top, self.rel, self.abs, self.name))
@ -127,6 +123,7 @@ class FileSlice(object):
def eprint(*a, **ka): def eprint(*a, **ka):
ka["file"] = sys.stderr ka["file"] = sys.stderr
ka["end"] = ""
if not PY2: if not PY2:
ka["flush"] = True ka["flush"] = True
@ -204,7 +201,7 @@ class CTermsize(object):
else: else:
self.g = 1 + self.h - margin self.g = 1 + self.h - margin
m = "{}\033[{}A".format("\n" * margin, margin) m = "{}\033[{}A".format("\n" * margin, margin)
eprint("{}\033[s\033[1;{}r\033[u".format(m, self.g - 1), end="") eprint("{}\033[s\033[1;{}r\033[u".format(m, self.g - 1))
ss = CTermsize() ss = CTermsize()
@ -224,7 +221,7 @@ def statdir(top):
def walkdir(top): def walkdir(top):
"""recursive statdir""" """recursive statdir"""
for ap, inf in statdir(top): for ap, inf in sorted(statdir(top)):
if stat.S_ISDIR(inf.st_mode): if stat.S_ISDIR(inf.st_mode):
for x in walkdir(ap): for x in walkdir(ap):
yield x yield x
@ -337,7 +334,14 @@ def handshake(req_ses, url, file, pw, search):
elif b"/" in file.rel: elif b"/" in file.rel:
url += file.rel.rsplit(b"/", 1)[0].decode("utf-8", "replace") url += file.rel.rsplit(b"/", 1)[0].decode("utf-8", "replace")
while True:
try:
r = req_ses.post(url, headers=headers, json=req) r = req_ses.post(url, headers=headers, json=req)
break
except:
eprint("handshake failed, retry...\n")
time.sleep(1)
try: try:
r = r.json() r = r.json()
except: except:
@ -396,12 +400,14 @@ class Ctl(object):
def __init__(self, ar): def __init__(self, ar):
self.ar = ar self.ar = ar
ar.url = ar.url.rstrip("/") + "/"
ar.files = [ ar.files = [
os.path.abspath(os.path.realpath(x.encode("utf-8"))) for x in ar.files os.path.abspath(os.path.realpath(x.encode("utf-8"))) for x in ar.files
] ]
ar.url = ar.url.rstrip("/") + "/"
if "://" not in ar.url:
ar.url = "http://" + ar.url
eprint("\nscanning {} locations".format(len(ar.files))) eprint("\nscanning {} locations\n".format(len(ar.files)))
nfiles = 0 nfiles = 0
nbytes = 0 nbytes = 0
@ -409,7 +415,7 @@ class Ctl(object):
nfiles += 1 nfiles += 1
nbytes += inf.st_size nbytes += inf.st_size
eprint("found {} files, {}\n".format(nfiles, humansize(nbytes))) eprint("found {} files, {}\n\n".format(nfiles, humansize(nbytes)))
self.nfiles = nfiles self.nfiles = nfiles
self.nbytes = nbytes self.nbytes = nbytes
@ -464,32 +470,33 @@ class Ctl(object):
self.up_f = 0 self.up_f = 0
self.up_c = 0 self.up_c = 0
self.up_b = 0 self.up_b = 0
self.up_br = 0
self.hasher_busy = 1 self.hasher_busy = 1
self.handshaker_busy = 0 self.handshaker_busy = 0
self.uploader_busy = 0 self.uploader_busy = 0
self.t0 = time.time()
self.t0_up = None
self.spd = None
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_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.cb_hasher = self._cb_hasher_basic
self.cb_uploader = self._cb_uploader_basic
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]
if VT100: if VT100:
self.cb_hasher = self._cb_hasher_vt100
self.cb_uploader = self._cb_uploader_vt100
atexit.register(self.cleanup_vt100) atexit.register(self.cleanup_vt100)
ss.scroll_region(3) ss.scroll_region(3)
# eprint("\033[s\033[{}Hhello from g\033[u".format(ss.g))
Daemon(target=self.hasher).start() Daemon(target=self.hasher).start()
for _ in range(self.ar.j): for _ in range(self.ar.j):
Daemon(target=self.handshaker).start() Daemon(target=self.handshaker).start()
Daemon(target=self.uploader).start() Daemon(target=self.uploader).start()
while True: idles = 0
while idles < 3:
time.sleep(0.07) time.sleep(0.07)
with self.mutex: with self.mutex:
if ( if (
@ -499,14 +506,16 @@ class Ctl(object):
and not self.handshaker_busy and not self.handshaker_busy
and not self.uploader_busy and not self.uploader_busy
): ):
break idles += 1
else:
idles = 0
if VT100: if VT100:
maxlen = ss.w - len(str(self.nfiles)) - 14 maxlen = ss.w - len(str(self.nfiles)) - 14
txt = "\033[s\033[{}H".format(ss.g) txt = "\033[s\033[{}H".format(ss.g)
for y, k, st, f, c, b in [ for y, k, st, f in [
[0, "hash", self.st_hash, self.hash_f, self.hash_c, self.hash_b], [0, "hash", self.st_hash, self.hash_f],
[1, "send", self.st_up, self.up_f, self.up_c, self.up_b], [1, "send", self.st_up, self.up_f],
]: ]:
txt += "\033[{}H{}:".format(ss.g + y, k) txt += "\033[{}H{}:".format(ss.g + y, k)
file, arg = st file, arg = st
@ -519,30 +528,41 @@ class Ctl(object):
p = 100 * arg / file.size p = 100 * arg / file.size
name = file.abs.decode("utf-8", "replace")[-maxlen:] name = file.abs.decode("utf-8", "replace")[-maxlen:]
if "/" in name:
name = "\033[36m{}\033[0m/{}".format(*name.rsplit("/", 1))
m = "{:6.1f}% {} {}\033[K" m = "{:6.1f}% {} {}\033[K"
txt += m.format(p, self.nfiles - f, name) txt += m.format(p, self.nfiles - f, name)
eprint(txt + "\033[u", end="") txt += "\033[{}H ".format(ss.g + 2)
else:
txt = " "
if not self.up_br:
spd = self.hash_b / (time.time() - self.t0)
eta = (self.nbytes - self.hash_b) / (spd + 1)
else:
spd = self.up_br / (time.time() - self.t0_up)
spd = self.spd = (self.spd or spd) * 0.9 + spd * 0.1
eta = (self.nbytes - self.up_b) / (spd + 1)
spd = humansize(spd)
eta = str(datetime.timedelta(seconds=int(eta)))
left = humansize(self.nbytes - self.up_b)
tail = "\033[K\033[u" if VT100 else "\r"
m = "eta: {} @ {}/s, {} left".format(eta, spd, left)
eprint(txt + "\033]0;{}\033\\\r{}{}".format(m, m, tail))
def cleanup_vt100(self): def cleanup_vt100(self):
ss.scroll_region(None) ss.scroll_region(None)
eprint("\033[J", end="") eprint("\033[J\033]0;\033\\")
def _cb_hasher_basic(self, file, ofs): def cb_hasher(self, file, ofs):
eprint(".", end="")
def _cb_uploader_basic(self, file, cid):
eprint("*", end="")
def _cb_hasher_vt100(self, file, ofs):
self.st_hash = [file, ofs] self.st_hash = [file, ofs]
def _cb_uploader_vt100(self, file, cid):
self.st_up = [file, cid]
def hasher(self): def hasher(self):
for nf, (top, rel, inf) in enumerate(self.filegen): for top, rel, inf in self.filegen:
file = File(top, rel, inf.st_size, inf.st_mtime) file = File(top, rel, inf.st_size, inf.st_mtime)
while True: while True:
with self.mutex: with self.mutex:
@ -562,10 +582,6 @@ class Ctl(object):
time.sleep(0.05) time.sleep(0.05)
if not VT100:
upath = file.abs.decode("utf-8", "replace")
eprint("\n{:6d} hash {}\n".format(self.nfiles - nf, upath), end="")
get_hashlist(file, self.cb_hasher) get_hashlist(file, self.cb_hasher)
with self.mutex: with self.mutex:
self.hash_f += 1 self.hash_f += 1
@ -595,8 +611,6 @@ 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")
if not VT100:
eprint("\n handshake {}\n".format(upath), end="")
try: try:
hs = handshake(req_ses, self.ar.url, file, self.ar.a, search) hs = handshake(req_ses, self.ar.url, file, self.ar.a, search)
@ -642,7 +656,7 @@ class Ctl(object):
file.ucids = hs file.ucids = hs
self.handshaker_busy -= 1 self.handshaker_busy -= 1
if not hs and VT100: if not hs:
print("uploaded {}".format(upath)) print("uploaded {}".format(upath))
for cid in hs: for cid in hs:
self.q_upload.put([file, cid]) self.q_upload.put([file, cid])
@ -656,9 +670,14 @@ class Ctl(object):
with self.mutex: with self.mutex:
self.uploader_busy += 1 self.uploader_busy += 1
self.t0_up = self.t0_up or time.time()
file, cid = task file, cid = task
try:
upload(req_ses, file, cid, self.ar.a) upload(req_ses, file, cid, self.ar.a)
except:
eprint("upload failed, retry...\n")
pass # handshake will fix it
with self.mutex: with self.mutex:
sz = file.kchunks[cid][1] sz = file.kchunks[cid][1]
@ -666,18 +685,18 @@ class Ctl(object):
if not file.ucids: if not file.ucids:
self.q_handshake.put(file) self.q_handshake.put(file)
self.st_up = [file, cid]
file.up_b += sz file.up_b += sz
self.up_b += sz self.up_b += sz
self.up_br += sz
file.up_c += 1 file.up_c += 1
self.up_c += 1 self.up_c += 1
self.uploader_busy -= 1 self.uploader_busy -= 1
self.cb_uploader(file, cid)
def main(): def main():
time.strptime("19970815", "%Y%m%d") # python#7980 time.strptime("19970815", "%Y%m%d") # python#7980
if WINDOWS: if not VT100:
os.system("rem") # enables colors os.system("rem") # enables colors
# fmt: off # fmt: off

0
bin/up2k.sh Executable file → Normal file
View file