mirror of
https://github.com/9001/copyparty.git
synced 2025-08-17 09:02:15 -06:00
u2cli: add eta, errorhandling, better windows support
This commit is contained in:
parent
9e10af6885
commit
e5b67d2b3a
111
bin/up2k.py
111
bin/up2k.py
|
@ -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
0
bin/up2k.sh
Executable file → Normal file
Loading…
Reference in a new issue