copyparty 1.9.12__py3-none-any.whl → 1.9.14__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
@@ -27,6 +27,8 @@ from .authsrv import expand_config_file, re_vol, split_cfg_ln, upgrade_cfg_fmt
27
27
  from .cfg import flagcats, onedash
28
28
  from .svchub import SvcHub
29
29
  from .util import (
30
+ DEF_MTE,
31
+ DEF_MTH,
30
32
  IMPLICATIONS,
31
33
  JINJA_VER,
32
34
  PYFTPD_VER,
@@ -178,7 +180,10 @@ def init_E(E ) :
178
180
 
179
181
  with open_binary("copyparty", "z.tar") as tgz:
180
182
  with tarfile.open(fileobj=tgz) as tf:
181
- tf.extractall(tdn) # nosec (archive is safe)
183
+ try:
184
+ tf.extractall(tdn, filter="tar")
185
+ except TypeError:
186
+ tf.extractall(tdn) # nosec (archive is safe)
182
187
 
183
188
  return tdn
184
189
 
@@ -800,6 +805,8 @@ def add_upload(ap):
800
805
  ap2.add_argument("--no-dedup", action="store_true", help="disable symlink/hardlink creation; copy file contents instead (volflag=copydupes)")
801
806
  ap2.add_argument("--no-dupe", action="store_true", help="reject duplicate files during upload; only matches within the same volume (volflag=nodupe)")
802
807
  ap2.add_argument("--no-snap", action="store_true", help="disable snapshots -- forget unfinished uploads on shutdown; don't create .hist/up2k.snap files -- abandoned/interrupted uploads must be cleaned up manually")
808
+ ap2.add_argument("--snap-wri", metavar="SEC", type=int, default=300, help="write upload state to ./hist/up2k.snap every SEC seconds; allows resuming incomplete uploads after a server crash")
809
+ ap2.add_argument("--snap-drop", metavar="MIN", type=float, default=1440, help="forget unfinished uploads after MIN minutes; impossible to resume them after that (360=6h, 1440=24h)")
803
810
  ap2.add_argument("--u2ts", metavar="TXT", type=u, default="c", help="how to timestamp uploaded files; [\033[32mc\033[0m]=client-last-modified, [\033[32mu\033[0m]=upload-time, [\033[32mfc\033[0m]=force-c, [\033[32mfu\033[0m]=force-u (volflag=u2ts)")
804
811
  ap2.add_argument("--rand", action="store_true", help="force randomized filenames, --nrand chars long (volflag=rand)")
805
812
  ap2.add_argument("--nrand", metavar="NUM", type=int, default=9, help="randomized filenames length (volflag=nrand)")
@@ -990,7 +997,7 @@ def add_optouts(ap):
990
997
  def add_safety(ap):
991
998
  ap2 = ap.add_argument_group('safety options')
992
999
  ap2.add_argument("-s", action="count", default=0, help="increase safety: Disable thumbnails / potentially dangerous software (ffmpeg/pillow/vips), hide partial uploads, avoid crawlers.\n └─Alias of\033[32m --dotpart --no-thumb --no-mtag-ff --no-robots --force-js")
993
- ap2.add_argument("-ss", action="store_true", help="further increase safety: Prevent js-injection, accidental move/delete, broken symlinks, webdav, 404 on 403, ban on excessive 404s.\n └─Alias of\033[32m -s --unpost=0 --no-del --no-mv --hardlink --vague-403 --ban-404=50,60,1440 --turbo=-1 -nih")
1000
+ ap2.add_argument("-ss", action="store_true", help="further increase safety: Prevent js-injection, accidental move/delete, broken symlinks, webdav, 404 on 403, ban on excessive 404s.\n └─Alias of\033[32m -s --unpost=0 --no-del --no-mv --hardlink --vague-403 -nih")
994
1001
  ap2.add_argument("-sss", action="store_true", help="further increase safety: Enable logging to disk, scan for dangerous symlinks.\n └─Alias of\033[32m -ss --no-dav --no-logues --no-readme -lo=cpp-%%Y-%%m%%d-%%H%%M%%S.txt.xz --ls=**,*,ln,p,r")
995
1002
  ap2.add_argument("--ls", metavar="U[,V[,F]]", type=u, help="do a sanity/safety check of all volumes on startup; arguments \033[33mUSER\033[0m,\033[33mVOL\033[0m,\033[33mFLAGS\033[0m; example [\033[32m**,*,ln,p,r\033[0m]")
996
1003
  ap2.add_argument("--xvol", action="store_true", help="never follow symlinks leaving the volume root, unless the link is into another volume where the user has similar access (volflag=xvol)")
@@ -1004,10 +1011,10 @@ def add_safety(ap):
1004
1011
  ap2.add_argument("--no-robots", action="store_true", help="adds http and html headers asking search engines to not index anything (volflag=norobots)")
1005
1012
  ap2.add_argument("--logout", metavar="H", type=float, default="8086", help="logout clients after H hours of inactivity; [\033[32m0.0028\033[0m]=10sec, [\033[32m0.1\033[0m]=6min, [\033[32m24\033[0m]=day, [\033[32m168\033[0m]=week, [\033[32m720\033[0m]=month, [\033[32m8760\033[0m]=year)")
1006
1013
  ap2.add_argument("--ban-pw", metavar="N,W,B", type=u, default="9,60,1440", help="more than \033[33mN\033[0m wrong passwords in \033[33mW\033[0m minutes = ban for \033[33mB\033[0m minutes; disable with [\033[32mno\033[0m]")
1007
- ap2.add_argument("--ban-404", metavar="N,W,B", type=u, default="no", help="hitting more than \033[33mN\033[0m 404's in \033[33mW\033[0m minutes = ban for \033[33mB\033[0m minutes (disabled by default since turbo-up2k counts as 404s)")
1014
+ ap2.add_argument("--ban-404", metavar="N,W,B", type=u, default="50,60,1440", help="hitting more than \033[33mN\033[0m 404's in \033[33mW\033[0m minutes = ban for \033[33mB\033[0m minutes; only affects users who cannot see directory listings because their access is either g/G/h")
1008
1015
  ap2.add_argument("--ban-403", metavar="N,W,B", type=u, default="9,2,1440", help="hitting more than \033[33mN\033[0m 403's in \033[33mW\033[0m minutes = ban for \033[33mB\033[0m minutes; [\033[32m1440\033[0m]=day, [\033[32m10080\033[0m]=week, [\033[32m43200\033[0m]=month")
1009
1016
  ap2.add_argument("--ban-422", metavar="N,W,B", type=u, default="9,2,1440", help="hitting more than \033[33mN\033[0m 422's in \033[33mW\033[0m minutes = ban for \033[33mB\033[0m minutes (422 is server fuzzing, invalid POSTs and so)")
1010
- ap2.add_argument("--ban-url", metavar="N,W,B", type=u, default="9,2,1440", help="hitting more than \033[33mN\033[0m sus URL's in \033[33mW\033[0m minutes = ban for \033[33mB\033[0m minutes (decent replacement for --ban-404 if that can't be used)")
1017
+ ap2.add_argument("--ban-url", metavar="N,W,B", type=u, default="9,2,1440", help="hitting more than \033[33mN\033[0m sus URL's in \033[33mW\033[0m minutes = ban for \033[33mB\033[0m minutes; applies only to access g/G/h (decent replacement for --ban-404 if that can't be used)")
1011
1018
  ap2.add_argument("--sus-urls", metavar="R", type=u, default=r"\.php$|(^|/)wp-(admin|content|includes)/", help="URLs which are considered sus / eligible for banning; disable with blank or [\033[32mno\033[0m]")
1012
1019
  ap2.add_argument("--nonsus-urls", metavar="R", type=u, default=r"^(favicon\.ico|robots\.txt)$|^apple-touch-icon|^\.well-known", help="harmless URLs ignored from 404-bans; disable with blank or [\033[32mno\033[0m]")
1013
1020
  ap2.add_argument("--aclose", metavar="MIN", type=int, default=10, help="if a client maxes out the server connection limit, downgrade it from connection:keep-alive to connection:close for MIN minutes (and also kill its active connections) -- disable with 0")
@@ -1126,10 +1133,8 @@ def add_db_metadata(ap):
1126
1133
  ap2.add_argument("--mtag-v", action="store_true", help="verbose tag scanning; print errors from mtp subprocesses and such")
1127
1134
  ap2.add_argument("--mtag-vv", action="store_true", help="debug mtp settings and mutagen/ffprobe parsers")
1128
1135
  ap2.add_argument("-mtm", metavar="M=t,t,t", type=u, action="append", help="add/replace metadata mapping")
1129
- ap2.add_argument("-mte", metavar="M,M,M", type=u, help="tags to index/display (comma-sep.)",
1130
- default="circle,album,.tn,artist,title,.bpm,key,.dur,.q,.vq,.aq,vc,ac,fmt,res,.fps,ahash,vhash,up_ip,.up_at")
1131
- ap2.add_argument("-mth", metavar="M,M,M", type=u, help="tags to hide by default (comma-sep.)",
1132
- default=".vq,.aq,vc,ac,fmt,res,.fps")
1136
+ ap2.add_argument("-mte", metavar="M,M,M", type=u, help="tags to index/display (comma-sep.); either an entire replacement list, or add/remove stuff on the default-list with +foo or /bar", default=DEF_MTE)
1137
+ ap2.add_argument("-mth", metavar="M,M,M", type=u, help="tags to hide by default (comma-sep.); assign/add/remove same as -mte", default=DEF_MTH)
1133
1138
  ap2.add_argument("-mtp", metavar="M=[f,]BIN", type=u, action="append", help="read tag M using program BIN to parse the file")
1134
1139
 
1135
1140
 
copyparty/__version__.py CHANGED
@@ -1,8 +1,8 @@
1
1
  # coding: utf-8
2
2
 
3
- VERSION = (1, 9, 12)
3
+ VERSION = (1, 9, 14)
4
4
  CODENAME = "prometheable"
5
- BUILD_DT = (2023, 10, 15)
5
+ BUILD_DT = (2023, 10, 21)
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
@@ -21,11 +21,14 @@ from .util import (
21
21
  META_NOBOTS,
22
22
  SQLITE_VER,
23
23
  UNPLICATIONS,
24
+ ODict,
24
25
  Pebkac,
26
+ UTC,
25
27
  absreal,
26
28
  afsenc,
27
29
  get_df,
28
30
  humansize,
31
+ odfusion,
29
32
  relchk,
30
33
  statdir,
31
34
  uncyg,
@@ -206,7 +209,7 @@ class Lim(object):
206
209
  if self.rot_re.search(path.replace("\\", "/")):
207
210
  return path, ""
208
211
 
209
- suf = datetime.utcnow().strftime(self.rotf)
212
+ suf = datetime.now(UTC).strftime(self.rotf)
210
213
  if path:
211
214
  path += "/"
212
215
 
@@ -1419,12 +1422,12 @@ class AuthSrv(object):
1419
1422
 
1420
1423
  for ga, vf in [["no_hash", "nohash"], ["no_idx", "noidx"]]:
1421
1424
  if vf in vol.flags:
1422
- ptn = vol.flags.pop(vf)
1425
+ ptn = re.compile(vol.flags.pop(vf))
1423
1426
  else:
1424
1427
  ptn = getattr(self.args, ga)
1425
1428
 
1426
1429
  if ptn:
1427
- vol.flags[vf] = re.compile(ptn)
1430
+ vol.flags[vf] = ptn
1428
1431
 
1429
1432
  for ga, vf in vf_bmap().items():
1430
1433
  if getattr(self.args, ga):
@@ -1470,13 +1473,14 @@ class AuthSrv(object):
1470
1473
 
1471
1474
  # default tag cfgs if unset
1472
1475
  if "mte" not in vol.flags:
1473
- vol.flags["mte"] = self.args.mte
1474
- elif vol.flags["mte"].startswith("+"):
1475
- vol.flags["mte"] = ",".join(
1476
- x for x in [self.args.mte, vol.flags["mte"][1:]] if x
1477
- )
1476
+ vol.flags["mte"] = self.args.mte.copy()
1477
+ else:
1478
+ vol.flags["mte"] = odfusion(self.args.mte, vol.flags["mte"])
1479
+
1478
1480
  if "mth" not in vol.flags:
1479
- vol.flags["mth"] = self.args.mth
1481
+ vol.flags["mth"] = self.args.mth.copy()
1482
+ else:
1483
+ vol.flags["mth"] = odfusion(self.args.mth, vol.flags["mth"])
1480
1484
 
1481
1485
  # append additive args from argv to volflags
1482
1486
  hooks = "xbu xau xiu xbr xar xbd xad xm xban".split()
@@ -1530,7 +1534,11 @@ class AuthSrv(object):
1530
1534
  if vol.flags.get(grp, False):
1531
1535
  continue
1532
1536
 
1533
- vol.flags = {k: v for k, v in vol.flags.items() if not k.startswith(rm)}
1537
+ vol.flags = {
1538
+ k: v
1539
+ for k, v in vol.flags.items()
1540
+ if not k.startswith(rm) or k == "mte"
1541
+ }
1534
1542
 
1535
1543
  for grp, rm in [["d2v", "e2v"]]:
1536
1544
  if not vol.flags.get(grp, False):
@@ -1577,12 +1585,12 @@ class AuthSrv(object):
1577
1585
  if local:
1578
1586
  local_only_mtp[a] = True
1579
1587
 
1580
- local_mte = {}
1581
- for a in vol.flags.get("mte", "").split(","):
1588
+ local_mte = ODict()
1589
+ for a in vol.flags.get("mte", {}).keys():
1582
1590
  local = True
1583
1591
  all_mte[a] = True
1584
1592
  local_mte[a] = True
1585
- for b in self.args.mte.split(","):
1593
+ for b in self.args.mte.keys():
1586
1594
  if not a or not b:
1587
1595
  continue
1588
1596
 
copyparty/cfg.py CHANGED
@@ -13,12 +13,20 @@ def vf_bmap() :
13
13
  "no_dedup": "copydupes",
14
14
  "no_dupe": "nodupe",
15
15
  "no_forget": "noforget",
16
+ "no_robots": "norobots",
17
+ "no_thumb": "dthumb",
18
+ "no_vthumb": "dvthumb",
19
+ "no_athumb": "dathumb",
20
+ "re_maxage": "scan",
16
21
  "th_no_crop": "nocrop",
17
22
  "dav_auth": "davauth",
18
23
  "dav_rt": "davrt",
19
24
  }
20
25
  for k in (
21
26
  "dotsrch",
27
+ "e2d",
28
+ "e2ds",
29
+ "e2dsa",
22
30
  "e2t",
23
31
  "e2ts",
24
32
  "e2tsr",
@@ -41,7 +49,12 @@ def vf_bmap() :
41
49
 
42
50
  def vf_vmap() :
43
51
  """argv-to-volflag: simple values"""
44
- ret = {"th_convt": "convt", "th_size": "thsize"}
52
+ ret = {
53
+ "no_hash": "nohash",
54
+ "no_idx": "noidx",
55
+ "th_convt": "convt",
56
+ "th_size": "thsize",
57
+ }
45
58
  for k in ("dbd", "lg_sbf", "md_sbf", "nrand", "sort", "unlist", "u2ts"):
46
59
  ret[k] = k
47
60
  return ret
@@ -50,7 +63,21 @@ def vf_vmap() :
50
63
  def vf_cmap() :
51
64
  """argv-to-volflag: complex/lists"""
52
65
  ret = {}
53
- for k in ("html_head", "mte", "mth"):
66
+ for k in (
67
+ "html_head",
68
+ "mte",
69
+ "mth",
70
+ "mtp",
71
+ "xad",
72
+ "xar",
73
+ "xau",
74
+ "xban",
75
+ "xbd",
76
+ "xbr",
77
+ "xbu",
78
+ "xiu",
79
+ "xm",
80
+ ):
54
81
  ret[k] = k
55
82
  return ret
56
83
 
copyparty/httpcli.py CHANGED
@@ -40,7 +40,9 @@ from .util import (
40
40
  HTTPCODE,
41
41
  META_NOBOTS,
42
42
  MultipartParser,
43
+ ODict,
43
44
  Pebkac,
45
+ UTC,
44
46
  UnrecvEOF,
45
47
  absreal,
46
48
  alltrace,
@@ -650,6 +652,7 @@ class HttpCli(object):
650
652
  and not body.startswith(b"<pre>source file busy")
651
653
  )
652
654
  )
655
+ and (status != 404 or (self.can_get and not self.can_read))
653
656
  ):
654
657
  if status == 404:
655
658
  g = self.conn.hsrv.g404
@@ -1967,8 +1970,7 @@ class HttpCli(object):
1967
1970
  self.log("q#: {} ({:.2f}s)".format(msg, idx.p_dur))
1968
1971
 
1969
1972
  order = []
1970
- cfg = self.args.mte.split(",")
1971
- for t in cfg:
1973
+ for t in self.args.mte:
1972
1974
  if t in taglist:
1973
1975
  order.append(t)
1974
1976
  for t in taglist:
@@ -3987,7 +3989,7 @@ class HttpCli(object):
3987
3989
  margin = "-"
3988
3990
 
3989
3991
  sz = inf.st_size
3990
- zd = datetime.utcfromtimestamp(linf.st_mtime)
3992
+ zd = datetime.fromtimestamp(linf.st_mtime, UTC)
3991
3993
  dt = "%04d-%02d-%02d %02d:%02d:%02d" % (
3992
3994
  zd.year,
3993
3995
  zd.month,
@@ -4047,6 +4049,9 @@ class HttpCli(object):
4047
4049
  ap = vn.canonical(rem)
4048
4050
  return self.tx_file(ap) # is no-cache
4049
4051
 
4052
+ mte = vn.flags.get("mte", {})
4053
+ add_up_at = ".up_at" in mte
4054
+ is_admin = self.can_admin
4050
4055
  tagset = set()
4051
4056
  for fe in files:
4052
4057
  fn = fe["name"]
@@ -4074,24 +4079,38 @@ class HttpCli(object):
4074
4079
  self.log(t.format(rd, fn, min_ex()))
4075
4080
  break
4076
4081
 
4077
- fe["tags"] = {k: v for k, v in r}
4082
+ tags = {k: v for k, v in r}
4078
4083
 
4079
- if self.can_admin:
4084
+ if is_admin:
4080
4085
  q = "select ip, at from up where rd=? and fn=?"
4081
4086
  try:
4082
4087
  zs1, zs2 = icur.execute(q, erd_efn).fetchone()
4083
- fe["tags"]["up_ip"] = zs1
4084
- fe["tags"][".up_at"] = zs2
4088
+ if zs1:
4089
+ tags["up_ip"] = zs1
4090
+ if zs2:
4091
+ tags[".up_at"] = zs2
4092
+ except:
4093
+ pass
4094
+ elif add_up_at:
4095
+ q = "select at from up where rd=? and fn=?"
4096
+ try:
4097
+ (zs1,) = icur.execute(q, erd_efn).fetchone()
4098
+ if zs1:
4099
+ tags[".up_at"] = zs1
4085
4100
  except:
4086
4101
  pass
4087
4102
 
4088
- _ = [tagset.add(k) for k in fe["tags"]]
4103
+ _ = [tagset.add(k) for k in tags]
4104
+ fe["tags"] = tags
4089
4105
 
4090
4106
  if icur:
4091
- mte = vn.flags.get("mte") or "up_ip,.up_at"
4092
- taglist = [k for k in mte.split(",") if k in tagset]
4107
+ lmte = list(mte)
4108
+ if self.can_admin:
4109
+ lmte += ["up_ip", ".up_at"]
4110
+
4111
+ taglist = [k for k in lmte if k in tagset]
4093
4112
  for fe in dirs:
4094
- fe["tags"] = {}
4113
+ fe["tags"] = ODict()
4095
4114
  else:
4096
4115
  taglist = list(tagset)
4097
4116
 
@@ -4138,7 +4157,7 @@ class HttpCli(object):
4138
4157
  j2a["txt_ext"] = self.args.textfiles.replace(",", " ")
4139
4158
 
4140
4159
  if "mth" in vn.flags:
4141
- j2a["def_hcols"] = vn.flags["mth"].split(",")
4160
+ j2a["def_hcols"] = list(vn.flags["mth"])
4142
4161
 
4143
4162
  html = self.j2s(tpl, **j2a)
4144
4163
  self.reply(html.encode("utf-8", "replace"))
copyparty/sutil.py CHANGED
@@ -8,7 +8,7 @@ from datetime import datetime
8
8
  from .__init__ import CORES
9
9
  from .bos import bos
10
10
  from .th_cli import ThumbCli
11
- from .util import vjoin
11
+ from .util import UTC, vjoin
12
12
 
13
13
  class StreamArc(object):
14
14
  def __init__(
@@ -102,7 +102,7 @@ def errdesc(errors ) :
102
102
  tf_path = tf.name
103
103
  tf.write("\r\n".join(report).encode("utf-8", "replace"))
104
104
 
105
- dt = datetime.utcnow().strftime("%Y-%m%d-%H%M%S")
105
+ dt = datetime.now(UTC).strftime("%Y-%m%d-%H%M%S")
106
106
 
107
107
  bos.chmod(tf_path, 0o444)
108
108
  return {
copyparty/svchub.py CHANGED
@@ -33,13 +33,18 @@ from .util import (
33
33
  FFMPEG_URL,
34
34
  VERSIONS,
35
35
  Daemon,
36
+ DEF_MTE,
37
+ DEF_MTH,
36
38
  Garda,
37
39
  HLog,
38
40
  HMaccas,
41
+ ODict,
42
+ UTC,
39
43
  alltrace,
40
44
  ansi_re,
41
45
  min_ex,
42
46
  mp,
47
+ odfusion,
43
48
  pybin,
44
49
  start_log_thrs,
45
50
  start_stackmon,
@@ -109,8 +114,6 @@ class SvcHub(object):
109
114
  args.no_mv = True
110
115
  args.hardlink = True
111
116
  args.vague_403 = True
112
- args.ban_404 = "50,60,1440"
113
- args.turbo = -1
114
117
  args.nih = True
115
118
 
116
119
  if args.s:
@@ -427,6 +430,17 @@ class SvcHub(object):
427
430
  zs = al.xff_src.replace(" ", "").replace(".", "\\.").replace(",", "|")
428
431
  al.xff_re = re.compile("^(?:" + zs + ")")
429
432
 
433
+ mte = ODict.fromkeys(DEF_MTE.split(","), True)
434
+ al.mte = odfusion(mte, al.mte)
435
+
436
+ mth = ODict.fromkeys(DEF_MTH.split(","), True)
437
+ al.mth = odfusion(mth, al.mth)
438
+
439
+ for k in ["no_hash", "no_idx"]:
440
+ ptn = getattr(self.args, k)
441
+ if ptn:
442
+ setattr(self.args, k, re.compile(ptn))
443
+
430
444
  return True
431
445
 
432
446
  def _setlimits(self) :
@@ -470,7 +484,7 @@ class SvcHub(object):
470
484
  self.args.nc = min(self.args.nc, soft // 2)
471
485
 
472
486
  def _logname(self) :
473
- dt = datetime.utcnow()
487
+ dt = datetime.now(UTC)
474
488
  fn = str(self.args.lo)
475
489
  for fs in "YmdHMS":
476
490
  fs = "%" + fs
@@ -719,7 +733,7 @@ class SvcHub(object):
719
733
  return
720
734
 
721
735
  with self.log_mutex:
722
- zd = datetime.utcnow()
736
+ zd = datetime.now(UTC)
723
737
  ts = self.log_dfmt % (
724
738
  zd.year,
725
739
  zd.month * 100 + zd.day,
@@ -737,7 +751,7 @@ class SvcHub(object):
737
751
  self.logf.close()
738
752
  self._setup_logfile("")
739
753
 
740
- dt = datetime.utcnow()
754
+ dt = datetime.now(UTC)
741
755
 
742
756
  # unix timestamp of next 00:00:00 (leap-seconds safe)
743
757
  day_now = dt.day
@@ -745,14 +759,20 @@ class SvcHub(object):
745
759
  dt += timedelta(hours=12)
746
760
 
747
761
  dt = dt.replace(hour=0, minute=0, second=0)
748
- self.next_day = calendar.timegm(dt.utctimetuple())
762
+ try:
763
+ tt = dt.utctimetuple()
764
+ except:
765
+ # still makes me hella uncomfortable
766
+ tt = dt.timetuple()
767
+
768
+ self.next_day = calendar.timegm(tt)
749
769
 
750
770
  def _log_enabled(self, src , msg , c = 0) :
751
771
  """handles logging from all components"""
752
772
  with self.log_mutex:
753
773
  now = time.time()
754
774
  if now >= self.next_day:
755
- dt = datetime.utcfromtimestamp(now)
775
+ dt = datetime.fromtimestamp(now, UTC)
756
776
  zs = "{}\n" if self.no_ansi else "\033[36m{}\033[0m\n"
757
777
  zs = zs.format(dt.strftime("%Y-%m-%d"))
758
778
  print(zs, end="")
@@ -775,7 +795,7 @@ class SvcHub(object):
775
795
  else:
776
796
  msg = "%s%s\033[0m" % (c, msg)
777
797
 
778
- zd = datetime.utcfromtimestamp(now)
798
+ zd = datetime.fromtimestamp(now, UTC)
779
799
  ts = self.log_efmt % (
780
800
  zd.hour,
781
801
  zd.minute,
copyparty/u2idx.py CHANGED
@@ -178,6 +178,11 @@ class U2idx(object):
178
178
  is_date = True
179
179
  have_up = True
180
180
 
181
+ elif v == "up_at":
182
+ v = "up.at"
183
+ is_date = True
184
+ have_up = True
185
+
181
186
  elif v == "path":
182
187
  v = "trim(?||up.rd,'/')"
183
188
  va.append("\nrd")
copyparty/up2k.py CHANGED
@@ -24,7 +24,7 @@ from queue import Queue
24
24
  from .__init__ import ANYWIN, PY2, TYPE_CHECKING, WINDOWS
25
25
  from .authsrv import LEELOO_DALLAS, SSEELOG, VFS, AuthSrv
26
26
  from .bos import bos
27
- from .cfg import vf_bmap, vf_vmap
27
+ from .cfg import vf_bmap, vf_cmap, vf_vmap
28
28
  from .fsutil import Fstab
29
29
  from .mtag import MParser, MTag
30
30
  from .util import (
@@ -131,8 +131,6 @@ class Up2k(object):
131
131
  self.vol_act = {}
132
132
  self.busy_aps = set()
133
133
  self.dupesched = {}
134
- self.snap_persist_interval = 300 # persist unfinished index every 5 min
135
- self.snap_discard_interval = 21600 # drop unfinished after 6 hours inactivity
136
134
  self.snap_prev = {}
137
135
 
138
136
  self.mtag = None
@@ -639,10 +637,7 @@ class Up2k(object):
639
637
  if self.stop:
640
638
  break
641
639
 
642
- en = set()
643
- if "mte" in vol.flags:
644
- en = set(vol.flags["mte"].split(","))
645
-
640
+ en = set(vol.flags.get("mte", {}))
646
641
  self.entags[vol.realpath] = en
647
642
 
648
643
  if "e2d" in vol.flags:
@@ -798,13 +793,25 @@ class Up2k(object):
798
793
  fv = "\033[0;36m{}:\033[90m{}"
799
794
  fx = set(("html_head",))
800
795
  fd = vf_bmap()
796
+ fd.update(vf_cmap())
801
797
  fd.update(vf_vmap())
802
798
  fd = {v: k for k, v in fd.items()}
803
799
  fl = {
804
800
  k: v
805
801
  for k, v in flags.items()
806
- if k not in fd or v != getattr(self.args, fd[k])
802
+ if k not in fd
803
+ or (
804
+ v != getattr(self.args, fd[k])
805
+ and str(v) != str(getattr(self.args, fd[k]))
806
+ )
807
807
  }
808
+ for k1, k2 in vf_cmap().items():
809
+ if k1 not in fl or k1 in fx:
810
+ continue
811
+ if str(fl[k1]) == str(getattr(self.args, k2)):
812
+ del fl[k1]
813
+ else:
814
+ fl[k1] = ",".join(x for x in fl)
808
815
  a = [
809
816
  (ft if v is True else ff if v is False else fv).format(k, str(v))
810
817
  for k, v in fl.items()
@@ -823,7 +830,7 @@ class Up2k(object):
823
830
  vpath += "/"
824
831
 
825
832
  zs = " ".join(sorted(a))
826
- zs = zs.replace("30mre.compile(", "30m(") # nohash
833
+ zs = zs.replace("90mre.compile(", "90m(") # nohash
827
834
  self.log("/{} {}".format(vpath, zs), "35")
828
835
 
829
836
  reg = {}
@@ -2132,7 +2139,7 @@ class Up2k(object):
2132
2139
 
2133
2140
  try:
2134
2141
  nfiles = next(cur.execute("select count(w) from up"))[0]
2135
- self.log("OK: {} |{}|".format(db_path, nfiles))
2142
+ self.log(" {} |{}|".format(db_path, nfiles), "90")
2136
2143
  return cur
2137
2144
  except:
2138
2145
  self.log("WARN: could not list files; DB corrupt?\n" + min_ex())
@@ -3745,13 +3752,16 @@ class Up2k(object):
3745
3752
  self._finish_upload(job["ptop"], job["wark"])
3746
3753
 
3747
3754
  def _snapshot(self) :
3748
- slp = self.snap_persist_interval
3755
+ slp = self.args.snap_wri
3756
+ if not slp or self.args.no_snap:
3757
+ return
3758
+
3749
3759
  while True:
3750
3760
  time.sleep(slp)
3751
3761
  if self.pp:
3752
3762
  slp = 5
3753
3763
  else:
3754
- slp = self.snap_persist_interval
3764
+ slp = self.args.snap_wri
3755
3765
  self.do_snapshot()
3756
3766
 
3757
3767
  def do_snapshot(self) :
@@ -3765,11 +3775,8 @@ class Up2k(object):
3765
3775
  if not histpath:
3766
3776
  return
3767
3777
 
3768
- rm = [
3769
- x
3770
- for x in reg.values()
3771
- if x["need"] and now - x["poke"] > self.snap_discard_interval
3772
- ]
3778
+ idrop = self.args.snap_drop * 60
3779
+ rm = [x for x in reg.values() if x["need"] and now - x["poke"] >= idrop]
3773
3780
 
3774
3781
  if self.args.nw:
3775
3782
  lost = []
copyparty/util.py CHANGED
@@ -25,7 +25,6 @@ import threading
25
25
  import time
26
26
  import traceback
27
27
  from collections import Counter
28
- from datetime import datetime
29
28
  from email.utils import formatdate
30
29
 
31
30
  from ipaddress import IPv4Address, IPv4Network, IPv6Address, IPv6Network
@@ -35,6 +34,35 @@ from .__init__ import ANYWIN, EXE, MACOS, PY2, TYPE_CHECKING, VT100, WINDOWS
35
34
  from .__version__ import S_BUILD_DT, S_VERSION
36
35
  from .stolen import surrogateescape
37
36
 
37
+ try:
38
+ from datetime import datetime, timezone
39
+
40
+ UTC = timezone.utc
41
+ except:
42
+ from datetime import datetime, timedelta, tzinfo
43
+
44
+ TD_ZERO = timedelta(0)
45
+
46
+ class _UTC(tzinfo):
47
+ def utcoffset(self, dt):
48
+ return TD_ZERO
49
+
50
+ def tzname(self, dt):
51
+ return "UTC"
52
+
53
+ def dst(self, dt):
54
+ return TD_ZERO
55
+
56
+ UTC = _UTC()
57
+
58
+
59
+ if sys.version_info >= (3, 7) or (
60
+ sys.version_info >= (3, 6) and platform.python_implementation() == "CPython"
61
+ ):
62
+ ODict = dict
63
+ else:
64
+ from collections import OrderedDict as ODict
65
+
38
66
 
39
67
  def _ens(want ) :
40
68
  ret = []
@@ -245,6 +273,11 @@ EXTS["vnd.mozilla.apng"] = "png"
245
273
  MAGIC_MAP = {"jpeg": "jpg"}
246
274
 
247
275
 
276
+ DEF_MTE = "circle,album,.tn,artist,title,.bpm,key,.dur,.q,.vq,.aq,vc,ac,fmt,res,.fps,ahash,vhash"
277
+
278
+ DEF_MTH = ".vq,.aq,vc,ac,fmt,res,.fps"
279
+
280
+
248
281
  REKOBO_KEY = {
249
282
  v: ln.split(" ", 1)[0]
250
283
  for ln in """
@@ -1102,7 +1135,7 @@ def stackmon(fp , ival , suffix ) :
1102
1135
  buf = lzma.compress(buf, preset=0)
1103
1136
 
1104
1137
  if "%" in fp:
1105
- dt = datetime.utcnow()
1138
+ dt = datetime.now(UTC)
1106
1139
  for fs in "YmdHMS":
1107
1140
  fs = "%" + fs
1108
1141
  if fs in fp:
@@ -1758,6 +1791,21 @@ def exclude_dotfiles(filepaths ) :
1758
1791
  return [x for x in filepaths if not x.split("/")[-1].startswith(".")]
1759
1792
 
1760
1793
 
1794
+ def odfusion(base , oth ) :
1795
+ # merge an "ordered set" (just a dict really) with another list of keys
1796
+ ret = base.copy()
1797
+ if oth.startswith("+"):
1798
+ for k in oth[1:].split(","):
1799
+ ret[k] = True
1800
+ elif oth[:1] in ("-", "/"):
1801
+ for k in oth[1:].split(","):
1802
+ ret.pop(k, None)
1803
+ else:
1804
+ ret = ODict.fromkeys(oth.split(","), True)
1805
+
1806
+ return ret
1807
+
1808
+
1761
1809
  def ipnorm(ip ) :
1762
1810
  if ":" in ip:
1763
1811
  # assume /64 clients; drop 4 groups
@@ -46,12 +46,13 @@ import traceback
46
46
  import http.client # py2: httplib
47
47
  import urllib.parse
48
48
  import calendar
49
- from datetime import datetime
49
+ from datetime import datetime, timezone
50
50
  from urllib.parse import quote_from_bytes as quote
51
51
  from urllib.parse import unquote_to_bytes as unquote
52
52
 
53
53
  WINDOWS = sys.platform == "win32"
54
54
  MACOS = platform.system() == "Darwin"
55
+ UTC = timezone.utc
55
56
  info = log = dbg = None
56
57
 
57
58
 
@@ -176,7 +177,7 @@ class RecentLog(object):
176
177
  def put(self, msg):
177
178
  msg = "{:10.6f} {} {}\n".format(time.time() % 900, rice_tid(), msg)
178
179
  if self.f:
179
- fmsg = " ".join([datetime.utcnow().strftime("%H%M%S.%f"), str(msg)])
180
+ fmsg = " ".join([datetime.now(UTC).strftime("%H%M%S.%f"), str(msg)])
180
181
  self.f.write(fmsg.encode("utf-8"))
181
182
 
182
183
  with self.mtx:
Binary file
Binary file
copyparty/web/up2k.js.gz CHANGED
Binary file
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: copyparty
3
- Version: 1.9.12
3
+ Version: 1.9.14
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
@@ -71,9 +71,8 @@ turn almost any device into a file server with resumable uploads/downloads using
71
71
  * [testimonials](#testimonials) - small collection of user feedback
72
72
  * [motivations](#motivations) - project goals / philosophy
73
73
  * [notes](#notes) - general notes
74
- * [bugs](#bugs)
75
- * [general bugs](#general-bugs)
76
- * [not my bugs](#not-my-bugs)
74
+ * [bugs](#bugs) - roughly sorted by chance of encounter
75
+ * [not my bugs](#not-my-bugs) - same order here too
77
76
  * [breaking changes](#breaking-changes) - upgrade notes
78
77
  * [FAQ](#FAQ) - "frequently" asked questions
79
78
  * [accounts and volumes](#accounts-and-volumes) - per-folder, per-user permissions
@@ -91,7 +90,7 @@ turn almost any device into a file server with resumable uploads/downloads using
91
90
  * [file manager](#file-manager) - cut/paste, rename, and delete files/folders (if you have permission)
92
91
  * [batch rename](#batch-rename) - select some files and press `F2` to bring up the rename UI
93
92
  * [media player](#media-player) - plays almost every audio format there is
94
- * [audio equalizer](#audio-equalizer) - bass boosted
93
+ * [audio equalizer](#audio-equalizer) - and [dynamic range compressor](https://en.wikipedia.org/wiki/Dynamic_range_compression)
95
94
  * [fix unreliable playback on android](#fix-unreliable-playback-on-android) - due to phone / app settings
96
95
  * [markdown viewer](#markdown-viewer) - and there are *two* editors
97
96
  * [other tricks](#other-tricks)
@@ -314,20 +313,28 @@ server notes:
314
313
 
315
314
  # bugs
316
315
 
317
- * Windows: python 2.7 cannot index non-ascii filenames with `-e2d`
318
- * Windows: python 2.7 cannot handle filenames with mojibake
319
- * `--th-ff-jpg` may fix video thumbnails on some FFmpeg versions (macos, some linux)
320
- * `--th-ff-swr` may fix audio thumbnails on some FFmpeg versions
316
+ roughly sorted by chance of encounter
321
317
 
322
- ## general bugs
318
+ * general:
319
+ * `--th-ff-jpg` may fix video thumbnails on some FFmpeg versions (macos, some linux)
320
+ * `--th-ff-swr` may fix audio thumbnails on some FFmpeg versions
321
+ * if the `up2k.db` (filesystem index) is on a samba-share or network disk, you'll get unpredictable behavior if the share is disconnected for a bit
322
+ * use `--hist` or the `hist` volflag (`-v [...]:c,hist=/tmp/foo`) to place the db on a local disk instead
323
+ * all volumes must exist / be available on startup; up2k (mtp especially) gets funky otherwise
324
+ * probably more, pls let me know
323
325
 
324
- * Windows: if the `up2k.db` (filesystem index) is on a samba-share or network disk, you'll get unpredictable behavior if the share is disconnected for a bit
325
- * use `--hist` or the `hist` volflag (`-v [...]:c,hist=/tmp/foo`) to place the db on a local disk instead
326
- * all volumes must exist / be available on startup; up2k (mtp especially) gets funky otherwise
327
- * probably more, pls let me know
326
+ * python 3.4 and older (including 2.7):
327
+ * many rare and exciting edge-cases because [python didn't handle EINTR yet](https://peps.python.org/pep-0475/)
328
+ * downloads from copyparty may suddenly fail, but uploads *should* be fine
329
+
330
+ * python 2.7 on Windows:
331
+ * cannot index non-ascii filenames with `-e2d`
332
+ * cannot handle filenames with mojibake
328
333
 
329
334
  ## not my bugs
330
335
 
336
+ same order here too
337
+
331
338
  * [Chrome issue 1317069](https://bugs.chromium.org/p/chromium/issues/detail?id=1317069) -- if you try to upload a folder which contains symlinks by dragging it into the browser, the symlinked files will not get uploaded
332
339
 
333
340
  * [Chrome issue 1352210](https://bugs.chromium.org/p/chromium/issues/detail?id=1352210) -- plaintext http may be faster at filehashing than https (but also extremely CPU-intensive)
@@ -410,7 +417,7 @@ permissions:
410
417
  * `g` (get): only download files, cannot see folder contents or zip/tar
411
418
  * `G` (upget): same as `g` except uploaders get to see their own [filekeys](#filekeys) (see `fk` in examples below)
412
419
  * `h` (html): same as `g` except folders return their index.html, and filekeys are not necessary for index.html
413
- * `a` (admin): can see uploader IPs, config-reload
420
+ * `a` (admin): can see upload time, uploader IPs, config-reload
414
421
 
415
422
  examples:
416
423
  * add accounts named u1, u2, u3 with passwords p1, p2, p3: `-a u1:p1 -a u2:p2 -a u3:p3`
@@ -787,7 +794,7 @@ open the `[🎺]` media-player-settings tab to configure it,
787
794
 
788
795
  ### audio equalizer
789
796
 
790
- bass boosted
797
+ and [dynamic range compressor](https://en.wikipedia.org/wiki/Dynamic_range_compression)
791
798
 
792
799
  can also boost the volume in general, or increase/decrease stereo width (like [crossfeed](https://www.foobar2000.org/components/view/foo_dsp_meiercf) just worse)
793
800
 
@@ -998,6 +1005,15 @@ authenticate with one of the following:
998
1005
  * username `$password`, password `k`
999
1006
 
1000
1007
 
1008
+ ## browser ux
1009
+
1010
+ tweaking the ui
1011
+
1012
+ * set default sort order globally with `--sort` or per-volume with the `sort` volflag; specify one or more comma-separated columns to sort by, and prefix the column name with `-` for reverse sort
1013
+ * column names are visible as tooltips when hovering over the column headers in the directory listing, for example `href ext sz ts tags/.up_at tags/Cirle tags/.tn tags/Artist tags/Title`
1014
+ * 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`
1015
+
1016
+
1001
1017
  ## file indexing
1002
1018
 
1003
1019
  enables dedup and music search ++
@@ -1024,6 +1040,7 @@ the same arguments can be set as volflags, in addition to `d2d`, `d2ds`, `d2t`,
1024
1040
  * `-v ~/music::r:c,d2ts` same except only affecting tags
1025
1041
 
1026
1042
  note:
1043
+ * upload-times can be displayed in the file listing by enabling the `.up_at` metadata key, either globally with `-e2d -mte +.up_at` or per-volume with volflags `e2d,mte=+.up_at` (will have a ~17% performance impact on directory listings)
1027
1044
  * `e2tsr` is probably always overkill, since `e2ds`/`e2dsa` would pick up any file modifications and `e2ts` would then reindex those, unless there is a new copyparty version with new parsers and the release note says otherwise
1028
1045
  * the rescan button in the admin panel has no effect unless the volume has `-e2ds` or higher
1029
1046
  * deduplication is possible on windows if you run copyparty as administrator (not saying you should!)
@@ -1687,8 +1704,6 @@ safety profiles:
1687
1704
  * `--hardlink` creates hardlinks instead of symlinks when deduplicating uploads, which is less maintenance
1688
1705
  * however note if you edit one file it will also affect the other copies
1689
1706
  * `--vague-403` returns a "404 not found" instead of "401 unauthorized" which is a common enterprise meme
1690
- * `--ban-404=50,60,1440` ban client for 1440min (24h) if they hit 50 404's in 60min
1691
- * `--turbo=-1` to force-disable turbo-mode in the uploader which could otherwise hit the 404-ban
1692
1707
  * `--nih` removes the server hostname from directory listings
1693
1708
 
1694
1709
  * option `-sss` is a shortcut for the above plus:
@@ -1,17 +1,17 @@
1
1
  copyparty/__init__.py,sha256=JA6xnECt2WITjgMEUsk7ZKYgxieda4QvXQtgxfnfkR4,1812
2
- copyparty/__main__.py,sha256=PbARYemugn8cIe46h9ETZmjP0-LNaguQgOGgJ-Pwcm8,82416
3
- copyparty/__version__.py,sha256=Ou6gDONoBs-QPeCmmPzCFZAYGWtREya3Y-EXVFHciKQ,255
4
- copyparty/authsrv.py,sha256=EcrgRS8SRIWR3hoi1S5V4rt6TFrIh4S6zjS-pW6r2pE,71161
2
+ copyparty/__main__.py,sha256=CeB5mT4r4lw8RX23nPI-MOMnJZlM2nOUVTDI__OMRZk,82974
3
+ copyparty/__version__.py,sha256=c1QkkzhyGQtqboArLfXkMPfdc-IatiVz-9MgujN-y7c,255
4
+ copyparty/authsrv.py,sha256=uUQwTB9v-0HbiT68VR2_gX1s22YmBpPzjfIQoqup90E,71301
5
5
  copyparty/broker_mp.py,sha256=OwxJk6mil0aPopz_JbONsk-b4Qmoa_sdS5G-tIIXjoM,3916
6
6
  copyparty/broker_mpw.py,sha256=GlSn4PRd_OqqeG39FiXgNvPzXVQW6UCiAcqmBSr2q6g,3200
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=r6zG-eLxwJW8AYz6ZTYzIlzuN6Wb62Ea4mZUrc_t6F0,7239
10
- copyparty/cfg.py,sha256=7-ZfUt3fKeAY5bOUNXU44b5Mj1d_7WIAmVG4AaMh-rI,8026
10
+ copyparty/cfg.py,sha256=uOG5GGNpDLwXW6VWDgfQMzBKUdWOfTPUJh9iBuLu_8c,8490
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=6o_HpezJ-afXLsOkKXyIMQYKVz_MqZSOoy5L4z4dEeU,15369
14
- copyparty/httpcli.py,sha256=qEuMvb58VaIjQO1_iks_Zzk9Im_zMJ_qagiW4zAXfl8,136145
14
+ copyparty/httpcli.py,sha256=bi501chz7ThECoGq5xaEjRPj5jPwf3jd19xkKBDKKcI,136717
15
15
  copyparty/httpconn.py,sha256=CJU4wK_6QJFvgK9mQwEV55j6Muh_9k-GTe4AIehzJiw,6825
16
16
  copyparty/httpsrv.py,sha256=B2wmyXfohtqxC0lZ3j9UACYS8f9Zxlh4QSRH8_1owkw,16044
17
17
  copyparty/ico.py,sha256=8QXApIqAVC1WnpM_RVFUgqerP7Y71DbxxF-IpUeV-n0,4042
@@ -23,15 +23,15 @@ copyparty/pwhash.py,sha256=6RcQE35B-vo7evU4SS2YktEvT1h-r0FZRWP5cSNp98c,3859
23
23
  copyparty/smbd.py,sha256=-ey_edugqUGCKZtsKC1TZnFdaeSw7M7RvphUIpL75V4,13997
24
24
  copyparty/ssdp.py,sha256=H6ZftXttydcnBxcg2-Prm4P-XiybgT3xiJRUXU1pbrE,6343
25
25
  copyparty/star.py,sha256=LUfDc6Oy3EC5IZHxAgVzqBPKEDb4n7U94HB6U0-GndQ,3771
26
- copyparty/sutil.py,sha256=Wseu6-8bWFpLAHTLQf77oWm-aJLcslqFG6czyyqhGdQ,2962
27
- copyparty/svchub.py,sha256=zwww0dP4QUhmcE_5ndWlbE8YJ9NT8ojjW8zl1BvRTgM,26150
26
+ copyparty/sutil.py,sha256=dZhDAzVAp02i6YmGqcedy4LaYVffnJCSp3F6WEwfz7c,2967
27
+ copyparty/svchub.py,sha256=JnafYOUphQ2s_fCfwVjVYFaEbd8MIveP7NSkZCHRjXg,26623
28
28
  copyparty/szip.py,sha256=SJg5nzN_5oaIsIYXlRvcLHVTqngLAOPfe4_WVsuhSkw,8520
29
29
  copyparty/tcpsrv.py,sha256=vz94zy-eUg5-U1lskN3VAQiTXVr80PPnwJfwzybVcW4,17169
30
30
  copyparty/th_cli.py,sha256=MSp2kpoAPiX1bndMthv6JK2gt3K6CjrloWuJsI_CL94,3869
31
31
  copyparty/th_srv.py,sha256=68MCzYVTm62Yct25Fs2udiLEB7tW7t8bLc3zext9IPs,23226
32
- copyparty/u2idx.py,sha256=xakOZ8TRB2VuwpcZmWw4TbATuv9bdbgT35b3IA9dXnY,10677
33
- copyparty/up2k.py,sha256=Q1XEXv7ZwIqAVChQ0DkCZwpvVEpsmJH1FRJzsK_U5BA,128526
34
- copyparty/util.py,sha256=UsxOQYJx2LTNMVXwVK_6tywHWG7YBz1Y2w8O32GBdI4,73532
32
+ copyparty/u2idx.py,sha256=hOnScTNMnZ5sdbU_vR5L75qZdQ-dn_gwHBVCVcIRaOQ,10815
33
+ copyparty/up2k.py,sha256=5czsvwrZqruid5Jq4YnDsn332ET7QMIhJVL98w-BB2w,128718
34
+ copyparty/util.py,sha256=zr3gL4_vfzlnq5Uu_StgJgNrkAk4UTwLjUTSe0lIlkM,74609
35
35
  copyparty/bos/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
36
36
  copyparty/bos/bos.py,sha256=Md2RwMauEdOS7SfK5aiHby8T1KgQoTaoEjVCYU98_4I,1560
37
37
  copyparty/bos/path.py,sha256=yEjCq2ki9CvxA5sCT8pS0keEXwugs0ZeUyUhdBziOCI,777
@@ -54,9 +54,9 @@ copyparty/stolen/ifaddr/_posix.py,sha256=-67NdfGrCktfQPakT2fLbjl2U00QMvyBGkSvrUu
54
54
  copyparty/stolen/ifaddr/_shared.py,sha256=cJACl8cOxQ-HSYphZTzKMAjAx_TAFyJwUPjfD102Xqw,6111
55
55
  copyparty/stolen/ifaddr/_win32.py,sha256=EE-QyoBgeB7lYQ6z62VjXNaRozaYfCkaJBHGNA8QtZM,4026
56
56
  copyparty/web/baguettebox.js.gz,sha256=fyJfNSipzg093wMQAWiqI6SGoPcCe4JdLIvlOYkFG9Q,7379
57
- copyparty/web/browser.css.gz,sha256=zdo1FFmxPKcQEetdSUgWgB6hMEiuDWbhpF_nnHb_yXs,11150
57
+ copyparty/web/browser.css.gz,sha256=w2lFkbfJgY1vt1eyMXOKqgMySS_YyJ1jxu4MrKhkaOQ,11206
58
58
  copyparty/web/browser.html,sha256=3L31n92yTaMpHFmvoiNjAgCUMi60YHN2qYN6YQdUcBs,4839
59
- copyparty/web/browser.js.gz,sha256=lsigw3wUlUtxPcvNxjoGg55QWhfq4KDxtE-f1xmEUDg,62445
59
+ copyparty/web/browser.js.gz,sha256=pX-jsYSBpi-RPI5exmzbU_X5WnU1NR5e13234nAWARo,63685
60
60
  copyparty/web/browser2.html,sha256=3kR3QiDXYqtAo7gOoBtdTP3eRvibUCZHVVAbmAfaAVU,1604
61
61
  copyparty/web/cf.html,sha256=_tgwgNtK5MpjvvthGXx6Q9sasDaiWruyZfXsXYVW2KA,588
62
62
  copyparty/web/dbg-audio.js.gz,sha256=Ma-KZtK8LnmiwNvNKFKXMPYl_Nn_3U7GsJ6-DRWC2HE,688
@@ -76,11 +76,11 @@ copyparty/web/splash.js.gz,sha256=3PMKsMLwEsHP8eonBmtvonm9yeNq69xjMi8ZqnVeKcA,12
76
76
  copyparty/web/svcs.html,sha256=esTRzw4tkgWR_BPFZpDP0NX9slnkNnysE1E5weUdlOA,10591
77
77
  copyparty/web/svcs.js.gz,sha256=k81ZvZ3I-f4fMHKrNGGOgOlvXnCBz0mVjD-8mieoWCA,520
78
78
  copyparty/web/ui.css.gz,sha256=_3liYHDeJjgtiQecpTHQ3qvcMGvx8347ViKpEm8DjWU,2456
79
- copyparty/web/up2k.js.gz,sha256=xeU0oNCBvf5n3pZgVG3akGJAyMe7RAPdQE8e8J039zk,21882
79
+ copyparty/web/up2k.js.gz,sha256=ZWxdh7v1OVCYPlWUQxxlGcdgupTtdYuXYlO8xJeP9EM,21887
80
80
  copyparty/web/util.js.gz,sha256=oJDTYgqi6tKLKbRYAtxW-RFzy6eNjbgayct6ZhB3SEE,13735
81
81
  copyparty/web/w.hash.js.gz,sha256=P9469QknH8-1aKwI_1n1_S4yKvIGOu7bGoty9N3zYMI,1060
82
82
  copyparty/web/a/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
83
- copyparty/web/a/partyfuse.py,sha256=mJKqR8LjX19P4Knqa02ob5ogwPVtJXSqxkEEGZU0r60,32276
83
+ copyparty/web/a/partyfuse.py,sha256=Ki0ViQr9TTSvDBFY1x8c8v531oMnhNwgnYlTVhqwF_s,32305
84
84
  copyparty/web/a/u2c.py,sha256=DendjoyF15_LlEblW_STKoLT7oSCPyzp9TOncsA4PgE,36595
85
85
  copyparty/web/a/webdav-cfg.bat,sha256=Y4NoGZlksAIg4cBMb7KdJrpKC6Nx97onaTl6yMjaimk,1449
86
86
  copyparty/web/dd/2.png,sha256=gJ14XFPzaw95L6z92fSq9eMPikSQyu-03P1lgiGe0_I,258
@@ -98,9 +98,9 @@ copyparty/web/deps/prismd.css.gz,sha256=ObUlksQVr-OuYlTz-I4B23TeBg2QDVVGRnWBz8cV
98
98
  copyparty/web/deps/scp.woff2,sha256=w99BDU5i8MukkMEL-iW0YO9H4vFFZSPWxbkH70ytaAg,8612
99
99
  copyparty/web/deps/sha512.ac.js.gz,sha256=lFZaCLumgWxrvEuDr4bqdKHsqjX82AbVAb7_F45Yk88,7033
100
100
  copyparty/web/deps/sha512.hw.js.gz,sha256=km3_b5IoaVwg02Ex6PxnhDLZxKiOMP2cHW35j9CSWFA,8107
101
- copyparty-1.9.12.dist-info/LICENSE,sha256=yyzj1id78vWoLs8zbMRJY7xkkLz0lv-9dfyeIauqdfM,1059
102
- copyparty-1.9.12.dist-info/METADATA,sha256=7C8UqsyHNe1zWvGyto8NcAqBRzjKgjbQvFjyY6--EkQ,104248
103
- copyparty-1.9.12.dist-info/WHEEL,sha256=yQN5g4mg4AybRjkgi-9yy4iQEFibGQmlz78Pik5Or-A,92
104
- copyparty-1.9.12.dist-info/entry_points.txt,sha256=4zw6a3rqASywQomiYLObjjlxybaI65LYYOTJwgKz7b0,128
105
- copyparty-1.9.12.dist-info/top_level.txt,sha256=LnYUPsDyk-8kFgM6YJLG4h820DQekn81cObKSu9g-sI,10
106
- copyparty-1.9.12.dist-info/RECORD,,
101
+ copyparty-1.9.14.dist-info/LICENSE,sha256=yyzj1id78vWoLs8zbMRJY7xkkLz0lv-9dfyeIauqdfM,1059
102
+ copyparty-1.9.14.dist-info/METADATA,sha256=KPpp97_-MbWj3Rk0VJ26hVpm7wyWDza6a0o_rmnJ4c8,105322
103
+ copyparty-1.9.14.dist-info/WHEEL,sha256=yQN5g4mg4AybRjkgi-9yy4iQEFibGQmlz78Pik5Or-A,92
104
+ copyparty-1.9.14.dist-info/entry_points.txt,sha256=4zw6a3rqASywQomiYLObjjlxybaI65LYYOTJwgKz7b0,128
105
+ copyparty-1.9.14.dist-info/top_level.txt,sha256=LnYUPsDyk-8kFgM6YJLG4h820DQekn81cObKSu9g-sI,10
106
+ copyparty-1.9.14.dist-info/RECORD,,