copyparty 1.13.0__py3-none-any.whl → 1.13.2__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/httpcli.py CHANGED
@@ -84,6 +84,9 @@ from .util import (
84
84
  sanitize_vpath,
85
85
  sendfile_kern,
86
86
  sendfile_py,
87
+ ub64dec,
88
+ ub64enc,
89
+ ujoin,
87
90
  undot,
88
91
  unescape_cookie,
89
92
  unquotep,
@@ -213,6 +216,13 @@ class HttpCli(object):
213
216
  ka["favico"] = self.args.favico
214
217
  ka["s_name"] = self.args.bname
215
218
  ka["s_doctitle"] = self.args.doctitle
219
+ ka["tcolor"] = self.vn.flags["tcolor"]
220
+
221
+ zso = self.vn.flags.get("html_head")
222
+ if zso:
223
+ ka["this"] = self
224
+ self._build_html_head(zso, ka)
225
+
216
226
  ka["html_head"] = self.html_head
217
227
  return tpl.render(**ka) # type: ignore
218
228
 
@@ -360,6 +370,21 @@ class HttpCli(object):
360
370
  if "&" in self.req and "?" not in self.req:
361
371
  self.hint = "did you mean '?' instead of '&'"
362
372
 
373
+ if self.args.uqe and "/.uqe/" in self.req:
374
+ try:
375
+ vpath, query = self.req.split("?")[0].split("/.uqe/")
376
+ query = query.split("/")[0] # discard trailing junk
377
+ # (usually a "filename" to trick discord into behaving)
378
+ query = ub64dec(query.encode("utf-8")).decode("utf-8", "replace")
379
+ if query.startswith("/"):
380
+ self.req = "%s/?%s" % (vpath, query[1:])
381
+ else:
382
+ self.req = "%s?%s" % (vpath, query)
383
+ except Exception as ex:
384
+ t = "bad uqe in request [%s]: %r" % (self.req, ex)
385
+ self.loud_reply(t, status=400)
386
+ return False
387
+
363
388
  # split req into vpath + uparam
364
389
  uparam = {}
365
390
  if "?" not in self.req:
@@ -426,7 +451,8 @@ class HttpCli(object):
426
451
  cookie_pw = ""
427
452
 
428
453
  if len(uparam) > 10 or len(cookies) > 50:
429
- raise Pebkac(400, "u wot m8")
454
+ self.loud_reply("u wot m8", status=400)
455
+ return False
430
456
 
431
457
  self.uparam = uparam
432
458
  self.cookies = cookies
@@ -714,6 +740,31 @@ class HttpCli(object):
714
740
  or ("; Trident/" in self.ua and not k304)
715
741
  )
716
742
 
743
+ def _build_html_head(self, maybe_html , kv ) :
744
+ html = str(maybe_html)
745
+ is_jinja = html[:2] in "%@%"
746
+ if is_jinja:
747
+ html = html.replace("%", "", 1)
748
+
749
+ if html.startswith("@"):
750
+ with open(html[1:], "rb") as f:
751
+ html = f.read().decode("utf-8")
752
+
753
+ if html.startswith("%"):
754
+ html = html[1:]
755
+ is_jinja = True
756
+
757
+ if is_jinja:
758
+ print("applying jinja")
759
+ with self.conn.hsrv.mutex:
760
+ if html not in self.conn.hsrv.j2:
761
+ j2env = jinja2.Environment()
762
+ tpl = j2env.from_string(html)
763
+ self.conn.hsrv.j2[html] = tpl
764
+ html = self.conn.hsrv.j2[html].render(**kv)
765
+
766
+ self.html_head += html + "\n"
767
+
717
768
  def send_headers(
718
769
  self,
719
770
  length ,
@@ -2247,6 +2298,10 @@ class HttpCli(object):
2247
2298
  def handle_login(self) :
2248
2299
  assert self.parser
2249
2300
  pwd = self.parser.require("cppwd", 64)
2301
+ try:
2302
+ uhash = self.parser.require("uhash", 256)
2303
+ except:
2304
+ uhash = ""
2250
2305
  self.parser.drop()
2251
2306
 
2252
2307
  self.out_headerlist = [
@@ -2259,6 +2314,11 @@ class HttpCli(object):
2259
2314
 
2260
2315
  dst += self.ourlq()
2261
2316
 
2317
+ uhash = uhash.lstrip("#")
2318
+ if uhash not in ("", "-"):
2319
+ dst += "&" if "?" in dst else "?"
2320
+ dst += "_=1#" + html_escape(uhash, True, True)
2321
+
2262
2322
  msg = self.get_pwd_cookie(pwd)
2263
2323
  html = self.j2s("msg", h1=msg, h2='<a href="' + dst + '">ack</a>', redir=dst)
2264
2324
  self.reply(html.encode("utf-8"))
@@ -3184,7 +3244,7 @@ class HttpCli(object):
3184
3244
  data_end = file_size
3185
3245
  break
3186
3246
 
3187
- if num_need != len(job["need"]):
3247
+ if num_need != len(job["need"]) and data_end - lower < 8 * M:
3188
3248
  num_need = len(job["need"])
3189
3249
  data_end = 0
3190
3250
  for cid in job["hash"]:
@@ -3349,7 +3409,7 @@ class HttpCli(object):
3349
3409
  # for f in fgen: print(repr({k: f[k] for k in ["vp", "ap"]}))
3350
3410
  cfmt = ""
3351
3411
  if self.thumbcli and not self.args.no_bacode:
3352
- for zs in ("opus", "mp3", "w", "j"):
3412
+ for zs in ("opus", "mp3", "w", "j", "p"):
3353
3413
  if zs in self.ouparam or uarg == zs:
3354
3414
  cfmt = zs
3355
3415
 
@@ -3480,7 +3540,6 @@ class HttpCli(object):
3480
3540
  targs = {
3481
3541
  "r": self.args.SR if self.is_vproxied else "",
3482
3542
  "ts": self.conn.hsrv.cachebuster(),
3483
- "html_head": self.html_head,
3484
3543
  "edit": "edit" in self.uparam,
3485
3544
  "title": html_escape(self.vpath, crlf=True),
3486
3545
  "lastmod": int(ts_md * 1000),
@@ -3491,6 +3550,13 @@ class HttpCli(object):
3491
3550
  "md": boundary,
3492
3551
  "arg_base": arg_base,
3493
3552
  }
3553
+
3554
+ zfv = self.vn.flags.get("html_head")
3555
+ if zfv:
3556
+ targs["this"] = self
3557
+ self._build_html_head(zfv, targs)
3558
+
3559
+ targs["html_head"] = self.html_head
3494
3560
  zs = template.render(**targs).encode("utf-8", "replace")
3495
3561
  html = zs.split(boundary.encode("utf-8"))
3496
3562
  if len(html) != 2:
@@ -3606,8 +3672,6 @@ class HttpCli(object):
3606
3672
  self.reply(zb, mime="text/plain; charset=utf-8")
3607
3673
  return True
3608
3674
 
3609
- self.html_head += self.vn.flags.get("html_head", "")
3610
-
3611
3675
  html = self.j2s(
3612
3676
  "splash",
3613
3677
  this=self,
@@ -3652,7 +3716,7 @@ class HttpCli(object):
3652
3716
  return True
3653
3717
 
3654
3718
  def set_cfg_reset(self) :
3655
- for k in ("k304", "js", "idxh", "cppwd", "cppws"):
3719
+ for k in ("k304", "js", "idxh", "dots", "cppwd", "cppws"):
3656
3720
  cookie = gencookie(k, "x", self.args.R, False)
3657
3721
  self.out_headerlist.append(("Set-Cookie", cookie))
3658
3722
 
@@ -3863,7 +3927,7 @@ class HttpCli(object):
3863
3927
  allvols = [x for x in allvols if "e2d" in x.flags]
3864
3928
 
3865
3929
  for vol in allvols:
3866
- cur = idx.get_cur(vol.realpath)
3930
+ cur = idx.get_cur(vol)
3867
3931
  if not cur:
3868
3932
  continue
3869
3933
 
@@ -4078,7 +4142,17 @@ class HttpCli(object):
4078
4142
  e2d = "e2d" in vn.flags
4079
4143
  e2t = "e2t" in vn.flags
4080
4144
 
4081
- self.html_head += vn.flags.get("html_head", "")
4145
+ add_og = "og" in vn.flags
4146
+ if add_og:
4147
+ if "th" in self.uparam or "raw" in self.uparam:
4148
+ og_ua = add_og = False
4149
+ elif self.args.og_ua:
4150
+ og_ua = add_og = self.args.og_ua.search(self.ua)
4151
+ else:
4152
+ og_ua = False
4153
+ add_og = True
4154
+ og_fn = ""
4155
+
4082
4156
  if "b" in self.uparam:
4083
4157
  self.out_headers["X-Robots-Tag"] = "noindex, nofollow"
4084
4158
 
@@ -4086,13 +4160,15 @@ class HttpCli(object):
4086
4160
  is_dk = False
4087
4161
  fk_pass = False
4088
4162
  icur = None
4089
- if is_dir and (e2t or e2d):
4163
+ if (e2t or e2d) and (is_dir or add_og):
4090
4164
  idx = self.conn.get_u2idx()
4091
4165
  if idx and hasattr(idx, "p_end"):
4092
- icur = idx.get_cur(dbv.realpath)
4166
+ icur = idx.get_cur(dbv)
4093
4167
 
4094
4168
  th_fmt = self.uparam.get("th")
4095
- if self.can_read or (self.can_get and vn.flags.get("dk")):
4169
+ if self.can_read or (
4170
+ self.can_get and (vn.flags.get("dk") or "fk" not in vn.flags)
4171
+ ):
4096
4172
  if th_fmt is not None:
4097
4173
  nothumb = "dthumb" in dbv.flags
4098
4174
  if is_dir:
@@ -4139,7 +4215,7 @@ class HttpCli(object):
4139
4215
  elif self.can_write and th_fmt is not None:
4140
4216
  return self.tx_svg("upload\nonly")
4141
4217
 
4142
- elif self.can_get and self.avn:
4218
+ if not self.can_read and self.can_get and self.avn:
4143
4219
  axs = self.avn.axs
4144
4220
  if self.uname not in axs.uhtml:
4145
4221
  pass
@@ -4185,6 +4261,17 @@ class HttpCli(object):
4185
4261
  self.log(t % (correct, got, self.req, abspath), 6)
4186
4262
  return self.tx_404()
4187
4263
 
4264
+ if add_og:
4265
+ if og_ua or self.host not in self.headers.get("referer", ""):
4266
+ self.vpath, og_fn = vsplit(self.vpath)
4267
+ vpath = self.vpath
4268
+ vn, rem = self.asrv.vfs.get(self.vpath, self.uname, False, False)
4269
+ abspath = vn.dcanonical(rem)
4270
+ dbv, vrem = vn.get_dbv(rem)
4271
+ is_dir = stat.S_ISDIR(st.st_mode)
4272
+ is_dk = True
4273
+ vpnodes.pop()
4274
+
4188
4275
  if (
4189
4276
  (abspath.endswith(".md") or self.can_delete)
4190
4277
  and "nohtml" not in vn.flags
@@ -4196,9 +4283,10 @@ class HttpCli(object):
4196
4283
  ):
4197
4284
  return self.tx_md(vn, abspath)
4198
4285
 
4199
- return self.tx_file(
4200
- abspath, None if st.st_size or "nopipe" in vn.flags else vn.realpath
4201
- )
4286
+ if not add_og or not og_fn:
4287
+ return self.tx_file(
4288
+ abspath, None if st.st_size or "nopipe" in vn.flags else vn.realpath
4289
+ )
4202
4290
 
4203
4291
  elif is_dir and not self.can_read:
4204
4292
  if self._use_dirkey(abspath):
@@ -4245,7 +4333,11 @@ class HttpCli(object):
4245
4333
  is_ls = "ls" in self.uparam
4246
4334
  is_js = self.args.force_js or self.cookies.get("js") == "y"
4247
4335
 
4248
- if not is_ls and (self.ua.startswith("curl/") or self.ua.startswith("fetch")):
4336
+ if (
4337
+ not is_ls
4338
+ and not add_og
4339
+ and (self.ua.startswith("curl/") or self.ua.startswith("fetch"))
4340
+ ):
4249
4341
  self.uparam["ls"] = "v"
4250
4342
  is_ls = True
4251
4343
 
@@ -4319,6 +4411,7 @@ class HttpCli(object):
4319
4411
  "dsort": vf["sort"],
4320
4412
  "dcrop": vf["crop"],
4321
4413
  "dth3x": vf["th3x"],
4414
+ "dvol": self.args.au_vol,
4322
4415
  "themes": self.args.themes,
4323
4416
  "turbolvl": self.args.turbo,
4324
4417
  "u2j": self.args.u2j,
@@ -4370,7 +4463,7 @@ class HttpCli(object):
4370
4463
 
4371
4464
  for k in ["zip", "tar"]:
4372
4465
  v = self.uparam.get(k)
4373
- if v is not None:
4466
+ if v is not None and (not add_og or not og_fn):
4374
4467
  return self.tx_zip(k, v, self.vpath, vn, rem, [])
4375
4468
 
4376
4469
  fsroot, vfs_ls, vfs_virt = vn.ls(
@@ -4384,6 +4477,10 @@ class HttpCli(object):
4384
4477
  ls_names = [x[0] for x in vfs_ls]
4385
4478
  ls_names.extend(list(vfs_virt.keys()))
4386
4479
 
4480
+ if add_og and og_fn and not self.can_read:
4481
+ ls_names = [og_fn]
4482
+ is_js = True
4483
+
4387
4484
  # check for old versions of files,
4388
4485
  # [num-backups, most-recent, hist-path]
4389
4486
  hist = {}
@@ -4445,12 +4542,14 @@ class HttpCli(object):
4445
4542
  margin = "DIR"
4446
4543
  elif add_dk:
4447
4544
  zs = absreal(fspath)
4448
- margin = '<a href="%s?k=%s&zip" rel="nofollow">zip</a>' % (
4545
+ margin = '<a href="%s?k=%s&zip=crc" rel="nofollow">zip</a>' % (
4449
4546
  quotep(href),
4450
4547
  self.gen_fk(2, self.args.dk_salt, zs, 0, 0)[:add_dk],
4451
4548
  )
4452
4549
  else:
4453
- margin = '<a href="%s?zip" rel="nofollow">zip</a>' % (quotep(href),)
4550
+ margin = '<a href="%s?zip=crc" rel="nofollow">zip</a>' % (
4551
+ quotep(href),
4552
+ )
4454
4553
  elif fn in hist:
4455
4554
  margin = '<a href="%s.hist/%s">#%s</a>' % (
4456
4555
  base,
@@ -4594,6 +4693,9 @@ class HttpCli(object):
4594
4693
  else:
4595
4694
  taglist = list(tagset)
4596
4695
 
4696
+ if not files and not dirs and not readme and not logues[0] and not logues[1]:
4697
+ logues[1] = "this folder is empty"
4698
+
4597
4699
  if is_ls:
4598
4700
  ls_ret["dirs"] = dirs
4599
4701
  ls_ret["files"] = files
@@ -4645,6 +4747,146 @@ class HttpCli(object):
4645
4747
  if "mth" in vn.flags:
4646
4748
  j2a["def_hcols"] = list(vn.flags["mth"])
4647
4749
 
4750
+ if add_og and "raw" not in self.uparam:
4751
+ j2a["this"] = self
4752
+ cgv["og_fn"] = og_fn
4753
+ if og_fn and vn.flags.get("og_tpl"):
4754
+ tpl = vn.flags["og_tpl"]
4755
+ if "EXT" in tpl:
4756
+ zs = og_fn.split(".")[-1].lower()
4757
+ tpl2 = tpl.replace("EXT", zs)
4758
+ if os.path.exists(tpl2):
4759
+ tpl = tpl2
4760
+ with self.conn.hsrv.mutex:
4761
+ if tpl not in self.conn.hsrv.j2:
4762
+ tdir, tname = os.path.split(tpl)
4763
+ j2env = jinja2.Environment()
4764
+ j2env.loader = jinja2.FileSystemLoader(tdir)
4765
+ self.conn.hsrv.j2[tpl] = j2env.get_template(tname)
4766
+ thumb = ""
4767
+ is_pic = is_vid = is_au = False
4768
+ covernames = self.args.th_coversd
4769
+ for fn in ls_names:
4770
+ if fn.lower() in covernames:
4771
+ thumb = fn
4772
+ break
4773
+ if og_fn:
4774
+ ext = og_fn.split(".")[-1].lower()
4775
+ if ext in self.thumbcli.thumbable:
4776
+ is_pic = (
4777
+ ext in self.thumbcli.fmt_pil
4778
+ or ext in self.thumbcli.fmt_vips
4779
+ or ext in self.thumbcli.fmt_ffi
4780
+ )
4781
+ is_vid = ext in self.thumbcli.fmt_ffv
4782
+ is_au = ext in self.thumbcli.fmt_ffa
4783
+ if not thumb or not is_au:
4784
+ thumb = og_fn
4785
+ file = next((x for x in files if x["name"] == og_fn), None)
4786
+ else:
4787
+ file = None
4788
+
4789
+ url_base = "%s://%s/%s" % (
4790
+ "https" if self.is_https else "http",
4791
+ self.host,
4792
+ self.args.RS + quotep(vpath),
4793
+ )
4794
+ j2a["og_is_pic"] = is_pic
4795
+ j2a["og_is_vid"] = is_vid
4796
+ j2a["og_is_au"] = is_au
4797
+ if thumb:
4798
+ fmt = vn.flags.get("og_th", "j")
4799
+ th_base = ujoin(url_base, quotep(thumb))
4800
+ query = "th=%s&cache" % (fmt,)
4801
+ query = ub64enc(query.encode("utf-8")).decode("utf-8")
4802
+ # discord looks at file extension, not content-type...
4803
+ query += "/th.jpg" if "j" in fmt else "/th.webp"
4804
+ j2a["og_thumb"] = "%s/.uqe/%s" % (th_base, query)
4805
+
4806
+ j2a["og_fn"] = og_fn
4807
+ j2a["og_file"] = file
4808
+ if og_fn:
4809
+ og_fn_q = quotep(og_fn)
4810
+ query = ub64enc(b"raw").decode("utf-8")
4811
+ query += "/%s" % (og_fn_q,)
4812
+ j2a["og_url"] = ujoin(url_base, og_fn_q)
4813
+ j2a["og_raw"] = j2a["og_url"] + "/.uqe/" + query
4814
+ else:
4815
+ j2a["og_url"] = j2a["og_raw"] = url_base
4816
+
4817
+ if not vn.flags.get("og_no_head"):
4818
+ ogh = {"twitter:card": "summary"}
4819
+
4820
+ title = str(vn.flags.get("og_title") or "")
4821
+
4822
+ if thumb:
4823
+ ogh["og:image"] = j2a["og_thumb"]
4824
+
4825
+ zso = vn.flags.get("og_desc") or ""
4826
+ if zso != "-":
4827
+ ogh["og:description"] = str(zso)
4828
+
4829
+ zs = vn.flags.get("og_site") or self.args.name
4830
+ if zs not in ("", "-"):
4831
+ ogh["og:site_name"] = zs
4832
+
4833
+ tagmap = {}
4834
+ if is_au:
4835
+ title = str(vn.flags.get("og_title_a") or "")
4836
+ ogh["og:type"] = "music.song"
4837
+ ogh["og:audio"] = j2a["og_raw"]
4838
+ tagmap = {
4839
+ "artist": "og:music:musician",
4840
+ "album": "og:music:album",
4841
+ ".dur": "og:music:duration",
4842
+ }
4843
+ elif is_vid:
4844
+ title = str(vn.flags.get("og_title_v") or "")
4845
+ ogh["og:type"] = "video.other"
4846
+ ogh["og:video"] = j2a["og_raw"]
4847
+ tagmap = {
4848
+ "title": "og:title",
4849
+ ".dur": "og:video:duration",
4850
+ }
4851
+ elif is_pic:
4852
+ title = str(vn.flags.get("og_title_i") or "")
4853
+ ogh["twitter:card"] = "summary_large_image"
4854
+ ogh["twitter:image"] = ogh["og:image"] = j2a["og_raw"]
4855
+
4856
+ try:
4857
+ for k, v in file["tags"].items():
4858
+ zs = "{{ %s }}" % (k,)
4859
+ title = title.replace(zs, str(v))
4860
+ except:
4861
+ pass
4862
+ title = re.sub(r"\{\{ [^}]+ \}\}", "", title)
4863
+ while title.startswith(" - "):
4864
+ title = title[3:]
4865
+ while title.endswith(" - "):
4866
+ title = title[:3]
4867
+
4868
+ if vn.flags.get("og_s_title") or not title:
4869
+ title = str(vn.flags.get("og_title") or "")
4870
+
4871
+ for tag, hname in tagmap.items():
4872
+ try:
4873
+ v = file["tags"][tag]
4874
+ if not v:
4875
+ continue
4876
+ ogh[hname] = int(v) if tag == ".dur" else v
4877
+ except:
4878
+ pass
4879
+
4880
+ ogh["og:title"] = title
4881
+
4882
+ oghs = [
4883
+ '\t<meta property="%s" content="%s">'
4884
+ % (k, html_escape(str(v), True, True))
4885
+ for k, v in ogh.items()
4886
+ ]
4887
+ zs = self.html_head + "\n%s\n" % ("\n".join(oghs),)
4888
+ self.html_head = zs.replace("\n\n", "\n")
4889
+
4648
4890
  html = self.j2s(tpl, **j2a)
4649
4891
  self.reply(html.encode("utf-8", "replace"))
4650
4892
  return True
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/metrics.py CHANGED
@@ -179,7 +179,7 @@ class Metrics(object):
179
179
  tnbytes = 0
180
180
  tnfiles = 0
181
181
  for vpath, vol in allvols:
182
- cur = idx.get_cur(vol.realpath)
182
+ cur = idx.get_cur(vol)
183
183
  if not cur:
184
184
  continue
185
185
 
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,51 @@ class MParser(object):
101
105
  raise Exception()
102
106
 
103
107
 
108
+ def au_unpk(log , fmt_map , abspath , vn = None) :
109
+ ret = ""
110
+ try:
111
+ ext = abspath.split(".")[-1].lower()
112
+ au, pk = fmt_map[ext].split(".")
113
+
114
+ fd, ret = tempfile.mkstemp("." + au)
115
+
116
+ if pk == "gz":
117
+ import gzip
118
+
119
+ fi = gzip.GzipFile(abspath, mode="rb")
120
+
121
+ elif pk == "xz":
122
+ import lzma
123
+
124
+ fi = lzma.open(abspath, "rb")
125
+
126
+ elif pk == "zip":
127
+ import zipfile
128
+
129
+ zf = zipfile.ZipFile(abspath, "r")
130
+ zil = zf.infolist()
131
+ zil = [x for x in zil if x.filename.lower().split(".")[-1] == au]
132
+ fi = zf.open(zil[0])
133
+
134
+ with os.fdopen(fd, "wb") as fo:
135
+ while True:
136
+ buf = fi.read(32768)
137
+ if not buf:
138
+ break
139
+
140
+ fo.write(buf)
141
+
142
+ return ret
143
+
144
+ except Exception as ex:
145
+ if ret:
146
+ t = "failed to decompress audio file [%s]: %r"
147
+ log(t % (abspath, ex))
148
+ wunlink(log, ret, vn.flags if vn else VF_CAREFUL)
149
+
150
+ return abspath
151
+
152
+
104
153
  def ffprobe(
105
154
  abspath , timeout = 60
106
155
  ) :
@@ -275,7 +324,7 @@ class MTag(object):
275
324
  or_ffprobe = " or FFprobe"
276
325
 
277
326
  if self.backend == "mutagen":
278
- self.get = self.get_mutagen
327
+ self._get = self.get_mutagen
279
328
  try:
280
329
  from mutagen import version # noqa: F401
281
330
  except:
@@ -284,7 +333,7 @@ class MTag(object):
284
333
 
285
334
  if self.backend == "ffprobe":
286
335
  self.usable = self.can_ffprobe
287
- self.get = self.get_ffprobe
336
+ self._get = self.get_ffprobe
288
337
  self.prefer_mt = True
289
338
 
290
339
  if not HAVE_FFPROBE:
@@ -454,6 +503,17 @@ class MTag(object):
454
503
 
455
504
  return r1
456
505
 
506
+ def get(self, abspath ) :
507
+ ext = abspath.split(".")[-1].lower()
508
+ if ext not in self.args.au_unpk:
509
+ return self._get(abspath)
510
+
511
+ ap = au_unpk(self.log, self.args.au_unpk, abspath)
512
+ ret = self._get(ap)
513
+ if ap != abspath:
514
+ wunlink(self.log, ap, VF_CAREFUL)
515
+ return ret
516
+
457
517
  def get_mutagen(self, abspath ) :
458
518
  ret = {}
459
519
 
@@ -547,10 +607,16 @@ class MTag(object):
547
607
  except:
548
608
  raise # might be expected outside cpython
549
609
 
610
+ ext = abspath.split(".")[-1].lower()
611
+ if ext in self.args.au_unpk:
612
+ ap = au_unpk(self.log, self.args.au_unpk, abspath)
613
+ else:
614
+ ap = abspath
615
+
550
616
  ret = {}
551
617
  for tagname, parser in sorted(parsers.items(), key=lambda x: (x[1].pri, x[0])):
552
618
  try:
553
- cmd = [parser.bin, abspath]
619
+ cmd = [parser.bin, ap]
554
620
  if parser.bin.endswith(".py"):
555
621
  cmd = [pybin] + cmd
556
622
 
@@ -587,4 +653,7 @@ class MTag(object):
587
653
  t = "mtag error: tagname {}, parser {}, file {} => {}"
588
654
  self.log(t.format(tagname, parser.bin, abspath, min_ex()))
589
655
 
656
+ if ap != abspath:
657
+ wunlink(self.log, ap, VF_CAREFUL)
658
+
590
659
  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/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
@@ -520,7 +527,7 @@ class SvcHub(object):
520
527
  al.exp_md = odfusion(exp, al.exp_md.replace(" ", ","))
521
528
  al.exp_lg = odfusion(exp, al.exp_lg.replace(" ", ","))
522
529
 
523
- for k in ["no_hash", "no_idx"]:
530
+ for k in ["no_hash", "no_idx", "og_ua"]:
524
531
  ptn = getattr(self.args, k)
525
532
  if ptn:
526
533
  setattr(self.args, k, re.compile(ptn))
@@ -551,6 +558,10 @@ class SvcHub(object):
551
558
  except:
552
559
  raise Exception("invalid --mv-retry [%s]" % (self.args.mv_retry,))
553
560
 
561
+ al.tcolor = al.tcolor.lstrip("#")
562
+ if len(al.tcolor) == 3: # fc5 => ffcc55
563
+ al.tcolor = "".join([x * 2 for x in al.tcolor])
564
+
554
565
  return True
555
566
 
556
567
  def _ipa2re(self, txt) :