Skip to content

Commit 08848be

Browse files
committed
u2c: add hashgen mode + fix shutdown lag
1 parent b599fba commit 08848be

File tree

1 file changed

+109
-42
lines changed

1 file changed

+109
-42
lines changed

bin/u2c.py

Lines changed: 109 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
11
#!/usr/bin/env python3
22
from __future__ import print_function, unicode_literals
33

4-
S_VERSION = "1.23"
5-
S_BUILD_DT = "2024-08-22"
4+
S_VERSION = "1.24"
5+
S_BUILD_DT = "2024-09-05"
66

77
"""
88
u2c.py: upload to copyparty
@@ -41,19 +41,25 @@
4141

4242
try:
4343
import requests
44+
45+
req_ses = requests.Session()
4446
except ImportError as ex:
45-
if EXE:
47+
if "-" in sys.argv or "-h" in sys.argv:
48+
m = ""
49+
elif EXE:
4650
raise
4751
elif sys.version_info > (2, 7):
48-
m = "\nERROR: need 'requests'; please run this command:\n {0} -m pip install --user requests\n"
52+
m = "\nERROR: need 'requests'{0}; please run this command:\n {1} -m pip install --user requests\n"
4953
else:
5054
m = "requests/2.18.4 urllib3/1.23 chardet/3.0.4 certifi/2020.4.5.1 idna/2.7"
5155
m = [" https://pypi.org/project/" + x + "/#files" for x in m.split()]
52-
m = "\n ERROR: need these:\n" + "\n".join(m) + "\n"
56+
m = "\n ERROR: need these{0}:\n" + "\n".join(m) + "\n"
5357
m += "\n for f in *.whl; do unzip $f; done; rm -r *.dist-info\n"
5458

55-
print(m.format(sys.executable), "\nspecifically,", ex)
56-
sys.exit(1)
59+
if m:
60+
t = " when not running with '-h' or url '-'"
61+
print(m.format(t, sys.executable), "\nspecifically,", ex)
62+
sys.exit(1)
5763

5864

5965
# from copyparty/__init__.py
@@ -76,7 +82,22 @@
7682
VT100 = platform.system() != "Windows"
7783

7884

79-
req_ses = requests.Session()
85+
try:
86+
UTC = datetime.timezone.utc
87+
except:
88+
TD_ZERO = datetime.timedelta(0)
89+
90+
class _UTC(datetime.tzinfo):
91+
def utcoffset(self, dt):
92+
return TD_ZERO
93+
94+
def tzname(self, dt):
95+
return "UTC"
96+
97+
def dst(self, dt):
98+
return TD_ZERO
99+
100+
UTC = _UTC()
80101

81102

82103
class Daemon(threading.Thread):
@@ -271,6 +292,12 @@ def hash_at(self, nch):
271292
_print = print
272293

273294

295+
def safe_print(*a, **ka):
296+
ka["end"] = ""
297+
zs = " ".join([unicode(x) for x in a])
298+
_print(zs + "\n", **ka)
299+
300+
274301
def eprint(*a, **ka):
275302
ka["file"] = sys.stderr
276303
ka["end"] = ""
@@ -284,18 +311,17 @@ def eprint(*a, **ka):
284311

285312
def flushing_print(*a, **ka):
286313
try:
287-
_print(*a, **ka)
314+
safe_print(*a, **ka)
288315
except:
289316
v = " ".join(str(x) for x in a)
290317
v = v.encode("ascii", "replace").decode("ascii")
291-
_print(v, **ka)
318+
safe_print(v, **ka)
292319

293320
if "flush" not in ka:
294321
sys.stdout.flush()
295322

296323

297-
if not VT100:
298-
print = flushing_print
324+
print = safe_print if VT100 else flushing_print
299325

300326

301327
def termsize():
@@ -770,8 +796,6 @@ def __init__(self, ar, stats=None):
770796
self.up_c = 0
771797
self.up_b = 0
772798
self.up_br = 0
773-
self.hasher_busy = 1
774-
self.handshaker_busy = 0
775799
self.uploader_busy = 0
776800
self.serialized = False
777801

@@ -781,6 +805,9 @@ def __init__(self, ar, stats=None):
781805
self.eta = "99:99:99"
782806

783807
self.mutex = threading.Lock()
808+
self.exit_cond = threading.Condition()
809+
self.uploader_alive = ar.j
810+
self.handshaker_alive = ar.j
784811
self.q_handshake = Queue() # type: Queue[File]
785812
self.q_upload = Queue() # type: Queue[FileSlice]
786813

@@ -851,27 +878,21 @@ def _fancy(self):
851878
Daemon(self.handshaker)
852879
Daemon(self.uploader)
853880

854-
idles = 0
855-
while idles < 3:
856-
time.sleep(0.07)
881+
while True:
882+
with self.exit_cond:
883+
self.exit_cond.wait(0.07)
857884
with self.mutex:
858-
if (
859-
self.q_handshake.empty()
860-
and self.q_upload.empty()
861-
and not self.hasher_busy
862-
and not self.handshaker_busy
863-
and not self.uploader_busy
864-
):
865-
idles += 1
866-
else:
867-
idles = 0
885+
if not self.handshaker_alive and not self.uploader_alive:
886+
break
887+
st_hash = self.st_hash[:]
888+
st_up = self.st_up[:]
868889

869890
if VT100 and not self.ar.ns:
870891
maxlen = ss.w - len(str(self.nfiles)) - 14
871892
txt = "\033[s\033[{0}H".format(ss.g)
872893
for y, k, st, f in [
873-
[0, "hash", self.st_hash, self.hash_f],
874-
[1, "send", self.st_up, self.up_f],
894+
[0, "hash", st_hash, self.hash_f],
895+
[1, "send", st_up, self.up_f],
875896
]:
876897
txt += "\033[{0}H{1}:".format(ss.g + y, k)
877898
file, arg = st
@@ -1027,20 +1048,53 @@ def hasher(self):
10271048
self.hash_f += 1
10281049
self.hash_c += len(file.cids)
10291050
self.hash_b += file.size
1051+
if self.ar.wlist:
1052+
self.up_f = self.hash_f
1053+
self.up_c = self.hash_c
1054+
self.up_b = self.hash_b
1055+
1056+
if self.ar.wlist:
1057+
zsl = [self.ar.wsalt, str(file.size)] + [x[0] for x in file.kchunks]
1058+
zb = hashlib.sha512("\n".join(zsl).encode("utf-8")).digest()[:33]
1059+
wark = base64.urlsafe_b64encode(zb).decode("utf-8")
1060+
vp = file.rel.decode("utf-8")
1061+
if self.ar.jw:
1062+
print("%s %s" % (wark, vp))
1063+
else:
1064+
zd = datetime.datetime.fromtimestamp(file.lmod, UTC)
1065+
dt = "%04d-%02d-%02d %02d:%02d:%02d" % (
1066+
zd.year,
1067+
zd.month,
1068+
zd.day,
1069+
zd.hour,
1070+
zd.minute,
1071+
zd.second,
1072+
)
1073+
print("%s %12d %s %s" % (dt, file.size, wark, vp))
1074+
continue
10301075

10311076
self.q_handshake.put(file)
10321077

1033-
self.hasher_busy = 0
10341078
self.st_hash = [None, "(finished)"]
1079+
self._check_if_done()
1080+
1081+
def _check_if_done(self):
1082+
with self.mutex:
1083+
if self.nfiles - self.up_f:
1084+
return
1085+
for _ in range(self.ar.j):
1086+
self.q_handshake.put(None)
10351087

10361088
def handshaker(self):
10371089
search = self.ar.s
10381090
burl = self.ar.url[:8] + self.ar.url[8:].split("/")[0] + "/"
10391091
while True:
10401092
file = self.q_handshake.get()
10411093
if not file:
1094+
with self.mutex:
1095+
self.handshaker_alive -= 1
10421096
self.q_upload.put(None)
1043-
break
1097+
return
10441098

10451099
upath = file.abs.decode("utf-8", "replace")
10461100
if not VT100:
@@ -1052,27 +1106,24 @@ def handshaker(self):
10521106
self.errs += 1
10531107
continue
10541108

1055-
with self.mutex:
1056-
self.handshaker_busy += 1
1057-
10581109
while time.time() < file.cd:
10591110
time.sleep(0.1)
10601111

10611112
hs, sprs = handshake(self.ar, file, search)
10621113
if search:
10631114
if hs:
10641115
for hit in hs:
1065-
t = "found: {0}\n {1}{2}\n"
1066-
print(t.format(upath, burl, hit["rp"]), end="")
1116+
t = "found: {0}\n {1}{2}"
1117+
print(t.format(upath, burl, hit["rp"]))
10671118
else:
1068-
print("NOT found: {0}\n".format(upath), end="")
1119+
print("NOT found: {0}".format(upath))
10691120

10701121
with self.mutex:
10711122
self.up_f += 1
10721123
self.up_c += len(file.cids)
10731124
self.up_b += file.size
1074-
self.handshaker_busy -= 1
10751125

1126+
self._check_if_done()
10761127
continue
10771128

10781129
if file.recheck:
@@ -1104,7 +1155,6 @@ def handshaker(self):
11041155
file.up_b -= sz
11051156

11061157
file.ucids = hs
1107-
self.handshaker_busy -= 1
11081158

11091159
if not hs:
11101160
self.at_hash += file.t_hash
@@ -1130,6 +1180,9 @@ def handshaker(self):
11301180
kw = "uploaded" if file.up_b else " found"
11311181
print("{0} {1}".format(kw, upath))
11321182

1183+
self._check_if_done()
1184+
continue
1185+
11331186
chunksz = up2k_chunksize(file.size)
11341187
njoin = (self.ar.sz * 1024 * 1024) // chunksz
11351188
cs = hs[:]
@@ -1149,8 +1202,16 @@ def uploader(self):
11491202
while True:
11501203
fsl = self.q_upload.get()
11511204
if not fsl:
1152-
self.st_up = [None, "(finished)"]
1153-
break
1205+
done = False
1206+
with self.mutex:
1207+
self.uploader_alive -= 1
1208+
if not self.uploader_alive:
1209+
done = not self.handshaker_alive
1210+
self.st_up = [None, "(finished)"]
1211+
if done:
1212+
with self.exit_cond:
1213+
self.exit_cond.notify_all()
1214+
return
11541215

11551216
file = fsl.file
11561217
cids = fsl.cids
@@ -1252,6 +1313,10 @@ def main():
12521313
ap.add_argument("--dr", action="store_true", help="delete remote files which don't exist locally (implies --ow)")
12531314
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")
12541315

1316+
ap = app.add_argument_group("file-ID calculator; enable with url '-' to list warks (file identifiers) instead of upload/search")
1317+
ap.add_argument("--wsalt", type=unicode, metavar="S", default="hunter2", help="salt to use when creating warks; must match server config")
1318+
ap.add_argument("--jw", action="store_true", help="just identifier+filepath, not mtime/size too")
1319+
12551320
ap = app.add_argument_group("performance tweaks")
12561321
ap.add_argument("-j", type=int, metavar="CONNS", default=2, help="parallel connections")
12571322
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 @@ def main():
12851350

12861351
ar.x = "|".join(ar.x or [])
12871352

1288-
for k in "dl dr drd".split():
1353+
setattr(ar, "wlist", ar.url == "-")
1354+
1355+
for k in "dl dr drd wlist".split():
12891356
errs = []
12901357
if ar.safe and getattr(ar, k):
12911358
errs.append(k)

0 commit comments

Comments
 (0)