1
1
#!/usr/bin/env python3
2
2
from __future__ import print_function , unicode_literals
3
3
4
- S_VERSION = "1.23 "
5
- S_BUILD_DT = "2024-08-22 "
4
+ S_VERSION = "1.24 "
5
+ S_BUILD_DT = "2024-09-05 "
6
6
7
7
"""
8
8
u2c.py: upload to copyparty
41
41
42
42
try :
43
43
import requests
44
+
45
+ req_ses = requests .Session ()
44
46
except ImportError as ex :
45
- if EXE :
47
+ if "-" in sys .argv or "-h" in sys .argv :
48
+ m = ""
49
+ elif EXE :
46
50
raise
47
51
elif sys .version_info > (2 , 7 ):
48
- m = "\n ERROR: need 'requests'; please run this command:\n {0 } -m pip install --user requests\n "
52
+ m = "\n ERROR: need 'requests'{0} ; please run this command:\n {1 } -m pip install --user requests\n "
49
53
else :
50
54
m = "requests/2.18.4 urllib3/1.23 chardet/3.0.4 certifi/2020.4.5.1 idna/2.7"
51
55
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 "
53
57
m += "\n for f in *.whl; do unzip $f; done; rm -r *.dist-info\n "
54
58
55
- print (m .format (sys .executable ), "\n specifically," , ex )
56
- sys .exit (1 )
59
+ if m :
60
+ t = " when not running with '-h' or url '-'"
61
+ print (m .format (t , sys .executable ), "\n specifically," , ex )
62
+ sys .exit (1 )
57
63
58
64
59
65
# from copyparty/__init__.py
76
82
VT100 = platform .system () != "Windows"
77
83
78
84
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 ()
80
101
81
102
82
103
class Daemon (threading .Thread ):
@@ -271,6 +292,12 @@ def hash_at(self, nch):
271
292
_print = print
272
293
273
294
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
+
274
301
def eprint (* a , ** ka ):
275
302
ka ["file" ] = sys .stderr
276
303
ka ["end" ] = ""
@@ -284,18 +311,17 @@ def eprint(*a, **ka):
284
311
285
312
def flushing_print (* a , ** ka ):
286
313
try :
287
- _print (* a , ** ka )
314
+ safe_print (* a , ** ka )
288
315
except :
289
316
v = " " .join (str (x ) for x in a )
290
317
v = v .encode ("ascii" , "replace" ).decode ("ascii" )
291
- _print (v , ** ka )
318
+ safe_print (v , ** ka )
292
319
293
320
if "flush" not in ka :
294
321
sys .stdout .flush ()
295
322
296
323
297
- if not VT100 :
298
- print = flushing_print
324
+ print = safe_print if VT100 else flushing_print
299
325
300
326
301
327
def termsize ():
@@ -770,8 +796,6 @@ def __init__(self, ar, stats=None):
770
796
self .up_c = 0
771
797
self .up_b = 0
772
798
self .up_br = 0
773
- self .hasher_busy = 1
774
- self .handshaker_busy = 0
775
799
self .uploader_busy = 0
776
800
self .serialized = False
777
801
@@ -781,6 +805,9 @@ def __init__(self, ar, stats=None):
781
805
self .eta = "99:99:99"
782
806
783
807
self .mutex = threading .Lock ()
808
+ self .exit_cond = threading .Condition ()
809
+ self .uploader_alive = ar .j
810
+ self .handshaker_alive = ar .j
784
811
self .q_handshake = Queue () # type: Queue[File]
785
812
self .q_upload = Queue () # type: Queue[FileSlice]
786
813
@@ -851,27 +878,21 @@ def _fancy(self):
851
878
Daemon (self .handshaker )
852
879
Daemon (self .uploader )
853
880
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 )
857
884
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 [:]
868
889
869
890
if VT100 and not self .ar .ns :
870
891
maxlen = ss .w - len (str (self .nfiles )) - 14
871
892
txt = "\033 [s\033 [{0}H" .format (ss .g )
872
893
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 ],
875
896
]:
876
897
txt += "\033 [{0}H{1}:" .format (ss .g + y , k )
877
898
file , arg = st
@@ -1027,20 +1048,53 @@ def hasher(self):
1027
1048
self .hash_f += 1
1028
1049
self .hash_c += len (file .cids )
1029
1050
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
1030
1075
1031
1076
self .q_handshake .put (file )
1032
1077
1033
- self .hasher_busy = 0
1034
1078
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 )
1035
1087
1036
1088
def handshaker (self ):
1037
1089
search = self .ar .s
1038
1090
burl = self .ar .url [:8 ] + self .ar .url [8 :].split ("/" )[0 ] + "/"
1039
1091
while True :
1040
1092
file = self .q_handshake .get ()
1041
1093
if not file :
1094
+ with self .mutex :
1095
+ self .handshaker_alive -= 1
1042
1096
self .q_upload .put (None )
1043
- break
1097
+ return
1044
1098
1045
1099
upath = file .abs .decode ("utf-8" , "replace" )
1046
1100
if not VT100 :
@@ -1052,27 +1106,24 @@ def handshaker(self):
1052
1106
self .errs += 1
1053
1107
continue
1054
1108
1055
- with self .mutex :
1056
- self .handshaker_busy += 1
1057
-
1058
1109
while time .time () < file .cd :
1059
1110
time .sleep (0.1 )
1060
1111
1061
1112
hs , sprs = handshake (self .ar , file , search )
1062
1113
if search :
1063
1114
if hs :
1064
1115
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" ]))
1067
1118
else :
1068
- print ("NOT found: {0}\n " .format (upath ), end = "" )
1119
+ print ("NOT found: {0}" .format (upath ))
1069
1120
1070
1121
with self .mutex :
1071
1122
self .up_f += 1
1072
1123
self .up_c += len (file .cids )
1073
1124
self .up_b += file .size
1074
- self .handshaker_busy -= 1
1075
1125
1126
+ self ._check_if_done ()
1076
1127
continue
1077
1128
1078
1129
if file .recheck :
@@ -1104,7 +1155,6 @@ def handshaker(self):
1104
1155
file .up_b -= sz
1105
1156
1106
1157
file .ucids = hs
1107
- self .handshaker_busy -= 1
1108
1158
1109
1159
if not hs :
1110
1160
self .at_hash += file .t_hash
@@ -1130,6 +1180,9 @@ def handshaker(self):
1130
1180
kw = "uploaded" if file .up_b else " found"
1131
1181
print ("{0} {1}" .format (kw , upath ))
1132
1182
1183
+ self ._check_if_done ()
1184
+ continue
1185
+
1133
1186
chunksz = up2k_chunksize (file .size )
1134
1187
njoin = (self .ar .sz * 1024 * 1024 ) // chunksz
1135
1188
cs = hs [:]
@@ -1149,8 +1202,16 @@ def uploader(self):
1149
1202
while True :
1150
1203
fsl = self .q_upload .get ()
1151
1204
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
1154
1215
1155
1216
file = fsl .file
1156
1217
cids = fsl .cids
@@ -1252,6 +1313,10 @@ def main():
1252
1313
ap .add_argument ("--dr" , action = "store_true" , help = "delete remote files which don't exist locally (implies --ow)" )
1253
1314
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" )
1254
1315
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
+
1255
1320
ap = app .add_argument_group ("performance tweaks" )
1256
1321
ap .add_argument ("-j" , type = int , metavar = "CONNS" , default = 2 , help = "parallel connections" )
1257
1322
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():
1285
1350
1286
1351
ar .x = "|" .join (ar .x or [])
1287
1352
1288
- for k in "dl dr drd" .split ():
1353
+ setattr (ar , "wlist" , ar .url == "-" )
1354
+
1355
+ for k in "dl dr drd wlist" .split ():
1289
1356
errs = []
1290
1357
if ar .safe and getattr (ar , k ):
1291
1358
errs .append (k )
0 commit comments