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 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
- pass
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 + "\033[0m"
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=float, default=0, help="ensure \033[33mGiB\033[0m free disk space by rejecting upload requests")
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
@@ -1,8 +1,8 @@
1
1
  # coding: utf-8
2
2
 
3
- VERSION = (1, 13, 1)
3
+ VERSION = (1, 13, 3)
4
4
  CODENAME = "race the beam"
5
- BUILD_DT = (2024, 5, 6)
5
+ BUILD_DT = (2024, 6, 1)
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
@@ -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
- "{}g".format(self.args.df) if self.args.df else ""
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
- thr = threading.Thread(
57
- target=proc.q_pend.put((0, "shutdown", [])),
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, lower, upper, f, self.s, self.args.s_wr_sz, self.args.s_wr_slp
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.args,
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 += "/a.jpg" if "j" in fmt else "/a.webp"
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
- if "." in og_fn:
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
- def fun() :
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
- rdy = select.select(self.srv, [], [], timeout)
300
- rx = rdy[0] # type: ignore
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.get = self.get_mutagen
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.get = self.get_ffprobe
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, abspath]
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
@@ -124,7 +124,7 @@ class SMB(object):
124
124
  self.log("smb", msg, c)
125
125
 
126
126
  def start(self) :
127
- Daemon(self.srv.start)
127
+ Daemon(self.srv.start, "smbd")
128
128
 
129
129
  def _auth_cb(self, *a, **ka):
130
130
  debug("auth-result: %s %s", a, ka)
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
- rdy = select.select(self.srv, [], [], self.args.z_chk or 180)
142
- rx = rdy[0] # type: ignore
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
- args ,
42
+ asrv ,
43
43
  fgen ,
44
44
  cmp = "",
45
45
  **kwargs
46
46
  ):
47
- super(StreamTar, self).__init__(log, args, fgen)
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
- args ,
18
+ asrv ,
19
19
  fgen ,
20
20
  **kwargs
21
21
  ):
22
22
  self.log = log
23
- self.args = args
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(errors ) :
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("\r\n".join(report).encode("utf-8", "replace"))
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
- os.kill(os.getpid(), signal.SIGTERM)
392
+ self.signal_handler(signal.SIGTERM, None)
386
393
 
387
394
  def cb_httpsrv_up(self) :
388
395
  self.httpsrv_up += 1