copyparty 1.13.1__py3-none-any.whl → 1.13.3__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 +52 -8
- copyparty/__version__.py +2 -2
- copyparty/authsrv.py +16 -4
- copyparty/broker_mp.py +2 -5
- copyparty/cfg.py +1 -0
- copyparty/httpcli.py +12 -8
- copyparty/httpsrv.py +1 -4
- copyparty/mdns.py +23 -2
- copyparty/mtag.py +74 -3
- copyparty/smbd.py +1 -1
- copyparty/ssdp.py +22 -2
- copyparty/star.py +4 -4
- copyparty/sutil.py +12 -6
- copyparty/svchub.py +9 -2
- copyparty/szip.py +4 -4
- copyparty/th_cli.py +5 -0
- copyparty/th_srv.py +51 -13
- copyparty/up2k.py +16 -10
- copyparty/util.py +54 -14
- copyparty/web/a/u2c.py +26 -9
- copyparty/web/baguettebox.js.gz +0 -0
- copyparty/web/browser.css.gz +0 -0
- copyparty/web/browser.js.gz +0 -0
- copyparty/web/deps/marked.js.gz +0 -0
- copyparty/web/md2.js.gz +0 -0
- copyparty/web/splash.js.gz +0 -0
- {copyparty-1.13.1.dist-info → copyparty-1.13.3.dist-info}/METADATA +7 -4
- {copyparty-1.13.1.dist-info → copyparty-1.13.3.dist-info}/RECORD +32 -32
- {copyparty-1.13.1.dist-info → copyparty-1.13.3.dist-info}/LICENSE +0 -0
- {copyparty-1.13.1.dist-info → copyparty-1.13.3.dist-info}/WHEEL +0 -0
- {copyparty-1.13.1.dist-info → copyparty-1.13.3.dist-info}/entry_points.txt +0 -0
- {copyparty-1.13.1.dist-info → copyparty-1.13.3.dist-info}/top_level.txt +0 -0
copyparty/__main__.py
CHANGED
@@ -13,6 +13,7 @@ import base64
|
|
13
13
|
import locale
|
14
14
|
import os
|
15
15
|
import re
|
16
|
+
import select
|
16
17
|
import socket
|
17
18
|
import sys
|
18
19
|
import threading
|
@@ -43,11 +44,13 @@ from .util import (
|
|
43
44
|
DEF_MTH,
|
44
45
|
IMPLICATIONS,
|
45
46
|
JINJA_VER,
|
47
|
+
MIMES,
|
46
48
|
PARTFTPY_VER,
|
47
49
|
PY_DESC,
|
48
50
|
PYFTPD_VER,
|
49
51
|
SQLITE_VER,
|
50
52
|
UNPLICATIONS,
|
53
|
+
Daemon,
|
51
54
|
align_tab,
|
52
55
|
ansi_re,
|
53
56
|
dedent,
|
@@ -165,8 +168,10 @@ def init_E(EE ) :
|
|
165
168
|
(os.environ.get, "TMP"),
|
166
169
|
(unicode, "/tmp"),
|
167
170
|
]
|
171
|
+
errs = []
|
168
172
|
for chk in [os.listdir, os.mkdir]:
|
169
|
-
for pf, pa in paths:
|
173
|
+
for npath, (pf, pa) in enumerate(paths):
|
174
|
+
p = ""
|
170
175
|
try:
|
171
176
|
p = pf(pa)
|
172
177
|
# print(chk.__name__, p, pa)
|
@@ -179,9 +184,20 @@ def init_E(EE ) :
|
|
179
184
|
if not os.path.isdir(p):
|
180
185
|
os.mkdir(p)
|
181
186
|
|
187
|
+
if npath > 1:
|
188
|
+
t = "Using [%s] for config; filekeys/dirkeys will change on every restart. Consider setting XDG_CONFIG_HOME or giving the unix-user a ~/.config/"
|
189
|
+
errs.append(t % (p,))
|
190
|
+
elif errs:
|
191
|
+
errs.append("Using [%s] instead" % (p,))
|
192
|
+
|
193
|
+
if errs:
|
194
|
+
print("WARNING: " + ". ".join(errs))
|
195
|
+
|
182
196
|
return p # type: ignore
|
183
|
-
except:
|
184
|
-
|
197
|
+
except Exception as ex:
|
198
|
+
if p and npath < 2:
|
199
|
+
t = "Unable to store config in [%s] due to %r"
|
200
|
+
errs.append(t % (p, ex))
|
185
201
|
|
186
202
|
raise Exception("could not find a writable path for config")
|
187
203
|
|
@@ -464,6 +480,16 @@ def disable_quickedit() :
|
|
464
480
|
cmode(True, mode | 4)
|
465
481
|
|
466
482
|
|
483
|
+
def sfx_tpoke(top ):
|
484
|
+
files = [os.path.join(dp, p) for dp, dd, df in os.walk(top) for p in dd + df]
|
485
|
+
while True:
|
486
|
+
t = int(time.time())
|
487
|
+
for f in [top] + files:
|
488
|
+
os.utime(f, (t, t))
|
489
|
+
|
490
|
+
time.sleep(78123)
|
491
|
+
|
492
|
+
|
467
493
|
def showlic() :
|
468
494
|
p = os.path.join(E.mod, "res", "COPYING.txt")
|
469
495
|
if not os.path.exists(p):
|
@@ -814,7 +840,7 @@ def build_flags_desc():
|
|
814
840
|
v = v.replace("\n", "\n ")
|
815
841
|
ret += "\n \033[36m{}\033[35m {}".format(k, v)
|
816
842
|
|
817
|
-
return ret
|
843
|
+
return ret
|
818
844
|
|
819
845
|
|
820
846
|
# fmt: off
|
@@ -832,6 +858,8 @@ def add_general(ap, nc, srvname):
|
|
832
858
|
ap2.add_argument("--urlform", metavar="MODE", type=u, default="print,get", help="how to handle url-form POSTs; see \033[33m--help-urlform\033[0m")
|
833
859
|
ap2.add_argument("--wintitle", metavar="TXT", type=u, default="cpp @ $pub", help="server terminal title, for example [\033[32m$ip-10.1.2.\033[0m] or [\033[32m$ip-]")
|
834
860
|
ap2.add_argument("--name", metavar="TXT", type=u, default=srvname, help="server name (displayed topleft in browser and in mDNS)")
|
861
|
+
ap2.add_argument("--mime", metavar="EXT=MIME", type=u, action="append", help="map file \033[33mEXT\033[0mension to \033[33mMIME\033[0mtype, for example [\033[32mjpg=image/jpeg\033[0m]")
|
862
|
+
ap2.add_argument("--mimes", action="store_true", help="list default mimetype mapping and exit")
|
835
863
|
ap2.add_argument("--license", action="store_true", help="show licenses and exit")
|
836
864
|
ap2.add_argument("--version", action="store_true", help="show versions and exit")
|
837
865
|
|
@@ -878,7 +906,7 @@ def add_upload(ap):
|
|
878
906
|
ap2.add_argument("--rand", action="store_true", help="force randomized filenames, \033[33m--nrand\033[0m chars long (volflag=rand)")
|
879
907
|
ap2.add_argument("--nrand", metavar="NUM", type=int, default=9, help="randomized filenames length (volflag=nrand)")
|
880
908
|
ap2.add_argument("--magic", action="store_true", help="enable filetype detection on nameless uploads (volflag=magic)")
|
881
|
-
ap2.add_argument("--df", metavar="GiB", type=
|
909
|
+
ap2.add_argument("--df", metavar="GiB", type=u, default="0", help="ensure \033[33mGiB\033[0m free disk space by rejecting upload requests; assumes gigabytes unless a unit suffix is given: [\033[32m256m\033[0m], [\033[32m4\033[0m], [\033[32m2T\033[0m] (volflag=df)")
|
882
910
|
ap2.add_argument("--sparse", metavar="MiB", type=int, default=4, help="windows-only: minimum size of incoming uploads through up2k before they are made into sparse files")
|
883
911
|
ap2.add_argument("--turbo", metavar="LVL", type=int, default=0, help="configure turbo-mode in up2k client; [\033[32m-1\033[0m] = forbidden/always-off, [\033[32m0\033[0m] = default-off and warn if enabled, [\033[32m1\033[0m] = default-off, [\033[32m2\033[0m] = on, [\033[32m3\033[0m] = on and disable datecheck")
|
884
912
|
ap2.add_argument("--u2j", metavar="JOBS", type=int, default=2, help="web-client: number of file chunks to upload in parallel; 1 or 2 is good for low-latency (same-country) connections, 4-8 for android clients, 16 for cross-atlantic (max=64)")
|
@@ -1188,7 +1216,8 @@ def add_thumbnail(ap):
|
|
1188
1216
|
ap2.add_argument("--th-r-vips", metavar="T,T", type=u, default="avif,exr,fit,fits,fts,gif,hdr,heic,jp2,jpeg,jpg,jpx,jxl,nii,pfm,pgm,png,ppm,svg,tif,tiff,webp", help="image formats to decode using pyvips")
|
1189
1217
|
ap2.add_argument("--th-r-ffi", metavar="T,T", type=u, default="apng,avif,avifs,bmp,dds,dib,fit,fits,fts,gif,hdr,heic,heics,heif,heifs,icns,ico,jp2,jpeg,jpg,jpx,jxl,pbm,pcx,pfm,pgm,png,pnm,ppm,psd,qoi,sgi,tga,tif,tiff,webp,xbm,xpm", help="image formats to decode using ffmpeg")
|
1190
1218
|
ap2.add_argument("--th-r-ffv", metavar="T,T", type=u, default="3gp,asf,av1,avc,avi,flv,h264,h265,hevc,m4v,mjpeg,mjpg,mkv,mov,mp4,mpeg,mpeg2,mpegts,mpg,mpg2,mts,nut,ogm,ogv,rm,ts,vob,webm,wmv", help="video formats to decode using ffmpeg")
|
1191
|
-
ap2.add_argument("--th-r-ffa", metavar="T,T", type=u, default="aac,ac3,aif,aiff,alac,alaw,amr,apac,ape,au,bonk,dfpwm,dts,flac,gsm,ilbc,it,m4a,mo3,mod,mp2,mp3,mpc,mptm,mt2,mulaw,ogg,okt,opus,ra,s3m,tak,tta,ulaw,wav,wma,wv,xm,xpk", help="audio formats to decode using ffmpeg")
|
1219
|
+
ap2.add_argument("--th-r-ffa", metavar="T,T", type=u, default="aac,ac3,aif,aiff,alac,alaw,amr,apac,ape,au,bonk,dfpwm,dts,flac,gsm,ilbc,it,itgz,itxz,itz,m4a,mdgz,mdxz,mdz,mo3,mod,mp2,mp3,mpc,mptm,mt2,mulaw,ogg,okt,opus,ra,s3m,s3gz,s3xz,s3z,tak,tta,ulaw,wav,wma,wv,xm,xmgz,xmxz,xmz,xpk", help="audio formats to decode using ffmpeg")
|
1220
|
+
ap2.add_argument("--au-unpk", metavar="E=F.C", type=u, default="mdz=mod.zip, mdgz=mod.gz, mdxz=mod.xz, s3z=s3m.zip, s3gz=s3m.gz, s3xz=s3m.xz, xmz=xm.zip, xmgz=xm.gz, xmxz=xm.xz, itz=it.zip, itgz=it.gz, itxz=it.xz", help="audio formats to decompress before passing to ffmpeg")
|
1192
1221
|
|
1193
1222
|
|
1194
1223
|
def add_transcoding(ap):
|
@@ -1301,6 +1330,8 @@ def add_debug(ap):
|
|
1301
1330
|
ap2 = ap.add_argument_group('debug options')
|
1302
1331
|
ap2.add_argument("--vc", action="store_true", help="verbose config file parser (explain config)")
|
1303
1332
|
ap2.add_argument("--cgen", action="store_true", help="generate config file from current config (best-effort; probably buggy)")
|
1333
|
+
if hasattr(select, "poll"):
|
1334
|
+
ap2.add_argument("--no-poll", action="store_true", help="kernel-bug workaround: disable poll; use select instead (limits max num clients to ~700)")
|
1304
1335
|
ap2.add_argument("--no-sendfile", action="store_true", help="kernel-bug workaround: disable sendfile; do a safe and slow read-send-loop instead")
|
1305
1336
|
ap2.add_argument("--no-scandir", action="store_true", help="kernel-bug workaround: disable scandir; do a listdir + stat on each file instead")
|
1306
1337
|
ap2.add_argument("--no-fastboot", action="store_true", help="wait for initial filesystem indexing before accepting client requests")
|
@@ -1399,7 +1430,7 @@ def run_argparse(
|
|
1399
1430
|
k2 = "help_" + k.replace("-", "_")
|
1400
1431
|
if vars(ret)[k2]:
|
1401
1432
|
lprint("# %s help page (%s)" % (k, h))
|
1402
|
-
lprint(t + "\033[0m")
|
1433
|
+
lprint(t.rstrip() + "\033[0m")
|
1403
1434
|
sys.exit(0)
|
1404
1435
|
|
1405
1436
|
return ret
|
@@ -1438,9 +1469,19 @@ def main(argv = None, rsrc = None) :
|
|
1438
1469
|
showlic()
|
1439
1470
|
sys.exit(0)
|
1440
1471
|
|
1472
|
+
if "--mimes" in argv:
|
1473
|
+
print("\n".join("%8s %s" % (k, v) for k, v in sorted(MIMES.items())))
|
1474
|
+
sys.exit(0)
|
1475
|
+
|
1441
1476
|
if EXE:
|
1442
1477
|
print("pybin: {}\n".format(pybin), end="")
|
1443
1478
|
|
1479
|
+
for n, zs in enumerate(argv):
|
1480
|
+
if zs.startswith("--sfx-tpoke="):
|
1481
|
+
Daemon(sfx_tpoke, "sfx-tpoke", (zs.split("=", 1)[1],))
|
1482
|
+
argv.pop(n)
|
1483
|
+
break
|
1484
|
+
|
1444
1485
|
ensure_locale()
|
1445
1486
|
|
1446
1487
|
ensure_webdeps()
|
@@ -1501,7 +1542,7 @@ def main(argv = None, rsrc = None) :
|
|
1501
1542
|
if hard > 0: # -1 == infinite
|
1502
1543
|
nc = min(nc, int(hard / 4))
|
1503
1544
|
except:
|
1504
|
-
nc = 512
|
1545
|
+
nc = 486 # mdns/ssdp restart headroom; select() maxfd is 512 on windows
|
1505
1546
|
|
1506
1547
|
retry = False
|
1507
1548
|
for fmtr in [RiceFormatter, RiceFormatter, Dodge11874, BasicDodge11874]:
|
@@ -1594,6 +1635,9 @@ def main(argv = None, rsrc = None) :
|
|
1594
1635
|
if not hasattr(os, "sendfile"):
|
1595
1636
|
al.no_sendfile = True
|
1596
1637
|
|
1638
|
+
if not hasattr(select, "poll"):
|
1639
|
+
al.no_poll = True
|
1640
|
+
|
1597
1641
|
# signal.signal(signal.SIGINT, sighandler)
|
1598
1642
|
|
1599
1643
|
SvcHub(al, dal, argv, "".join(printed)).run()
|
copyparty/__version__.py
CHANGED
copyparty/authsrv.py
CHANGED
@@ -17,7 +17,9 @@ from .bos import bos
|
|
17
17
|
from .cfg import flagdescs, permdescs, vf_bmap, vf_cmap, vf_vmap
|
18
18
|
from .pwhash import PWHash
|
19
19
|
from .util import (
|
20
|
+
EXTS,
|
20
21
|
IMPLICATIONS,
|
22
|
+
MIMES,
|
21
23
|
SQLITE_VER,
|
22
24
|
UNPLICATIONS,
|
23
25
|
UTC,
|
@@ -1608,11 +1610,14 @@ class AuthSrv(object):
|
|
1608
1610
|
use = True
|
1609
1611
|
lim.nosub = True
|
1610
1612
|
|
1611
|
-
zs = vol.flags.get("df") or
|
1612
|
-
|
1613
|
-
)
|
1614
|
-
if zs:
|
1613
|
+
zs = vol.flags.get("df") or self.args.df or ""
|
1614
|
+
if zs not in ("", "0"):
|
1615
1615
|
use = True
|
1616
|
+
try:
|
1617
|
+
_ = float(zs)
|
1618
|
+
zs = "%sg" % (zs)
|
1619
|
+
except:
|
1620
|
+
pass
|
1616
1621
|
lim.dfl = unhumanize(zs)
|
1617
1622
|
|
1618
1623
|
zs = vol.flags.get("sz")
|
@@ -2058,6 +2063,13 @@ class AuthSrv(object):
|
|
2058
2063
|
|
2059
2064
|
self.re_pwd = re.compile(zs)
|
2060
2065
|
|
2066
|
+
# to ensure it propagates into tcpsrv with mp on
|
2067
|
+
if self.args.mime:
|
2068
|
+
for zs in self.args.mime:
|
2069
|
+
ext, mime = zs.split("=", 1)
|
2070
|
+
MIMES[ext] = mime
|
2071
|
+
EXTS.update({v: k for k, v in MIMES.items()})
|
2072
|
+
|
2061
2073
|
def setup_pwhash(self, acct ) :
|
2062
2074
|
self.ah = PWHash(self.args)
|
2063
2075
|
if not self.ah.on:
|
copyparty/broker_mp.py
CHANGED
@@ -53,11 +53,8 @@ class BrokerMp(object):
|
|
53
53
|
def shutdown(self) :
|
54
54
|
self.log("broker", "shutting down")
|
55
55
|
for n, proc in enumerate(self.procs):
|
56
|
-
|
57
|
-
|
58
|
-
name="mp-shutdown-{}-{}".format(n, len(self.procs)),
|
59
|
-
)
|
60
|
-
thr.start()
|
56
|
+
name = "mp-shut-%d-%d" % (n, len(self.procs))
|
57
|
+
Daemon(proc.q_pend.put, name, ((0, "shutdown", []),))
|
61
58
|
|
62
59
|
with self.mutex:
|
63
60
|
procs = self.procs
|
copyparty/cfg.py
CHANGED
@@ -190,6 +190,7 @@ flagcats = {
|
|
190
190
|
"dvthumb": "disables video thumbnails",
|
191
191
|
"dathumb": "disables audio thumbnails (spectrograms)",
|
192
192
|
"dithumb": "disables image thumbnails",
|
193
|
+
"pngquant": "compress audio waveforms 33% better",
|
193
194
|
"thsize": "thumbnail res; WxH",
|
194
195
|
"crop": "center-cropping (y/n/fy/fn)",
|
195
196
|
"th3x": "3x resolution (y/n/fy/fn)",
|
copyparty/httpcli.py
CHANGED
@@ -755,7 +755,6 @@ class HttpCli(object):
|
|
755
755
|
is_jinja = True
|
756
756
|
|
757
757
|
if is_jinja:
|
758
|
-
print("applying jinja")
|
759
758
|
with self.conn.hsrv.mutex:
|
760
759
|
if html not in self.conn.hsrv.j2:
|
761
760
|
j2env = jinja2.Environment()
|
@@ -3190,7 +3189,14 @@ class HttpCli(object):
|
|
3190
3189
|
|
3191
3190
|
sendfun = sendfile_kern if use_sendfile else sendfile_py
|
3192
3191
|
remains = sendfun(
|
3193
|
-
self.log,
|
3192
|
+
self.log,
|
3193
|
+
lower,
|
3194
|
+
upper,
|
3195
|
+
f,
|
3196
|
+
self.s,
|
3197
|
+
self.args.s_wr_sz,
|
3198
|
+
self.args.s_wr_slp,
|
3199
|
+
not self.args.no_poll,
|
3194
3200
|
)
|
3195
3201
|
|
3196
3202
|
if remains > 0:
|
@@ -3409,7 +3415,7 @@ class HttpCli(object):
|
|
3409
3415
|
# for f in fgen: print(repr({k: f[k] for k in ["vp", "ap"]}))
|
3410
3416
|
cfmt = ""
|
3411
3417
|
if self.thumbcli and not self.args.no_bacode:
|
3412
|
-
for zs in ("opus", "mp3", "w", "j"):
|
3418
|
+
for zs in ("opus", "mp3", "w", "j", "p"):
|
3413
3419
|
if zs in self.ouparam or uarg == zs:
|
3414
3420
|
cfmt = zs
|
3415
3421
|
|
@@ -3419,7 +3425,7 @@ class HttpCli(object):
|
|
3419
3425
|
|
3420
3426
|
bgen = packer(
|
3421
3427
|
self.log,
|
3422
|
-
self.
|
3428
|
+
self.asrv,
|
3423
3429
|
fgen,
|
3424
3430
|
utf8="utf" in uarg,
|
3425
3431
|
pre_crc="crc" in uarg,
|
@@ -4800,7 +4806,7 @@ class HttpCli(object):
|
|
4800
4806
|
query = "th=%s&cache" % (fmt,)
|
4801
4807
|
query = ub64enc(query.encode("utf-8")).decode("utf-8")
|
4802
4808
|
# discord looks at file extension, not content-type...
|
4803
|
-
query += "/
|
4809
|
+
query += "/th.jpg" if "j" in fmt else "/th.webp"
|
4804
4810
|
j2a["og_thumb"] = "%s/.uqe/%s" % (th_base, query)
|
4805
4811
|
|
4806
4812
|
j2a["og_fn"] = og_fn
|
@@ -4808,9 +4814,7 @@ class HttpCli(object):
|
|
4808
4814
|
if og_fn:
|
4809
4815
|
og_fn_q = quotep(og_fn)
|
4810
4816
|
query = ub64enc(b"raw").decode("utf-8")
|
4811
|
-
|
4812
|
-
query += "/a.%s" % (og_fn.split(".")[-1])
|
4813
|
-
|
4817
|
+
query += "/%s" % (og_fn_q,)
|
4814
4818
|
j2a["og_url"] = ujoin(url_base, og_fn_q)
|
4815
4819
|
j2a["og_raw"] = j2a["og_url"] + "/.uqe/" + query
|
4816
4820
|
else:
|
copyparty/httpsrv.py
CHANGED
@@ -262,10 +262,7 @@ class HttpSrv(object):
|
|
262
262
|
msg = "subscribed @ {}:{} f{} p{}".format(hip, port, fno, os.getpid())
|
263
263
|
self.log(self.name, msg)
|
264
264
|
|
265
|
-
|
266
|
-
self.broker.say("cb_httpsrv_up")
|
267
|
-
|
268
|
-
threading.Thread(target=fun, name="sig-hsrv-up1").start()
|
265
|
+
Daemon(self.broker.say, "sig-hsrv-up1", ("cb_httpsrv_up",))
|
269
266
|
|
270
267
|
while not self.stopping:
|
271
268
|
if self.args.log_conn:
|
copyparty/mdns.py
CHANGED
@@ -288,6 +288,22 @@ class MDNS(MCast):
|
|
288
288
|
def run2(self) :
|
289
289
|
last_hop = time.time()
|
290
290
|
ihop = self.args.mc_hop
|
291
|
+
|
292
|
+
try:
|
293
|
+
if self.args.no_poll:
|
294
|
+
raise Exception()
|
295
|
+
fd2sck = {}
|
296
|
+
srvpoll = select.poll()
|
297
|
+
for sck in self.srv:
|
298
|
+
fd = sck.fileno()
|
299
|
+
fd2sck[fd] = sck
|
300
|
+
srvpoll.register(fd, select.POLLIN)
|
301
|
+
except Exception as ex:
|
302
|
+
srvpoll = None
|
303
|
+
if not self.args.no_poll:
|
304
|
+
t = "WARNING: failed to poll(), will use select() instead: %r"
|
305
|
+
self.log(t % (ex,), 3)
|
306
|
+
|
291
307
|
while self.running:
|
292
308
|
timeout = (
|
293
309
|
0.02 + random.random() * 0.07
|
@@ -296,8 +312,13 @@ class MDNS(MCast):
|
|
296
312
|
if self.unsolicited
|
297
313
|
else (last_hop + ihop if ihop else 180)
|
298
314
|
)
|
299
|
-
|
300
|
-
|
315
|
+
if srvpoll:
|
316
|
+
pr = srvpoll.poll(timeout * 1000)
|
317
|
+
rx = [fd2sck[x[0]] for x in pr if x[1] & select.POLLIN]
|
318
|
+
else:
|
319
|
+
rdy = select.select(self.srv, [], [], timeout)
|
320
|
+
rx = rdy[0] # type: ignore
|
321
|
+
|
301
322
|
self.rx4.cln()
|
302
323
|
self.rx6.cln()
|
303
324
|
buf = b""
|
copyparty/mtag.py
CHANGED
@@ -7,12 +7,15 @@ import os
|
|
7
7
|
import shutil
|
8
8
|
import subprocess as sp
|
9
9
|
import sys
|
10
|
+
import tempfile
|
10
11
|
|
11
12
|
from .__init__ import ANYWIN, EXE, PY2, WINDOWS, E, unicode
|
13
|
+
from .authsrv import VFS
|
12
14
|
from .bos import bos
|
13
15
|
from .util import (
|
14
16
|
FFMPEG_URL,
|
15
17
|
REKOBO_LKEY,
|
18
|
+
VF_CAREFUL,
|
16
19
|
fsenc,
|
17
20
|
min_ex,
|
18
21
|
pybin,
|
@@ -20,6 +23,7 @@ from .util import (
|
|
20
23
|
runcmd,
|
21
24
|
sfsenc,
|
22
25
|
uncyg,
|
26
|
+
wunlink,
|
23
27
|
)
|
24
28
|
|
25
29
|
def have_ff(scmd ) :
|
@@ -101,6 +105,53 @@ class MParser(object):
|
|
101
105
|
raise Exception()
|
102
106
|
|
103
107
|
|
108
|
+
def au_unpk(
|
109
|
+
log , fmt_map , abspath , vn = None
|
110
|
+
) :
|
111
|
+
ret = ""
|
112
|
+
try:
|
113
|
+
ext = abspath.split(".")[-1].lower()
|
114
|
+
au, pk = fmt_map[ext].split(".")
|
115
|
+
|
116
|
+
fd, ret = tempfile.mkstemp("." + au)
|
117
|
+
|
118
|
+
if pk == "gz":
|
119
|
+
import gzip
|
120
|
+
|
121
|
+
fi = gzip.GzipFile(abspath, mode="rb")
|
122
|
+
|
123
|
+
elif pk == "xz":
|
124
|
+
import lzma
|
125
|
+
|
126
|
+
fi = lzma.open(abspath, "rb")
|
127
|
+
|
128
|
+
elif pk == "zip":
|
129
|
+
import zipfile
|
130
|
+
|
131
|
+
zf = zipfile.ZipFile(abspath, "r")
|
132
|
+
zil = zf.infolist()
|
133
|
+
zil = [x for x in zil if x.filename.lower().split(".")[-1] == au]
|
134
|
+
fi = zf.open(zil[0])
|
135
|
+
|
136
|
+
with os.fdopen(fd, "wb") as fo:
|
137
|
+
while True:
|
138
|
+
buf = fi.read(32768)
|
139
|
+
if not buf:
|
140
|
+
break
|
141
|
+
|
142
|
+
fo.write(buf)
|
143
|
+
|
144
|
+
return ret
|
145
|
+
|
146
|
+
except Exception as ex:
|
147
|
+
if ret:
|
148
|
+
t = "failed to decompress audio file [%s]: %r"
|
149
|
+
log(t % (abspath, ex))
|
150
|
+
wunlink(log, ret, vn.flags if vn else VF_CAREFUL)
|
151
|
+
|
152
|
+
return abspath
|
153
|
+
|
154
|
+
|
104
155
|
def ffprobe(
|
105
156
|
abspath , timeout = 60
|
106
157
|
) :
|
@@ -275,7 +326,7 @@ class MTag(object):
|
|
275
326
|
or_ffprobe = " or FFprobe"
|
276
327
|
|
277
328
|
if self.backend == "mutagen":
|
278
|
-
self.
|
329
|
+
self._get = self.get_mutagen
|
279
330
|
try:
|
280
331
|
from mutagen import version # noqa: F401
|
281
332
|
except:
|
@@ -284,7 +335,7 @@ class MTag(object):
|
|
284
335
|
|
285
336
|
if self.backend == "ffprobe":
|
286
337
|
self.usable = self.can_ffprobe
|
287
|
-
self.
|
338
|
+
self._get = self.get_ffprobe
|
288
339
|
self.prefer_mt = True
|
289
340
|
|
290
341
|
if not HAVE_FFPROBE:
|
@@ -454,6 +505,17 @@ class MTag(object):
|
|
454
505
|
|
455
506
|
return r1
|
456
507
|
|
508
|
+
def get(self, abspath ) :
|
509
|
+
ext = abspath.split(".")[-1].lower()
|
510
|
+
if ext not in self.args.au_unpk:
|
511
|
+
return self._get(abspath)
|
512
|
+
|
513
|
+
ap = au_unpk(self.log, self.args.au_unpk, abspath)
|
514
|
+
ret = self._get(ap)
|
515
|
+
if ap != abspath:
|
516
|
+
wunlink(self.log, ap, VF_CAREFUL)
|
517
|
+
return ret
|
518
|
+
|
457
519
|
def get_mutagen(self, abspath ) :
|
458
520
|
ret = {}
|
459
521
|
|
@@ -547,10 +609,16 @@ class MTag(object):
|
|
547
609
|
except:
|
548
610
|
raise # might be expected outside cpython
|
549
611
|
|
612
|
+
ext = abspath.split(".")[-1].lower()
|
613
|
+
if ext in self.args.au_unpk:
|
614
|
+
ap = au_unpk(self.log, self.args.au_unpk, abspath)
|
615
|
+
else:
|
616
|
+
ap = abspath
|
617
|
+
|
550
618
|
ret = {}
|
551
619
|
for tagname, parser in sorted(parsers.items(), key=lambda x: (x[1].pri, x[0])):
|
552
620
|
try:
|
553
|
-
cmd = [parser.bin,
|
621
|
+
cmd = [parser.bin, ap]
|
554
622
|
if parser.bin.endswith(".py"):
|
555
623
|
cmd = [pybin] + cmd
|
556
624
|
|
@@ -587,4 +655,7 @@ class MTag(object):
|
|
587
655
|
t = "mtag error: tagname {}, parser {}, file {} => {}"
|
588
656
|
self.log(t.format(tagname, parser.bin, abspath, min_ex()))
|
589
657
|
|
658
|
+
if ap != abspath:
|
659
|
+
wunlink(self.log, ap, VF_CAREFUL)
|
660
|
+
|
590
661
|
return ret
|
copyparty/smbd.py
CHANGED
copyparty/ssdp.py
CHANGED
@@ -137,9 +137,29 @@ class SSDPd(MCast):
|
|
137
137
|
self.log("stopped", 2)
|
138
138
|
|
139
139
|
def run2(self) :
|
140
|
+
try:
|
141
|
+
if self.args.no_poll:
|
142
|
+
raise Exception()
|
143
|
+
fd2sck = {}
|
144
|
+
srvpoll = select.poll()
|
145
|
+
for sck in self.srv:
|
146
|
+
fd = sck.fileno()
|
147
|
+
fd2sck[fd] = sck
|
148
|
+
srvpoll.register(fd, select.POLLIN)
|
149
|
+
except Exception as ex:
|
150
|
+
srvpoll = None
|
151
|
+
if not self.args.no_poll:
|
152
|
+
t = "WARNING: failed to poll(), will use select() instead: %r"
|
153
|
+
self.log(t % (ex,), 3)
|
154
|
+
|
140
155
|
while self.running:
|
141
|
-
|
142
|
-
|
156
|
+
if srvpoll:
|
157
|
+
pr = srvpoll.poll((self.args.z_chk or 180) * 1000)
|
158
|
+
rx = [fd2sck[x[0]] for x in pr if x[1] & select.POLLIN]
|
159
|
+
else:
|
160
|
+
rdy = select.select(self.srv, [], [], self.args.z_chk or 180)
|
161
|
+
rx = rdy[0] # type: ignore
|
162
|
+
|
143
163
|
self.rxc.cln()
|
144
164
|
buf = b""
|
145
165
|
addr = ("0", 0)
|
copyparty/star.py
CHANGED
@@ -1,13 +1,13 @@
|
|
1
1
|
# coding: utf-8
|
2
2
|
from __future__ import print_function, unicode_literals
|
3
3
|
|
4
|
-
import argparse
|
5
4
|
import re
|
6
5
|
import stat
|
7
6
|
import tarfile
|
8
7
|
|
9
8
|
from queue import Queue
|
10
9
|
|
10
|
+
from .authsrv import AuthSrv
|
11
11
|
from .bos import bos
|
12
12
|
from .sutil import StreamArc, errdesc
|
13
13
|
from .util import Daemon, fsenc, min_ex
|
@@ -39,12 +39,12 @@ class StreamTar(StreamArc):
|
|
39
39
|
def __init__(
|
40
40
|
self,
|
41
41
|
log ,
|
42
|
-
|
42
|
+
asrv ,
|
43
43
|
fgen ,
|
44
44
|
cmp = "",
|
45
45
|
**kwargs
|
46
46
|
):
|
47
|
-
super(StreamTar, self).__init__(log,
|
47
|
+
super(StreamTar, self).__init__(log, asrv, fgen)
|
48
48
|
|
49
49
|
self.ci = 0
|
50
50
|
self.co = 0
|
@@ -142,7 +142,7 @@ class StreamTar(StreamArc):
|
|
142
142
|
errors.append((f["vp"], ex))
|
143
143
|
|
144
144
|
if errors:
|
145
|
-
self.errf, txt = errdesc(errors)
|
145
|
+
self.errf, txt = errdesc(self.asrv.vfs, errors)
|
146
146
|
self.log("\n".join(([repr(self.errf)] + txt[1:])))
|
147
147
|
self.ser(self.errf)
|
148
148
|
|
copyparty/sutil.py
CHANGED
@@ -1,26 +1,27 @@
|
|
1
1
|
# coding: utf-8
|
2
2
|
from __future__ import print_function, unicode_literals
|
3
3
|
|
4
|
-
import argparse
|
5
4
|
import os
|
6
5
|
import tempfile
|
7
6
|
from datetime import datetime
|
8
7
|
|
9
8
|
from .__init__ import CORES
|
9
|
+
from .authsrv import AuthSrv, VFS
|
10
10
|
from .bos import bos
|
11
11
|
from .th_cli import ThumbCli
|
12
|
-
from .util import UTC, vjoin
|
12
|
+
from .util import UTC, vjoin, vol_san
|
13
13
|
|
14
14
|
class StreamArc(object):
|
15
15
|
def __init__(
|
16
16
|
self,
|
17
17
|
log ,
|
18
|
-
|
18
|
+
asrv ,
|
19
19
|
fgen ,
|
20
20
|
**kwargs
|
21
21
|
):
|
22
22
|
self.log = log
|
23
|
-
self.
|
23
|
+
self.asrv = asrv
|
24
|
+
self.args = asrv.args
|
24
25
|
self.fgen = fgen
|
25
26
|
self.stopped = False
|
26
27
|
|
@@ -97,15 +98,20 @@ def enthumb(
|
|
97
98
|
return f
|
98
99
|
|
99
100
|
|
100
|
-
def errdesc(
|
101
|
+
def errdesc(
|
102
|
+
vfs , errors
|
103
|
+
) :
|
101
104
|
report = ["copyparty failed to add the following files to the archive:", ""]
|
102
105
|
|
103
106
|
for fn, err in errors:
|
104
107
|
report.extend([" file: {}".format(fn), "error: {}".format(err), ""])
|
105
108
|
|
109
|
+
btxt = "\r\n".join(report).encode("utf-8", "replace")
|
110
|
+
btxt = vol_san(list(vfs.all_vols.values()), btxt)
|
111
|
+
|
106
112
|
with tempfile.NamedTemporaryFile(prefix="copyparty-", delete=False) as tf:
|
107
113
|
tf_path = tf.name
|
108
|
-
tf.write(
|
114
|
+
tf.write(btxt)
|
109
115
|
|
110
116
|
dt = datetime.now(UTC).strftime("%Y-%m%d-%H%M%S")
|
111
117
|
|
copyparty/svchub.py
CHANGED
@@ -234,6 +234,10 @@ class SvcHub(object):
|
|
234
234
|
if not HAVE_FFMPEG or not HAVE_FFPROBE:
|
235
235
|
decs.pop("ff", None)
|
236
236
|
|
237
|
+
# compressed formats; "s3z=s3m.zip, s3gz=s3m.gz, ..."
|
238
|
+
zlss = [x.strip().lower().split("=", 1) for x in args.au_unpk.split(",")]
|
239
|
+
args.au_unpk = {x[0]: x[1] for x in zlss}
|
240
|
+
|
237
241
|
self.args.th_dec = list(decs.keys())
|
238
242
|
self.thumbsrv = None
|
239
243
|
want_ff = False
|
@@ -274,6 +278,8 @@ class SvcHub(object):
|
|
274
278
|
if not re.match("^(0|[qv][0-9]|[0-9]{2,3}k)$", args.q_mp3.lower()):
|
275
279
|
t = "invalid mp3 transcoding quality [%s] specified; only supports [0] to disable, a CBR value such as [192k], or a CQ/CRF value such as [v2]"
|
276
280
|
raise Exception(t % (args.q_mp3,))
|
281
|
+
else:
|
282
|
+
args.au_unpk = {}
|
277
283
|
|
278
284
|
args.th_poke = min(args.th_poke, args.th_maxage, args.ac_maxage)
|
279
285
|
|
@@ -287,13 +293,14 @@ class SvcHub(object):
|
|
287
293
|
from .ftpd import Ftpd
|
288
294
|
|
289
295
|
self.ftpd = None
|
290
|
-
Daemon(self.start_ftpd, "start_ftpd")
|
291
296
|
zms += "f" if args.ftp else "F"
|
292
297
|
|
293
298
|
if args.tftp:
|
294
299
|
from .tftpd import Tftpd
|
295
300
|
|
296
301
|
self.tftpd = None
|
302
|
+
|
303
|
+
if args.ftp or args.ftps or args.tftp:
|
297
304
|
Daemon(self.start_ftpd, "start_tftpd")
|
298
305
|
|
299
306
|
if args.smb:
|
@@ -382,7 +389,7 @@ class SvcHub(object):
|
|
382
389
|
self.sigterm()
|
383
390
|
|
384
391
|
def sigterm(self) :
|
385
|
-
|
392
|
+
self.signal_handler(signal.SIGTERM, None)
|
386
393
|
|
387
394
|
def cb_httpsrv_up(self) :
|
388
395
|
self.httpsrv_up += 1
|