copyparty 1.13.1__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/__main__.py CHANGED
@@ -43,11 +43,13 @@ from .util import (
43
43
  DEF_MTH,
44
44
  IMPLICATIONS,
45
45
  JINJA_VER,
46
+ MIMES,
46
47
  PARTFTPY_VER,
47
48
  PY_DESC,
48
49
  PYFTPD_VER,
49
50
  SQLITE_VER,
50
51
  UNPLICATIONS,
52
+ Daemon,
51
53
  align_tab,
52
54
  ansi_re,
53
55
  dedent,
@@ -464,6 +466,16 @@ def disable_quickedit() :
464
466
  cmode(True, mode | 4)
465
467
 
466
468
 
469
+ def sfx_tpoke(top ):
470
+ files = [os.path.join(dp, p) for dp, dd, df in os.walk(top) for p in dd + df]
471
+ while True:
472
+ t = int(time.time())
473
+ for f in [top] + files:
474
+ os.utime(f, (t, t))
475
+
476
+ time.sleep(78123)
477
+
478
+
467
479
  def showlic() :
468
480
  p = os.path.join(E.mod, "res", "COPYING.txt")
469
481
  if not os.path.exists(p):
@@ -814,7 +826,7 @@ def build_flags_desc():
814
826
  v = v.replace("\n", "\n ")
815
827
  ret += "\n \033[36m{}\033[35m {}".format(k, v)
816
828
 
817
- return ret + "\033[0m"
829
+ return ret
818
830
 
819
831
 
820
832
  # fmt: off
@@ -832,6 +844,8 @@ def add_general(ap, nc, srvname):
832
844
  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
845
  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
846
  ap2.add_argument("--name", metavar="TXT", type=u, default=srvname, help="server name (displayed topleft in browser and in mDNS)")
847
+ 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]")
848
+ ap2.add_argument("--mimes", action="store_true", help="list default mimetype mapping and exit")
835
849
  ap2.add_argument("--license", action="store_true", help="show licenses and exit")
836
850
  ap2.add_argument("--version", action="store_true", help="show versions and exit")
837
851
 
@@ -1188,7 +1202,8 @@ def add_thumbnail(ap):
1188
1202
  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
1203
  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
1204
  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")
1205
+ 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")
1206
+ 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
1207
 
1193
1208
 
1194
1209
  def add_transcoding(ap):
@@ -1399,7 +1414,7 @@ def run_argparse(
1399
1414
  k2 = "help_" + k.replace("-", "_")
1400
1415
  if vars(ret)[k2]:
1401
1416
  lprint("# %s help page (%s)" % (k, h))
1402
- lprint(t + "\033[0m")
1417
+ lprint(t.rstrip() + "\033[0m")
1403
1418
  sys.exit(0)
1404
1419
 
1405
1420
  return ret
@@ -1438,9 +1453,19 @@ def main(argv = None, rsrc = None) :
1438
1453
  showlic()
1439
1454
  sys.exit(0)
1440
1455
 
1456
+ if "--mimes" in argv:
1457
+ print("\n".join("%8s %s" % (k, v) for k, v in sorted(MIMES.items())))
1458
+ sys.exit(0)
1459
+
1441
1460
  if EXE:
1442
1461
  print("pybin: {}\n".format(pybin), end="")
1443
1462
 
1463
+ for n, zs in enumerate(argv):
1464
+ if zs.startswith("--sfx-tpoke="):
1465
+ Daemon(sfx_tpoke, "sfx-tpoke", (zs.split("=", 1)[1],))
1466
+ argv.pop(n)
1467
+ break
1468
+
1444
1469
  ensure_locale()
1445
1470
 
1446
1471
  ensure_webdeps()
copyparty/__version__.py CHANGED
@@ -1,8 +1,8 @@
1
1
  # coding: utf-8
2
2
 
3
- VERSION = (1, 13, 1)
3
+ VERSION = (1, 13, 2)
4
4
  CODENAME = "race the beam"
5
- BUILD_DT = (2024, 5, 6)
5
+ BUILD_DT = (2024, 5, 10)
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,
@@ -2058,6 +2060,13 @@ class AuthSrv(object):
2058
2060
 
2059
2061
  self.re_pwd = re.compile(zs)
2060
2062
 
2063
+ # to ensure it propagates into tcpsrv with mp on
2064
+ if self.args.mime:
2065
+ for zs in self.args.mime:
2066
+ ext, mime = zs.split("=", 1)
2067
+ MIMES[ext] = mime
2068
+ EXTS.update({v: k for k, v in MIMES.items()})
2069
+
2061
2070
  def setup_pwhash(self, acct ) :
2062
2071
  self.ah = PWHash(self.args)
2063
2072
  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
@@ -3409,7 +3409,7 @@ class HttpCli(object):
3409
3409
  # for f in fgen: print(repr({k: f[k] for k in ["vp", "ap"]}))
3410
3410
  cfmt = ""
3411
3411
  if self.thumbcli and not self.args.no_bacode:
3412
- for zs in ("opus", "mp3", "w", "j"):
3412
+ for zs in ("opus", "mp3", "w", "j", "p"):
3413
3413
  if zs in self.ouparam or uarg == zs:
3414
3414
  cfmt = zs
3415
3415
 
@@ -4800,7 +4800,7 @@ class HttpCli(object):
4800
4800
  query = "th=%s&cache" % (fmt,)
4801
4801
  query = ub64enc(query.encode("utf-8")).decode("utf-8")
4802
4802
  # discord looks at file extension, not content-type...
4803
- query += "/a.jpg" if "j" in fmt else "/a.webp"
4803
+ query += "/th.jpg" if "j" in fmt else "/th.webp"
4804
4804
  j2a["og_thumb"] = "%s/.uqe/%s" % (th_base, query)
4805
4805
 
4806
4806
  j2a["og_fn"] = og_fn
@@ -4808,9 +4808,7 @@ class HttpCli(object):
4808
4808
  if og_fn:
4809
4809
  og_fn_q = quotep(og_fn)
4810
4810
  query = ub64enc(b"raw").decode("utf-8")
4811
- if "." in og_fn:
4812
- query += "/a.%s" % (og_fn.split(".")[-1])
4813
-
4811
+ query += "/%s" % (og_fn_q,)
4814
4812
  j2a["og_url"] = ujoin(url_base, og_fn_q)
4815
4813
  j2a["og_raw"] = j2a["og_url"] + "/.uqe/" + query
4816
4814
  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/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
copyparty/th_cli.py CHANGED
@@ -103,6 +103,11 @@ class ThumbCli(object):
103
103
  sfmt += "3" if "3" in fmt else ""
104
104
 
105
105
  fmt = sfmt
106
+
107
+ elif fmt[:1] == "p" and not is_au:
108
+ t = "cannot thumbnail [%s]: png only allowed for waveforms"
109
+ self.log(t % (rem), 6)
110
+ return None
106
111
 
107
112
  histpath = self.asrv.vfs.histtab.get(ptop)
108
113
  if not histpath:
copyparty/th_srv.py CHANGED
@@ -15,7 +15,7 @@ from queue import Queue
15
15
  from .__init__ import ANYWIN, TYPE_CHECKING
16
16
  from .authsrv import VFS
17
17
  from .bos import bos
18
- from .mtag import HAVE_FFMPEG, HAVE_FFPROBE, ffprobe
18
+ from .mtag import HAVE_FFMPEG, HAVE_FFPROBE, au_unpk, ffprobe
19
19
  from .util import BytesIO # type: ignore
20
20
  from .util import (
21
21
  FFMPEG_URL,
@@ -294,6 +294,12 @@ class ThumbSrv(object):
294
294
  ext = abspath.split(".")[-1].lower()
295
295
  png_ok = False
296
296
  funs = []
297
+
298
+ if ext in self.args.au_unpk:
299
+ ap_unpk = au_unpk(self.log, self.args.au_unpk, abspath, vn)
300
+ else:
301
+ ap_unpk = abspath
302
+
297
303
  if not bos.path.exists(tpath):
298
304
  for lib in self.args.th_dec:
299
305
  if lib == "pil" and ext in self.fmt_pil:
@@ -313,9 +319,6 @@ class ThumbSrv(object):
313
319
  else:
314
320
  funs.append(self.conv_spec)
315
321
 
316
- if not png_ok and tpath.endswith(".png"):
317
- raise Pebkac(400, "png only allowed for waveforms")
318
-
319
322
  tdir, tfn = os.path.split(tpath)
320
323
  ttpath = os.path.join(tdir, "w", tfn)
321
324
  try:
@@ -325,7 +328,10 @@ class ThumbSrv(object):
325
328
 
326
329
  for fun in funs:
327
330
  try:
328
- fun(abspath, ttpath, fmt, vn)
331
+ if not png_ok and tpath.endswith(".png"):
332
+ raise Exception("png only allowed for waveforms")
333
+
334
+ fun(ap_unpk, ttpath, fmt, vn)
329
335
  break
330
336
  except Exception as ex:
331
337
  msg = "{} could not create thumbnail of {}\n{}"
@@ -343,6 +349,9 @@ class ThumbSrv(object):
343
349
  except:
344
350
  pass
345
351
 
352
+ if abspath != ap_unpk:
353
+ wunlink(self.log, ap_unpk, vn.flags)
354
+
346
355
  try:
347
356
  wrename(self.log, ttpath, tpath, vn.flags)
348
357
  except:
@@ -581,6 +590,24 @@ class ThumbSrv(object):
581
590
  cmd += [fsenc(tpath)]
582
591
  self._run_ff(cmd, vn)
583
592
 
593
+ if "pngquant" in vn.flags:
594
+ wtpath = tpath + ".png"
595
+ cmd = [
596
+ b"pngquant",
597
+ b"--strip",
598
+ b"--nofs",
599
+ b"--output", fsenc(wtpath),
600
+ fsenc(tpath)
601
+ ]
602
+ ret = runcmd(cmd, timeout=vn.flags["convt"], nice=True, oom=400)[0]
603
+ if ret:
604
+ try:
605
+ wunlink(self.log, wtpath, vn.flags)
606
+ except:
607
+ pass
608
+ else:
609
+ wrename(self.log, wtpath, tpath, vn.flags)
610
+
584
611
  def conv_spec(self, abspath , tpath , fmt , vn ) :
585
612
  ret, _ = ffprobe(abspath, int(vn.flags["convt"] / 2))
586
613
  if "ac" not in ret:
copyparty/up2k.py CHANGED
@@ -10,7 +10,6 @@ import math
10
10
  import os
11
11
  import re
12
12
  import shutil
13
- import signal
14
13
  import stat
15
14
  import subprocess as sp
16
15
  import tempfile
@@ -29,6 +28,7 @@ from .fsutil import Fstab
29
28
  from .mtag import MParser, MTag
30
29
  from .util import (
31
30
  HAVE_SQLITE3,
31
+ VF_CAREFUL,
32
32
  SYMTIME,
33
33
  Daemon,
34
34
  MTHash,
@@ -88,9 +88,6 @@ CV_EXTS = set(zsg.split(","))
88
88
  HINT_HISTPATH = "you could try moving the database to another location (preferably an SSD or NVME drive) using either the --hist argument (global option for all volumes), or the hist volflag (just for this volume)"
89
89
 
90
90
 
91
- VF_CAREFUL = {"mv_re_t": 5, "rm_re_t": 5, "mv_re_r": 0.1, "rm_re_r": 0.1}
92
-
93
-
94
91
  class Dbw(object):
95
92
  def __init__(self, c , n , t ) :
96
93
  self.c = c
@@ -458,7 +455,13 @@ class Up2k(object):
458
455
  # important; not deferred by db_act
459
456
  timeout = self._check_lifetimes()
460
457
 
461
- timeout = min(timeout, now + self._check_xiu())
458
+ try:
459
+ timeout = min(timeout, now + self._check_xiu())
460
+ except Exception as ex:
461
+ if "closed cursor" in str(ex):
462
+ self.log("sched_rescan: lost db")
463
+ return
464
+ raise
462
465
 
463
466
  with self.mutex:
464
467
  for vp, vol in sorted(self.asrv.vfs.all_vols.items()):
@@ -1036,8 +1039,11 @@ class Up2k(object):
1036
1039
  return None
1037
1040
 
1038
1041
  def _verify_db_cache(self, cur , vpath ) :
1039
- # check if volume config changed since last use; drop caches if so
1040
- zsl = [vpath] + list(sorted(self.asrv.vfs.all_vols.keys()))
1042
+ # check if list of intersecting volumes changed since last use; drop caches if so
1043
+ prefix = (vpath + "/").lstrip("/")
1044
+ zsl = [x for x in self.asrv.vfs.all_vols if x.startswith(prefix)]
1045
+ zsl = [x[len(prefix) :] for x in zsl]
1046
+ zsl.sort()
1041
1047
  zb = hashlib.sha1("\n".join(zsl).encode("utf-8", "replace")).digest()
1042
1048
  vcfg = base64.urlsafe_b64encode(zb[:18]).decode("ascii")
1043
1049
 
@@ -1647,7 +1653,7 @@ class Up2k(object):
1647
1653
 
1648
1654
  if e2vp and rewark:
1649
1655
  self.hub.retcode = 1
1650
- os.kill(os.getpid(), signal.SIGTERM)
1656
+ Daemon(self.hub.sigterm)
1651
1657
  raise Exception("{} files have incorrect hashes".format(len(rewark)))
1652
1658
 
1653
1659
  if not e2vu or not rewark:
copyparty/util.py CHANGED
@@ -337,6 +337,9 @@ APPLESAN_TXT = r"/(__MACOS|Icon\r\r)|/\.(_|DS_Store|AppleDouble|LSOverride|Docum
337
337
  APPLESAN_RE = re.compile(APPLESAN_TXT)
338
338
 
339
339
 
340
+ VF_CAREFUL = {"mv_re_t": 5, "rm_re_t": 5, "mv_re_r": 0.1, "rm_re_r": 0.1}
341
+
342
+
340
343
  pybin = sys.executable or ""
341
344
  if EXE:
342
345
  pybin = ""
@@ -442,13 +445,22 @@ class Daemon(threading.Thread):
442
445
  r = True,
443
446
  ka = None,
444
447
  ) :
445
- threading.Thread.__init__(
446
- self, target=target, name=name, args=a or (), kwargs=ka
447
- )
448
+ threading.Thread.__init__(self, name=name)
449
+ self.a = a or ()
450
+ self.ka = ka or {}
451
+ self.fun = target
448
452
  self.daemon = True
449
453
  if r:
450
454
  self.start()
451
455
 
456
+ def run(self):
457
+ if not ANYWIN and not PY2:
458
+ signal.pthread_sigmask(
459
+ signal.SIG_BLOCK, [signal.SIGINT, signal.SIGTERM, signal.SIGUSR1]
460
+ )
461
+
462
+ self.fun(*self.a, **self.ka)
463
+
452
464
 
453
465
  class Netdev(object):
454
466
  def __init__(self, ip , idx , name , desc ):
@@ -843,6 +855,7 @@ class ProgressPrinter(threading.Thread):
843
855
  self.start()
844
856
 
845
857
  def run(self) :
858
+ sigblock()
846
859
  tp = 0
847
860
  msg = None
848
861
  no_stdout = self.args.q
@@ -1287,6 +1300,15 @@ def log_thrs(log , ival , name ) :
1287
1300
  log(name, "\033[0m \033[33m".join(tv), 3)
1288
1301
 
1289
1302
 
1303
+ def sigblock():
1304
+ if ANYWIN or PY2:
1305
+ return
1306
+
1307
+ signal.pthread_sigmask(
1308
+ signal.SIG_BLOCK, [signal.SIGINT, signal.SIGTERM, signal.SIGUSR1]
1309
+ )
1310
+
1311
+
1290
1312
  def vol_san(vols , txt ) :
1291
1313
  txt0 = txt
1292
1314
  for vol in vols:
@@ -1308,10 +1330,11 @@ def vol_san(vols , txt ) :
1308
1330
 
1309
1331
  def min_ex(max_lines = 8, reverse = False) :
1310
1332
  et, ev, tb = sys.exc_info()
1311
- stb = traceback.extract_tb(tb)
1333
+ stb = traceback.extract_tb(tb) if tb else traceback.extract_stack()[:-1]
1312
1334
  fmt = "%s @ %d <%s>: %s"
1313
1335
  ex = [fmt % (fp.split(os.sep)[-1], ln, fun, txt) for fp, ln, fun, txt in stb]
1314
- ex.append("[%s] %s" % (et.__name__ if et else "(anonymous)", ev))
1336
+ if et or ev or tb:
1337
+ ex.append("[%s] %s" % (et.__name__ if et else "(anonymous)", ev))
1315
1338
  return "\n".join(ex[-max_lines:][:: -1 if reverse else 1])
1316
1339
 
1317
1340
 
@@ -2660,7 +2683,7 @@ def unescape_cookie(orig ) :
2660
2683
 
2661
2684
  def guess_mime(url , fallback = "application/octet-stream") :
2662
2685
  try:
2663
- _, ext = url.rsplit(".", 1)
2686
+ ext = url.rsplit(".", 1)[1].lower()
2664
2687
  except:
2665
2688
  return fallback
2666
2689
 
copyparty/web/a/u2c.py CHANGED
@@ -1,8 +1,8 @@
1
1
  #!/usr/bin/env python3
2
2
  from __future__ import print_function, unicode_literals
3
3
 
4
- S_VERSION = "1.16"
5
- S_BUILD_DT = "2024-04-20"
4
+ S_VERSION = "1.17"
5
+ S_BUILD_DT = "2024-05-09"
6
6
 
7
7
  """
8
8
  u2c.py: upload to copyparty
@@ -79,12 +79,21 @@ req_ses = requests.Session()
79
79
 
80
80
 
81
81
  class Daemon(threading.Thread):
82
- def __init__(self, target, name=None, a=None):
83
- # type: (Any, Any, Any) -> None
84
- threading.Thread.__init__(self, target=target, args=a or (), name=name)
82
+ def __init__(self, target, name = None, a = None):
83
+ threading.Thread.__init__(self, name=name)
84
+ self.a = a or ()
85
+ self.fun = target
85
86
  self.daemon = True
86
87
  self.start()
87
88
 
89
+ def run(self):
90
+ try:
91
+ signal.pthread_sigmask(signal.SIG_BLOCK, [signal.SIGINT, signal.SIGTERM])
92
+ except:
93
+ pass
94
+
95
+ self.fun(*self.a)
96
+
88
97
 
89
98
  class File(object):
90
99
  """an up2k upload task; represents a single file"""
Binary file
Binary file
Binary file
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: copyparty
3
- Version: 1.13.1
3
+ Version: 1.13.2
4
4
  Summary: Portable file server with accelerated resumable uploads, deduplication, WebDAV, FTP, zeroconf, media indexer, video thumbnails, audio transcoding, and write-only folders
5
5
  Author-email: ed <copyparty@ocv.me>
6
6
  License: MIT
@@ -284,6 +284,7 @@ also see [comparison to similar software](./docs/versus.md)
284
284
  * ☑ ...of videos using FFmpeg
285
285
  * ☑ ...of audio (spectrograms) using FFmpeg
286
286
  * ☑ cache eviction (max-age; maybe max-size eventually)
287
+ * ☑ multilingual UI (english, norwegian, [add your own](./docs/rice/#translations)))
287
288
  * ☑ SPA (browse while uploading)
288
289
  * server indexing
289
290
  * ☑ [locate files by contents](#file-search)
@@ -466,7 +467,7 @@ configuring accounts/volumes with arguments:
466
467
  `-v .::r,usr1,usr2:rw,usr3,usr4` = usr1/2 read-only, 3/4 read-write
467
468
 
468
469
  permissions:
469
- * `r` (read): browse folder contents, download files, download as zip/tar
470
+ * `r` (read): browse folder contents, download files, download as zip/tar, see filekeys/dirkeys
470
471
  * `w` (write): upload files, move files *into* this folder
471
472
  * `m` (move): move files/folders *from* this folder
472
473
  * `d` (delete): delete files/folders
@@ -668,7 +669,7 @@ you can also zip a selection of files or folders by clicking them in the browser
668
669
 
669
670
  cool trick: download a folder by appending url-params `?tar&opus` or `?tar&mp3` to transcode all audio files (except aac|m4a|mp3|ogg|opus|wma) to opus/mp3 before they're added to the archive
670
671
  * super useful if you're 5 minutes away from takeoff and realize you don't have any music on your phone but your server only has flac files and downloading those will burn through all your data + there wouldn't be enough time anyways
671
- * and url-params `&j` / `&w` produce jpeg/webm thumbnails/spectrograms instead of the original audio/video/images
672
+ * and url-params `&j` / `&w` produce jpeg/webm thumbnails/spectrograms instead of the original audio/video/images (`&p` for audio waveforms)
672
673
  * can also be used to pregenerate thumbnails; combine with `--th-maxage=9999999` or `--th-clean=0`
673
674
 
674
675
 
@@ -964,6 +965,8 @@ using arguments or config files, or a mix of both:
964
965
 
965
966
  **NB:** as humongous as this readme is, there is also a lot of undocumented features. Run copyparty with `--help` to see all available global options; all of those can be used in the `[global]` section of config files, and everything listed in `--help-flags` can be used in volumes as volflags.
966
967
  * if running in docker/podman, try this: `docker run --rm -it copyparty/ac --help`
968
+ * or see this (probably outdated): https://ocv.me/copyparty/helptext.html
969
+ * or if you prefer plaintext, https://ocv.me/copyparty/helptext.txt
967
970
 
968
971
 
969
972
  ## zeroconf
@@ -1140,7 +1143,7 @@ tweaking the ui
1140
1143
  * to sort in music order (album, track, artist, title) with filename as fallback, you could `--sort tags/Cirle,tags/.tn,tags/Artist,tags/Title,href`
1141
1144
  * to sort by upload date, first enable showing the upload date in the listing with `-e2d -mte +.up_at` and then `--sort tags/.up_at`
1142
1145
 
1143
- see [./docs/rice](./docs/rice) for more, including how to add stuff (css/`<meta>`/...) to the html `<head>` tag
1146
+ see [./docs/rice](./docs/rice) for more, including how to add stuff (css/`<meta>`/...) to the html `<head>` tag, or to add your own translation
1144
1147
 
1145
1148
 
1146
1149
  ## opengraph
@@ -1,38 +1,38 @@
1
1
  copyparty/__init__.py,sha256=fUINM1abqDGzCCH_JcXdOnLdKOV-SrTI2Xo2QgQW2P4,1703
2
- copyparty/__main__.py,sha256=7fOoPuMQe4KzHAGS5K7KI35-fyj-3rERZVM0dTInOV4,98648
3
- copyparty/__version__.py,sha256=Ev7V864ayuRrHZmn9gLsfpBYVJs05PYyOWl3-oCL_uU,254
4
- copyparty/authsrv.py,sha256=UaWwotBSZBEst8LENH7BIaVSL7k2EnUrVzVftTEvtmU,84896
5
- copyparty/broker_mp.py,sha256=4mEZC5tiHUazJMgYuwInNo2dxS7jrbzrGb1qs2UBt9k,3948
2
+ copyparty/__main__.py,sha256=eHd9z3x3VWKFULKcb37iBuWCUbYDRokq1UFjGHI5njU,99846
3
+ copyparty/__version__.py,sha256=HXaSfRCwDZ1gm9Ow7Po9NDC2KxGntKPiZkjTR_ujvpM,255
4
+ copyparty/authsrv.py,sha256=4YPt9_VqqcDAdaur4JN809jKUlpAyamVe-1JaeMUkOQ,85177
5
+ copyparty/broker_mp.py,sha256=YFe1S6Zziht8Qc__dCLj_ff8z0DDny9lqk_Mi5ajsJk,3868
6
6
  copyparty/broker_mpw.py,sha256=4ZI7bJYOwUibeAJVv9_FPGNmHrr9eOtkj_Kz0JEppTU,3197
7
7
  copyparty/broker_thr.py,sha256=eKr--HJGig5zqvNGwH9UoBG9Nvi9mT2axrRmJwknd0s,1759
8
8
  copyparty/broker_util.py,sha256=CnX_LAhQQqouONcDLtVkVlcBX3Z6pWuKDQDmmbHGEg4,1489
9
9
  copyparty/cert.py,sha256=nCeDdzcCpvjPPUcxT4Oh7wvL_8zvddu4oXtbA-zOb8g,7607
10
- copyparty/cfg.py,sha256=PSv7T4nm0_ISlijbAP7re61ucvr37xObv8_495ynPws,9584
10
+ copyparty/cfg.py,sha256=gdsFudDxliRNwYm1YdImO5miWzyDQ2i1vHRMkokxCm0,9643
11
11
  copyparty/dxml.py,sha256=lZpg-kn-kQsXRtNY1n6fRaS-b7uXzMCyv8ovKnhZcZc,1548
12
12
  copyparty/fsutil.py,sha256=NEdhYYgQxDQ7MmgTbtjMKorikCjDls2AXVX16EH2JfQ,4613
13
13
  copyparty/ftpd.py,sha256=OIExjfqOEw-Y_ygez6cIZUQec4SFOmoxEH_WOVvw-aE,15961
14
- copyparty/httpcli.py,sha256=nuJOPt2T9mjLLRreaCRUmpjoLN5nNkzsXJcCtzn963Q,164338
14
+ copyparty/httpcli.py,sha256=wif37Oa89iAer5sIMoZPWChyWif80AFH7lOaG00h2z0,164293
15
15
  copyparty/httpconn.py,sha256=6MOQgBtOGrlVRr6ZiHBKYzkzcls-YWwaWEtqE6DweM0,6873
16
- copyparty/httpsrv.py,sha256=Xf6wI5V25gzAoyEpiKH8VjEFwUqTzW5z8pcRfo2J40c,16421
16
+ copyparty/httpsrv.py,sha256=RpROXBJPgTXmwFbLYrAT15ovGYkIMrksluuetKWAJTM,16356
17
17
  copyparty/ico.py,sha256=AYHdK6NlYBfBgafVYXia3jHQ9XHZdUL1D8WftLMAzIU,3545
18
18
  copyparty/mdns.py,sha256=CcraggbDxTT1ntYzD8Ebgqmw5Q4HkyZcfh5ymtCV_ak,17469
19
19
  copyparty/metrics.py,sha256=O8qiPNDxNjub_PI8C8Qu9rBQ_z0J1mnKonqkcTeAtf4,8845
20
- copyparty/mtag.py,sha256=wiXd26ZSYgOu4lkRDn4KLaqo6H2V7cpqUMepTHTCfKE,16851
20
+ copyparty/mtag.py,sha256=MpRkwAj_OAhE9alRzYMyoyPUtedSIuBmVxhCkLaIpMA,18593
21
21
  copyparty/multicast.py,sha256=Ha27l2oATEa-Qo2WOzkeRgjAm6G_YDCfbVJWR-ao2UE,12319
22
22
  copyparty/pwhash.py,sha256=D82y8emnwpHDQq7Cr8lNuppHshbNA9ptcR2XsGOOk6E,3937
23
- copyparty/smbd.py,sha256=iACj5pbiKsX7bVu20BK3ebPQLB_qA7WS2l-ytrSfT3Y,14054
23
+ copyparty/smbd.py,sha256=CwsjLzwfIkqY9Fr_w7po_giO2tnXq9_7bdjIBNdiuTY,14062
24
24
  copyparty/ssdp.py,sha256=H6ZftXttydcnBxcg2-Prm4P-XiybgT3xiJRUXU1pbrE,6343
25
25
  copyparty/star.py,sha256=K4NuzyfT4956uoW6GJSQ2II-JsSV57apQZwRZ4mjFoo,3790
26
26
  copyparty/sutil.py,sha256=_G4TM0YFa1vXzhRypHJ88QBdZWtYgDbom4CZjGvGIwc,3074
27
- copyparty/svchub.py,sha256=9DTZLgrHRMrlnq6pc_3UngNpxtdlT2SWnhlE2dPaoBk,32419
27
+ copyparty/svchub.py,sha256=v81cj-KQ3P7pLcV4sOWEjJSpGdjseZ2tpqL4VruPtmI,32661
28
28
  copyparty/szip.py,sha256=631TsEwGKV22yAnusJtvE-9fGFWr61HPGBinu-jk1QA,8591
29
29
  copyparty/tcpsrv.py,sha256=ym6rda7svl_M4DjNesHMI1_6wO7Csu01UV1zGzXEMxI,17637
30
30
  copyparty/tftpd.py,sha256=7EHAZ9LnjAXupwRNIENJ2eA8Q0lFynnwwbziV3fyzns,13157
31
- copyparty/th_cli.py,sha256=eSW7sBiaZAsh_XffXFzb035CTSbS3J3Q0G-BMzQGuSY,4385
32
- copyparty/th_srv.py,sha256=C2ZBE6ddINCuYDympRQQmhj0ULdlD6HOM6qNK-UB4so,27191
31
+ copyparty/th_cli.py,sha256=VO2Eo0SGwgSJaHowMLeX3_vxEdQcQh5RFDSf0OraTTw,4568
32
+ copyparty/th_srv.py,sha256=utK8kA1mlYI0BsAdLL-l0-51gXoIN0BS5zQkGAcERXg,28041
33
33
  copyparty/u2idx.py,sha256=uEUcEbye1jzGlQfEJkLtD060XA6Rv_6lXLgeg6oAU5M,13033
34
- copyparty/up2k.py,sha256=D-E5y27lJopCtrxUjEi1-oH-kqrOnHL2hQO1Q_X6tHw,143080
35
- copyparty/util.py,sha256=N8rIBtCX_-oEhfLURsyhf3WfrdUzsdsN4Q1qkFtYKHg,83167
34
+ copyparty/up2k.py,sha256=hhh7PrUzRgnlvsapwjG1-X6_bqI51r3Tf5zo4jLvveU,143330
35
+ copyparty/util.py,sha256=w22CaPQy0Otq8sJMt9hCPpRbKM2sRLTQtBYAE6DTaHQ,83744
36
36
  copyparty/bos/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
37
37
  copyparty/bos/bos.py,sha256=Wb7eWsXJgR5AFlBR9ZOyKrLTwy-Kct9RrGiOu4Jo37Y,1622
38
38
  copyparty/bos/path.py,sha256=yEjCq2ki9CvxA5sCT8pS0keEXwugs0ZeUyUhdBziOCI,777
@@ -57,7 +57,7 @@ copyparty/stolen/ifaddr/_win32.py,sha256=EE-QyoBgeB7lYQ6z62VjXNaRozaYfCkaJBHGNA8
57
57
  copyparty/web/baguettebox.js.gz,sha256=HdRHC_4Lvepp1DrRwusdcxvAn8IKGMdrKdggGIshKek,7869
58
58
  copyparty/web/browser.css.gz,sha256=GGyPK9BBOX63x9XWqO2jrXewHtfAeZ-Jo0BBxqTOGUM,11491
59
59
  copyparty/web/browser.html,sha256=-tLasq2GKe9mUceqXG4PczQ7odBMrX0qlWuyaA9SjPI,4882
60
- copyparty/web/browser.js.gz,sha256=9w6hKIv_v8QERNxRQ5DOdZxBijStf-YE8H7SaWLbi7Y,68707
60
+ copyparty/web/browser.js.gz,sha256=Lci32Xx_OxmC9bjEuiQSzfMrb8UGj9Qg5j2D7kOcFjY,68838
61
61
  copyparty/web/browser2.html,sha256=ciQlgr9GWuIapdsRBFNRvRFvN5T_5n920LqDMbsj5-g,1605
62
62
  copyparty/web/cf.html,sha256=lJThtNFNAQT1ClCHHlivAkDGE0LutedwopXD62Z8Nys,589
63
63
  copyparty/web/dbg-audio.js.gz,sha256=Ma-KZtK8LnmiwNvNKFKXMPYl_Nn_3U7GsJ6-DRWC2HE,688
@@ -73,7 +73,7 @@ copyparty/web/msg.css.gz,sha256=u90fXYAVrMD-jqwf5XFVC1ptSpSHZUe8Mez6PX101P8,300
73
73
  copyparty/web/msg.html,sha256=HcBeXXpcF2JKwcj8KD3dGCvONMnTZ6lXYmm4SYgBMlA,905
74
74
  copyparty/web/splash.css.gz,sha256=zgDs-SY3VrInsXeARRPcGHziVOUs-1hUtSObzybwD1g,1006
75
75
  copyparty/web/splash.html,sha256=z5OrfZqA5RBxeY86BJiQ5NZNHIIDHDvPlTuht-Q0v64,3917
76
- copyparty/web/splash.js.gz,sha256=kPLyo_LaoEdRswRNHU32G4rQY8x5Jes4iAIIvK2ug9U,1442
76
+ copyparty/web/splash.js.gz,sha256=P4BLL_SBqfqWniq_gzUD-opVAkblAPgKDwmfxyfDB7o,1469
77
77
  copyparty/web/svcs.html,sha256=Lniv3ndzV1ALGOdvMNKg6za5rafrqltuwoknYbExRxM,11711
78
78
  copyparty/web/svcs.js.gz,sha256=k81ZvZ3I-f4fMHKrNGGOgOlvXnCBz0mVjD-8mieoWCA,520
79
79
  copyparty/web/ui.css.gz,sha256=skuzZHqTU0ag5hButpQmKI9wM7ro-UJ2PnpTodTWYF4,2616
@@ -82,7 +82,7 @@ copyparty/web/util.js.gz,sha256=eNRKtW7fM9AvipRdJGQyy9mbQIqMda73o8Bo64UWr7s,1441
82
82
  copyparty/web/w.hash.js.gz,sha256=__hBMd5oZWfTrb8ZCJNT21isoSqyrxKE6qdaKGQVAhc,1060
83
83
  copyparty/web/a/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
84
84
  copyparty/web/a/partyfuse.py,sha256=MuRkaSuYsdfWfBFMOkbPwDXqSvNTw3sd7QhhlKCDZ8I,32311
85
- copyparty/web/a/u2c.py,sha256=qyK4G1mkICAjmo99YV8ubi2Zk6GG8S8yldW6D18Pnos,38626
85
+ copyparty/web/a/u2c.py,sha256=mCVYSJo6wSiPrT_p7QxlIoIGSclcC7qw4COcIaAKS-w,38791
86
86
  copyparty/web/a/webdav-cfg.bat,sha256=Y4NoGZlksAIg4cBMb7KdJrpKC6Nx97onaTl6yMjaimk,1449
87
87
  copyparty/web/dd/2.png,sha256=gJ14XFPzaw95L6z92fSq9eMPikSQyu-03P1lgiGe0_I,258
88
88
  copyparty/web/dd/3.png,sha256=4lho8Koz5tV7jJ4ODo6GMTScZfkqsT05yp48EDFIlyg,252
@@ -93,7 +93,7 @@ copyparty/web/deps/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuF
93
93
  copyparty/web/deps/busy.mp3.gz,sha256=EVphk1_HYyRKJmtpeK99vbAstF7ub1f9ndu020H8PQ8,106
94
94
  copyparty/web/deps/easymde.css.gz,sha256=vWxfueI64rPikuqFj69wJBtGisqf93AheQtOZqgUI_c,3041
95
95
  copyparty/web/deps/easymde.js.gz,sha256=1FykpDM7_FiL4EeZAg4Qcggjoo4PE_MBTgRcBWvjD90,77000
96
- copyparty/web/deps/marked.js.gz,sha256=ltXpV8Z7mOCygryr9tkSw1Ydo2lV0CSLxOOsTqGUDiw,22422
96
+ copyparty/web/deps/marked.js.gz,sha256=elpt4-fI9Fs5zgMYxHuQn7XL4MXMBfINU4WQ0sBF5HY,22582
97
97
  copyparty/web/deps/mini-fa.css.gz,sha256=CTPrNaH8OTVmxajrGP88E2MkjadY9_81TBVnd9sw9Y8,572
98
98
  copyparty/web/deps/mini-fa.woff,sha256=L9DNncV2TIyvsrspMbJouvnnt7F068Hbn7YZYvN76AU,2784
99
99
  copyparty/web/deps/prism.css.gz,sha256=Z_A6rJ3MN5KWnjvXaV787aTW_5DT-xjFd0YZ7_W-Krk,1468
@@ -102,9 +102,9 @@ copyparty/web/deps/prismd.css.gz,sha256=ObUlksQVr-OuYlTz-I4B23TeBg2QDVVGRnWBz8cV
102
102
  copyparty/web/deps/scp.woff2,sha256=w99BDU5i8MukkMEL-iW0YO9H4vFFZSPWxbkH70ytaAg,8612
103
103
  copyparty/web/deps/sha512.ac.js.gz,sha256=lFZaCLumgWxrvEuDr4bqdKHsqjX82AbVAb7_F45Yk88,7033
104
104
  copyparty/web/deps/sha512.hw.js.gz,sha256=vqoXeracj-99Z5MfY3jK2N4WiSzYQdfjy0RnUlQDhSU,8110
105
- copyparty-1.13.1.dist-info/LICENSE,sha256=gOr4h33pCsBEg9uIy9AYmb7qlocL4V9t2uPJS5wllr0,1072
106
- copyparty-1.13.1.dist-info/METADATA,sha256=HMm1ZQpKi3_hfKuT0-Rowdv0kdJXGsK2AK4YU7yaW7Q,122167
107
- copyparty-1.13.1.dist-info/WHEEL,sha256=GJ7t_kWBFywbagK5eo9IoUwLW6oyOeTKmQ-9iHFVNxQ,92
108
- copyparty-1.13.1.dist-info/entry_points.txt,sha256=4zw6a3rqASywQomiYLObjjlxybaI65LYYOTJwgKz7b0,128
109
- copyparty-1.13.1.dist-info/top_level.txt,sha256=LnYUPsDyk-8kFgM6YJLG4h820DQekn81cObKSu9g-sI,10
110
- copyparty-1.13.1.dist-info/RECORD,,
105
+ copyparty-1.13.2.dist-info/LICENSE,sha256=gOr4h33pCsBEg9uIy9AYmb7qlocL4V9t2uPJS5wllr0,1072
106
+ copyparty-1.13.2.dist-info/METADATA,sha256=mqJMKOtNHic9_FIsW46owpSjsmGYFLF7pUSwjzIQZyg,122479
107
+ copyparty-1.13.2.dist-info/WHEEL,sha256=GJ7t_kWBFywbagK5eo9IoUwLW6oyOeTKmQ-9iHFVNxQ,92
108
+ copyparty-1.13.2.dist-info/entry_points.txt,sha256=4zw6a3rqASywQomiYLObjjlxybaI65LYYOTJwgKz7b0,128
109
+ copyparty-1.13.2.dist-info/top_level.txt,sha256=LnYUPsDyk-8kFgM6YJLG4h820DQekn81cObKSu9g-sI,10
110
+ copyparty-1.13.2.dist-info/RECORD,,