copyparty 1.11.2__py3-none-any.whl → 1.12.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
copyparty/__init__.py CHANGED
@@ -53,7 +53,6 @@ class EnvParams(object):
53
53
  self.t0 = time.time()
54
54
  self.mod = ""
55
55
  self.cfg = ""
56
- self.ox = getattr(sys, "oxidized", None)
57
56
 
58
57
 
59
58
  E = EnvParams()
copyparty/__main__.py CHANGED
@@ -151,7 +151,8 @@ def warn(msg ) :
151
151
 
152
152
 
153
153
  def init_E(EE ) :
154
- # __init__ runs 18 times when oxidized; do expensive stuff here
154
+ # some cpython alternatives (such as pyoxidizer) can
155
+ # __init__ several times, so do expensive stuff here
155
156
 
156
157
  E = EE # pylint: disable=redefined-outer-name
157
158
 
@@ -184,34 +185,9 @@ def init_E(EE ) :
184
185
 
185
186
  raise Exception("could not find a writable path for config")
186
187
 
187
- def _unpack() :
188
- import atexit
189
- import tarfile
190
- import tempfile
191
- from importlib.resources import open_binary
192
-
193
- td = tempfile.TemporaryDirectory(prefix="")
194
- atexit.register(td.cleanup)
195
- tdn = td.name
196
-
197
- with open_binary("copyparty", "z.tar") as tgz:
198
- with tarfile.open(fileobj=tgz) as tf:
199
- try:
200
- tf.extractall(tdn, filter="tar")
201
- except TypeError:
202
- tf.extractall(tdn) # nosec (archive is safe)
203
-
204
- return tdn
205
-
206
- try:
207
- E.mod = os.path.dirname(os.path.realpath(__file__))
208
- if E.mod.endswith("__init__"):
209
- E.mod = os.path.dirname(E.mod)
210
- except:
211
- if not E.ox:
212
- raise
213
-
214
- E.mod = _unpack()
188
+ E.mod = os.path.dirname(os.path.realpath(__file__))
189
+ if E.mod.endswith("__init__"):
190
+ E.mod = os.path.dirname(E.mod)
215
191
 
216
192
  if sys.platform == "win32":
217
193
  bdir = os.environ.get("APPDATA") or os.environ.get("TEMP") or "."
@@ -268,6 +244,19 @@ def get_fk_salt() :
268
244
  return ret.decode("utf-8")
269
245
 
270
246
 
247
+ def get_dk_salt() :
248
+ fp = os.path.join(E.cfg, "dk-salt.txt")
249
+ try:
250
+ with open(fp, "rb") as f:
251
+ ret = f.read().strip()
252
+ except:
253
+ ret = base64.b64encode(os.urandom(30))
254
+ with open(fp, "wb") as f:
255
+ f.write(ret + b"\n")
256
+
257
+ return ret.decode("utf-8")
258
+
259
+
271
260
  def get_ah_salt() :
272
261
  fp = os.path.join(E.cfg, "ah-salt.txt")
273
262
  try:
@@ -1125,13 +1114,14 @@ def add_safety(ap):
1125
1114
  ap2.add_argument("--acam", metavar="V[,V]", type=u, default="GET,HEAD", help="Access-Control-Allow-Methods; list of methods to accept from offsite ('*' behaves like \033[33m--acao\033[0m's description)")
1126
1115
 
1127
1116
 
1128
- def add_salt(ap, fk_salt, ah_salt):
1117
+ def add_salt(ap, fk_salt, dk_salt, ah_salt):
1129
1118
  ap2 = ap.add_argument_group('salting options')
1130
1119
  ap2.add_argument("--ah-alg", metavar="ALG", type=u, default="none", help="account-pw hashing algorithm; one of these, best to worst: \033[32margon2 scrypt sha2 none\033[0m (each optionally followed by alg-specific comma-sep. config)")
1131
1120
  ap2.add_argument("--ah-salt", metavar="SALT", type=u, default=ah_salt, help="account-pw salt; ignored if \033[33m--ah-alg\033[0m is none (default)")
1132
1121
  ap2.add_argument("--ah-gen", metavar="PW", type=u, default="", help="generate hashed password for \033[33mPW\033[0m, or read passwords from STDIN if \033[33mPW\033[0m is [\033[32m-\033[0m]")
1133
1122
  ap2.add_argument("--ah-cli", action="store_true", help="launch an interactive shell which hashes passwords without ever storing or displaying the original passwords")
1134
1123
  ap2.add_argument("--fk-salt", metavar="SALT", type=u, default=fk_salt, help="per-file accesskey salt; used to generate unpredictable URLs for hidden files")
1124
+ ap2.add_argument("--dk-salt", metavar="SALT", type=u, default=dk_salt, help="per-directory accesskey salt; used to generate unpredictable URLs to share folders with users who only have the 'get' permission")
1135
1125
  ap2.add_argument("--warksalt", metavar="SALT", type=u, default="hunter2", help="up2k file-hash salt; serves no purpose, no reason to change this (but delete all databases if you do)")
1136
1126
 
1137
1127
 
@@ -1197,6 +1187,8 @@ def add_thumbnail(ap):
1197
1187
 
1198
1188
  def add_transcoding(ap):
1199
1189
  ap2 = ap.add_argument_group('transcoding options')
1190
+ ap2.add_argument("--q-opus", metavar="KBPS", type=int, default=128, help="target bitrate for transcoding to opus; set 0 to disable")
1191
+ ap2.add_argument("--q-mp3", metavar="QUALITY", type=u, default="q2", help="target quality for transcoding to mp3, for example [\033[32m192k\033[0m] (CBR) or [\033[32mq0\033[0m] (CQ/CRF, q0=maxquality, q9=smallest); set 0 to disable")
1200
1192
  ap2.add_argument("--no-acode", action="store_true", help="disable audio transcoding")
1201
1193
  ap2.add_argument("--no-bacode", action="store_true", help="disable batch audio transcoding by folder download (zip/tar)")
1202
1194
  ap2.add_argument("--ac-maxage", metavar="SEC", type=int, default=86400, help="delete cached transcode output after \033[33mSEC\033[0m seconds")
@@ -1313,6 +1305,7 @@ def run_argparse(
1313
1305
  cert_path = os.path.join(E.cfg, "cert.pem")
1314
1306
 
1315
1307
  fk_salt = get_fk_salt()
1308
+ dk_salt = get_dk_salt()
1316
1309
  ah_salt = get_ah_salt()
1317
1310
 
1318
1311
  # alpine peaks at 5 threads for some reason,
@@ -1344,7 +1337,7 @@ def run_argparse(
1344
1337
  add_tftp(ap)
1345
1338
  add_smb(ap)
1346
1339
  add_safety(ap)
1347
- add_salt(ap, fk_salt, ah_salt)
1340
+ add_salt(ap, fk_salt, dk_salt, ah_salt)
1348
1341
  add_optouts(ap)
1349
1342
  add_shutdown(ap)
1350
1343
  add_yolo(ap)
copyparty/__version__.py CHANGED
@@ -1,8 +1,8 @@
1
1
  # coding: utf-8
2
2
 
3
- VERSION = (1, 11, 2)
4
- CODENAME = "You Can (Not) Proceed"
5
- BUILD_DT = (2024, 3, 23)
3
+ VERSION = (1, 12, 0)
4
+ CODENAME = "locksmith"
5
+ BUILD_DT = (2024, 4, 6)
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
@@ -548,7 +548,12 @@ class VFS(object):
548
548
  # no vfs nodes in the list of real inodes
549
549
  real = [x for x in real if x[0] not in self.nodes]
550
550
 
551
+ dbv = self.dbv or self
551
552
  for name, vn2 in sorted(self.nodes.items()):
553
+ if vn2.dbv == dbv and self.flags.get("dk"):
554
+ virt_vis[name] = vn2
555
+ continue
556
+
552
557
  ok = False
553
558
  zx = vn2.axs
554
559
  axs = [zx.uread, zx.uwrite, zx.umove, zx.udel, zx.uget]
@@ -1674,6 +1679,20 @@ class AuthSrv(object):
1674
1679
  vol.flags["fk"] = int(fk) if fk is not True else 8
1675
1680
  have_fk = True
1676
1681
 
1682
+ dk = vol.flags.get("dk")
1683
+ dks = vol.flags.get("dks")
1684
+ dky = vol.flags.get("dky")
1685
+ if dks is not None and dky is not None:
1686
+ t = "WARNING: volume /%s has both dks and dky enabled; this is too yolo and not permitted"
1687
+ raise Exception(t % (vol.vpath,))
1688
+
1689
+ if dks and not dk:
1690
+ dk = dks
1691
+ if dky and not dk:
1692
+ dk = dky
1693
+ if dk:
1694
+ vol.flags["dk"] = int(dk) if dk is not True else 8
1695
+
1677
1696
  if have_fk and re.match(r"^[0-9\.]+$", self.args.fk_salt):
1678
1697
  self.log("filekey salt: {}".format(self.args.fk_salt))
1679
1698
 
copyparty/httpcli.py CHANGED
@@ -1962,7 +1962,12 @@ class HttpCli(object):
1962
1962
 
1963
1963
  v = self.uparam[k]
1964
1964
 
1965
- vn, rem = self.asrv.vfs.get(self.vpath, self.uname, True, False)
1965
+ if self._use_dirkey():
1966
+ vn = self.vn
1967
+ rem = self.rem
1968
+ else:
1969
+ vn, rem = self.asrv.vfs.get(self.vpath, self.uname, True, False)
1970
+
1966
1971
  zs = self.parser.require("files", 1024 * 1024)
1967
1972
  if not zs:
1968
1973
  raise Pebkac(422, "need files list")
@@ -2441,7 +2446,7 @@ class HttpCli(object):
2441
2446
  self.log("user not allowed to overwrite with ?replace")
2442
2447
  elif bos.path.exists(abspath):
2443
2448
  try:
2444
- bos.unlink(abspath)
2449
+ wunlink(self.log, abspath, vfs.flags)
2445
2450
  t = "overwriting file with new upload: %s"
2446
2451
  except:
2447
2452
  t = "toctou while deleting for ?replace: %s"
@@ -2866,6 +2871,30 @@ class HttpCli(object):
2866
2871
 
2867
2872
  return file_lastmod, True
2868
2873
 
2874
+ def _use_dirkey(self, ap = "") :
2875
+ if self.can_read or not self.can_get:
2876
+ return False
2877
+
2878
+ if self.vn.flags.get("dky"):
2879
+ return True
2880
+
2881
+ req = self.uparam.get("k") or ""
2882
+ if not req:
2883
+ return False
2884
+
2885
+ dk_len = self.vn.flags.get("dk")
2886
+ if not dk_len:
2887
+ return False
2888
+
2889
+ ap = ap or self.vn.canonical(self.rem)
2890
+ zs = self.gen_fk(2, self.args.dk_salt, ap, 0, 0)[:dk_len]
2891
+ if req == zs:
2892
+ return True
2893
+
2894
+ t = "wrong dirkey, want %s, got %s\n vp: %s\n ap: %s"
2895
+ self.log(t % (zs, req, self.req, ap), 6)
2896
+ return False
2897
+
2869
2898
  def _expand(self, txt , phs ) :
2870
2899
  for ph in phs:
2871
2900
  if ph.startswith("hdr."):
@@ -3144,7 +3173,7 @@ class HttpCli(object):
3144
3173
  # for f in fgen: print(repr({k: f[k] for k in ["vp", "ap"]}))
3145
3174
  cfmt = ""
3146
3175
  if self.thumbcli and not self.args.no_bacode:
3147
- for zs in ("opus", "w", "j"):
3176
+ for zs in ("opus", "mp3", "w", "j"):
3148
3177
  if zs in self.ouparam or uarg == zs:
3149
3178
  cfmt = zs
3150
3179
 
@@ -3553,7 +3582,7 @@ class HttpCli(object):
3553
3582
 
3554
3583
  dst = dst[len(top) + 1 :]
3555
3584
 
3556
- ret = self.gen_tree(top, dst)
3585
+ ret = self.gen_tree(top, dst, self.uparam.get("k", ""))
3557
3586
  if self.is_vproxied:
3558
3587
  parents = self.args.R.split("/")
3559
3588
  for parent in reversed(parents):
@@ -3563,18 +3592,25 @@ class HttpCli(object):
3563
3592
  self.reply(zs.encode("utf-8"), mime="application/json")
3564
3593
  return True
3565
3594
 
3566
- def gen_tree(self, top , target ) :
3595
+ def gen_tree(self, top , target , dk ) :
3567
3596
  ret = {}
3568
3597
  excl = None
3569
3598
  if target:
3570
3599
  excl, target = (target.split("/", 1) + [""])[:2]
3571
- sub = self.gen_tree("/".join([top, excl]).strip("/"), target)
3600
+ sub = self.gen_tree("/".join([top, excl]).strip("/"), target, dk)
3572
3601
  ret["k" + quotep(excl)] = sub
3573
3602
 
3574
3603
  vfs = self.asrv.vfs
3604
+ dk_sz = False
3605
+ if dk:
3606
+ vn, rem = vfs.get(top, self.uname, False, False)
3607
+ if vn.flags.get("dks") and self._use_dirkey(vn.canonical(rem)):
3608
+ dk_sz = vn.flags.get("dk")
3609
+
3575
3610
  dots = False
3611
+ fsroot = ""
3576
3612
  try:
3577
- vn, rem = vfs.get(top, self.uname, True, False)
3613
+ vn, rem = vfs.get(top, self.uname, not dk_sz, False)
3578
3614
  fsroot, vfs_ls, vfs_virt = vn.ls(
3579
3615
  rem,
3580
3616
  self.uname,
@@ -3582,7 +3618,9 @@ class HttpCli(object):
3582
3618
  [[True, False], [False, True]],
3583
3619
  )
3584
3620
  dots = self.uname in vn.axs.udot
3621
+ dk_sz = vn.flags.get("dk")
3585
3622
  except:
3623
+ dk_sz = None
3586
3624
  vfs_ls = []
3587
3625
  vfs_virt = {}
3588
3626
  for v in self.rvol:
@@ -3597,6 +3635,14 @@ class HttpCli(object):
3597
3635
 
3598
3636
  dirs = [quotep(x) for x in dirs if x != excl]
3599
3637
 
3638
+ if dk_sz and fsroot:
3639
+ kdirs = []
3640
+ for dn in dirs:
3641
+ ap = os.path.join(fsroot, dn)
3642
+ zs = self.gen_fk(2, self.args.dk_salt, ap, 0, 0)[:dk_sz]
3643
+ kdirs.append(dn + "?k=" + zs)
3644
+ dirs = kdirs
3645
+
3600
3646
  for x in vfs_virt:
3601
3647
  if x != excl:
3602
3648
  try:
@@ -3861,6 +3907,7 @@ class HttpCli(object):
3861
3907
  self.out_headers["X-Robots-Tag"] = "noindex, nofollow"
3862
3908
 
3863
3909
  is_dir = stat.S_ISDIR(st.st_mode)
3910
+ is_dk = False
3864
3911
  fk_pass = False
3865
3912
  icur = None
3866
3913
  if is_dir and (e2t or e2d):
@@ -3869,7 +3916,7 @@ class HttpCli(object):
3869
3916
  icur = idx.get_cur(dbv.realpath)
3870
3917
 
3871
3918
  th_fmt = self.uparam.get("th")
3872
- if self.can_read:
3919
+ if self.can_read or (self.can_get and vn.flags.get("dk")):
3873
3920
  if th_fmt is not None:
3874
3921
  nothumb = "dthumb" in dbv.flags
3875
3922
  if is_dir:
@@ -3975,8 +4022,11 @@ class HttpCli(object):
3975
4022
 
3976
4023
  return self.tx_file(abspath)
3977
4024
 
3978
- elif is_dir and not self.can_read and not self.can_write:
3979
- return self.tx_404(True)
4025
+ elif is_dir and not self.can_read:
4026
+ if self._use_dirkey(abspath):
4027
+ is_dk = True
4028
+ elif not self.can_write:
4029
+ return self.tx_404(True)
3980
4030
 
3981
4031
  srv_info = []
3982
4032
 
@@ -3998,7 +4048,7 @@ class HttpCli(object):
3998
4048
  srv_infot = "</span> // <span>".join(srv_info)
3999
4049
 
4000
4050
  perms = []
4001
- if self.can_read:
4051
+ if self.can_read or is_dk:
4002
4052
  perms.append("read")
4003
4053
  if self.can_write:
4004
4054
  perms.append("write")
@@ -4126,7 +4176,7 @@ class HttpCli(object):
4126
4176
  if not self.conn.hsrv.prism:
4127
4177
  j2a["no_prism"] = True
4128
4178
 
4129
- if not self.can_read:
4179
+ if not self.can_read and not is_dk:
4130
4180
  if is_ls:
4131
4181
  return self.tx_ls(ls_ret)
4132
4182
 
@@ -4179,8 +4229,15 @@ class HttpCli(object):
4179
4229
  ):
4180
4230
  ls_names = exclude_dotfiles(ls_names)
4181
4231
 
4232
+ add_dk = vf.get("dk")
4182
4233
  add_fk = vf.get("fk")
4183
4234
  fk_alg = 2 if "fka" in vf else 1
4235
+ if add_dk:
4236
+ if vf.get("dky"):
4237
+ add_dk = False
4238
+ else:
4239
+ zs = self.gen_fk(2, self.args.dk_salt, abspath, 0, 0)[:add_dk]
4240
+ ls_ret["dk"] = cgv["dk"] = zs
4184
4241
 
4185
4242
  dirs = []
4186
4243
  files = []
@@ -4208,6 +4265,12 @@ class HttpCli(object):
4208
4265
  href += "/"
4209
4266
  if self.args.no_zip:
4210
4267
  margin = "DIR"
4268
+ elif add_dk:
4269
+ zs = absreal(fspath)
4270
+ margin = '<a href="%s?k=%s&zip" rel="nofollow">zip</a>' % (
4271
+ quotep(href),
4272
+ self.gen_fk(2, self.args.dk_salt, zs, 0, 0)[:add_dk],
4273
+ )
4211
4274
  else:
4212
4275
  margin = '<a href="%s?zip" rel="nofollow">zip</a>' % (quotep(href),)
4213
4276
  elif fn in hist:
@@ -4248,6 +4311,11 @@ class HttpCli(object):
4248
4311
  0 if ANYWIN else inf.st_ino,
4249
4312
  )[:add_fk],
4250
4313
  )
4314
+ elif add_dk and is_dir:
4315
+ href = "%s?k=%s" % (
4316
+ quotep(href),
4317
+ self.gen_fk(2, self.args.dk_salt, fspath, 0, 0)[:add_dk],
4318
+ )
4251
4319
  else:
4252
4320
  href = quotep(href)
4253
4321
 
@@ -4266,6 +4334,9 @@ class HttpCli(object):
4266
4334
  files.append(item)
4267
4335
  item["rd"] = rem
4268
4336
 
4337
+ if is_dk and not vf.get("dks"):
4338
+ dirs = []
4339
+
4269
4340
  if (
4270
4341
  self.cookies.get("idxh") == "y"
4271
4342
  and "ls" not in self.uparam
copyparty/mtag.py CHANGED
@@ -545,8 +545,7 @@ class MTag(object):
545
545
  pypath = str(os.pathsep.join(zsl))
546
546
  env["PYTHONPATH"] = pypath
547
547
  except:
548
- if not E.ox and not EXE:
549
- raise
548
+ raise # might be expected outside cpython
550
549
 
551
550
  ret = {}
552
551
  for tagname, parser in sorted(parsers.items(), key=lambda x: (x[1].pri, x[0])):
copyparty/sutil.py CHANGED
@@ -75,7 +75,9 @@ def enthumb(
75
75
  ) :
76
76
  rem = f["vp"]
77
77
  ext = rem.rsplit(".", 1)[-1].lower()
78
- if fmt == "opus" and ext in "aac|m4a|mp3|ogg|opus|wma".split("|"):
78
+ if (fmt == "mp3" and ext == "mp3") or (
79
+ fmt == "opus" and ext in "aac|m4a|mp3|ogg|opus|wma".split("|")
80
+ ):
79
81
  raise Exception()
80
82
 
81
83
  vp = vjoin(vtop, rem.split("/", 1)[1])
copyparty/svchub.py CHANGED
@@ -270,6 +270,11 @@ class SvcHub(object):
270
270
  if want_ff and ANYWIN:
271
271
  self.log("thumb", "download FFmpeg to fix it:\033[0m " + FFMPEG_URL, 3)
272
272
 
273
+ if not args.no_acode:
274
+ if not re.match("^(0|[qv][0-9]|[0-9]{2,3}k)$", args.q_mp3.lower()):
275
+ 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
+ raise Exception(t % (args.q_mp3,))
277
+
273
278
  args.th_poke = min(args.th_poke, args.th_maxage, args.ac_maxage)
274
279
 
275
280
  zms = ""
copyparty/th_cli.py CHANGED
@@ -54,7 +54,7 @@ class ThumbCli(object):
54
54
  if is_vid and "dvthumb" in dbv.flags:
55
55
  return None
56
56
 
57
- want_opus = fmt in ("opus", "caf")
57
+ want_opus = fmt in ("opus", "caf", "mp3")
58
58
  is_au = ext in self.fmt_ffa
59
59
  if is_au:
60
60
  if want_opus:
copyparty/th_srv.py CHANGED
@@ -106,7 +106,7 @@ def thumb_path(histpath , rem , mtime , fmt , ffa ) :
106
106
  h = hashlib.sha512(afsenc(fn)).digest()
107
107
  fn = base64.urlsafe_b64encode(h).decode("ascii")[:24]
108
108
 
109
- if fmt in ("opus", "caf"):
109
+ if fmt in ("opus", "caf", "mp3"):
110
110
  cat = "ac"
111
111
  else:
112
112
  fc = fmt[:1]
@@ -304,6 +304,8 @@ class ThumbSrv(object):
304
304
  elif lib == "ff" and ext in self.fmt_ffa:
305
305
  if tpath.endswith(".opus") or tpath.endswith(".caf"):
306
306
  funs.append(self.conv_opus)
307
+ elif tpath.endswith(".mp3"):
308
+ funs.append(self.conv_mp3)
307
309
  elif tpath.endswith(".png"):
308
310
  funs.append(self.conv_waves)
309
311
  png_ok = True
@@ -634,8 +636,47 @@ class ThumbSrv(object):
634
636
  cmd += [fsenc(tpath)]
635
637
  self._run_ff(cmd, vn)
636
638
 
639
+ def conv_mp3(self, abspath , tpath , fmt , vn ) :
640
+ quality = self.args.q_mp3.lower()
641
+ if self.args.no_acode or not quality:
642
+ raise Exception("disabled in server config")
643
+
644
+ self.wait4ram(0.2, tpath)
645
+ ret, _ = ffprobe(abspath, int(vn.flags["convt"] / 2))
646
+ if "ac" not in ret:
647
+ raise Exception("not audio")
648
+
649
+ if quality.endswith("k"):
650
+ qk = b"-b:a"
651
+ qv = quality.encode("ascii")
652
+ else:
653
+ qk = b"-q:a"
654
+ qv = quality[1:].encode("ascii")
655
+
656
+ # extremely conservative choices for output format
657
+ # (always 2ch 44k1) because if a device is old enough
658
+ # to not support opus then it's probably also super picky
659
+
660
+ # fmt: off
661
+ cmd = [
662
+ b"ffmpeg",
663
+ b"-nostdin",
664
+ b"-v", b"error",
665
+ b"-hide_banner",
666
+ b"-i", fsenc(abspath),
667
+ b"-map_metadata", b"-1",
668
+ b"-map", b"0:a:0",
669
+ b"-ar", b"44100",
670
+ b"-ac", b"2",
671
+ b"-c:a", b"libmp3lame",
672
+ qk, qv,
673
+ fsenc(tpath)
674
+ ]
675
+ # fmt: on
676
+ self._run_ff(cmd, vn, oom=300)
677
+
637
678
  def conv_opus(self, abspath , tpath , fmt , vn ) :
638
- if self.args.no_acode:
679
+ if self.args.no_acode or not self.args.q_opus:
639
680
  raise Exception("disabled in server config")
640
681
 
641
682
  self.wait4ram(0.2, tpath)
@@ -659,6 +700,7 @@ class ThumbSrv(object):
659
700
  pass
660
701
 
661
702
  caf_src = abspath if src_opus else tmp_opus
703
+ bq = ("%dk" % (self.args.q_opus,)).encode("ascii")
662
704
 
663
705
  if not want_caf or not src_opus:
664
706
  # fmt: off
@@ -671,7 +713,7 @@ class ThumbSrv(object):
671
713
  b"-map_metadata", b"-1",
672
714
  b"-map", b"0:a:0",
673
715
  b"-c:a", b"libopus",
674
- b"-b:a", b"128k",
716
+ b"-b:a", bq,
675
717
  fsenc(tmp_opus)
676
718
  ]
677
719
  # fmt: on
@@ -694,7 +736,7 @@ class ThumbSrv(object):
694
736
  b"-map_metadata", b"-1",
695
737
  b"-ac", b"2",
696
738
  b"-c:a", b"libopus",
697
- b"-b:a", b"128k",
739
+ b"-b:a", bq,
698
740
  b"-f", b"caf",
699
741
  fsenc(tpath)
700
742
  ]
@@ -768,7 +810,7 @@ class ThumbSrv(object):
768
810
 
769
811
  def _clean(self, cat , thumbpath ) :
770
812
  # self.log("cln {}".format(thumbpath))
771
- exts = ["jpg", "webp", "png"] if cat == "th" else ["opus", "caf"]
813
+ exts = ["jpg", "webp", "png"] if cat == "th" else ["opus", "caf", "mp3"]
772
814
  maxage = getattr(self.args, cat + "_maxage")
773
815
  now = time.time()
774
816
  prev_b64 = None
Binary file
Binary file
Binary file
Binary file
Binary file
copyparty/web/md.css.gz CHANGED
Binary file
copyparty/web/md.js.gz CHANGED
Binary file
copyparty/web/md2.css.gz CHANGED
Binary file
copyparty/web/md2.js.gz CHANGED
Binary file
copyparty/web/mde.css.gz CHANGED
Binary file
copyparty/web/mde.js.gz CHANGED
Binary file
copyparty/web/msg.css.gz CHANGED
Binary file
Binary file
Binary file
copyparty/web/ui.css.gz CHANGED
Binary file
copyparty/web/up2k.js.gz CHANGED
Binary file
copyparty/web/util.js.gz CHANGED
Binary file
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: copyparty
3
- Version: 1.11.2
3
+ Version: 1.12.0
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
@@ -148,6 +148,7 @@ turn almost any device into a file server with resumable uploads/downloads using
148
148
  * [gotchas](#gotchas) - behavior that might be unexpected
149
149
  * [cors](#cors) - cross-site request config
150
150
  * [filekeys](#filekeys) - prevent filename bruteforcing
151
+ * [dirkeys](#dirkeys) - share specific folders in a volume
151
152
  * [password hashing](#password-hashing) - you can hash passwords
152
153
  * [https](#https) - both HTTP and HTTPS are accepted
153
154
  * [recovering from crashes](#recovering-from-crashes)
@@ -253,7 +254,7 @@ firewall-cmd --reload
253
254
  * browser
254
255
  * ☑ [navpane](#navpane) (directory tree sidebar)
255
256
  * ☑ file manager (cut/paste, delete, [batch-rename](#batch-rename))
256
- * ☑ audio player (with [OS media controls](https://user-images.githubusercontent.com/241032/215347492-b4250797-6c90-4e09-9a4c-721edf2fb15c.png) and opus transcoding)
257
+ * ☑ audio player (with [OS media controls](https://user-images.githubusercontent.com/241032/215347492-b4250797-6c90-4e09-9a4c-721edf2fb15c.png) and opus/mp3 transcoding)
257
258
  * ☑ image gallery with webm player
258
259
  * ☑ textfile browser with syntax hilighting
259
260
  * ☑ [thumbnails](#thumbnails)
@@ -641,7 +642,7 @@ you can also zip a selection of files or folders by clicking them in the browser
641
642
 
642
643
  ![copyparty-zipsel-fs8](https://user-images.githubusercontent.com/241032/129635374-e5136e01-470a-49b1-a762-848e8a4c9cdc.png)
643
644
 
644
- cool trick: download a folder by appending url-params `?tar&opus` to transcode all audio files (except aac|m4a|mp3|ogg|opus|wma) to opus before they're added to the archive
645
+ 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
645
646
  * 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
646
647
  * and url-params `&j` / `&w` produce jpeg/webm thumbnails/spectrograms instead of the original audio/video/images
647
648
  * can also be used to pregenerate thumbnails; combine with `--th-maxage=9999999` or `--th-clean=0`
@@ -832,9 +833,9 @@ open the `[🎺]` media-player-settings tab to configure it,
832
833
  * `[loop]` keeps looping the folder
833
834
  * `[next]` plays into the next folder
834
835
  * "transcode":
835
- * `[flac]` converts `flac` and `wav` files into opus
836
- * `[aac]` converts `aac` and `m4a` files into opus
837
- * `[oth]` converts all other known formats into opus
836
+ * `[flac]` converts `flac` and `wav` files into opus (if supported by browser) or mp3
837
+ * `[aac]` converts `aac` and `m4a` files into opus (if supported by browser) or mp3
838
+ * `[oth]` converts all other known formats into opus (if supported by browser) or mp3
838
839
  * `aac|ac3|aif|aiff|alac|alaw|amr|ape|au|dfpwm|dts|flac|gsm|it|m4a|mo3|mod|mp2|mp3|mpc|mptm|mt2|mulaw|ogg|okt|opus|ra|s3m|tak|tta|ulaw|wav|wma|wv|xm|xpk`
839
840
  * "tint" reduces the contrast of the playback bar
840
841
 
@@ -1893,12 +1894,29 @@ cors can be configured with `--acao` and `--acam`, or the protections entirely d
1893
1894
 
1894
1895
  prevent filename bruteforcing
1895
1896
 
1896
- volflag `c,fk` generates filekeys (per-file accesskeys) for all files; users which have full read-access (permission `r`) will then see URLs with the correct filekey `?k=...` appended to the end, and `g` users must provide that URL including the correct key to avoid a 404
1897
+ volflag `fk` generates filekeys (per-file accesskeys) for all files; users which have full read-access (permission `r`) will then see URLs with the correct filekey `?k=...` appended to the end, and `g` users must provide that URL including the correct key to avoid a 404
1897
1898
 
1898
1899
  by default, filekeys are generated based on salt (`--fk-salt`) + filesystem-path + file-size + inode (if not windows); add volflag `fka` to generate slightly weaker filekeys which will not be invalidated if the file is edited (only salt + path)
1899
1900
 
1900
1901
  permissions `wG` (write + upget) lets users upload files and receive their own filekeys, still without being able to see other uploads
1901
1902
 
1903
+ ### dirkeys
1904
+
1905
+ share specific folders in a volume without giving away full read-access to the rest -- the visitor only needs the `g` (get) permission to view the link
1906
+
1907
+ volflag `dk` generates dirkeys (per-directory accesskeys) for all folders, granting read-access to that folder; by default only that folder itself, no subfolders
1908
+
1909
+ volflag `dky` disables the actual key-check, meaning anyone can see the contents of a folder where they have `g` access, but not its subdirectories
1910
+
1911
+ * `dk` + `dky` gives the same behavior as if all users with `g` access have full read-access, but subfolders are hidden files (their names start with a dot), so `dky` is an alternative to renaming all the folders for that purpose, maybe just for some users
1912
+
1913
+ volflag `dks` lets people enter subfolders as well, and also enables download-as-zip/tar
1914
+
1915
+ dirkeys are generated based on another salt (`--dk-salt`) + filesystem-path and have a few limitations:
1916
+ * the key does not change if the contents of the folder is modified
1917
+ * if you need a new dirkey, either change the salt or rename the folder
1918
+ * linking to a textfile (so it opens in the textfile viewer) is not possible if recipient doesn't have read-access
1919
+
1902
1920
 
1903
1921
  ## password hashing
1904
1922
 
@@ -1,7 +1,7 @@
1
- copyparty/__init__.py,sha256=34xcU8AoRRQscgVSx2gC6DeUyu7ZLmEVlXjttdQgXnI,1752
2
- copyparty/__main__.py,sha256=p_AwNJFrqYwdwKMCogyotymzRdQg5Boy6aLYbnb5ovo,94134
3
- copyparty/__version__.py,sha256=DhzErMpToEEDArJELZ6n8XXipdTWkgxqlp9-cqszin4,263
4
- copyparty/authsrv.py,sha256=D5WGr17mWSAA0unFU-MVwf7-H_N59RavZliDEf77wYA,83746
1
+ copyparty/__init__.py,sha256=fUINM1abqDGzCCH_JcXdOnLdKOV-SrTI2Xo2QgQW2P4,1703
2
+ copyparty/__main__.py,sha256=e9hqYxGdNJDzebB6wcUoc0J_MJ7QjDFOd8U0lHMg6so,94463
3
+ copyparty/__version__.py,sha256=jARmXj1ooaOsD7tTXj7PQut35A5JlMrdGV1wNuozsHs,250
4
+ copyparty/authsrv.py,sha256=qHC_sSExXnd4LLKdSKQM5RA_Xrvl2zdNT_TZRJhA454,84436
5
5
  copyparty/broker_mp.py,sha256=4mEZC5tiHUazJMgYuwInNo2dxS7jrbzrGb1qs2UBt9k,3948
6
6
  copyparty/broker_mpw.py,sha256=4ZI7bJYOwUibeAJVv9_FPGNmHrr9eOtkj_Kz0JEppTU,3197
7
7
  copyparty/broker_thr.py,sha256=eKr--HJGig5zqvNGwH9UoBG9Nvi9mT2axrRmJwknd0s,1759
@@ -11,25 +11,25 @@ copyparty/cfg.py,sha256=CQKSyixyhxeM2narwbOfjUsjn0DZ_VYXbdMSa_tv4gw,9189
11
11
  copyparty/dxml.py,sha256=lZpg-kn-kQsXRtNY1n6fRaS-b7uXzMCyv8ovKnhZcZc,1548
12
12
  copyparty/fsutil.py,sha256=c4fTvmclKbVABNsjU4rGddsjCgRwi9YExAyo-06ATc8,3932
13
13
  copyparty/ftpd.py,sha256=OIExjfqOEw-Y_ygez6cIZUQec4SFOmoxEH_WOVvw-aE,15961
14
- copyparty/httpcli.py,sha256=qi_UpQ0iOKSxvxgatZoiHWDdYZSetfUvZhAk32CDaPg,146368
14
+ copyparty/httpcli.py,sha256=oxMEY8rzE-f6LN2JlzvqY-dc7v85swecpFffvHjEqck,148735
15
15
  copyparty/httpconn.py,sha256=gLOURB2Nb1w6n2ihGBspEnzEfUND9Osa4klzYuAbgzI,6829
16
16
  copyparty/httpsrv.py,sha256=af6LdApfj-Q4mWC5mVQjhnyrFzNy8_bXK3VUe0xKkeY,16368
17
17
  copyparty/ico.py,sha256=AYHdK6NlYBfBgafVYXia3jHQ9XHZdUL1D8WftLMAzIU,3545
18
18
  copyparty/mdns.py,sha256=CcraggbDxTT1ntYzD8Ebgqmw5Q4HkyZcfh5ymtCV_ak,17469
19
19
  copyparty/metrics.py,sha256=OqXFkAuoVhayGAGd_Sv-OQ9SVmdXYV8M7CxitkzE3lo,8854
20
- copyparty/mtag.py,sha256=OZM6cVJafOe_LZ4bPJzVJbORA_omzewwJh-N11u3nak,16855
20
+ copyparty/mtag.py,sha256=wiXd26ZSYgOu4lkRDn4KLaqo6H2V7cpqUMepTHTCfKE,16851
21
21
  copyparty/multicast.py,sha256=4N_NQVIrGSF5ZNWYdpAfYHoZefkSowN3w1GfWAxhTL0,12291
22
22
  copyparty/pwhash.py,sha256=D82y8emnwpHDQq7Cr8lNuppHshbNA9ptcR2XsGOOk6E,3937
23
23
  copyparty/smbd.py,sha256=iACj5pbiKsX7bVu20BK3ebPQLB_qA7WS2l-ytrSfT3Y,14054
24
24
  copyparty/ssdp.py,sha256=H6ZftXttydcnBxcg2-Prm4P-XiybgT3xiJRUXU1pbrE,6343
25
25
  copyparty/star.py,sha256=K4NuzyfT4956uoW6GJSQ2II-JsSV57apQZwRZ4mjFoo,3790
26
- copyparty/sutil.py,sha256=FnYgRSV-cR_IVOz7BNqGKfmJN4xvjbso4gM1U1yjJIk,3023
27
- copyparty/svchub.py,sha256=AZFFHofABeqwnqhp3BAEh7jeMDFrfBNRAon7sIIXLWo,31380
26
+ copyparty/sutil.py,sha256=_G4TM0YFa1vXzhRypHJ88QBdZWtYgDbom4CZjGvGIwc,3074
27
+ copyparty/svchub.py,sha256=mf101Y51z4H86DYjJ0YD1LOWzygTKdHWevUNaQUOGR8,31701
28
28
  copyparty/szip.py,sha256=631TsEwGKV22yAnusJtvE-9fGFWr61HPGBinu-jk1QA,8591
29
29
  copyparty/tcpsrv.py,sha256=2LGUqOBAIrsmL-1pwrbsPXR71gutHccqRp-hjzt91Us,17289
30
30
  copyparty/tftpd.py,sha256=7EHAZ9LnjAXupwRNIENJ2eA8Q0lFynnwwbziV3fyzns,13157
31
- copyparty/th_cli.py,sha256=e2FF-wVY4qcwAO8hrtTlwn3EJIOHyZt5sYhl0N8eudk,4378
32
- copyparty/th_srv.py,sha256=S7rnx6JOjac8ZtZhNvNbIQvrXFiUC_6BrXuiC8re1Xo,25777
31
+ copyparty/th_cli.py,sha256=eSW7sBiaZAsh_XffXFzb035CTSbS3J3Q0G-BMzQGuSY,4385
32
+ copyparty/th_srv.py,sha256=X_nQB7YlwPuj5_4v2jVGh7H2UtNgf_lDAD8aBjcmJag,27161
33
33
  copyparty/u2idx.py,sha256=JBEqKX1ZM8GIvQrDYb5VQ_5QiFNFsjWF6H9drHlPVEY,12709
34
34
  copyparty/up2k.py,sha256=834EGhamt6oXmdHdHS9BxG2LiHm4Q2Ax3NpUv8DbMr4,140723
35
35
  copyparty/util.py,sha256=sClrlGCL-sGVSYAuMAFR-SNXIIj5T2Tl12y6XLR_ikE,81016
@@ -54,31 +54,31 @@ copyparty/stolen/ifaddr/__init__.py,sha256=_BUN7eM5oD2Jgib6B22tEFSb20fD9urNPPaAl
54
54
  copyparty/stolen/ifaddr/_posix.py,sha256=-67NdfGrCktfQPakT2fLbjl2U00QMvyBGkSvrUuTOrU,2626
55
55
  copyparty/stolen/ifaddr/_shared.py,sha256=cJACl8cOxQ-HSYphZTzKMAjAx_TAFyJwUPjfD102Xqw,6111
56
56
  copyparty/stolen/ifaddr/_win32.py,sha256=EE-QyoBgeB7lYQ6z62VjXNaRozaYfCkaJBHGNA8QtZM,4026
57
- copyparty/web/baguettebox.js.gz,sha256=63fwdFjmxwu4uLxfex7S6XHSoBBxwSCDv871TFXJ5i8,7671
58
- copyparty/web/browser.css.gz,sha256=qNsSKexPMkT5uTC6ADVuE2Z_oKVCbHyfzzIznQcOdvc,11434
57
+ copyparty/web/baguettebox.js.gz,sha256=zf2x1n512edRty2zjP6L3-N_jpb2pMWDOjuLFynNyM8,7669
58
+ copyparty/web/browser.css.gz,sha256=dvCrWbY4MswsS5tmDR50eaWk1rhsAecos3x8iKd3v6w,11434
59
59
  copyparty/web/browser.html,sha256=uAejLJd11rV_tQx3h2nHnJ1XY6zn1JV-meIAv74Lc8o,4873
60
- copyparty/web/browser.js.gz,sha256=qZcukHL_Ebe9dah24DF_eFpA81iungYW9_0ypDByuuo,66357
60
+ copyparty/web/browser.js.gz,sha256=hzmgXRCwVPg7rxWnOCGcSsAmOYFjVpgztWU0Yo4639g,67267
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
64
- copyparty/web/md.css.gz,sha256=t7qPxsL-ETVc-yZhdh8vR1e69vI0f1DX85YLMpNlga4,2032
64
+ copyparty/web/md.css.gz,sha256=UZpN0J7ubVM05CZkbZYkQRJeGgJt_GNDEzKTGSQd8h4,2032
65
65
  copyparty/web/md.html,sha256=qnJpj_5-MoVYr9j5Rhy7dS40wctqdWbjELmNa-J9cUY,4110
66
- copyparty/web/md.js.gz,sha256=OMecpy-UHxn8rWM9Bj6BIq0jpjXNHri1A2NLe8de4_E,4179
67
- copyparty/web/md2.css.gz,sha256=NfHqy77R1TfKn6ZXfthdLb74v1dgcwrDH0dhoH0rbgA,699
68
- copyparty/web/md2.js.gz,sha256=LI1dxu7-IaF1DMb6_vgqV-5_AiUvGlAq2tkYNYNs6X0,8350
69
- copyparty/web/mde.css.gz,sha256=QgC399DmXxcCL285jnsbeJH0fKJx-bWhS_PhNjHkMtM,942
66
+ copyparty/web/md.js.gz,sha256=AHRQ3a-PZq_UiGh4CjNwXRllJCvA0IqqYmeHhFWhCig,4179
67
+ copyparty/web/md2.css.gz,sha256=uIVHKScThdbcfhXNSHgKZnALYpxbnXC-WuEzOJ20Lpc,699
68
+ copyparty/web/md2.js.gz,sha256=8xLixaTfTXC808538OOSLhp9AqKowYaunjDeBsbiBEw,8350
69
+ copyparty/web/mde.css.gz,sha256=2SkAEDKIRPqywNJ8t_heQaeBQ_R73Rf-pQI_bDoKF6o,942
70
70
  copyparty/web/mde.html,sha256=FMMq4ySXoOrQV5E836KmQCry3COOhMu0DSstAdJZL_g,1678
71
- copyparty/web/mde.js.gz,sha256=WkK28WXGGwWuhzeoV0MedaHaHtuXCPnzEx3MMa4qgJ4,2224
72
- copyparty/web/msg.css.gz,sha256=RHvNHYTne6HuYkS8i6rlJu6jleVUVQut4zgRa--EKbI,300
71
+ copyparty/web/mde.js.gz,sha256=kN2eUSvr4mFuksfK4-4LimJmWdwsao39Sea2lWtu8L0,2224
72
+ copyparty/web/msg.css.gz,sha256=u90fXYAVrMD-jqwf5XFVC1ptSpSHZUe8Mez6PX101P8,300
73
73
  copyparty/web/msg.html,sha256=XDg51WLO7RruZnoFnKpeJ33k47-tBHP3bR7l55Jwre4,896
74
- copyparty/web/splash.css.gz,sha256=ImOPcJ4KvuMYYesHvEyKAjMTOrybWNvWtjyMUDOIETU,949
74
+ copyparty/web/splash.css.gz,sha256=1IV-0cAs-RNCnvHu-wbSVzKMCkHIblManOLB8d3xcqk,949
75
75
  copyparty/web/splash.html,sha256=KFAvTnofyVURBQyTnrYwuDdEKN_iGtvdjicn5b33tL8,3822
76
- copyparty/web/splash.js.gz,sha256=J8d5JEzEe7absMjw2_5RprS6mc0yduqfUD9aqlbhb5Q,1420
76
+ copyparty/web/splash.js.gz,sha256=2R8UYlAN8WpIABg8clgWckWqgD8nKtz3eGZFu2y1g88,1420
77
77
  copyparty/web/svcs.html,sha256=s7vUSrCrELC3iTemksodRBhQpssO7s4xW1vA-CX6vU8,11702
78
78
  copyparty/web/svcs.js.gz,sha256=k81ZvZ3I-f4fMHKrNGGOgOlvXnCBz0mVjD-8mieoWCA,520
79
- copyparty/web/ui.css.gz,sha256=dm7bH5LWyqxHDdLo5azIRdN1P9pULYtJ9a-_iBM4qOM,2616
80
- copyparty/web/up2k.js.gz,sha256=G2O-0EUj81mmWSMbQ-nnj4UxnQD4X1TskCH9ypAVnSM,22120
81
- copyparty/web/util.js.gz,sha256=-eCegkkPnAsoj33Rspwy93nyvWeYldon1DMgFoiG_1A,14368
79
+ copyparty/web/ui.css.gz,sha256=skuzZHqTU0ag5hButpQmKI9wM7ro-UJ2PnpTodTWYF4,2616
80
+ copyparty/web/up2k.js.gz,sha256=ZuxLQW8mJSvLu_Aa8fDT3F9rptuAzNDmaOLd0MeMrd8,22114
81
+ copyparty/web/util.js.gz,sha256=Ag3zWWzbHoWYQtRnn8frhl3PcUzTIjSOUQiSxQWp1M8,14330
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
@@ -90,9 +90,10 @@ copyparty/web/dd/4.png,sha256=fIwEVmtZNZtloZuVEKPKnkx3SELwRJmB3US61y7t2lI,248
90
90
  copyparty/web/dd/5.png,sha256=Lfpu8-yOlhONuoMbygloKqQVPXSm9gjxH2gUYn5QQAE,250
91
91
  copyparty/web/dd/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
92
92
  copyparty/web/deps/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
93
+ copyparty/web/deps/busy.mp3.gz,sha256=EVphk1_HYyRKJmtpeK99vbAstF7ub1f9ndu020H8PQ8,106
93
94
  copyparty/web/deps/easymde.css.gz,sha256=vWxfueI64rPikuqFj69wJBtGisqf93AheQtOZqgUI_c,3041
94
95
  copyparty/web/deps/easymde.js.gz,sha256=1FykpDM7_FiL4EeZAg4Qcggjoo4PE_MBTgRcBWvjD90,77000
95
- copyparty/web/deps/marked.js.gz,sha256=WhUw0LPWs5cxqORQmW-zajQiUc5rSRkcvf3eT3-XSZc,22372
96
+ copyparty/web/deps/marked.js.gz,sha256=ltXpV8Z7mOCygryr9tkSw1Ydo2lV0CSLxOOsTqGUDiw,22422
96
97
  copyparty/web/deps/mini-fa.css.gz,sha256=CTPrNaH8OTVmxajrGP88E2MkjadY9_81TBVnd9sw9Y8,572
97
98
  copyparty/web/deps/mini-fa.woff,sha256=L9DNncV2TIyvsrspMbJouvnnt7F068Hbn7YZYvN76AU,2784
98
99
  copyparty/web/deps/prism.css.gz,sha256=Z_A6rJ3MN5KWnjvXaV787aTW_5DT-xjFd0YZ7_W-Krk,1468
@@ -101,9 +102,9 @@ copyparty/web/deps/prismd.css.gz,sha256=ObUlksQVr-OuYlTz-I4B23TeBg2QDVVGRnWBz8cV
101
102
  copyparty/web/deps/scp.woff2,sha256=w99BDU5i8MukkMEL-iW0YO9H4vFFZSPWxbkH70ytaAg,8612
102
103
  copyparty/web/deps/sha512.ac.js.gz,sha256=lFZaCLumgWxrvEuDr4bqdKHsqjX82AbVAb7_F45Yk88,7033
103
104
  copyparty/web/deps/sha512.hw.js.gz,sha256=vqoXeracj-99Z5MfY3jK2N4WiSzYQdfjy0RnUlQDhSU,8110
104
- copyparty-1.11.2.dist-info/LICENSE,sha256=gOr4h33pCsBEg9uIy9AYmb7qlocL4V9t2uPJS5wllr0,1072
105
- copyparty-1.11.2.dist-info/METADATA,sha256=SCmvF9MUka_VWs7bT9XLaAUhusVnqbV9dG9KQvLCvEM,116223
106
- copyparty-1.11.2.dist-info/WHEEL,sha256=GJ7t_kWBFywbagK5eo9IoUwLW6oyOeTKmQ-9iHFVNxQ,92
107
- copyparty-1.11.2.dist-info/entry_points.txt,sha256=4zw6a3rqASywQomiYLObjjlxybaI65LYYOTJwgKz7b0,128
108
- copyparty-1.11.2.dist-info/top_level.txt,sha256=LnYUPsDyk-8kFgM6YJLG4h820DQekn81cObKSu9g-sI,10
109
- copyparty-1.11.2.dist-info/RECORD,,
105
+ copyparty-1.12.0.dist-info/LICENSE,sha256=gOr4h33pCsBEg9uIy9AYmb7qlocL4V9t2uPJS5wllr0,1072
106
+ copyparty-1.12.0.dist-info/METADATA,sha256=aJEMFtvG-b_ofuoUiX9c9oD8ilOhsZyafcf7eeGpfAU,117598
107
+ copyparty-1.12.0.dist-info/WHEEL,sha256=GJ7t_kWBFywbagK5eo9IoUwLW6oyOeTKmQ-9iHFVNxQ,92
108
+ copyparty-1.12.0.dist-info/entry_points.txt,sha256=4zw6a3rqASywQomiYLObjjlxybaI65LYYOTJwgKz7b0,128
109
+ copyparty-1.12.0.dist-info/top_level.txt,sha256=LnYUPsDyk-8kFgM6YJLG4h820DQekn81cObKSu9g-sI,10
110
+ copyparty-1.12.0.dist-info/RECORD,,