copyparty 1.15.2__py3-none-any.whl → 1.15.4__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- copyparty/__init__.py +55 -0
- copyparty/__main__.py +15 -12
- copyparty/__version__.py +2 -2
- copyparty/cert.py +20 -8
- copyparty/cfg.py +1 -1
- copyparty/ftpd.py +1 -1
- copyparty/httpcli.py +227 -68
- copyparty/httpconn.py +0 -3
- copyparty/httpsrv.py +10 -15
- copyparty/metrics.py +1 -1
- copyparty/smbd.py +6 -3
- copyparty/stolen/qrcodegen.py +17 -0
- copyparty/szip.py +1 -1
- copyparty/u2idx.py +1 -0
- copyparty/up2k.py +31 -13
- copyparty/util.py +145 -64
- copyparty/web/a/partyfuse.py +233 -357
- copyparty/web/a/u2c.py +249 -153
- copyparty/web/browser.css.gz +0 -0
- copyparty/web/browser.html +3 -6
- copyparty/web/browser.js.gz +0 -0
- copyparty/web/deps/fuse.py +1064 -0
- copyparty/web/deps/marked.js.gz +0 -0
- copyparty/web/shares.css.gz +0 -0
- copyparty/web/shares.html +7 -4
- copyparty/web/shares.js.gz +0 -0
- copyparty/web/splash.html +12 -12
- copyparty/web/splash.js.gz +0 -0
- copyparty/web/svcs.html +1 -1
- copyparty/web/ui.css.gz +0 -0
- copyparty/web/util.js.gz +0 -0
- {copyparty-1.15.2.dist-info → copyparty-1.15.4.dist-info}/METADATA +9 -8
- {copyparty-1.15.2.dist-info → copyparty-1.15.4.dist-info}/RECORD +37 -36
- {copyparty-1.15.2.dist-info → copyparty-1.15.4.dist-info}/WHEEL +1 -1
- {copyparty-1.15.2.dist-info → copyparty-1.15.4.dist-info}/LICENSE +0 -0
- {copyparty-1.15.2.dist-info → copyparty-1.15.4.dist-info}/entry_points.txt +0 -0
- {copyparty-1.15.2.dist-info → copyparty-1.15.4.dist-info}/top_level.txt +0 -0
copyparty/web/a/u2c.py
CHANGED
@@ -1,34 +1,35 @@
|
|
1
1
|
#!/usr/bin/env python3
|
2
2
|
from __future__ import print_function, unicode_literals
|
3
3
|
|
4
|
-
S_VERSION = "1
|
5
|
-
S_BUILD_DT = "2024-09-
|
4
|
+
S_VERSION = "2.1"
|
5
|
+
S_BUILD_DT = "2024-09-23"
|
6
6
|
|
7
7
|
"""
|
8
8
|
u2c.py: upload to copyparty
|
9
9
|
2021, ed <irc.rizon.net>, MIT-Licensed
|
10
10
|
https://github.com/9001/copyparty/blob/hovudstraum/bin/u2c.py
|
11
11
|
|
12
|
-
- dependencies:
|
12
|
+
- dependencies: no
|
13
13
|
- supports python 2.6, 2.7, and 3.3 through 3.12
|
14
14
|
- if something breaks just try again and it'll autoresume
|
15
15
|
"""
|
16
16
|
|
17
|
-
import re
|
18
|
-
import os
|
19
|
-
import sys
|
20
|
-
import stat
|
21
|
-
import math
|
22
|
-
import time
|
23
|
-
import json
|
24
17
|
import atexit
|
25
|
-
import signal
|
26
|
-
import socket
|
27
18
|
import base64
|
19
|
+
import binascii
|
20
|
+
import datetime
|
28
21
|
import hashlib
|
22
|
+
import json
|
23
|
+
import math
|
24
|
+
import os
|
29
25
|
import platform
|
26
|
+
import re
|
27
|
+
import signal
|
28
|
+
import socket
|
29
|
+
import stat
|
30
|
+
import sys
|
30
31
|
import threading
|
31
|
-
import
|
32
|
+
import time
|
32
33
|
|
33
34
|
EXE = bool(getattr(sys, "frozen", False))
|
34
35
|
|
@@ -39,32 +40,12 @@ except:
|
|
39
40
|
print(m)
|
40
41
|
raise
|
41
42
|
|
42
|
-
try:
|
43
|
-
import requests
|
44
43
|
|
45
|
-
req_ses = requests.Session()
|
46
|
-
except ImportError as ex:
|
47
|
-
if "-" in sys.argv or "-h" in sys.argv:
|
48
|
-
m = ""
|
49
|
-
elif EXE:
|
50
|
-
raise
|
51
|
-
elif sys.version_info > (2, 7):
|
52
|
-
m = "\nERROR: need 'requests'{0}; please run this command:\n {1} -m pip install --user requests\n"
|
53
|
-
else:
|
54
|
-
m = "requests/2.18.4 urllib3/1.23 chardet/3.0.4 certifi/2020.4.5.1 idna/2.7"
|
55
|
-
m = [" https://pypi.org/project/" + x + "/#files" for x in m.split()]
|
56
|
-
m = "\n ERROR: need these{0}:\n" + "\n".join(m) + "\n"
|
57
|
-
m += "\n for f in *.whl; do unzip $f; done; rm -r *.dist-info\n"
|
58
|
-
|
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)
|
63
|
-
|
64
|
-
|
65
|
-
# from copyparty/__init__.py
|
66
44
|
PY2 = sys.version_info < (3,)
|
45
|
+
PY27 = sys.version_info > (2, 7) and PY2
|
46
|
+
PY37 = sys.version_info > (3, 7)
|
67
47
|
if PY2:
|
48
|
+
import httplib as http_client
|
68
49
|
from Queue import Queue
|
69
50
|
from urllib import quote, unquote
|
70
51
|
from urlparse import urlsplit, urlunsplit
|
@@ -72,11 +53,13 @@ if PY2:
|
|
72
53
|
sys.dont_write_bytecode = True
|
73
54
|
bytes = str
|
74
55
|
else:
|
75
|
-
from queue import Queue
|
76
|
-
from urllib.parse import unquote_to_bytes as unquote
|
77
56
|
from urllib.parse import quote_from_bytes as quote
|
57
|
+
from urllib.parse import unquote_to_bytes as unquote
|
78
58
|
from urllib.parse import urlsplit, urlunsplit
|
79
59
|
|
60
|
+
import http.client as http_client
|
61
|
+
from queue import Queue
|
62
|
+
|
80
63
|
unicode = str
|
81
64
|
|
82
65
|
VT100 = platform.system() != "Windows"
|
@@ -100,6 +83,22 @@ except:
|
|
100
83
|
UTC = _UTC()
|
101
84
|
|
102
85
|
|
86
|
+
try:
|
87
|
+
_b64etl = bytes.maketrans(b"+/", b"-_")
|
88
|
+
|
89
|
+
def ub64enc(bs):
|
90
|
+
x = binascii.b2a_base64(bs, newline=False)
|
91
|
+
return x.translate(_b64etl)
|
92
|
+
|
93
|
+
ub64enc(b"a")
|
94
|
+
except:
|
95
|
+
ub64enc = base64.urlsafe_b64encode
|
96
|
+
|
97
|
+
|
98
|
+
class BadAuth(Exception):
|
99
|
+
pass
|
100
|
+
|
101
|
+
|
103
102
|
class Daemon(threading.Thread):
|
104
103
|
def __init__(self, target, name=None, a=None):
|
105
104
|
threading.Thread.__init__(self, name=name)
|
@@ -117,6 +116,108 @@ class Daemon(threading.Thread):
|
|
117
116
|
self.fun(*self.a)
|
118
117
|
|
119
118
|
|
119
|
+
class HSQueue(Queue):
|
120
|
+
def _init(self, maxsize):
|
121
|
+
from collections import deque
|
122
|
+
|
123
|
+
self.q = deque()
|
124
|
+
|
125
|
+
def _qsize(self):
|
126
|
+
return len(self.q)
|
127
|
+
|
128
|
+
def _put(self, item):
|
129
|
+
if item and item.nhs:
|
130
|
+
self.q.appendleft(item)
|
131
|
+
else:
|
132
|
+
self.q.append(item)
|
133
|
+
|
134
|
+
def _get(self):
|
135
|
+
return self.q.popleft()
|
136
|
+
|
137
|
+
|
138
|
+
class HCli(object):
|
139
|
+
def __init__(self, ar):
|
140
|
+
self.ar = ar
|
141
|
+
url = urlsplit(ar.url)
|
142
|
+
tls = url.scheme.lower() == "https"
|
143
|
+
try:
|
144
|
+
addr, port = url.netloc.split(":")
|
145
|
+
except:
|
146
|
+
addr = url.netloc
|
147
|
+
port = 443 if tls else 80
|
148
|
+
|
149
|
+
self.addr = addr
|
150
|
+
self.port = int(port)
|
151
|
+
self.tls = tls
|
152
|
+
self.verify = ar.te or not ar.td
|
153
|
+
self.conns = []
|
154
|
+
if tls:
|
155
|
+
import ssl
|
156
|
+
|
157
|
+
if not self.verify:
|
158
|
+
self.ctx = ssl._create_unverified_context()
|
159
|
+
elif self.verify is True:
|
160
|
+
self.ctx = None
|
161
|
+
else:
|
162
|
+
self.ctx = ssl.SSLContext(ssl.PROTOCOL_TLS)
|
163
|
+
self.ctx.load_verify_locations(self.verify)
|
164
|
+
|
165
|
+
self.base_hdrs = {
|
166
|
+
"Accept": "*/*",
|
167
|
+
"Connection": "keep-alive",
|
168
|
+
"Host": url.netloc,
|
169
|
+
"Origin": self.ar.burl,
|
170
|
+
"User-Agent": "u2c/%s" % (S_VERSION,),
|
171
|
+
}
|
172
|
+
|
173
|
+
def _connect(self):
|
174
|
+
args = {}
|
175
|
+
if PY37:
|
176
|
+
args["blocksize"] = 1048576
|
177
|
+
|
178
|
+
if not self.tls:
|
179
|
+
C = http_client.HTTPConnection
|
180
|
+
else:
|
181
|
+
C = http_client.HTTPSConnection
|
182
|
+
if self.ctx:
|
183
|
+
args = {"context": self.ctx}
|
184
|
+
|
185
|
+
return C(self.addr, self.port, timeout=999, **args)
|
186
|
+
|
187
|
+
def req(self, meth, vpath, hdrs, body=None, ctype=None):
|
188
|
+
hdrs.update(self.base_hdrs)
|
189
|
+
if self.ar.a:
|
190
|
+
hdrs["PW"] = self.ar.a
|
191
|
+
if ctype:
|
192
|
+
hdrs["Content-Type"] = ctype
|
193
|
+
if meth == "POST" and CLEN not in hdrs:
|
194
|
+
hdrs[CLEN] = (
|
195
|
+
0 if not body else body.len if hasattr(body, "len") else len(body)
|
196
|
+
)
|
197
|
+
|
198
|
+
c = self.conns.pop() if self.conns else self._connect()
|
199
|
+
try:
|
200
|
+
c.request(meth, vpath, body, hdrs)
|
201
|
+
if PY27:
|
202
|
+
rsp = c.getresponse(buffering=True)
|
203
|
+
else:
|
204
|
+
rsp = c.getresponse()
|
205
|
+
|
206
|
+
data = rsp.read()
|
207
|
+
self.conns.append(c)
|
208
|
+
return rsp.status, data.decode("utf-8")
|
209
|
+
except:
|
210
|
+
c.close()
|
211
|
+
raise
|
212
|
+
|
213
|
+
|
214
|
+
MJ = "application/json"
|
215
|
+
MO = "application/octet-stream"
|
216
|
+
CLEN = "Content-Length"
|
217
|
+
|
218
|
+
web = None # type: HCli
|
219
|
+
|
220
|
+
|
120
221
|
class File(object):
|
121
222
|
"""an up2k upload task; represents a single file"""
|
122
223
|
|
@@ -149,9 +250,6 @@ class File(object):
|
|
149
250
|
self.up_c = 0 # type: int
|
150
251
|
self.cd = 0 # type: int
|
151
252
|
|
152
|
-
# t = "size({}) lmod({}) top({}) rel({}) abs({}) name({})\n"
|
153
|
-
# eprint(t.format(self.size, self.lmod, self.top, self.rel, self.abs, self.name))
|
154
|
-
|
155
253
|
|
156
254
|
class FileSlice(object):
|
157
255
|
"""file-like object providing a fixed window into a file"""
|
@@ -284,8 +382,7 @@ class MTHash(object):
|
|
284
382
|
chunk_rem -= len(buf)
|
285
383
|
ofs += len(buf)
|
286
384
|
|
287
|
-
digest = hashobj.digest()[:33]
|
288
|
-
digest = base64.urlsafe_b64encode(digest).decode("utf-8")
|
385
|
+
digest = ub64enc(hashobj.digest()[:33]).decode("utf-8")
|
289
386
|
return nch, digest, ofs0, chunk_sz
|
290
387
|
|
291
388
|
|
@@ -329,7 +426,9 @@ def termsize():
|
|
329
426
|
|
330
427
|
def ioctl_GWINSZ(fd):
|
331
428
|
try:
|
332
|
-
import fcntl
|
429
|
+
import fcntl
|
430
|
+
import struct
|
431
|
+
import termios
|
333
432
|
|
334
433
|
r = struct.unpack(b"hh", fcntl.ioctl(fd, termios.TIOCGWINSZ, b"AAAA"))
|
335
434
|
return r[::-1]
|
@@ -387,8 +486,8 @@ class CTermsize(object):
|
|
387
486
|
eprint("\033[s\033[r\033[u")
|
388
487
|
else:
|
389
488
|
self.g = 1 + self.h - margin
|
390
|
-
t = "
|
391
|
-
eprint("
|
489
|
+
t = "%s\033[%dA" % ("\n" * margin, margin)
|
490
|
+
eprint("%s\033[s\033[1;%dr\033[u" % (t, self.g - 1))
|
392
491
|
|
393
492
|
|
394
493
|
ss = CTermsize()
|
@@ -405,14 +504,14 @@ def undns(url):
|
|
405
504
|
except KeyboardInterrupt:
|
406
505
|
raise
|
407
506
|
except:
|
408
|
-
t = "\n\033[31mfailed to resolve upload destination host;\033[0m\ngai
|
409
|
-
eprint(t
|
507
|
+
t = "\n\033[31mfailed to resolve upload destination host;\033[0m\ngai=%r\n"
|
508
|
+
eprint(t % (gai,))
|
410
509
|
raise
|
411
510
|
|
412
511
|
if usp.port:
|
413
|
-
hn = "
|
512
|
+
hn = "%s:%s" % (hn, usp.port)
|
414
513
|
if usp.username or usp.password:
|
415
|
-
hn = "
|
514
|
+
hn = "%s:%s@%s" % (usp.username, usp.password, hn)
|
416
515
|
|
417
516
|
usp = usp._replace(netloc=hn)
|
418
517
|
url = urlunsplit(usp)
|
@@ -447,7 +546,7 @@ else:
|
|
447
546
|
statdir = _lsd
|
448
547
|
|
449
548
|
|
450
|
-
def walkdir(err, top, seen):
|
549
|
+
def walkdir(err, top, excl, seen):
|
451
550
|
"""recursive statdir"""
|
452
551
|
atop = os.path.abspath(os.path.realpath(top))
|
453
552
|
if atop in seen:
|
@@ -456,10 +555,12 @@ def walkdir(err, top, seen):
|
|
456
555
|
|
457
556
|
seen = seen[:] + [atop]
|
458
557
|
for ap, inf in sorted(statdir(err, top)):
|
558
|
+
if excl.match(ap):
|
559
|
+
continue
|
459
560
|
yield ap, inf
|
460
561
|
if stat.S_ISDIR(inf.st_mode):
|
461
562
|
try:
|
462
|
-
for x in walkdir(err, ap, seen):
|
563
|
+
for x in walkdir(err, ap, excl, seen):
|
463
564
|
yield x
|
464
565
|
except Exception as ex:
|
465
566
|
err.append((ap, str(ex)))
|
@@ -499,9 +600,7 @@ def walkdirs(err, tops, excl):
|
|
499
600
|
yield stop, dn, os.stat(stop)
|
500
601
|
|
501
602
|
if isdir:
|
502
|
-
for ap, inf in walkdir(err, top, []):
|
503
|
-
if ptn.match(ap):
|
504
|
-
continue
|
603
|
+
for ap, inf in walkdir(err, top, ptn, []):
|
505
604
|
yield stop, ap[len(stop) :].lstrip(sep), inf
|
506
605
|
else:
|
507
606
|
d, n = top.rsplit(sep, 1)
|
@@ -577,8 +676,7 @@ def get_hashlist(file, pcb, mth):
|
|
577
676
|
hashobj.update(buf)
|
578
677
|
chunk_rem -= len(buf)
|
579
678
|
|
580
|
-
digest = hashobj.digest()[:33]
|
581
|
-
digest = base64.urlsafe_b64encode(digest).decode("utf-8")
|
679
|
+
digest = ub64enc(hashobj.digest()[:33]).decode("utf-8")
|
582
680
|
|
583
681
|
ret.append([digest, file_ofs, chunk_sz])
|
584
682
|
file_ofs += chunk_sz
|
@@ -603,9 +701,6 @@ def handshake(ar, file, search):
|
|
603
701
|
otherwise, a list of chunks to upload
|
604
702
|
"""
|
605
703
|
|
606
|
-
url = ar.url
|
607
|
-
pw = ar.a
|
608
|
-
|
609
704
|
req = {
|
610
705
|
"hash": [x[0] for x in file.cids],
|
611
706
|
"name": file.name,
|
@@ -620,28 +715,26 @@ def handshake(ar, file, search):
|
|
620
715
|
if ar.ow:
|
621
716
|
req["replace"] = True
|
622
717
|
|
623
|
-
headers = {"Content-Type": "text/plain"} # <=1.5.1 compat
|
624
|
-
if pw:
|
625
|
-
headers["Cookie"] = "=".join(["cppwd", pw])
|
626
|
-
|
627
718
|
file.recheck = False
|
628
719
|
if file.url:
|
629
720
|
url = file.url
|
630
|
-
|
631
|
-
|
721
|
+
else:
|
722
|
+
if b"/" in file.rel:
|
723
|
+
url = quotep(file.rel.rsplit(b"/", 1)[0]).decode("utf-8", "replace")
|
724
|
+
else:
|
725
|
+
url = ""
|
726
|
+
url = ar.vtop + url
|
632
727
|
|
633
728
|
while True:
|
634
729
|
sc = 600
|
635
730
|
txt = ""
|
636
731
|
try:
|
637
732
|
zs = json.dumps(req, separators=(",\n", ": "))
|
638
|
-
|
639
|
-
sc = r.status_code
|
640
|
-
txt = r.text
|
733
|
+
sc, txt = web.req("POST", url, {}, zs.encode("utf-8"), MJ)
|
641
734
|
if sc < 400:
|
642
735
|
break
|
643
736
|
|
644
|
-
raise Exception("http
|
737
|
+
raise Exception("http %d: %s" % (sc, txt))
|
645
738
|
|
646
739
|
except Exception as ex:
|
647
740
|
em = str(ex).split("SSLError(")[-1].split("\nURL: ")[0].strip()
|
@@ -655,35 +748,30 @@ def handshake(ar, file, search):
|
|
655
748
|
return [], False
|
656
749
|
elif sc == 409 or "<pre>upload rejected, file already exists" in txt:
|
657
750
|
return [], False
|
658
|
-
elif
|
659
|
-
|
751
|
+
elif sc == 403:
|
752
|
+
print("\nERROR: login required, or wrong password:\n%s" % (txt,))
|
753
|
+
raise BadAuth()
|
660
754
|
|
661
|
-
eprint("handshake failed, retrying:
|
755
|
+
eprint("handshake failed, retrying: %s\n %s\n\n" % (file.name, em))
|
662
756
|
time.sleep(ar.cd)
|
663
757
|
|
664
758
|
try:
|
665
|
-
r =
|
759
|
+
r = json.loads(txt)
|
666
760
|
except:
|
667
|
-
raise Exception(
|
761
|
+
raise Exception(txt)
|
668
762
|
|
669
763
|
if search:
|
670
764
|
return r["hits"], False
|
671
765
|
|
672
|
-
|
673
|
-
pre, url = url.split("://")
|
674
|
-
pre += "://"
|
675
|
-
except:
|
676
|
-
pre = ""
|
677
|
-
|
678
|
-
file.url = pre + url.split("/")[0] + r["purl"]
|
766
|
+
file.url = r["purl"]
|
679
767
|
file.name = r["name"]
|
680
768
|
file.wark = r["wark"]
|
681
769
|
|
682
770
|
return r["hash"], r["sprs"]
|
683
771
|
|
684
772
|
|
685
|
-
def upload(fsl,
|
686
|
-
# type: (FileSlice, str
|
773
|
+
def upload(fsl, stats):
|
774
|
+
# type: (FileSlice, str) -> None
|
687
775
|
"""upload a range of file data, defined by one or more `cid` (chunk-hash)"""
|
688
776
|
|
689
777
|
ctxt = fsl.cids[0]
|
@@ -696,20 +784,15 @@ def upload(fsl, pw, stats):
|
|
696
784
|
headers = {
|
697
785
|
"X-Up2k-Hash": ctxt,
|
698
786
|
"X-Up2k-Wark": fsl.file.wark,
|
699
|
-
"Content-Type": "application/octet-stream",
|
700
787
|
}
|
701
788
|
|
702
789
|
if stats:
|
703
790
|
headers["X-Up2k-Stat"] = stats
|
704
791
|
|
705
|
-
if pw:
|
706
|
-
headers["Cookie"] = "=".join(["cppwd", pw])
|
707
|
-
|
708
792
|
try:
|
709
|
-
|
793
|
+
sc, txt = web.req("POST", fsl.file.url, headers, fsl, MO)
|
710
794
|
|
711
|
-
if
|
712
|
-
txt = r.text
|
795
|
+
if sc == 400:
|
713
796
|
if (
|
714
797
|
"already being written" in txt
|
715
798
|
or "already got that" in txt
|
@@ -717,10 +800,8 @@ def upload(fsl, pw, stats):
|
|
717
800
|
):
|
718
801
|
fsl.file.nojoin = 1
|
719
802
|
|
720
|
-
if
|
721
|
-
raise Exception(
|
722
|
-
|
723
|
-
_ = r.content
|
803
|
+
if sc >= 400:
|
804
|
+
raise Exception("http %s: %s" % (sc, txt))
|
724
805
|
finally:
|
725
806
|
fsl.f.close()
|
726
807
|
|
@@ -733,7 +814,7 @@ class Ctl(object):
|
|
733
814
|
|
734
815
|
def _scan(self):
|
735
816
|
ar = self.ar
|
736
|
-
eprint("\nscanning
|
817
|
+
eprint("\nscanning %d locations\n" % (len(ar.files),))
|
737
818
|
nfiles = 0
|
738
819
|
nbytes = 0
|
739
820
|
err = []
|
@@ -745,14 +826,14 @@ class Ctl(object):
|
|
745
826
|
nbytes += inf.st_size
|
746
827
|
|
747
828
|
if err:
|
748
|
-
eprint("\n# failed to access
|
829
|
+
eprint("\n# failed to access %d paths:\n" % (len(err),))
|
749
830
|
for ap, msg in err:
|
750
831
|
if ar.v:
|
751
|
-
eprint("
|
832
|
+
eprint("%s\n `-%s\n\n" % (ap.decode("utf-8", "replace"), msg))
|
752
833
|
else:
|
753
834
|
eprint(ap.decode("utf-8", "replace") + "\n")
|
754
835
|
|
755
|
-
eprint("^ failed to access those
|
836
|
+
eprint("^ failed to access those %d paths ^\n\n" % (len(err),))
|
756
837
|
|
757
838
|
if not ar.v:
|
758
839
|
eprint("hint: set -v for detailed error messages\n")
|
@@ -761,11 +842,12 @@ class Ctl(object):
|
|
761
842
|
eprint("hint: aborting because --ok is not set\n")
|
762
843
|
return
|
763
844
|
|
764
|
-
eprint("found
|
845
|
+
eprint("found %d files, %s\n\n" % (nfiles, humansize(nbytes)))
|
765
846
|
return nfiles, nbytes
|
766
847
|
|
767
848
|
def __init__(self, ar, stats=None):
|
768
849
|
self.ok = False
|
850
|
+
self.panik = 0
|
769
851
|
self.errs = 0
|
770
852
|
self.ar = ar
|
771
853
|
self.stats = stats or self._scan()
|
@@ -773,13 +855,6 @@ class Ctl(object):
|
|
773
855
|
return
|
774
856
|
|
775
857
|
self.nfiles, self.nbytes = self.stats
|
776
|
-
|
777
|
-
if ar.td:
|
778
|
-
requests.packages.urllib3.disable_warnings()
|
779
|
-
req_ses.verify = False
|
780
|
-
if ar.te:
|
781
|
-
req_ses.verify = ar.te
|
782
|
-
|
783
858
|
self.filegen = walkdirs([], ar.files, ar.x)
|
784
859
|
self.recheck = [] # type: list[File]
|
785
860
|
|
@@ -808,7 +883,7 @@ class Ctl(object):
|
|
808
883
|
self.exit_cond = threading.Condition()
|
809
884
|
self.uploader_alive = ar.j
|
810
885
|
self.handshaker_alive = ar.j
|
811
|
-
self.q_handshake =
|
886
|
+
self.q_handshake = HSQueue() # type: Queue[File]
|
812
887
|
self.q_upload = Queue() # type: Queue[FileSlice]
|
813
888
|
|
814
889
|
self.st_hash = [None, "(idle, starting...)"] # type: tuple[File, int]
|
@@ -823,24 +898,29 @@ class Ctl(object):
|
|
823
898
|
def _safe(self):
|
824
899
|
"""minimal basic slow boring fallback codepath"""
|
825
900
|
search = self.ar.s
|
826
|
-
|
901
|
+
nf = 0
|
902
|
+
for top, rel, inf in self.filegen:
|
827
903
|
if stat.S_ISDIR(inf.st_mode) or not rel:
|
828
904
|
continue
|
829
905
|
|
906
|
+
nf += 1
|
830
907
|
file = File(top, rel, inf.st_size, inf.st_mtime)
|
831
908
|
upath = file.abs.decode("utf-8", "replace")
|
832
909
|
|
833
|
-
print("
|
910
|
+
print("%d %s\n hash..." % (self.nfiles - nf, upath))
|
834
911
|
get_hashlist(file, None, None)
|
835
912
|
|
836
|
-
burl = self.ar.url[:12] + self.ar.url[8:].split("/")[0] + "/"
|
837
913
|
while True:
|
838
914
|
print(" hs...")
|
839
|
-
|
915
|
+
try:
|
916
|
+
hs, _ = handshake(self.ar, file, search)
|
917
|
+
except BadAuth:
|
918
|
+
sys.exit(1)
|
919
|
+
|
840
920
|
if search:
|
841
921
|
if hs:
|
842
922
|
for hit in hs:
|
843
|
-
print(" found:
|
923
|
+
print(" found: %s/%s" % (self.ar.burl, hit["rp"]))
|
844
924
|
else:
|
845
925
|
print(" NOT found")
|
846
926
|
break
|
@@ -849,13 +929,13 @@ class Ctl(object):
|
|
849
929
|
if not hs:
|
850
930
|
break
|
851
931
|
|
852
|
-
print("
|
932
|
+
print("%d %s" % (self.nfiles - nf, upath))
|
853
933
|
ncs = len(hs)
|
854
934
|
for nc, cid in enumerate(hs):
|
855
|
-
print("
|
856
|
-
stats = "
|
935
|
+
print(" %d up %s" % (ncs - nc, cid))
|
936
|
+
stats = "%d/0/0/%d" % (nf, self.nfiles - nf)
|
857
937
|
fslice = FileSlice(file, [cid])
|
858
|
-
upload(fslice,
|
938
|
+
upload(fslice, stats)
|
859
939
|
|
860
940
|
print(" ok!")
|
861
941
|
if file.recheck:
|
@@ -866,7 +946,7 @@ class Ctl(object):
|
|
866
946
|
|
867
947
|
eprint("finalizing %d duplicate files\n" % (len(self.recheck),))
|
868
948
|
for file in self.recheck:
|
869
|
-
handshake(self.ar, file,
|
949
|
+
handshake(self.ar, file, False)
|
870
950
|
|
871
951
|
def _fancy(self):
|
872
952
|
if VT100 and not self.ar.ns:
|
@@ -881,6 +961,8 @@ class Ctl(object):
|
|
881
961
|
while True:
|
882
962
|
with self.exit_cond:
|
883
963
|
self.exit_cond.wait(0.07)
|
964
|
+
if self.panik:
|
965
|
+
sys.exit(1)
|
884
966
|
with self.mutex:
|
885
967
|
if not self.handshaker_alive and not self.uploader_alive:
|
886
968
|
break
|
@@ -889,15 +971,15 @@ class Ctl(object):
|
|
889
971
|
|
890
972
|
if VT100 and not self.ar.ns:
|
891
973
|
maxlen = ss.w - len(str(self.nfiles)) - 14
|
892
|
-
txt = "\033[s\033[
|
974
|
+
txt = "\033[s\033[%dH" % (ss.g,)
|
893
975
|
for y, k, st, f in [
|
894
976
|
[0, "hash", st_hash, self.hash_f],
|
895
977
|
[1, "send", st_up, self.up_f],
|
896
978
|
]:
|
897
|
-
txt += "\033[
|
979
|
+
txt += "\033[%dH%s:" % (ss.g + y, k)
|
898
980
|
file, arg = st
|
899
981
|
if not file:
|
900
|
-
txt += "
|
982
|
+
txt += " %s\033[K" % (arg,)
|
901
983
|
else:
|
902
984
|
if y:
|
903
985
|
p = 100 * file.up_b / file.size
|
@@ -906,12 +988,11 @@ class Ctl(object):
|
|
906
988
|
|
907
989
|
name = file.abs.decode("utf-8", "replace")[-maxlen:]
|
908
990
|
if "/" in name:
|
909
|
-
name = "\033[36m
|
991
|
+
name = "\033[36m%s\033[0m/%s" % tuple(name.rsplit("/", 1))
|
910
992
|
|
911
|
-
|
912
|
-
txt += t.format(p, self.nfiles - f, name)
|
993
|
+
txt += "%6.1f%% %d %s\033[K" % (p, self.nfiles - f, name)
|
913
994
|
|
914
|
-
txt += "\033[
|
995
|
+
txt += "\033[%dH " % (ss.g + 2,)
|
915
996
|
else:
|
916
997
|
txt = " "
|
917
998
|
|
@@ -929,7 +1010,7 @@ class Ctl(object):
|
|
929
1010
|
nleft = self.nfiles - self.up_f
|
930
1011
|
tail = "\033[K\033[u" if VT100 and not self.ar.ns else "\r"
|
931
1012
|
|
932
|
-
t = "
|
1013
|
+
t = "%s eta @ %s/s, %s, %d# left\033[K" % (self.eta, spd, sleft, nleft)
|
933
1014
|
eprint(txt + "\033]0;{0}\033\\\r{0}{1}".format(t, tail))
|
934
1015
|
|
935
1016
|
if self.hash_b and self.at_hash:
|
@@ -965,20 +1046,18 @@ class Ctl(object):
|
|
965
1046
|
srd = rd.decode("utf-8", "replace").replace("\\", "/")
|
966
1047
|
if prd != rd:
|
967
1048
|
prd = rd
|
968
|
-
headers = {}
|
969
|
-
if self.ar.a:
|
970
|
-
headers["Cookie"] = "=".join(["cppwd", self.ar.a])
|
971
|
-
|
972
1049
|
ls = {}
|
973
1050
|
try:
|
974
1051
|
print(" ls ~{0}".format(srd))
|
975
|
-
|
976
|
-
|
977
|
-
|
978
|
-
|
979
|
-
|
1052
|
+
zt = (
|
1053
|
+
self.ar.vtop,
|
1054
|
+
quotep(rd.replace(b"\\", b"/")).decode("utf-8", "replace"),
|
1055
|
+
)
|
1056
|
+
sc, txt = web.req("GET", "%s%s?ls<&dots" % zt, {})
|
1057
|
+
if sc >= 400:
|
1058
|
+
raise Exception("http %s" % (sc,))
|
980
1059
|
|
981
|
-
j =
|
1060
|
+
j = json.loads(txt)
|
982
1061
|
for f in j["dirs"] + j["files"]:
|
983
1062
|
rfn = f["href"].split("?")[0].rstrip("/")
|
984
1063
|
ls[unquote(rfn.encode("utf-8", "replace"))] = f
|
@@ -1001,14 +1080,17 @@ class Ctl(object):
|
|
1001
1080
|
req = locs
|
1002
1081
|
while req:
|
1003
1082
|
print("DELETING ~%s/#%s" % (srd, len(req)))
|
1004
|
-
|
1005
|
-
|
1083
|
+
body = json.dumps(req).encode("utf-8")
|
1084
|
+
sc, txt = web.req(
|
1085
|
+
"POST", self.ar.url + "?delete", {}, body, MJ
|
1086
|
+
)
|
1087
|
+
if sc == 413 and "json 2big" in txt:
|
1006
1088
|
print(" (delete request too big; slicing...)")
|
1007
1089
|
req = req[: len(req) // 2]
|
1008
1090
|
continue
|
1009
|
-
elif
|
1010
|
-
t = "delete request failed: %
|
1011
|
-
raise Exception(t % (
|
1091
|
+
elif sc >= 400:
|
1092
|
+
t = "delete request failed: %s %s"
|
1093
|
+
raise Exception(t % (sc, txt))
|
1012
1094
|
break
|
1013
1095
|
locs = locs[len(req) :]
|
1014
1096
|
|
@@ -1056,7 +1138,7 @@ class Ctl(object):
|
|
1056
1138
|
if self.ar.wlist:
|
1057
1139
|
zsl = [self.ar.wsalt, str(file.size)] + [x[0] for x in file.kchunks]
|
1058
1140
|
zb = hashlib.sha512("\n".join(zsl).encode("utf-8")).digest()[:33]
|
1059
|
-
wark =
|
1141
|
+
wark = ub64enc(zb).decode("utf-8")
|
1060
1142
|
vp = file.rel.decode("utf-8")
|
1061
1143
|
if self.ar.jw:
|
1062
1144
|
print("%s %s" % (wark, vp))
|
@@ -1087,7 +1169,6 @@ class Ctl(object):
|
|
1087
1169
|
|
1088
1170
|
def handshaker(self):
|
1089
1171
|
search = self.ar.s
|
1090
|
-
burl = self.ar.url[:8] + self.ar.url[8:].split("/")[0] + "/"
|
1091
1172
|
while True:
|
1092
1173
|
file = self.q_handshake.get()
|
1093
1174
|
if not file:
|
@@ -1109,12 +1190,16 @@ class Ctl(object):
|
|
1109
1190
|
while time.time() < file.cd:
|
1110
1191
|
time.sleep(0.1)
|
1111
1192
|
|
1112
|
-
|
1193
|
+
try:
|
1194
|
+
hs, sprs = handshake(self.ar, file, search)
|
1195
|
+
except BadAuth:
|
1196
|
+
self.panik = 1
|
1197
|
+
break
|
1198
|
+
|
1113
1199
|
if search:
|
1114
1200
|
if hs:
|
1115
1201
|
for hit in hs:
|
1116
|
-
|
1117
|
-
print(t.format(upath, burl, hit["rp"]))
|
1202
|
+
print("found: %s\n %s/%s" % (upath, self.ar.burl, hit["rp"]))
|
1118
1203
|
else:
|
1119
1204
|
print("NOT found: {0}".format(upath))
|
1120
1205
|
|
@@ -1236,7 +1321,7 @@ class Ctl(object):
|
|
1236
1321
|
)
|
1237
1322
|
|
1238
1323
|
try:
|
1239
|
-
upload(fsl,
|
1324
|
+
upload(fsl, stats)
|
1240
1325
|
except Exception as ex:
|
1241
1326
|
t = "upload failed, retrying: %s #%s+%d (%s)\n"
|
1242
1327
|
eprint(t % (file.name, cids[0][:8], len(cids) - 1, ex))
|
@@ -1270,14 +1355,17 @@ class APF(argparse.ArgumentDefaultsHelpFormatter, argparse.RawDescriptionHelpFor
|
|
1270
1355
|
|
1271
1356
|
|
1272
1357
|
def main():
|
1358
|
+
global web
|
1359
|
+
|
1273
1360
|
time.strptime("19970815", "%Y%m%d") # python#7980
|
1361
|
+
"".encode("idna") # python#29288
|
1274
1362
|
if not VT100:
|
1275
1363
|
os.system("rem") # enables colors
|
1276
1364
|
|
1277
1365
|
cores = (os.cpu_count() if hasattr(os, "cpu_count") else 0) or 2
|
1278
1366
|
hcores = min(cores, 3) # 4% faster than 4+ on py3.9 @ r5-4500U
|
1279
1367
|
|
1280
|
-
ver = "{0}
|
1368
|
+
ver = "{0} v{1} https://youtu.be/BIcOO6TLKaY".format(S_BUILD_DT, S_VERSION)
|
1281
1369
|
if "--version" in sys.argv:
|
1282
1370
|
print(ver)
|
1283
1371
|
return
|
@@ -1285,7 +1373,7 @@ def main():
|
|
1285
1373
|
sys.argv = [x for x in sys.argv if x != "--ws"]
|
1286
1374
|
|
1287
1375
|
# fmt: off
|
1288
|
-
ap = app = argparse.ArgumentParser(formatter_class=APF, description="copyparty up2k uploader / filesearch tool
|
1376
|
+
ap = app = argparse.ArgumentParser(formatter_class=APF, description="copyparty up2k uploader / filesearch tool " + ver, epilog="""
|
1289
1377
|
NOTE:
|
1290
1378
|
source file/folder selection uses rsync syntax, meaning that:
|
1291
1379
|
"foo" uploads the entire folder to URL/foo/
|
@@ -1366,13 +1454,20 @@ source file/folder selection uses rsync syntax, meaning that:
|
|
1366
1454
|
for x in ar.files
|
1367
1455
|
]
|
1368
1456
|
|
1369
|
-
|
1370
|
-
|
1371
|
-
|
1457
|
+
# urlsplit needs scheme;
|
1458
|
+
zs = ar.url.rstrip("/") + "/"
|
1459
|
+
if "://" not in zs:
|
1460
|
+
zs = "http://" + zs
|
1461
|
+
ar.url = zs
|
1462
|
+
|
1463
|
+
url = urlsplit(zs)
|
1464
|
+
ar.burl = "%s://%s" % (url.scheme, url.netloc)
|
1465
|
+
ar.vtop = url.path
|
1372
1466
|
|
1373
1467
|
if "https://" in ar.url.lower():
|
1374
1468
|
try:
|
1375
|
-
import ssl
|
1469
|
+
import ssl
|
1470
|
+
import zipfile
|
1376
1471
|
except:
|
1377
1472
|
t = "ERROR: https is not available for some reason; please use http"
|
1378
1473
|
print("\n\n %s\n\n" % (t,))
|
@@ -1397,6 +1492,7 @@ source file/folder selection uses rsync syntax, meaning that:
|
|
1397
1492
|
if ar.cls:
|
1398
1493
|
eprint("\033[H\033[2J\033[3J", end="")
|
1399
1494
|
|
1495
|
+
web = HCli(ar)
|
1400
1496
|
ctl = Ctl(ar)
|
1401
1497
|
|
1402
1498
|
if ar.dr and not ar.drd and ctl.ok:
|