mirror of
https://github.com/9001/copyparty.git
synced 2025-08-17 09:02:15 -06:00
u2c: add hashgen mode + fix shutdown lag
This commit is contained in:
parent
b599fbae97
commit
08848be784
151
bin/u2c.py
151
bin/u2c.py
|
@ -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 = "1.23"
|
S_VERSION = "1.24"
|
||||||
S_BUILD_DT = "2024-08-22"
|
S_BUILD_DT = "2024-09-05"
|
||||||
|
|
||||||
"""
|
"""
|
||||||
u2c.py: upload to copyparty
|
u2c.py: upload to copyparty
|
||||||
|
@ -41,19 +41,25 @@ except:
|
||||||
|
|
||||||
try:
|
try:
|
||||||
import requests
|
import requests
|
||||||
|
|
||||||
|
req_ses = requests.Session()
|
||||||
except ImportError as ex:
|
except ImportError as ex:
|
||||||
if EXE:
|
if "-" in sys.argv or "-h" in sys.argv:
|
||||||
|
m = ""
|
||||||
|
elif EXE:
|
||||||
raise
|
raise
|
||||||
elif sys.version_info > (2, 7):
|
elif sys.version_info > (2, 7):
|
||||||
m = "\nERROR: need 'requests'; please run this command:\n {0} -m pip install --user requests\n"
|
m = "\nERROR: need 'requests'{0}; please run this command:\n {1} -m pip install --user requests\n"
|
||||||
else:
|
else:
|
||||||
m = "requests/2.18.4 urllib3/1.23 chardet/3.0.4 certifi/2020.4.5.1 idna/2.7"
|
m = "requests/2.18.4 urllib3/1.23 chardet/3.0.4 certifi/2020.4.5.1 idna/2.7"
|
||||||
m = [" https://pypi.org/project/" + x + "/#files" for x in m.split()]
|
m = [" https://pypi.org/project/" + x + "/#files" for x in m.split()]
|
||||||
m = "\n ERROR: need these:\n" + "\n".join(m) + "\n"
|
m = "\n ERROR: need these{0}:\n" + "\n".join(m) + "\n"
|
||||||
m += "\n for f in *.whl; do unzip $f; done; rm -r *.dist-info\n"
|
m += "\n for f in *.whl; do unzip $f; done; rm -r *.dist-info\n"
|
||||||
|
|
||||||
print(m.format(sys.executable), "\nspecifically,", ex)
|
if m:
|
||||||
sys.exit(1)
|
t = " when not running with '-h' or url '-'"
|
||||||
|
print(m.format(t, sys.executable), "\nspecifically,", ex)
|
||||||
|
sys.exit(1)
|
||||||
|
|
||||||
|
|
||||||
# from copyparty/__init__.py
|
# from copyparty/__init__.py
|
||||||
|
@ -76,7 +82,22 @@ else:
|
||||||
VT100 = platform.system() != "Windows"
|
VT100 = platform.system() != "Windows"
|
||||||
|
|
||||||
|
|
||||||
req_ses = requests.Session()
|
try:
|
||||||
|
UTC = datetime.timezone.utc
|
||||||
|
except:
|
||||||
|
TD_ZERO = datetime.timedelta(0)
|
||||||
|
|
||||||
|
class _UTC(datetime.tzinfo):
|
||||||
|
def utcoffset(self, dt):
|
||||||
|
return TD_ZERO
|
||||||
|
|
||||||
|
def tzname(self, dt):
|
||||||
|
return "UTC"
|
||||||
|
|
||||||
|
def dst(self, dt):
|
||||||
|
return TD_ZERO
|
||||||
|
|
||||||
|
UTC = _UTC()
|
||||||
|
|
||||||
|
|
||||||
class Daemon(threading.Thread):
|
class Daemon(threading.Thread):
|
||||||
|
@ -271,6 +292,12 @@ class MTHash(object):
|
||||||
_print = print
|
_print = print
|
||||||
|
|
||||||
|
|
||||||
|
def safe_print(*a, **ka):
|
||||||
|
ka["end"] = ""
|
||||||
|
zs = " ".join([unicode(x) for x in a])
|
||||||
|
_print(zs + "\n", **ka)
|
||||||
|
|
||||||
|
|
||||||
def eprint(*a, **ka):
|
def eprint(*a, **ka):
|
||||||
ka["file"] = sys.stderr
|
ka["file"] = sys.stderr
|
||||||
ka["end"] = ""
|
ka["end"] = ""
|
||||||
|
@ -284,18 +311,17 @@ def eprint(*a, **ka):
|
||||||
|
|
||||||
def flushing_print(*a, **ka):
|
def flushing_print(*a, **ka):
|
||||||
try:
|
try:
|
||||||
_print(*a, **ka)
|
safe_print(*a, **ka)
|
||||||
except:
|
except:
|
||||||
v = " ".join(str(x) for x in a)
|
v = " ".join(str(x) for x in a)
|
||||||
v = v.encode("ascii", "replace").decode("ascii")
|
v = v.encode("ascii", "replace").decode("ascii")
|
||||||
_print(v, **ka)
|
safe_print(v, **ka)
|
||||||
|
|
||||||
if "flush" not in ka:
|
if "flush" not in ka:
|
||||||
sys.stdout.flush()
|
sys.stdout.flush()
|
||||||
|
|
||||||
|
|
||||||
if not VT100:
|
print = safe_print if VT100 else flushing_print
|
||||||
print = flushing_print
|
|
||||||
|
|
||||||
|
|
||||||
def termsize():
|
def termsize():
|
||||||
|
@ -770,8 +796,6 @@ class Ctl(object):
|
||||||
self.up_c = 0
|
self.up_c = 0
|
||||||
self.up_b = 0
|
self.up_b = 0
|
||||||
self.up_br = 0
|
self.up_br = 0
|
||||||
self.hasher_busy = 1
|
|
||||||
self.handshaker_busy = 0
|
|
||||||
self.uploader_busy = 0
|
self.uploader_busy = 0
|
||||||
self.serialized = False
|
self.serialized = False
|
||||||
|
|
||||||
|
@ -781,6 +805,9 @@ class Ctl(object):
|
||||||
self.eta = "99:99:99"
|
self.eta = "99:99:99"
|
||||||
|
|
||||||
self.mutex = threading.Lock()
|
self.mutex = threading.Lock()
|
||||||
|
self.exit_cond = threading.Condition()
|
||||||
|
self.uploader_alive = ar.j
|
||||||
|
self.handshaker_alive = ar.j
|
||||||
self.q_handshake = Queue() # type: Queue[File]
|
self.q_handshake = Queue() # type: Queue[File]
|
||||||
self.q_upload = Queue() # type: Queue[FileSlice]
|
self.q_upload = Queue() # type: Queue[FileSlice]
|
||||||
|
|
||||||
|
@ -851,27 +878,21 @@ class Ctl(object):
|
||||||
Daemon(self.handshaker)
|
Daemon(self.handshaker)
|
||||||
Daemon(self.uploader)
|
Daemon(self.uploader)
|
||||||
|
|
||||||
idles = 0
|
while True:
|
||||||
while idles < 3:
|
with self.exit_cond:
|
||||||
time.sleep(0.07)
|
self.exit_cond.wait(0.07)
|
||||||
with self.mutex:
|
with self.mutex:
|
||||||
if (
|
if not self.handshaker_alive and not self.uploader_alive:
|
||||||
self.q_handshake.empty()
|
break
|
||||||
and self.q_upload.empty()
|
st_hash = self.st_hash[:]
|
||||||
and not self.hasher_busy
|
st_up = self.st_up[:]
|
||||||
and not self.handshaker_busy
|
|
||||||
and not self.uploader_busy
|
|
||||||
):
|
|
||||||
idles += 1
|
|
||||||
else:
|
|
||||||
idles = 0
|
|
||||||
|
|
||||||
if VT100 and not self.ar.ns:
|
if VT100 and not self.ar.ns:
|
||||||
maxlen = ss.w - len(str(self.nfiles)) - 14
|
maxlen = ss.w - len(str(self.nfiles)) - 14
|
||||||
txt = "\033[s\033[{0}H".format(ss.g)
|
txt = "\033[s\033[{0}H".format(ss.g)
|
||||||
for y, k, st, f in [
|
for y, k, st, f in [
|
||||||
[0, "hash", self.st_hash, self.hash_f],
|
[0, "hash", st_hash, self.hash_f],
|
||||||
[1, "send", self.st_up, self.up_f],
|
[1, "send", st_up, self.up_f],
|
||||||
]:
|
]:
|
||||||
txt += "\033[{0}H{1}:".format(ss.g + y, k)
|
txt += "\033[{0}H{1}:".format(ss.g + y, k)
|
||||||
file, arg = st
|
file, arg = st
|
||||||
|
@ -1027,11 +1048,42 @@ class Ctl(object):
|
||||||
self.hash_f += 1
|
self.hash_f += 1
|
||||||
self.hash_c += len(file.cids)
|
self.hash_c += len(file.cids)
|
||||||
self.hash_b += file.size
|
self.hash_b += file.size
|
||||||
|
if self.ar.wlist:
|
||||||
|
self.up_f = self.hash_f
|
||||||
|
self.up_c = self.hash_c
|
||||||
|
self.up_b = self.hash_b
|
||||||
|
|
||||||
|
if self.ar.wlist:
|
||||||
|
zsl = [self.ar.wsalt, str(file.size)] + [x[0] for x in file.kchunks]
|
||||||
|
zb = hashlib.sha512("\n".join(zsl).encode("utf-8")).digest()[:33]
|
||||||
|
wark = base64.urlsafe_b64encode(zb).decode("utf-8")
|
||||||
|
vp = file.rel.decode("utf-8")
|
||||||
|
if self.ar.jw:
|
||||||
|
print("%s %s" % (wark, vp))
|
||||||
|
else:
|
||||||
|
zd = datetime.datetime.fromtimestamp(file.lmod, UTC)
|
||||||
|
dt = "%04d-%02d-%02d %02d:%02d:%02d" % (
|
||||||
|
zd.year,
|
||||||
|
zd.month,
|
||||||
|
zd.day,
|
||||||
|
zd.hour,
|
||||||
|
zd.minute,
|
||||||
|
zd.second,
|
||||||
|
)
|
||||||
|
print("%s %12d %s %s" % (dt, file.size, wark, vp))
|
||||||
|
continue
|
||||||
|
|
||||||
self.q_handshake.put(file)
|
self.q_handshake.put(file)
|
||||||
|
|
||||||
self.hasher_busy = 0
|
|
||||||
self.st_hash = [None, "(finished)"]
|
self.st_hash = [None, "(finished)"]
|
||||||
|
self._check_if_done()
|
||||||
|
|
||||||
|
def _check_if_done(self):
|
||||||
|
with self.mutex:
|
||||||
|
if self.nfiles - self.up_f:
|
||||||
|
return
|
||||||
|
for _ in range(self.ar.j):
|
||||||
|
self.q_handshake.put(None)
|
||||||
|
|
||||||
def handshaker(self):
|
def handshaker(self):
|
||||||
search = self.ar.s
|
search = self.ar.s
|
||||||
|
@ -1039,8 +1091,10 @@ class Ctl(object):
|
||||||
while True:
|
while True:
|
||||||
file = self.q_handshake.get()
|
file = self.q_handshake.get()
|
||||||
if not file:
|
if not file:
|
||||||
|
with self.mutex:
|
||||||
|
self.handshaker_alive -= 1
|
||||||
self.q_upload.put(None)
|
self.q_upload.put(None)
|
||||||
break
|
return
|
||||||
|
|
||||||
upath = file.abs.decode("utf-8", "replace")
|
upath = file.abs.decode("utf-8", "replace")
|
||||||
if not VT100:
|
if not VT100:
|
||||||
|
@ -1052,9 +1106,6 @@ class Ctl(object):
|
||||||
self.errs += 1
|
self.errs += 1
|
||||||
continue
|
continue
|
||||||
|
|
||||||
with self.mutex:
|
|
||||||
self.handshaker_busy += 1
|
|
||||||
|
|
||||||
while time.time() < file.cd:
|
while time.time() < file.cd:
|
||||||
time.sleep(0.1)
|
time.sleep(0.1)
|
||||||
|
|
||||||
|
@ -1062,17 +1113,17 @@ class Ctl(object):
|
||||||
if search:
|
if search:
|
||||||
if hs:
|
if hs:
|
||||||
for hit in hs:
|
for hit in hs:
|
||||||
t = "found: {0}\n {1}{2}\n"
|
t = "found: {0}\n {1}{2}"
|
||||||
print(t.format(upath, burl, hit["rp"]), end="")
|
print(t.format(upath, burl, hit["rp"]))
|
||||||
else:
|
else:
|
||||||
print("NOT found: {0}\n".format(upath), end="")
|
print("NOT found: {0}".format(upath))
|
||||||
|
|
||||||
with self.mutex:
|
with self.mutex:
|
||||||
self.up_f += 1
|
self.up_f += 1
|
||||||
self.up_c += len(file.cids)
|
self.up_c += len(file.cids)
|
||||||
self.up_b += file.size
|
self.up_b += file.size
|
||||||
self.handshaker_busy -= 1
|
|
||||||
|
|
||||||
|
self._check_if_done()
|
||||||
continue
|
continue
|
||||||
|
|
||||||
if file.recheck:
|
if file.recheck:
|
||||||
|
@ -1104,7 +1155,6 @@ class Ctl(object):
|
||||||
file.up_b -= sz
|
file.up_b -= sz
|
||||||
|
|
||||||
file.ucids = hs
|
file.ucids = hs
|
||||||
self.handshaker_busy -= 1
|
|
||||||
|
|
||||||
if not hs:
|
if not hs:
|
||||||
self.at_hash += file.t_hash
|
self.at_hash += file.t_hash
|
||||||
|
@ -1130,6 +1180,9 @@ class Ctl(object):
|
||||||
kw = "uploaded" if file.up_b else " found"
|
kw = "uploaded" if file.up_b else " found"
|
||||||
print("{0} {1}".format(kw, upath))
|
print("{0} {1}".format(kw, upath))
|
||||||
|
|
||||||
|
self._check_if_done()
|
||||||
|
continue
|
||||||
|
|
||||||
chunksz = up2k_chunksize(file.size)
|
chunksz = up2k_chunksize(file.size)
|
||||||
njoin = (self.ar.sz * 1024 * 1024) // chunksz
|
njoin = (self.ar.sz * 1024 * 1024) // chunksz
|
||||||
cs = hs[:]
|
cs = hs[:]
|
||||||
|
@ -1149,8 +1202,16 @@ class Ctl(object):
|
||||||
while True:
|
while True:
|
||||||
fsl = self.q_upload.get()
|
fsl = self.q_upload.get()
|
||||||
if not fsl:
|
if not fsl:
|
||||||
self.st_up = [None, "(finished)"]
|
done = False
|
||||||
break
|
with self.mutex:
|
||||||
|
self.uploader_alive -= 1
|
||||||
|
if not self.uploader_alive:
|
||||||
|
done = not self.handshaker_alive
|
||||||
|
self.st_up = [None, "(finished)"]
|
||||||
|
if done:
|
||||||
|
with self.exit_cond:
|
||||||
|
self.exit_cond.notify_all()
|
||||||
|
return
|
||||||
|
|
||||||
file = fsl.file
|
file = fsl.file
|
||||||
cids = fsl.cids
|
cids = fsl.cids
|
||||||
|
@ -1252,6 +1313,10 @@ source file/folder selection uses rsync syntax, meaning that:
|
||||||
ap.add_argument("--dr", action="store_true", help="delete remote files which don't exist locally (implies --ow)")
|
ap.add_argument("--dr", action="store_true", help="delete remote files which don't exist locally (implies --ow)")
|
||||||
ap.add_argument("--drd", action="store_true", help="delete remote files during upload instead of afterwards; reduces peak disk space usage, but will reupload instead of detecting renames")
|
ap.add_argument("--drd", action="store_true", help="delete remote files during upload instead of afterwards; reduces peak disk space usage, but will reupload instead of detecting renames")
|
||||||
|
|
||||||
|
ap = app.add_argument_group("file-ID calculator; enable with url '-' to list warks (file identifiers) instead of upload/search")
|
||||||
|
ap.add_argument("--wsalt", type=unicode, metavar="S", default="hunter2", help="salt to use when creating warks; must match server config")
|
||||||
|
ap.add_argument("--jw", action="store_true", help="just identifier+filepath, not mtime/size too")
|
||||||
|
|
||||||
ap = app.add_argument_group("performance tweaks")
|
ap = app.add_argument_group("performance tweaks")
|
||||||
ap.add_argument("-j", type=int, metavar="CONNS", default=2, help="parallel connections")
|
ap.add_argument("-j", type=int, metavar="CONNS", default=2, help="parallel connections")
|
||||||
ap.add_argument("-J", type=int, metavar="CORES", default=hcores, help="num cpu-cores to use for hashing; set 0 or 1 for single-core hashing")
|
ap.add_argument("-J", type=int, metavar="CORES", default=hcores, help="num cpu-cores to use for hashing; set 0 or 1 for single-core hashing")
|
||||||
|
@ -1285,7 +1350,9 @@ source file/folder selection uses rsync syntax, meaning that:
|
||||||
|
|
||||||
ar.x = "|".join(ar.x or [])
|
ar.x = "|".join(ar.x or [])
|
||||||
|
|
||||||
for k in "dl dr drd".split():
|
setattr(ar, "wlist", ar.url == "-")
|
||||||
|
|
||||||
|
for k in "dl dr drd wlist".split():
|
||||||
errs = []
|
errs = []
|
||||||
if ar.safe and getattr(ar, k):
|
if ar.safe and getattr(ar, k):
|
||||||
errs.append(k)
|
errs.append(k)
|
||||||
|
|
Loading…
Reference in a new issue