copyparty 1.12.1__py3-none-any.whl → 1.13.0__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/__main__.py +7 -2
- copyparty/__version__.py +3 -3
- copyparty/authsrv.py +8 -7
- copyparty/cert.py +17 -6
- copyparty/cfg.py +3 -0
- copyparty/httpcli.py +188 -10
- copyparty/httpconn.py +1 -0
- copyparty/httpsrv.py +2 -0
- copyparty/svchub.py +13 -0
- copyparty/th_srv.py +2 -1
- copyparty/up2k.py +124 -60
- copyparty/util.py +101 -29
- copyparty/web/a/u2c.py +8 -4
- copyparty/web/browser.css.gz +0 -0
- copyparty/web/browser.js.gz +0 -0
- copyparty/web/splash.css.gz +0 -0
- copyparty/web/splash.html +1 -1
- copyparty/web/up2k.js.gz +0 -0
- copyparty/web/util.js.gz +0 -0
- {copyparty-1.12.1.dist-info → copyparty-1.13.0.dist-info}/METADATA +29 -4
- {copyparty-1.12.1.dist-info → copyparty-1.13.0.dist-info}/RECORD +25 -25
- {copyparty-1.12.1.dist-info → copyparty-1.13.0.dist-info}/LICENSE +0 -0
- {copyparty-1.12.1.dist-info → copyparty-1.13.0.dist-info}/WHEEL +0 -0
- {copyparty-1.12.1.dist-info → copyparty-1.13.0.dist-info}/entry_points.txt +0 -0
- {copyparty-1.12.1.dist-info → copyparty-1.13.0.dist-info}/top_level.txt +0 -0
copyparty/__main__.py
CHANGED
@@ -850,8 +850,9 @@ def add_qr(ap, tty):
|
|
850
850
|
|
851
851
|
def add_fs(ap):
|
852
852
|
ap2 = ap.add_argument_group("filesystem options")
|
853
|
-
rm_re_def = "
|
853
|
+
rm_re_def = "15/0.1" if ANYWIN else "0/0"
|
854
854
|
ap2.add_argument("--rm-retry", metavar="T/R", type=u, default=rm_re_def, help="if a file cannot be deleted because it is busy, continue trying for \033[33mT\033[0m seconds, retry every \033[33mR\033[0m seconds; disable with 0/0 (volflag=rm_retry)")
|
855
|
+
ap2.add_argument("--mv-retry", metavar="T/R", type=u, default=rm_re_def, help="if a file cannot be renamed because it is busy, continue trying for \033[33mT\033[0m seconds, retry every \033[33mR\033[0m seconds; disable with 0/0 (volflag=mv_retry)")
|
855
856
|
ap2.add_argument("--iobuf", metavar="BYTES", type=int, default=256*1024, help="file I/O buffer-size; if your volumes are on a network drive, try increasing to \033[32m524288\033[0m or even \033[32m4194304\033[0m (and let me know if that improves your performance)")
|
856
857
|
|
857
858
|
|
@@ -943,6 +944,8 @@ def add_auth(ap):
|
|
943
944
|
ap2.add_argument("--idp-h-grp", metavar="HN", type=u, default="", help="assume the request-header \033[33mHN\033[0m contains the groupname of the requesting user; can be referenced in config files for group-based access control")
|
944
945
|
ap2.add_argument("--idp-h-key", metavar="HN", type=u, default="", help="optional but recommended safeguard; your reverse-proxy will insert a secret header named \033[33mHN\033[0m into all requests, and the other IdP headers will be ignored if this header is not present")
|
945
946
|
ap2.add_argument("--idp-gsep", metavar="RE", type=u, default="|:;+,", help="if there are multiple groups in \033[33m--idp-h-grp\033[0m, they are separated by one of the characters in \033[33mRE\033[0m")
|
947
|
+
ap2.add_argument("--no-bauth", action="store_true", help="disable basic-authentication support; do not accept passwords from the 'Authenticate' header at all. NOTE: This breaks support for the android app")
|
948
|
+
ap2.add_argument("--bauth-last", action="store_true", help="keeps basic-authentication enabled, but only as a last-resort; if a cookie is also provided then the cookie wins")
|
946
949
|
|
947
950
|
|
948
951
|
def add_zeroconf(ap):
|
@@ -1082,6 +1085,8 @@ def add_optouts(ap):
|
|
1082
1085
|
ap2.add_argument("--no-zip", action="store_true", help="disable download as zip/tar")
|
1083
1086
|
ap2.add_argument("--no-tarcmp", action="store_true", help="disable download as compressed tar (?tar=gz, ?tar=bz2, ?tar=xz, ?tar=gz:9, ...)")
|
1084
1087
|
ap2.add_argument("--no-lifetime", action="store_true", help="do not allow clients (or server config) to schedule an upload to be deleted after a given time")
|
1088
|
+
ap2.add_argument("--no-pipe", action="store_true", help="disable race-the-beam (lockstep download of files which are currently being uploaded) (volflag=nopipe)")
|
1089
|
+
ap2.add_argument("--no-db-ip", action="store_true", help="do not write uploader IPs into the database")
|
1085
1090
|
|
1086
1091
|
|
1087
1092
|
def add_safety(ap):
|
@@ -1207,7 +1212,7 @@ def add_db_general(ap, hcores):
|
|
1207
1212
|
ap2.add_argument("--no-hash", metavar="PTN", type=u, help="regex: disable hashing of matching absolute-filesystem-paths during e2ds folder scans (volflag=nohash)")
|
1208
1213
|
ap2.add_argument("--no-idx", metavar="PTN", type=u, default=noidx, help="regex: disable indexing of matching absolute-filesystem-paths during e2ds folder scans (volflag=noidx)")
|
1209
1214
|
ap2.add_argument("--no-dhash", action="store_true", help="disable rescan acceleration; do full database integrity check -- makes the db ~5%% smaller and bootup/rescans 3~10x slower")
|
1210
|
-
ap2.add_argument("--re-dhash", action="store_true", help="
|
1215
|
+
ap2.add_argument("--re-dhash", action="store_true", help="force a cache rebuild on startup; enable this once if it gets out of sync (should never be necessary)")
|
1211
1216
|
ap2.add_argument("--no-forget", action="store_true", help="never forget indexed files, even when deleted from disk -- makes it impossible to ever upload the same file twice -- only useful for offloading uploads to a cloud service or something (volflag=noforget)")
|
1212
1217
|
ap2.add_argument("--dbd", metavar="PROFILE", default="wal", help="database durability profile; sets the tradeoff between robustness and speed, see \033[33m--help-dbd\033[0m (volflag=dbd)")
|
1213
1218
|
ap2.add_argument("--xlink", action="store_true", help="on upload: check all volumes for dupes, not just the target volume (volflag=xlink)")
|
copyparty/__version__.py
CHANGED
@@ -1,8 +1,8 @@
|
|
1
1
|
# coding: utf-8
|
2
2
|
|
3
|
-
VERSION = (1,
|
4
|
-
CODENAME = "
|
5
|
-
BUILD_DT = (2024, 4,
|
3
|
+
VERSION = (1, 13, 0)
|
4
|
+
CODENAME = "race the beam"
|
5
|
+
BUILD_DT = (2024, 4, 20)
|
6
6
|
|
7
7
|
S_VERSION = ".".join(map(str, VERSION))
|
8
8
|
S_BUILD_DT = "{0:04d}-{1:02d}-{2:02d}".format(*BUILD_DT)
|
copyparty/authsrv.py
CHANGED
@@ -1757,13 +1757,14 @@ class AuthSrv(object):
|
|
1757
1757
|
if k in vol.flags:
|
1758
1758
|
vol.flags[k] = float(vol.flags[k])
|
1759
1759
|
|
1760
|
-
|
1761
|
-
|
1762
|
-
|
1763
|
-
|
1764
|
-
|
1765
|
-
|
1766
|
-
|
1760
|
+
for k in ("mv_re", "rm_re"):
|
1761
|
+
try:
|
1762
|
+
zs1, zs2 = vol.flags[k + "try"].split("/")
|
1763
|
+
vol.flags[k + "_t"] = float(zs1)
|
1764
|
+
vol.flags[k + "_r"] = float(zs2)
|
1765
|
+
except:
|
1766
|
+
t = 'volume "/%s" has invalid %stry [%s]'
|
1767
|
+
raise Exception(t % (vol.vpath, k, vol.flags.get(k + "try")))
|
1767
1768
|
|
1768
1769
|
for k1, k2 in IMPLICATIONS:
|
1769
1770
|
if k1 in vol.flags:
|
copyparty/cert.py
CHANGED
@@ -6,10 +6,17 @@ import os
|
|
6
6
|
import shutil
|
7
7
|
import time
|
8
8
|
|
9
|
-
from .
|
9
|
+
from .__init__ import ANYWIN
|
10
|
+
from .util import Netdev, runcmd, wrename, wunlink
|
10
11
|
|
11
12
|
HAVE_CFSSL = True
|
12
13
|
|
14
|
+
if ANYWIN:
|
15
|
+
VF = {"mv_re_t": 5, "rm_re_t": 5, "mv_re_r": 0.1, "rm_re_r": 0.1}
|
16
|
+
else:
|
17
|
+
VF = {"mv_re_t": 0, "rm_re_t": 0}
|
18
|
+
|
19
|
+
|
13
20
|
def ensure_cert(log , args) :
|
14
21
|
"""
|
15
22
|
the default cert (and the entire TLS support) is only here to enable the
|
@@ -101,8 +108,12 @@ def _gen_ca(log , args):
|
|
101
108
|
raise Exception("failed to translate ca-cert: {}, {}".format(rc, se), 3)
|
102
109
|
|
103
110
|
bname = os.path.join(args.crt_dir, "ca")
|
104
|
-
|
105
|
-
|
111
|
+
try:
|
112
|
+
wunlink(log, bname + ".key", VF)
|
113
|
+
except:
|
114
|
+
pass
|
115
|
+
wrename(log, bname + "-key.pem", bname + ".key", VF)
|
116
|
+
wunlink(log, bname + ".csr", VF)
|
106
117
|
|
107
118
|
log("cert", "new ca OK", 2)
|
108
119
|
|
@@ -181,11 +192,11 @@ def _gen_srv(log , args, netdevs ):
|
|
181
192
|
|
182
193
|
bname = os.path.join(args.crt_dir, "srv")
|
183
194
|
try:
|
184
|
-
|
195
|
+
wunlink(log, bname + ".key", VF)
|
185
196
|
except:
|
186
197
|
pass
|
187
|
-
|
188
|
-
|
198
|
+
wrename(log, bname + "-key.pem", bname + ".key", VF)
|
199
|
+
wunlink(log, bname + ".csr", VF)
|
189
200
|
|
190
201
|
with open(os.path.join(args.crt_dir, "ca.pem"), "rb") as f:
|
191
202
|
ca = f.read()
|
copyparty/cfg.py
CHANGED
@@ -16,6 +16,7 @@ def vf_bmap() :
|
|
16
16
|
"no_dedup": "copydupes",
|
17
17
|
"no_dupe": "nodupe",
|
18
18
|
"no_forget": "noforget",
|
19
|
+
"no_pipe": "nopipe",
|
19
20
|
"no_robots": "norobots",
|
20
21
|
"no_thumb": "dthumb",
|
21
22
|
"no_vthumb": "dvthumb",
|
@@ -63,6 +64,7 @@ def vf_vmap() :
|
|
63
64
|
"lg_sbf",
|
64
65
|
"md_sbf",
|
65
66
|
"nrand",
|
67
|
+
"mv_retry",
|
66
68
|
"rm_retry",
|
67
69
|
"sort",
|
68
70
|
"unlist",
|
@@ -214,6 +216,7 @@ flagcats = {
|
|
214
216
|
"dots": "allow all users with read-access to\nenable the option to show dotfiles in listings",
|
215
217
|
"fk=8": 'generates per-file accesskeys,\nwhich are then required at the "g" permission;\nkeys are invalidated if filesize or inode changes',
|
216
218
|
"fka=8": 'generates slightly weaker per-file accesskeys,\nwhich are then required at the "g" permission;\nnot affected by filesize or inode numbers',
|
219
|
+
"mv_retry": "ms-windows: timeout for renaming busy files",
|
217
220
|
"rm_retry": "ms-windows: timeout for deleting busy files",
|
218
221
|
"davauth": "ask webdav clients to login for all folders",
|
219
222
|
"davrt": "show lastmod time of symlink destination, not the link itself\n(note: this option is always enabled for recursive listings)",
|
copyparty/httpcli.py
CHANGED
@@ -36,6 +36,7 @@ from .bos import bos
|
|
36
36
|
from .star import StreamTar
|
37
37
|
from .sutil import StreamArc, gfilter
|
38
38
|
from .szip import StreamZip
|
39
|
+
from .up2k import up2k_chunksize
|
39
40
|
from .util import unquote # type: ignore
|
40
41
|
from .util import (
|
41
42
|
APPLESAN_RE,
|
@@ -89,6 +90,7 @@ from .util import (
|
|
89
90
|
vjoin,
|
90
91
|
vol_san,
|
91
92
|
vsplit,
|
93
|
+
wrename,
|
92
94
|
wunlink,
|
93
95
|
yieldfile,
|
94
96
|
)
|
@@ -122,6 +124,7 @@ class HttpCli(object):
|
|
122
124
|
self.ico = conn.ico # mypy404
|
123
125
|
self.thumbcli = conn.thumbcli # mypy404
|
124
126
|
self.u2fh = conn.u2fh # mypy404
|
127
|
+
self.pipes = conn.pipes # mypy404
|
125
128
|
self.log_func = conn.log_func # mypy404
|
126
129
|
self.log_src = conn.log_src # mypy404
|
127
130
|
self.gen_fk = self._gen_fk if self.args.log_fk else gen_filekey
|
@@ -439,7 +442,11 @@ class HttpCli(object):
|
|
439
442
|
|
440
443
|
zso = self.headers.get("authorization")
|
441
444
|
bauth = ""
|
442
|
-
if
|
445
|
+
if (
|
446
|
+
zso
|
447
|
+
and not self.args.no_bauth
|
448
|
+
and (not cookie_pw or not self.args.bauth_last)
|
449
|
+
):
|
443
450
|
try:
|
444
451
|
zb = zso.split(" ")[1].encode("ascii")
|
445
452
|
zs = base64.b64decode(zb).decode("utf-8")
|
@@ -1796,7 +1803,7 @@ class HttpCli(object):
|
|
1796
1803
|
f, fn = zfw["orz"]
|
1797
1804
|
|
1798
1805
|
path2 = os.path.join(fdir, fn2)
|
1799
|
-
atomic_move(path, path2)
|
1806
|
+
atomic_move(self.log, path, path2, vfs.flags)
|
1800
1807
|
fn = fn2
|
1801
1808
|
path = path2
|
1802
1809
|
|
@@ -1877,7 +1884,9 @@ class HttpCli(object):
|
|
1877
1884
|
self.reply(t.encode("utf-8"), 201, headers=h)
|
1878
1885
|
return True
|
1879
1886
|
|
1880
|
-
def bakflip(
|
1887
|
+
def bakflip(
|
1888
|
+
self, f , ofs , sz , sha , flags
|
1889
|
+
) :
|
1881
1890
|
if not self.args.bak_flips or self.args.nw:
|
1882
1891
|
return
|
1883
1892
|
|
@@ -1905,7 +1914,7 @@ class HttpCli(object):
|
|
1905
1914
|
|
1906
1915
|
if nrem:
|
1907
1916
|
self.log("bakflip truncated; {} remains".format(nrem), 1)
|
1908
|
-
atomic_move(fp, fp + ".trunc")
|
1917
|
+
atomic_move(self.log, fp, fp + ".trunc", flags)
|
1909
1918
|
else:
|
1910
1919
|
self.log("bakflip ok", 2)
|
1911
1920
|
|
@@ -2171,7 +2180,7 @@ class HttpCli(object):
|
|
2171
2180
|
|
2172
2181
|
if sha_b64 != chash:
|
2173
2182
|
try:
|
2174
|
-
self.bakflip(f, cstart[0], post_sz, sha_b64)
|
2183
|
+
self.bakflip(f, cstart[0], post_sz, sha_b64, vfs.flags)
|
2175
2184
|
except:
|
2176
2185
|
self.log("bakflip failed: " + min_ex())
|
2177
2186
|
|
@@ -2523,7 +2532,7 @@ class HttpCli(object):
|
|
2523
2532
|
raise
|
2524
2533
|
|
2525
2534
|
if not nullwrite:
|
2526
|
-
atomic_move(tabspath, abspath)
|
2535
|
+
atomic_move(self.log, tabspath, abspath, vfs.flags)
|
2527
2536
|
|
2528
2537
|
tabspath = ""
|
2529
2538
|
|
@@ -2763,7 +2772,7 @@ class HttpCli(object):
|
|
2763
2772
|
hidedir(dp)
|
2764
2773
|
except:
|
2765
2774
|
pass
|
2766
|
-
|
2775
|
+
wrename(self.log, fp, os.path.join(mdir, ".hist", mfile2), vfs.flags)
|
2767
2776
|
|
2768
2777
|
assert self.parser.gen
|
2769
2778
|
p_field, _, p_data = next(self.parser.gen)
|
@@ -2918,17 +2927,42 @@ class HttpCli(object):
|
|
2918
2927
|
|
2919
2928
|
return txt
|
2920
2929
|
|
2921
|
-
def tx_file(self, req_path ) :
|
2930
|
+
def tx_file(self, req_path , ptop = None) :
|
2922
2931
|
status = 200
|
2923
2932
|
logmsg = "{:4} {} ".format("", self.req)
|
2924
2933
|
logtail = ""
|
2925
2934
|
|
2935
|
+
if ptop is not None:
|
2936
|
+
try:
|
2937
|
+
dp, fn = os.path.split(req_path)
|
2938
|
+
tnam = fn + ".PARTIAL"
|
2939
|
+
if self.args.dotpart:
|
2940
|
+
tnam = "." + tnam
|
2941
|
+
ap_data = os.path.join(dp, tnam)
|
2942
|
+
st_data = bos.stat(ap_data)
|
2943
|
+
if not st_data.st_size:
|
2944
|
+
raise Exception("partial is empty")
|
2945
|
+
x = self.conn.hsrv.broker.ask("up2k.find_job_by_ap", ptop, req_path)
|
2946
|
+
job = json.loads(x.get())
|
2947
|
+
if not job:
|
2948
|
+
raise Exception("not found in registry")
|
2949
|
+
self.pipes.set(req_path, job)
|
2950
|
+
except Exception as ex:
|
2951
|
+
self.log("will not pipe [%s]; %s" % (ap_data, ex), 6)
|
2952
|
+
ptop = None
|
2953
|
+
|
2926
2954
|
#
|
2927
2955
|
# if request is for foo.js, check if we have foo.js.gz
|
2928
2956
|
|
2929
2957
|
file_ts = 0.0
|
2930
2958
|
editions = {}
|
2931
2959
|
for ext in ("", ".gz"):
|
2960
|
+
if ptop is not None:
|
2961
|
+
sz = job["size"]
|
2962
|
+
file_ts = job["lmod"]
|
2963
|
+
editions["plain"] = (ap_data, sz)
|
2964
|
+
break
|
2965
|
+
|
2932
2966
|
try:
|
2933
2967
|
fs_path = req_path + ext
|
2934
2968
|
st = bos.stat(fs_path)
|
@@ -3085,6 +3119,11 @@ class HttpCli(object):
|
|
3085
3119
|
self.send_headers(length=upper - lower, status=status, mime=mime)
|
3086
3120
|
return True
|
3087
3121
|
|
3122
|
+
if ptop is not None:
|
3123
|
+
return self.tx_pipe(
|
3124
|
+
ptop, req_path, ap_data, job, lower, upper, status, mime, logmsg
|
3125
|
+
)
|
3126
|
+
|
3088
3127
|
ret = True
|
3089
3128
|
with open_func(*open_args) as f:
|
3090
3129
|
self.send_headers(length=upper - lower, status=status, mime=mime)
|
@@ -3104,6 +3143,143 @@ class HttpCli(object):
|
|
3104
3143
|
|
3105
3144
|
return ret
|
3106
3145
|
|
3146
|
+
def tx_pipe(
|
3147
|
+
self,
|
3148
|
+
ptop ,
|
3149
|
+
req_path ,
|
3150
|
+
ap_data ,
|
3151
|
+
job ,
|
3152
|
+
lower ,
|
3153
|
+
upper ,
|
3154
|
+
status ,
|
3155
|
+
mime ,
|
3156
|
+
logmsg ,
|
3157
|
+
) :
|
3158
|
+
M = 1048576
|
3159
|
+
self.send_headers(length=upper - lower, status=status, mime=mime)
|
3160
|
+
wr_slp = self.args.s_wr_slp
|
3161
|
+
wr_sz = self.args.s_wr_sz
|
3162
|
+
file_size = job["size"]
|
3163
|
+
chunk_size = up2k_chunksize(file_size)
|
3164
|
+
num_need = -1
|
3165
|
+
data_end = 0
|
3166
|
+
remains = upper - lower
|
3167
|
+
broken = False
|
3168
|
+
spins = 0
|
3169
|
+
tier = 0
|
3170
|
+
tiers = ["uncapped", "reduced speed", "one byte per sec"]
|
3171
|
+
|
3172
|
+
while lower < upper and not broken:
|
3173
|
+
with self.u2mutex:
|
3174
|
+
job = self.pipes.get(req_path)
|
3175
|
+
if not job:
|
3176
|
+
x = self.conn.hsrv.broker.ask("up2k.find_job_by_ap", ptop, req_path)
|
3177
|
+
job = json.loads(x.get())
|
3178
|
+
if job:
|
3179
|
+
self.pipes.set(req_path, job)
|
3180
|
+
|
3181
|
+
if not job:
|
3182
|
+
t = "pipe: OK, upload has finished; yeeting remainder"
|
3183
|
+
self.log(t, 2)
|
3184
|
+
data_end = file_size
|
3185
|
+
break
|
3186
|
+
|
3187
|
+
if num_need != len(job["need"]):
|
3188
|
+
num_need = len(job["need"])
|
3189
|
+
data_end = 0
|
3190
|
+
for cid in job["hash"]:
|
3191
|
+
if cid in job["need"]:
|
3192
|
+
break
|
3193
|
+
data_end += chunk_size
|
3194
|
+
t = "pipe: can stream %.2f MiB; requested range is %.2f to %.2f"
|
3195
|
+
self.log(t % (data_end / M, lower / M, upper / M), 6)
|
3196
|
+
with self.u2mutex:
|
3197
|
+
if data_end > self.u2fh.aps.get(ap_data, data_end):
|
3198
|
+
try:
|
3199
|
+
fhs = self.u2fh.cache[ap_data].all_fhs
|
3200
|
+
for fh in fhs:
|
3201
|
+
fh.flush()
|
3202
|
+
self.u2fh.aps[ap_data] = data_end
|
3203
|
+
self.log("pipe: flushed %d up2k-FDs" % (len(fhs),))
|
3204
|
+
except Exception as ex:
|
3205
|
+
self.log("pipe: u2fh flush failed: %r" % (ex,))
|
3206
|
+
|
3207
|
+
if lower >= data_end:
|
3208
|
+
if data_end:
|
3209
|
+
t = "pipe: uploader is too slow; aborting download at %.2f MiB"
|
3210
|
+
self.log(t % (data_end / M))
|
3211
|
+
raise Pebkac(416, "uploader is too slow")
|
3212
|
+
|
3213
|
+
raise Pebkac(416, "no data available yet; please retry in a bit")
|
3214
|
+
|
3215
|
+
slack = data_end - lower
|
3216
|
+
if slack >= 8 * M:
|
3217
|
+
ntier = 0
|
3218
|
+
winsz = M
|
3219
|
+
bufsz = wr_sz
|
3220
|
+
slp = wr_slp
|
3221
|
+
else:
|
3222
|
+
winsz = max(40, int(M * (slack / (12 * M))))
|
3223
|
+
base_rate = M if not wr_slp else wr_sz / wr_slp
|
3224
|
+
if winsz > base_rate:
|
3225
|
+
ntier = 0
|
3226
|
+
bufsz = wr_sz
|
3227
|
+
slp = wr_slp
|
3228
|
+
elif winsz > 300:
|
3229
|
+
ntier = 1
|
3230
|
+
bufsz = winsz // 5
|
3231
|
+
slp = 0.2
|
3232
|
+
else:
|
3233
|
+
ntier = 2
|
3234
|
+
bufsz = winsz = slp = 1
|
3235
|
+
|
3236
|
+
if tier != ntier:
|
3237
|
+
tier = ntier
|
3238
|
+
self.log("moved to tier %d (%s)" % (tier, tiers[tier]))
|
3239
|
+
|
3240
|
+
try:
|
3241
|
+
with open(ap_data, "rb", self.args.iobuf) as f:
|
3242
|
+
f.seek(lower)
|
3243
|
+
page = f.read(min(winsz, data_end - lower, upper - lower))
|
3244
|
+
if not page:
|
3245
|
+
raise Exception("got 0 bytes (EOF?)")
|
3246
|
+
except Exception as ex:
|
3247
|
+
self.log("pipe: read failed at %.2f MiB: %s" % (lower / M, ex), 3)
|
3248
|
+
with self.u2mutex:
|
3249
|
+
self.pipes.c.pop(req_path, None)
|
3250
|
+
spins += 1
|
3251
|
+
if spins > 3:
|
3252
|
+
raise Pebkac(500, "file became unreadable")
|
3253
|
+
time.sleep(2)
|
3254
|
+
continue
|
3255
|
+
|
3256
|
+
spins = 0
|
3257
|
+
pofs = 0
|
3258
|
+
while pofs < len(page):
|
3259
|
+
if slp:
|
3260
|
+
time.sleep(slp)
|
3261
|
+
|
3262
|
+
try:
|
3263
|
+
buf = page[pofs : pofs + bufsz]
|
3264
|
+
self.s.sendall(buf)
|
3265
|
+
zi = len(buf)
|
3266
|
+
remains -= zi
|
3267
|
+
lower += zi
|
3268
|
+
pofs += zi
|
3269
|
+
except:
|
3270
|
+
broken = True
|
3271
|
+
break
|
3272
|
+
|
3273
|
+
if lower < upper and not broken:
|
3274
|
+
with open(req_path, "rb") as f:
|
3275
|
+
remains = sendfile_py(self.log, lower, upper, f, self.s, wr_sz, wr_slp)
|
3276
|
+
|
3277
|
+
spd = self._spd((upper - lower) - remains)
|
3278
|
+
if self.do_log:
|
3279
|
+
self.log("{}, {}".format(logmsg, spd))
|
3280
|
+
|
3281
|
+
return not broken
|
3282
|
+
|
3107
3283
|
def tx_zip(
|
3108
3284
|
self,
|
3109
3285
|
fmt ,
|
@@ -3741,7 +3917,7 @@ class HttpCli(object):
|
|
3741
3917
|
if not allvols:
|
3742
3918
|
ret = [{"kinshi": 1}]
|
3743
3919
|
|
3744
|
-
jtxt = '{"u":%s,"c":%s}' % (uret, json.dumps(ret,
|
3920
|
+
jtxt = '{"u":%s,"c":%s}' % (uret, json.dumps(ret, separators=(",\n", ": ")))
|
3745
3921
|
zi = len(uret.split('\n"pd":')) - 1
|
3746
3922
|
self.log("%s #%d+%d %.2fsec" % (lm, zi, len(ret), time.time() - t0))
|
3747
3923
|
self.reply(jtxt.encode("utf-8", "replace"), mime="application/json")
|
@@ -4020,7 +4196,9 @@ class HttpCli(object):
|
|
4020
4196
|
):
|
4021
4197
|
return self.tx_md(vn, abspath)
|
4022
4198
|
|
4023
|
-
return self.tx_file(
|
4199
|
+
return self.tx_file(
|
4200
|
+
abspath, None if st.st_size or "nopipe" in vn.flags else vn.realpath
|
4201
|
+
)
|
4024
4202
|
|
4025
4203
|
elif is_dir and not self.can_read:
|
4026
4204
|
if self._use_dirkey(abspath):
|
copyparty/httpconn.py
CHANGED
copyparty/httpsrv.py
CHANGED
@@ -61,6 +61,7 @@ from .u2idx import U2idx
|
|
61
61
|
from .util import (
|
62
62
|
E_SCK,
|
63
63
|
FHC,
|
64
|
+
CachedDict,
|
64
65
|
Daemon,
|
65
66
|
Garda,
|
66
67
|
Magician,
|
@@ -126,6 +127,7 @@ class HttpSrv(object):
|
|
126
127
|
self.t_periodic = None
|
127
128
|
|
128
129
|
self.u2fh = FHC()
|
130
|
+
self.pipes = CachedDict(0.2)
|
129
131
|
self.metrics = Metrics(self)
|
130
132
|
self.nreq = 0
|
131
133
|
self.nsus = 0
|
copyparty/svchub.py
CHANGED
@@ -418,6 +418,12 @@ class SvcHub(object):
|
|
418
418
|
t = "WARNING: found config files in [%s]: %s\n config files are not expected here, and will NOT be loaded (unless your setup is intentionally hella funky)"
|
419
419
|
self.log("root", t % (E.cfg, ", ".join(hits)), 3)
|
420
420
|
|
421
|
+
if self.args.no_bauth:
|
422
|
+
t = "WARNING: --no-bauth disables support for the Android app; you may want to use --bauth-last instead"
|
423
|
+
self.log("root", t, 3)
|
424
|
+
if self.args.bauth_last:
|
425
|
+
self.log("root", "WARNING: ignoring --bauth-last due to --no-bauth", 3)
|
426
|
+
|
421
427
|
def _process_config(self) :
|
422
428
|
al = self.args
|
423
429
|
|
@@ -538,6 +544,13 @@ class SvcHub(object):
|
|
538
544
|
except:
|
539
545
|
raise Exception("invalid --rm-retry [%s]" % (self.args.rm_retry,))
|
540
546
|
|
547
|
+
try:
|
548
|
+
zf1, zf2 = self.args.mv_retry.split("/")
|
549
|
+
self.args.mv_re_t = float(zf1)
|
550
|
+
self.args.mv_re_r = float(zf2)
|
551
|
+
except:
|
552
|
+
raise Exception("invalid --mv-retry [%s]" % (self.args.mv_retry,))
|
553
|
+
|
541
554
|
return True
|
542
555
|
|
543
556
|
def _ipa2re(self, txt) :
|
copyparty/th_srv.py
CHANGED
@@ -28,6 +28,7 @@ from .util import (
|
|
28
28
|
runcmd,
|
29
29
|
statdir,
|
30
30
|
vsplit,
|
31
|
+
wrename,
|
31
32
|
wunlink,
|
32
33
|
)
|
33
34
|
|
@@ -343,7 +344,7 @@ class ThumbSrv(object):
|
|
343
344
|
pass
|
344
345
|
|
345
346
|
try:
|
346
|
-
|
347
|
+
wrename(self.log, ttpath, tpath, vn.flags)
|
347
348
|
except:
|
348
349
|
pass
|
349
350
|
|