copyparty 1.10.0__py3-none-any.whl → 1.10.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
@@ -1013,6 +1013,7 @@ def add_tftp(ap):
1013
1013
  ap2.add_argument("--tftp", metavar="PORT", type=int, help="enable TFTP server on \033[33mPORT\033[0m, for example \033[32m69 \033[0mor \033[32m3969")
1014
1014
  ap2.add_argument("--tftpv", action="store_true", help="verbose")
1015
1015
  ap2.add_argument("--tftpvv", action="store_true", help="verboser")
1016
+ ap2.add_argument("--tftp-no-fast", action="store_true", help="debug: disable optimizations")
1016
1017
  ap2.add_argument("--tftp-lsf", metavar="PTN", type=u, default="\\.?(dir|ls)(\\.txt)?", help="return a directory listing if a file with this name is requested and it does not exist; defaults matches .ls, dir, .dir.txt, ls.txt, ...")
1017
1018
  ap2.add_argument("--tftp-nols", action="store_true", help="if someone tries to download a directory, return an error instead of showing its directory listing")
1018
1019
  ap2.add_argument("--tftp-ipa", metavar="PFX", type=u, default="", help="only accept connections from IP-addresses starting with \033[33mPFX\033[0m; specify [\033[32many\033[0m] to disable inheriting \033[33m--ipa\033[0m. Example: [\033[32m127., 10.89., 192.168.\033[0m]")
@@ -1163,7 +1164,8 @@ def add_thumbnail(ap):
1163
1164
  ap2.add_argument("--th-mt", metavar="CORES", type=int, default=CORES, help="num cpu cores to use for generating thumbnails")
1164
1165
  ap2.add_argument("--th-convt", metavar="SEC", type=float, default=60, help="conversion timeout in seconds (volflag=convt)")
1165
1166
  ap2.add_argument("--th-ram-max", metavar="GB", type=float, default=6, help="max memory usage (GiB) permitted by thumbnailer; not very accurate")
1166
- ap2.add_argument("--th-no-crop", action="store_true", help="dynamic height; show full image by default (client can override in UI) (volflag=nocrop)")
1167
+ ap2.add_argument("--th-crop", metavar="TXT", type=u, default="y", help="crop thumbnails to 4:3 or keep dynamic height; client can override in UI unless force. [\033[32mfy\033[0m]=crop, [\033[32mfn\033[0m]=nocrop, [\033[32mfy\033[0m]=force-y, [\033[32mfn\033[0m]=force-n (volflag=crop)")
1168
+ ap2.add_argument("--th-x3", metavar="TXT", type=u, default="n", help="show thumbs at 3x resolution; client can override in UI unless force. [\033[32mfy\033[0m]=yes, [\033[32mfn\033[0m]=no, [\033[32mfy\033[0m]=force-yes, [\033[32mfn\033[0m]=force-no (volflag=th3x)")
1167
1169
  ap2.add_argument("--th-dec", metavar="LIBS", default="vips,pil,ff", help="image decoders, in order of preference")
1168
1170
  ap2.add_argument("--th-no-jpg", action="store_true", help="disable jpg output")
1169
1171
  ap2.add_argument("--th-no-webp", action="store_true", help="disable webp output")
@@ -1423,6 +1425,7 @@ def main(argv = None) :
1423
1425
  deprecated = [
1424
1426
  ("--salt", "--warksalt"),
1425
1427
  ("--hdr-au-usr", "--idp-h-usr"),
1428
+ ("--th-no-crop", "--th-crop=n"),
1426
1429
  ]
1427
1430
  for dk, nk in deprecated:
1428
1431
  idx = -1
copyparty/__version__.py CHANGED
@@ -1,8 +1,8 @@
1
1
  # coding: utf-8
2
2
 
3
- VERSION = (1, 10, 0)
3
+ VERSION = (1, 10, 2)
4
4
  CODENAME = "tftp"
5
- BUILD_DT = (2024, 2, 15)
5
+ BUILD_DT = (2024, 2, 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
@@ -186,7 +186,7 @@ class Lim(object):
186
186
  self.dft = int(time.time()) + 300
187
187
  self.dfv = get_df(abspath)[0] or 0
188
188
  for j in list(self.reg.values()) if self.reg else []:
189
- self.dfv -= int(j["size"] / len(j["hash"]) * len(j["need"]))
189
+ self.dfv -= int(j["size"] / (len(j["hash"]) or 999) * len(j["need"]))
190
190
 
191
191
  if already_written:
192
192
  sz = 0
copyparty/cfg.py CHANGED
@@ -20,7 +20,6 @@ def vf_bmap() :
20
20
  "no_thumb": "dthumb",
21
21
  "no_vthumb": "dvthumb",
22
22
  "no_athumb": "dathumb",
23
- "th_no_crop": "nocrop",
24
23
  }
25
24
  for k in (
26
25
  "dotsrch",
@@ -56,6 +55,8 @@ def vf_vmap() :
56
55
  "re_maxage": "scan",
57
56
  "th_convt": "convt",
58
57
  "th_size": "thsize",
58
+ "th_crop": "crop",
59
+ "th_x3": "th3x",
59
60
  }
60
61
  for k in (
61
62
  "dbd",
@@ -172,7 +173,8 @@ flagcats = {
172
173
  "dathumb": "disables audio thumbnails (spectrograms)",
173
174
  "dithumb": "disables image thumbnails",
174
175
  "thsize": "thumbnail res; WxH",
175
- "nocrop": "disable center-cropping by default",
176
+ "crop": "center-cropping (y/n/fy/fn)",
177
+ "th3x": "3x resolution (y/n/fy/fn)",
176
178
  "convt": "conversion timeout in seconds",
177
179
  },
178
180
  "handlers\n(better explained in --help-handlers)": {
copyparty/ftpd.py CHANGED
@@ -20,6 +20,7 @@ from .authsrv import VFS
20
20
  from .bos import bos
21
21
  from .util import (
22
22
  Daemon,
23
+ ODict,
23
24
  Pebkac,
24
25
  exclude_dotfiles,
25
26
  fsenc,
@@ -540,6 +541,8 @@ class Ftpd(object):
540
541
  if self.args.ftp4:
541
542
  ips = [x for x in ips if ":" not in x]
542
543
 
544
+ ips = list(ODict.fromkeys(ips)) # dedup
545
+
543
546
  ioloop = IOLoop()
544
547
  for ip in ips:
545
548
  for h, lp in hs:
copyparty/httpcli.py CHANGED
@@ -3135,11 +3135,15 @@ class HttpCli(object):
3135
3135
 
3136
3136
  ext = ext.rstrip(".") or "unk"
3137
3137
  if len(ext) > 11:
3138
- ext = "" + ext[-9:]
3138
+ ext = "~" + ext[-9:]
3139
3139
 
3140
+ return self.tx_svg(ext, exact)
3141
+
3142
+ def tx_svg(self, txt , small = False) :
3140
3143
  # chrome cannot handle more than ~2000 unique SVGs
3141
- chrome = " rv:" not in self.ua
3142
- mime, ico = self.ico.get(ext, not exact, chrome)
3144
+ # so url-param "raster" returns a png/webp instead
3145
+ # (useragent-sniffing kinshi due to caching proxies)
3146
+ mime, ico = self.ico.get(txt, not small, "raster" in self.uparam)
3143
3147
 
3144
3148
  lm = formatdate(self.E.t0, usegmt=True)
3145
3149
  self.reply(ico, mime=mime, headers={"Last-Modified": lm})
@@ -3403,6 +3407,9 @@ class HttpCli(object):
3403
3407
  self.reply(pt.encode("utf-8"), status=rc)
3404
3408
  return True
3405
3409
 
3410
+ if "th" in self.ouparam:
3411
+ return self.tx_svg("e" + pt[:3])
3412
+
3406
3413
  t = t.format(self.args.SR)
3407
3414
  qv = quotep(self.vpaths) + self.ourlq()
3408
3415
  html = self.j2s("splash", this=self, qvpath=qv, msg=t)
@@ -3783,12 +3790,15 @@ class HttpCli(object):
3783
3790
  if idx and hasattr(idx, "p_end"):
3784
3791
  icur = idx.get_cur(dbv.realpath)
3785
3792
 
3793
+ th_fmt = self.uparam.get("th")
3786
3794
  if self.can_read:
3787
- th_fmt = self.uparam.get("th")
3788
3795
  if th_fmt is not None:
3796
+ nothumb = "dthumb" in dbv.flags
3789
3797
  if is_dir:
3790
3798
  vrem = vrem.rstrip("/")
3791
- if icur and vrem:
3799
+ if nothumb:
3800
+ pass
3801
+ elif icur and vrem:
3792
3802
  q = "select fn from cv where rd=? and dn=?"
3793
3803
  crd, cdn = vrem.rsplit("/", 1) if "/" in vrem else ("", vrem)
3794
3804
  # no mojibake support:
@@ -3811,10 +3821,10 @@ class HttpCli(object):
3811
3821
  break
3812
3822
 
3813
3823
  if is_dir:
3814
- return self.tx_ico("a.folder")
3824
+ return self.tx_svg("folder")
3815
3825
 
3816
3826
  thp = None
3817
- if self.thumbcli:
3827
+ if self.thumbcli and not nothumb:
3818
3828
  thp = self.thumbcli.get(dbv, vrem, int(st.st_mtime), th_fmt)
3819
3829
 
3820
3830
  if thp:
@@ -3825,6 +3835,9 @@ class HttpCli(object):
3825
3835
 
3826
3836
  return self.tx_ico(rem)
3827
3837
 
3838
+ elif self.can_write and th_fmt is not None:
3839
+ return self.tx_svg("upload\nonly")
3840
+
3828
3841
  elif self.can_get and self.avn:
3829
3842
  axs = self.avn.axs
3830
3843
  if self.uname not in axs.uhtml:
@@ -3969,7 +3982,8 @@ class HttpCli(object):
3969
3982
  "idx": e2d,
3970
3983
  "itag": e2t,
3971
3984
  "dsort": vf["sort"],
3972
- "dfull": "nocrop" in vf,
3985
+ "dcrop": vf["crop"],
3986
+ "dth3x": vf["th3x"],
3973
3987
  "u2ts": vf["u2ts"],
3974
3988
  "lifetime": vn.flags.get("lifetime") or 0,
3975
3989
  "frand": bool(vn.flags.get("rand")),
@@ -3996,8 +4010,9 @@ class HttpCli(object):
3996
4010
  "sb_md": "" if "no_sb_md" in vf else (vf.get("md_sbf") or "y"),
3997
4011
  "readme": readme,
3998
4012
  "dgrid": "grid" in vf,
3999
- "dfull": "nocrop" in vf,
4000
4013
  "dsort": vf["sort"],
4014
+ "dcrop": vf["crop"],
4015
+ "dth3x": vf["th3x"],
4001
4016
  "themes": self.args.themes,
4002
4017
  "turbolvl": self.args.turbo,
4003
4018
  "u2j": self.args.u2j,
copyparty/ico.py CHANGED
@@ -8,7 +8,7 @@ import re
8
8
 
9
9
  from .__init__ import PY2
10
10
  from .th_srv import HAVE_PIL, HAVE_PILF
11
- from .util import BytesIO # type: ignore
11
+ from .util import BytesIO, html_escape # type: ignore
12
12
 
13
13
 
14
14
  class Ico(object):
@@ -31,10 +31,9 @@ class Ico(object):
31
31
 
32
32
  w = 100
33
33
  h = 30
34
- if not self.args.th_no_crop and as_thumb:
34
+ if as_thumb:
35
35
  sw, sh = self.args.th_size.split("x")
36
36
  h = int(100.0 / (float(sw) / float(sh)))
37
- w = 100
38
37
 
39
38
  if chrome:
40
39
  # cannot handle more than ~2000 unique SVGs
@@ -99,6 +98,6 @@ class Ico(object):
99
98
  fill="#{}" font-family="monospace" font-size="14px" style="letter-spacing:.5px">{}</text>
100
99
  </g></svg>
101
100
  """
102
- svg = svg.format(h, c[:6], c[6:], ext)
101
+ svg = svg.format(h, c[:6], c[6:], html_escape(ext, True))
103
102
 
104
103
  return "image/svg+xml", svg.encode("utf-8")
copyparty/tftpd.py CHANGED
@@ -10,19 +10,33 @@ except:
10
10
  self.__dict__.update(attr)
11
11
 
12
12
 
13
- import inspect
14
13
  import logging
15
14
  import os
15
+ import re
16
+ import socket
16
17
  import stat
18
+ import threading
19
+ import time
17
20
  from datetime import datetime
18
21
 
19
- from partftpy import TftpContexts, TftpServer, TftpStates
22
+ try:
23
+ import inspect
24
+ except:
25
+ pass
26
+
27
+ from partftpy import (
28
+ TftpContexts,
29
+ TftpPacketFactory,
30
+ TftpPacketTypes,
31
+ TftpServer,
32
+ TftpStates,
33
+ )
20
34
  from partftpy.TftpShared import TftpException
21
35
 
22
- from .__init__ import PY2, TYPE_CHECKING
36
+ from .__init__ import EXE, TYPE_CHECKING
23
37
  from .authsrv import VFS
24
38
  from .bos import bos
25
- from .util import BytesIO, Daemon, exclude_dotfiles, runhook, undot
39
+ from .util import BytesIO, Daemon, ODict, exclude_dotfiles, min_ex, runhook, undot
26
40
 
27
41
  if TYPE_CHECKING:
28
42
  from .svchub import SvcHub
@@ -32,6 +46,10 @@ lg = logging.getLogger("tftp")
32
46
  debug, info, warning, error = (lg.debug, lg.info, lg.warning, lg.error)
33
47
 
34
48
 
49
+ def noop(*a, **ka) :
50
+ pass
51
+
52
+
35
53
  def _serverInitial(self, pkt , raddress , rport ) :
36
54
  info("connection from %s:%s", raddress, rport)
37
55
  ret = _orig_serverInitial(self, pkt, raddress, rport)
@@ -53,6 +71,7 @@ class Tftpd(object):
53
71
  self.args = hub.args
54
72
  self.asrv = hub.asrv
55
73
  self.log = hub.log
74
+ self.mutex = threading.Lock()
56
75
 
57
76
  _hub[:] = []
58
77
  _hub.append(hub)
@@ -62,6 +81,38 @@ class Tftpd(object):
62
81
  lgr = logging.getLogger(x)
63
82
  lgr.setLevel(logging.DEBUG if self.args.tftpv else logging.INFO)
64
83
 
84
+ if not self.args.tftpv and not self.args.tftpvv:
85
+ # contexts -> states -> packettypes -> shared
86
+ # contexts -> packetfactory
87
+ # packetfactory -> packettypes
88
+ Cs = [
89
+ TftpPacketTypes,
90
+ TftpPacketFactory,
91
+ TftpStates,
92
+ TftpContexts,
93
+ TftpServer,
94
+ ]
95
+ cbak = []
96
+ if not self.args.tftp_no_fast and not EXE:
97
+ try:
98
+ import inspect
99
+
100
+ ptn = re.compile(r"(^\s*)log\.debug\(.*\)$")
101
+ for C in Cs:
102
+ cbak.append(C.__dict__)
103
+ src1 = inspect.getsource(C).split("\n")
104
+ src2 = "\n".join([ptn.sub("\\1pass", ln) for ln in src1])
105
+ cfn = C.__spec__.origin
106
+ exec (compile(src2, filename=cfn, mode="exec"), C.__dict__)
107
+ except Exception:
108
+ t = "failed to optimize tftp code; run with --tftp-noopt if there are issues:\n"
109
+ self.log("tftp", t + min_ex(), 3)
110
+ for n, zd in enumerate(cbak):
111
+ Cs[n].__dict__ = zd
112
+
113
+ for C in Cs:
114
+ C.log.debug = noop
115
+
65
116
  # patch vfs into partftpy
66
117
  TftpContexts.open = self._open
67
118
  TftpStates.open = self._open
@@ -99,21 +150,90 @@ class Tftpd(object):
99
150
  self.log("tftp", "IPv6 not supported for tftp; listening on 0.0.0.0", 3)
100
151
  ip = "0.0.0.0"
101
152
 
102
- self.ip = ip
103
153
  self.port = int(self.args.tftp)
104
- self.srv = TftpServer.TftpServer("/", self._ls)
105
- self.stop = self.srv.stop
154
+ self.srv = []
155
+ self.ips = []
106
156
 
107
157
  ports = []
108
158
  if self.args.tftp_pr:
109
159
  p1, p2 = [int(x) for x in self.args.tftp_pr.split("-")]
110
160
  ports = list(range(p1, p2 + 1))
111
161
 
112
- Daemon(self.srv.listen, "tftp", [self.ip, self.port], ka={"ports": ports})
162
+ ips = self.args.i
163
+ if "::" in ips:
164
+ ips.append("0.0.0.0")
165
+
166
+ if self.args.ftp4:
167
+ ips = [x for x in ips if ":" not in x]
168
+
169
+ ips = list(ODict.fromkeys(ips)) # dedup
170
+
171
+ for ip in ips:
172
+ name = "tftp_%s" % (ip,)
173
+ Daemon(self._start, name, [ip, ports])
174
+ time.sleep(0.2) # give dualstack a chance
113
175
 
114
176
  def nlog(self, msg , c = 0) :
115
177
  self.log("tftp", msg, c)
116
178
 
179
+ def _start(self, ip, ports):
180
+ fam = socket.AF_INET6 if ":" in ip else socket.AF_INET
181
+ have_been_alive = False
182
+ while True:
183
+ srv = TftpServer.TftpServer("/", self._ls)
184
+ with self.mutex:
185
+ self.srv.append(srv)
186
+ self.ips.append(ip)
187
+
188
+ try:
189
+ # this is the listen loop; it should block forever
190
+ srv.listen(ip, self.port, af_family=fam, ports=ports)
191
+ except:
192
+ with self.mutex:
193
+ self.srv.remove(srv)
194
+ self.ips.remove(ip)
195
+
196
+ try:
197
+ srv.sock.close()
198
+ except:
199
+ pass
200
+
201
+ try:
202
+ bound = bool(srv.listenport)
203
+ except:
204
+ bound = False
205
+
206
+ if bound:
207
+ # this instance has managed to bind at least once
208
+ have_been_alive = True
209
+
210
+ if have_been_alive:
211
+ t = "tftp server [%s]:%d crashed; restarting in 3 sec:\n%s"
212
+ error(t, ip, self.port, min_ex())
213
+ time.sleep(3)
214
+ continue
215
+
216
+ # server failed to start; could be due to dualstack (ipv6 managed to bind and this is ipv4)
217
+ if ip != "0.0.0.0" or "::" not in self.ips:
218
+ # nope, it's fatal
219
+ t = "tftp server [%s]:%d failed to start:\n%s"
220
+ error(t, ip, self.port, min_ex())
221
+
222
+ # yep; ignore
223
+ # (TODO: move the "listening @ ..." infolog in partftpy to
224
+ # after the bind attempt so it doesn't print twice)
225
+ return
226
+
227
+ info("tftp server [%s]:%d terminated", ip, self.port)
228
+ break
229
+
230
+ def stop(self):
231
+ with self.mutex:
232
+ srvs = self.srv[:]
233
+
234
+ for srv in srvs:
235
+ srv.stop()
236
+
117
237
  def _v2a(self, caller , vpath , perms , *a ) :
118
238
  vpath = vpath.replace("\\", "/").lstrip("/")
119
239
  if not perms:
@@ -187,7 +307,7 @@ class Tftpd(object):
187
307
  retl = ["# permissions: %s" % (", ".join(perms),)]
188
308
  retl += [fmt.format(*x) for x in ls]
189
309
  ret = "\n".join(retl).encode("utf-8", "replace")
190
- return BytesIO(ret)
310
+ return BytesIO(ret + b"\n")
191
311
 
192
312
  def _open(self, vpath , mode , *a , **ka ) :
193
313
  rd = wr = False
copyparty/th_cli.py CHANGED
@@ -75,16 +75,34 @@ class ThumbCli(object):
75
75
  if rem.startswith(".hist/th/") and rem.split(".")[-1] in ["webp", "jpg", "png"]:
76
76
  return os.path.join(ptop, rem)
77
77
 
78
- if fmt == "j" and self.args.th_no_jpg:
79
- fmt = "w"
78
+ if fmt[:1] in "jw":
79
+ sfmt = fmt[:1]
80
80
 
81
- if fmt == "w":
82
- if (
83
- self.args.th_no_webp
84
- or (is_img and not self.can_webp)
85
- or (self.args.th_ff_jpg and (not is_img or preferred == "ff"))
86
- ):
87
- fmt = "j"
81
+ if sfmt == "j" and self.args.th_no_jpg:
82
+ sfmt = "w"
83
+
84
+ if sfmt == "w":
85
+ if (
86
+ self.args.th_no_webp
87
+ or (is_img and not self.can_webp)
88
+ or (self.args.th_ff_jpg and (not is_img or preferred == "ff"))
89
+ ):
90
+ sfmt = "j"
91
+
92
+ vf_crop = dbv.flags["crop"]
93
+ vf_th3x = dbv.flags["th3x"]
94
+
95
+ if "f" in vf_crop:
96
+ sfmt += "f" if "n" in vf_crop else ""
97
+ else:
98
+ sfmt += "f" if "f" in fmt else ""
99
+
100
+ if "f" in vf_th3x:
101
+ sfmt += "3" if "y" in vf_th3x else ""
102
+ else:
103
+ sfmt += "3" if "3" in fmt else ""
104
+
105
+ fmt = sfmt
88
106
 
89
107
  histpath = self.asrv.vfs.histtab.get(ptop)
90
108
  if not histpath:
copyparty/th_srv.py CHANGED
@@ -94,8 +94,8 @@ def thumb_path(histpath , rem , mtime , fmt , ffa ) :
94
94
 
95
95
  # spectrograms are never cropped; strip fullsize flag
96
96
  ext = rem.split(".")[-1].lower()
97
- if ext in ffa and fmt in ("wf", "jf"):
98
- fmt = fmt[:1]
97
+ if ext in ffa and fmt[:2] in ("wf", "jf"):
98
+ fmt = fmt.replace("f", "")
99
99
 
100
100
  rd += "\n" + fmt
101
101
  h = hashlib.sha512(afsenc(rd)).digest()
@@ -197,9 +197,10 @@ class ThumbSrv(object):
197
197
  with self.mutex:
198
198
  return not self.nthr
199
199
 
200
- def getres(self, vn ) :
200
+ def getres(self, vn , fmt ) :
201
+ mul = 3 if "3" in fmt else 1
201
202
  w, h = vn.flags["thsize"].split("x")
202
- return int(w), int(h)
203
+ return int(w) * mul, int(h) * mul
203
204
 
204
205
  def get(self, ptop , rem , mtime , fmt ) :
205
206
  histpath = self.asrv.vfs.histtab.get(ptop)
@@ -361,7 +362,7 @@ class ThumbSrv(object):
361
362
 
362
363
  def fancy_pillow(self, im , fmt , vn ) :
363
364
  # exif_transpose is expensive (loads full image + unconditional copy)
364
- res = self.getres(vn)
365
+ res = self.getres(vn, fmt)
365
366
  r = max(*res) * 2
366
367
  im.thumbnail((r, r), resample=Image.LANCZOS)
367
368
  try:
@@ -376,7 +377,7 @@ class ThumbSrv(object):
376
377
  if rot in rots:
377
378
  im = im.transpose(rots[rot])
378
379
 
379
- if fmt.endswith("f"):
380
+ if "f" in fmt:
380
381
  im.thumbnail(res, resample=Image.LANCZOS)
381
382
  else:
382
383
  iw, ih = im.size
@@ -393,7 +394,7 @@ class ThumbSrv(object):
393
394
  im = self.fancy_pillow(im, fmt, vn)
394
395
  except Exception as ex:
395
396
  self.log("fancy_pillow {}".format(ex), "90")
396
- im.thumbnail(self.getres(vn))
397
+ im.thumbnail(self.getres(vn, fmt))
397
398
 
398
399
  fmts = ["RGB", "L"]
399
400
  args = {"quality": 40}
@@ -419,10 +420,10 @@ class ThumbSrv(object):
419
420
  def conv_vips(self, abspath , tpath , fmt , vn ) :
420
421
  self.wait4ram(0.2, tpath)
421
422
  crops = ["centre", "none"]
422
- if fmt.endswith("f"):
423
+ if "f" in fmt:
423
424
  crops = ["none"]
424
425
 
425
- w, h = self.getres(vn)
426
+ w, h = self.getres(vn, fmt)
426
427
  kw = {"height": h, "size": "down", "intent": "relative"}
427
428
 
428
429
  for c in crops:
@@ -451,12 +452,12 @@ class ThumbSrv(object):
451
452
  seek = [b"-ss", "{:.0f}".format(dur / 3).encode("utf-8")]
452
453
 
453
454
  scale = "scale={0}:{1}:force_original_aspect_ratio="
454
- if fmt.endswith("f"):
455
+ if "f" in fmt:
455
456
  scale += "decrease,setsar=1:1"
456
457
  else:
457
458
  scale += "increase,crop={0}:{1},setsar=1:1"
458
459
 
459
- res = self.getres(vn)
460
+ res = self.getres(vn, fmt)
460
461
  bscale = scale.format(*list(res)).encode("utf-8")
461
462
  # fmt: off
462
463
  cmd = [
@@ -591,7 +592,11 @@ class ThumbSrv(object):
591
592
  need = 0.2 + dur / coeff
592
593
  self.wait4ram(need, tpath)
593
594
 
594
- fc = "[0:a:0]aresample=48000{},showspectrumpic=s=640x512,crop=780:544:70:50[o]"
595
+ fc = "[0:a:0]aresample=48000{},showspectrumpic=s="
596
+ if "3" in fmt:
597
+ fc += "1280x1024,crop=1420:1056:70:48[o]"
598
+ else:
599
+ fc += "640x512,crop=780:544:70:48[o]"
595
600
 
596
601
  if self.args.th_ff_swr:
597
602
  fco = ":filter_size=128:cutoff=0.877"
copyparty/up2k.py CHANGED
@@ -151,7 +151,7 @@ class Up2k(object):
151
151
  self.hashq = Queue()
152
152
 
153
153
 
154
- self.tagq = Queue()
154
+ self.tagq = Queue()
155
155
  self.tag_event = threading.Condition()
156
156
  self.hashq_mutex = threading.Lock()
157
157
  self.n_hashq = 0
@@ -549,7 +549,7 @@ class Up2k(object):
549
549
  runihook(self.log, cmd, vol, ups)
550
550
 
551
551
  def _vis_job_progress(self, job ) :
552
- perc = 100 - (len(job["need"]) * 100.0 / len(job["hash"]))
552
+ perc = 100 - (len(job["need"]) * 100.0 / (len(job["hash"]) or 1))
553
553
  path = djoin(job["ptop"], job["prel"], job["name"])
554
554
  return "{:5.1f}% {}".format(perc, path)
555
555
 
@@ -2052,12 +2052,13 @@ class Up2k(object):
2052
2052
  return
2053
2053
 
2054
2054
  try:
2055
+ st = bos.stat(qe.abspath)
2055
2056
  if not qe.mtp:
2056
2057
  if self.args.mtag_vv:
2057
2058
  t = "tag-thr: {}({})"
2058
2059
  self.log(t.format(self.mtag.backend, qe.abspath), "90")
2059
2060
 
2060
- tags = self.mtag.get(qe.abspath)
2061
+ tags = self.mtag.get(qe.abspath) if st.st_size else {}
2061
2062
  else:
2062
2063
  if self.args.mtag_vv:
2063
2064
  t = "tag-thr: {}({})"
@@ -2098,11 +2099,16 @@ class Up2k(object):
2098
2099
  """will mutex"""
2099
2100
  assert self.mtag
2100
2101
 
2101
- if not bos.path.isfile(abspath):
2102
+ try:
2103
+ st = bos.stat(abspath)
2104
+ except:
2105
+ return 0
2106
+
2107
+ if not stat.S_ISREG(st.st_mode):
2102
2108
  return 0
2103
2109
 
2104
2110
  try:
2105
- tags = self.mtag.get(abspath)
2111
+ tags = self.mtag.get(abspath) if st.st_size else {}
2106
2112
  except Exception as ex:
2107
2113
  self._log_tag_err("", abspath, ex)
2108
2114
  return 0
@@ -3095,7 +3101,7 @@ class Up2k(object):
3095
3101
  raise
3096
3102
 
3097
3103
  if "e2t" in self.flags[ptop]:
3098
- self.tagq.put((ptop, wark, rd, fn, ip, at))
3104
+ self.tagq.put((ptop, wark, rd, fn, sz, ip, at))
3099
3105
  self.n_tagq += 1
3100
3106
 
3101
3107
  return True
@@ -3677,9 +3683,10 @@ class Up2k(object):
3677
3683
  )
3678
3684
  job = reg.get(wark) if wark else None
3679
3685
  if job:
3680
- t = "forgetting partial upload {} ({})"
3681
- p = self._vis_job_progress(job)
3682
- self.log(t.format(wark, p))
3686
+ if job["need"]:
3687
+ t = "forgetting partial upload {} ({})"
3688
+ p = self._vis_job_progress(job)
3689
+ self.log(t.format(wark, p))
3683
3690
  assert wark
3684
3691
  del reg[wark]
3685
3692
 
@@ -4052,14 +4059,14 @@ class Up2k(object):
4052
4059
  with self.mutex:
4053
4060
  self.n_tagq -= 1
4054
4061
 
4055
- ptop, wark, rd, fn, ip, at = self.tagq.get()
4062
+ ptop, wark, rd, fn, sz, ip, at = self.tagq.get()
4056
4063
  if "e2t" not in self.flags[ptop]:
4057
4064
  continue
4058
4065
 
4059
4066
  # self.log("\n " + repr([ptop, rd, fn]))
4060
4067
  abspath = djoin(ptop, rd, fn)
4061
4068
  try:
4062
- tags = self.mtag.get(abspath)
4069
+ tags = self.mtag.get(abspath) if sz else {}
4063
4070
  ntags1 = len(tags)
4064
4071
  parsers = self._get_parsers(ptop, tags, abspath)
4065
4072
  if self.args.mtag_vv:
copyparty/util.py CHANGED
@@ -1747,7 +1747,7 @@ def get_spd(nbyte , t0 , t = None) :
1747
1747
  if t is None:
1748
1748
  t = time.time()
1749
1749
 
1750
- bps = nbyte / ((t - t0) + 0.001)
1750
+ bps = nbyte / ((t - t0) or 0.001)
1751
1751
  s1 = humansize(nbyte).replace(" ", "\033[33m").replace("iB", "")
1752
1752
  s2 = humansize(bps).replace(" ", "\033[35m").replace("iB", "")
1753
1753
  return "%s \033[0m%s/s\033[0m" % (s1, s2)
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.14"
5
- S_BUILD_DT = "2024-01-27"
4
+ S_VERSION = "1.15"
5
+ S_BUILD_DT = "2024-02-18"
6
6
 
7
7
  """
8
8
  u2c.py: upload to copyparty
@@ -29,7 +29,7 @@ import platform
29
29
  import threading
30
30
  import datetime
31
31
 
32
- EXE = sys.executable.endswith("exe")
32
+ EXE = bool(getattr(sys, "frozen", False))
33
33
 
34
34
  try:
35
35
  import argparse
@@ -846,12 +846,12 @@ class Ctl(object):
846
846
  txt = " "
847
847
 
848
848
  if not self.up_br:
849
- spd = self.hash_b / (time.time() - self.t0)
850
- eta = (self.nbytes - self.hash_b) / (spd + 1)
849
+ spd = self.hash_b / ((time.time() - self.t0) or 1)
850
+ eta = (self.nbytes - self.hash_b) / (spd or 1)
851
851
  else:
852
- spd = self.up_br / (time.time() - self.t0_up)
852
+ spd = self.up_br / ((time.time() - self.t0_up) or 1)
853
853
  spd = self.spd = (self.spd or spd) * 0.9 + spd * 0.1
854
- eta = (self.nbytes - self.up_b) / (spd + 1)
854
+ eta = (self.nbytes - self.up_b) / (spd or 1)
855
855
 
856
856
  spd = humansize(spd)
857
857
  self.eta = str(datetime.timedelta(seconds=int(eta)))
Binary file
Binary file
@@ -161,3 +161,4 @@
161
161
  </body>
162
162
 
163
163
  </html>
164
+
Binary file
@@ -61,3 +61,4 @@
61
61
 
62
62
  </body>
63
63
  </html>
64
+
copyparty/web/cf.html CHANGED
@@ -25,3 +25,4 @@
25
25
  </body>
26
26
 
27
27
  </html>
28
+
Binary file
copyparty/web/md.html CHANGED
@@ -160,3 +160,4 @@ try { l.light = drk? 0:1; } catch (ex) { }
160
160
  <script src="{{ r }}/.cpr/md2.js?_={{ ts }}"></script>
161
161
  {%- endif %}
162
162
  </body></html>
163
+
copyparty/web/mde.html CHANGED
@@ -54,3 +54,4 @@ try { l.light = drk? 0:1; } catch (ex) { }
54
54
  <script src="{{ r }}/.cpr/deps/easymde.js?_={{ ts }}"></script>
55
55
  <script src="{{ r }}/.cpr/mde.js?_={{ ts }}"></script>
56
56
  </body></html>
57
+
copyparty/web/msg.html CHANGED
@@ -48,4 +48,5 @@
48
48
  {%- endif %}
49
49
  </body>
50
50
 
51
- </html>
51
+ </html>
52
+
copyparty/web/splash.html CHANGED
@@ -118,3 +118,4 @@ document.documentElement.className = (STG && STG.cpp_thm) || "{{ this.args.theme
118
118
  <script src="{{ r }}/.cpr/splash.js?_={{ ts }}"></script>
119
119
  </body>
120
120
  </html>
121
+
copyparty/web/svcs.html CHANGED
@@ -246,3 +246,4 @@ document.documentElement.className = (STG && STG.cpp_thm) || "{{ args.theme }}";
246
246
  <script src="{{ r }}/.cpr/svcs.js?_={{ ts }}"></script>
247
247
  </body>
248
248
  </html>
249
+
copyparty/web/ui.css.gz CHANGED
Binary file
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: copyparty
3
- Version: 1.10.0
3
+ Version: 1.10.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
@@ -46,7 +46,7 @@ Requires-Dist: pyopenssl ; extra == 'ftps'
46
46
  Provides-Extra: pwhash
47
47
  Requires-Dist: argon2-cffi ; extra == 'pwhash'
48
48
  Provides-Extra: tftpd
49
- Requires-Dist: partftpy >=0.2.0 ; extra == 'tftpd'
49
+ Requires-Dist: partftpy >=0.3.0 ; extra == 'tftpd'
50
50
  Provides-Extra: thumbnails
51
51
  Requires-Dist: Pillow ; extra == 'thumbnails'
52
52
  Provides-Extra: thumbnails2
@@ -1008,17 +1008,24 @@ a TFTP server (read/write) can be started using `--tftp 3969` (you probably wan
1008
1008
  * based on [partftpy](https://github.com/9001/partftpy)
1009
1009
  * no accounts; read from world-readable folders, write to world-writable, overwrite in world-deletable
1010
1010
  * needs a dedicated port (cannot share with the HTTP/HTTPS API)
1011
- * run as root to use the spec-recommended port `69` (nice)
1011
+ * run as root (or see below) to use the spec-recommended port `69` (nice)
1012
1012
  * can reply from a predefined portrange (good for firewalls)
1013
1013
  * only supports the binary/octet/image transfer mode (no netascii)
1014
1014
  * [RFC 7440](https://datatracker.ietf.org/doc/html/rfc7440) is **not** supported, so will be extremely slow over WAN
1015
- * expect 1100 KiB/s over 1000BASE-T, 400-500 KiB/s over wifi, 200 on bad wifi
1015
+ * assuming default blksize (512), expect 1100 KiB/s over 100BASE-T, 400-500 KiB/s over wifi, 200 on bad wifi
1016
+
1017
+ most clients expect to find TFTP on port 69, but on linux and macos you need to be root to listen on that. Alternatively, listen on 3969 and use NAT on the server to forward 69 to that port;
1018
+ * on linux: `iptables -t nat -A PREROUTING -i eth0 -p udp --dport 69 -j REDIRECT --to-port 3969`
1016
1019
 
1017
1020
  some recommended TFTP clients:
1021
+ * curl (cross-platform, read/write)
1022
+ * get: `curl --tftp-blksize 1428 tftp://127.0.0.1:3969/firmware.bin`
1023
+ * put: `curl --tftp-blksize 1428 -T firmware.bin tftp://127.0.0.1:3969/`
1018
1024
  * windows: `tftp.exe` (you probably already have it)
1025
+ * `tftp -i 127.0.0.1 put firmware.bin`
1019
1026
  * linux: `tftp-hpa`, `atftp`
1020
- * `tftp 127.0.0.1 3969 -v -m binary -c put firmware.bin`
1021
- * `curl tftp://127.0.0.1:3969/firmware.bin` (read-only)
1027
+ * `atftp --option "blksize 1428" 127.0.0.1 3969 -p -l firmware.bin -r firmware.bin`
1028
+ * `tftp -v -m binary 127.0.0.1 3969 -c put firmware.bin`
1022
1029
 
1023
1030
 
1024
1031
  ## smb server
@@ -1051,7 +1058,7 @@ known client bugs:
1051
1058
  * however smb1 is buggy and is not enabled by default on win10 onwards
1052
1059
  * windows cannot access folders which contain filenames with invalid unicode or forbidden characters (`<>:"/\|?*`), or names ending with `.`
1053
1060
 
1054
- the smb protocol listens on TCP port 445, which is a privileged port on linux and macos, which would require running copyparty as root. However, this can be avoided by listening on another port using `--smb-port 3945` and then using NAT to forward the traffic from 445 to there;
1061
+ the smb protocol listens on TCP port 445, which is a privileged port on linux and macos, which would require running copyparty as root. However, this can be avoided by listening on another port using `--smb-port 3945` and then using NAT on the server to forward the traffic from 445 to there;
1055
1062
  * on linux: `iptables -t nat -A PREROUTING -i eth0 -p tcp --dport 445 -j REDIRECT --to-port 3945`
1056
1063
 
1057
1064
  authenticate with one of the following:
@@ -1,20 +1,20 @@
1
1
  copyparty/__init__.py,sha256=34xcU8AoRRQscgVSx2gC6DeUyu7ZLmEVlXjttdQgXnI,1752
2
- copyparty/__main__.py,sha256=EmtEs47maDLbYA23ybbNBiFvU1MvIXi645s6v0drjhA,91050
3
- copyparty/__version__.py,sha256=TYIV6QFFwIMkehKjFilFYTNPGcnALPk1DcPfRseJlZQ,246
4
- copyparty/authsrv.py,sha256=vcf0aIYD1o25pW1z-9xQfsupZdErr54uGLXUSkb3Aa0,73461
2
+ copyparty/__main__.py,sha256=wK7Fop32aq7LUKmBCQcp3A6bJ2abdpj3em9IigFyniI,91595
3
+ copyparty/__version__.py,sha256=z7G2R3a_jwf2rpKu1PpKRwOFSv86M6MOa1nRTzu_t9I,246
4
+ copyparty/authsrv.py,sha256=BzJwPbYhTr9etW50DMY0UXRHBdNOKdnSdTuZ4qXlNvs,73470
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
8
8
  copyparty/broker_util.py,sha256=CnX_LAhQQqouONcDLtVkVlcBX3Z6pWuKDQDmmbHGEg4,1489
9
9
  copyparty/cert.py,sha256=bt1n-629-Gtez1C_CroXq53gXEmUsVjkTbkQ6Gu2Zfc,7323
10
- copyparty/cfg.py,sha256=LQAO206hdj8JATfXKxJEaV48T6GmI7-bMGxc3edsV4g,8944
10
+ copyparty/cfg.py,sha256=XMqgS5JMcTe4vCmAnUkSXrKBPTzn0zHYsBenXcZNK_s,9000
11
11
  copyparty/dxml.py,sha256=lZpg-kn-kQsXRtNY1n6fRaS-b7uXzMCyv8ovKnhZcZc,1548
12
12
  copyparty/fsutil.py,sha256=c4fTvmclKbVABNsjU4rGddsjCgRwi9YExAyo-06ATc8,3932
13
- copyparty/ftpd.py,sha256=wBoNl-DBMXJhP5irFKEFisMg10vouB5-VA9c6Juy1tI,15878
14
- copyparty/httpcli.py,sha256=_Vxa5eHHmLcLHjO34ycYBKcmCrbYmaLOLCksP-MAs8M,141783
13
+ copyparty/ftpd.py,sha256=BqxH4j-mLrjRvxW3Lnz-qrR4_ETv3OnmIRNifSf_3Fk,15939
14
+ copyparty/httpcli.py,sha256=YoumInsa3mzoJXQHjznsPpDYuKuzx0DoLXgx6vy8Oew,142323
15
15
  copyparty/httpconn.py,sha256=XyFv6yIsyq0InrqulwovsmAcqziEq_O2zsI41K8bJWs,6698
16
16
  copyparty/httpsrv.py,sha256=q0z4RuoTMiZz_QbUSCFikIDbKHm9_JT1uGTGuDvOdO0,16218
17
- copyparty/ico.py,sha256=GRoDHI3GGP8JlGLNp-S_44g6MLAeDvVg2TSIOMsVLhw,3562
17
+ copyparty/ico.py,sha256=AYHdK6NlYBfBgafVYXia3jHQ9XHZdUL1D8WftLMAzIU,3545
18
18
  copyparty/mdns.py,sha256=CcraggbDxTT1ntYzD8Ebgqmw5Q4HkyZcfh5ymtCV_ak,17469
19
19
  copyparty/metrics.py,sha256=glBeNYGWMF4zEK9__aKks7qKnSwwJuWAzPx6ljRYt4k,8754
20
20
  copyparty/mtag.py,sha256=OZM6cVJafOe_LZ4bPJzVJbORA_omzewwJh-N11u3nak,16855
@@ -27,12 +27,12 @@ copyparty/sutil.py,sha256=dZhDAzVAp02i6YmGqcedy4LaYVffnJCSp3F6WEwfz7c,2967
27
27
  copyparty/svchub.py,sha256=LyhbfI4pYga-X7VSsbyzx3xM5kn2e4Z6bSObU4cRU0w,29036
28
28
  copyparty/szip.py,sha256=SJg5nzN_5oaIsIYXlRvcLHVTqngLAOPfe4_WVsuhSkw,8520
29
29
  copyparty/tcpsrv.py,sha256=2LGUqOBAIrsmL-1pwrbsPXR71gutHccqRp-hjzt91Us,17289
30
- copyparty/tftpd.py,sha256=OODldqgWFgzlnNbypOjtjy2Qape7jYXGv5PK3K3zBn4,9330
31
- copyparty/th_cli.py,sha256=MSp2kpoAPiX1bndMthv6JK2gt3K6CjrloWuJsI_CL94,3869
32
- copyparty/th_srv.py,sha256=u6pMngAs5LK2vknqvLR2GqDkgOtuRWT7cEolp0RFEgY,25581
30
+ copyparty/tftpd.py,sha256=yM_byDfFI2c7TwgDbTtw9xt3LWJ3PBBHfIsMsB6MDow,13028
31
+ copyparty/th_cli.py,sha256=e2FF-wVY4qcwAO8hrtTlwn3EJIOHyZt5sYhl0N8eudk,4378
32
+ copyparty/th_srv.py,sha256=HBw6mq0RrJkAJupJpwFAjCMNS67Y9hda6sv7gFSzRwk,25764
33
33
  copyparty/u2idx.py,sha256=JBEqKX1ZM8GIvQrDYb5VQ_5QiFNFsjWF6H9drHlPVEY,12709
34
- copyparty/up2k.py,sha256=q0_vkf87nEz00EL1VdmmvHor8BEhLqr8sSoVLOINFao,136898
35
- copyparty/util.py,sha256=KO6Fz3_o6Z8-0eoXYuxGgqLeoGMfHzGoj2Dcw1a3HgM,79682
34
+ copyparty/up2k.py,sha256=zWPrcddZyxwy2rEfjJEOm-bQgt6ydzDSSOtVcYCnWYA,137144
35
+ copyparty/util.py,sha256=9WQ9itm24ZP1n5qB9lsWpiQqSB2IKdHPxl3y6RIgQAM,79683
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
@@ -54,35 +54,35 @@ 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=7nAWVL6pk_RbEPMAF2HSGz_6Ajg_zne0S5Rl3gO0VxY,7641
58
- copyparty/web/browser.css.gz,sha256=yl6xtgbs7Ci-TdjwH3h-ApKoCvF1R8zfeGcjm1VgnJo,11254
59
- copyparty/web/browser.html,sha256=pIkjj1J5vY-yy4_BsN0gZrBHiooEeLcMIeqnvcIotoY,4872
60
- copyparty/web/browser.js.gz,sha256=TsbLohiiNV_5bYkfNxdOUr70N_aQTFh68Ncplf-Ea90,65976
61
- copyparty/web/browser2.html,sha256=3kR3QiDXYqtAo7gOoBtdTP3eRvibUCZHVVAbmAfaAVU,1604
62
- copyparty/web/cf.html,sha256=_tgwgNtK5MpjvvthGXx6Q9sasDaiWruyZfXsXYVW2KA,588
57
+ copyparty/web/baguettebox.js.gz,sha256=BxwvSjky72hZprWofueRrlskiIeRrgGERUpKiZXQ5C8,7671
58
+ copyparty/web/browser.css.gz,sha256=_C5z5xYMkKGnwT_y_dmQzbQBkXbYn6h0KWGdiMv_7yQ,11380
59
+ copyparty/web/browser.html,sha256=72E2Q6grz_MtkmFVIXE_0rOVzliyQo-XwuawkJsB-qY,4873
60
+ copyparty/web/browser.js.gz,sha256=NdOb9VBrO_ETpDt6LBhoUPPhiehYEckDCzVZndfpmG0,66579
61
+ copyparty/web/browser2.html,sha256=AtGRGtb_rsyu-gWXEuXDMg9WqbjJFcDDJXel3ScG-4c,1605
62
+ copyparty/web/cf.html,sha256=lJThtNFNAQT1ClCHHlivAkDGE0LutedwopXD62Z8Nys,589
63
63
  copyparty/web/dbg-audio.js.gz,sha256=Ma-KZtK8LnmiwNvNKFKXMPYl_Nn_3U7GsJ6-DRWC2HE,688
64
64
  copyparty/web/md.css.gz,sha256=DadSn01UeCBE47AKqCjmrc7x2ZLZMwl4f__RhGCDzS4,2011
65
- copyparty/web/md.html,sha256=Wv7QgWgOH4q_i1g89YaNNQO9SQ7xfbIAgqTp4FlI1vM,4109
65
+ copyparty/web/md.html,sha256=QdrN1-58IgdXj6QnT9w3_nbT825A6UWknj6WOKxn69I,4110
66
66
  copyparty/web/md.js.gz,sha256=7uNDAB8ctTLm4vr3ui-hfqusn7rORDOJfydonOFEWCU,4277
67
67
  copyparty/web/md2.css.gz,sha256=bM_1hsegXnEig1b6OQ28yWWUlMPje6u9M58VZ1Z-C9g,681
68
68
  copyparty/web/md2.js.gz,sha256=P47IdvqfOaDSnAxKk-0K8au9yUkH3be6t4YZ8AosDtI,8343
69
69
  copyparty/web/mde.css.gz,sha256=L6YhxnYBoqT5bSwD99rx8ALXDh8d_1-sCvbc_p3MoAU,929
70
- copyparty/web/mde.html,sha256=VOvill4xAy7B5ZC_m-z4Xwu5_y_Ta4J7GzzxAE7dMJ8,1677
70
+ copyparty/web/mde.html,sha256=1KPTKGFxTTuJ9Zp9OBdmm1DREtSvijqnlsZoNTpv7q0,1678
71
71
  copyparty/web/mde.js.gz,sha256=eo-UjMKIbRcPL6LR8BDl4IkCGcpJyFK0mhYte7WgqLc,2219
72
72
  copyparty/web/msg.css.gz,sha256=-wOmwxU0_Xg2W53rIAjKA2TeFAVQNJeT8ZMtmiJiviY,252
73
- copyparty/web/msg.html,sha256=V-X8PDeu64itAYijLjXTKJJzXylLxFVrJXdh-XM9-50,894
73
+ copyparty/web/msg.html,sha256=-MPzXZnHzQNuX04-aSn_fA7L15GASOHnCyeS1eVCYUI,896
74
74
  copyparty/web/splash.css.gz,sha256=ZJ1E7wWGYBVl50ytkxdYkLsLDhRJtGwP2-YdMUo7MgI,927
75
- copyparty/web/splash.html,sha256=eYBC4h23RSGQMYoFYAIn3hwr-fycqM86hhC9kt85LMM,3781
75
+ copyparty/web/splash.html,sha256=4ZgHc7v1C9ABwzde4UvlsyTCtkmIwKVMXm-3rHcFXsM,3782
76
76
  copyparty/web/splash.js.gz,sha256=3PMKsMLwEsHP8eonBmtvonm9yeNq69xjMi8ZqnVeKcA,1255
77
- copyparty/web/svcs.html,sha256=xwxvs7Fad8h9xn7xe3D7_zbR-GF-sMRyDgPi2m5yOiA,11701
77
+ copyparty/web/svcs.html,sha256=Jn-G2QMF09oc01C_tmJwGZAdjFzbeu4unLSs6dowluM,11702
78
78
  copyparty/web/svcs.js.gz,sha256=k81ZvZ3I-f4fMHKrNGGOgOlvXnCBz0mVjD-8mieoWCA,520
79
- copyparty/web/ui.css.gz,sha256=y_8bujqSA5YaI6pBpFpQDj3KGjk8_tDOP4bE0m2uNc4,2532
79
+ copyparty/web/ui.css.gz,sha256=oyrc_sf0u-KycOUV7FoRI7uF6oK6ruQ13Qzyz-7rad8,2567
80
80
  copyparty/web/up2k.js.gz,sha256=mT9u6jZFWjTTVgNodHrMvmuQ-sp7N4S6AtJHlrbOqoE,22027
81
81
  copyparty/web/util.js.gz,sha256=ig-Cs_fMqw4yZ0psAPTagr_0GkKQNXH2iCEprmfB9W4,14283
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=n9ihrDXb8qa_WEiEUwE9MzHVItyUvP2mCtB8S7cMPZs,38448
85
+ copyparty/web/a/u2c.py,sha256=Yo_zsjBg1Op53sPFhzcbAkd-VZlm6nZ4DMVN_s0Yu2k,38469
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
@@ -90,7 +90,7 @@ 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/marked.js.gz,sha256=jS4XXdh9KkF-isrI_nObhQOhQnlnnYZd90xzuRoeenI,22376
93
+ copyparty/web/deps/marked.js.gz,sha256=WhUw0LPWs5cxqORQmW-zajQiUc5rSRkcvf3eT3-XSZc,22372
94
94
  copyparty/web/deps/mini-fa.css.gz,sha256=CTPrNaH8OTVmxajrGP88E2MkjadY9_81TBVnd9sw9Y8,572
95
95
  copyparty/web/deps/mini-fa.woff,sha256=L9DNncV2TIyvsrspMbJouvnnt7F068Hbn7YZYvN76AU,2784
96
96
  copyparty/web/deps/prism.css.gz,sha256=Z_A6rJ3MN5KWnjvXaV787aTW_5DT-xjFd0YZ7_W-Krk,1468
@@ -99,9 +99,9 @@ copyparty/web/deps/prismd.css.gz,sha256=ObUlksQVr-OuYlTz-I4B23TeBg2QDVVGRnWBz8cV
99
99
  copyparty/web/deps/scp.woff2,sha256=w99BDU5i8MukkMEL-iW0YO9H4vFFZSPWxbkH70ytaAg,8612
100
100
  copyparty/web/deps/sha512.ac.js.gz,sha256=lFZaCLumgWxrvEuDr4bqdKHsqjX82AbVAb7_F45Yk88,7033
101
101
  copyparty/web/deps/sha512.hw.js.gz,sha256=vqoXeracj-99Z5MfY3jK2N4WiSzYQdfjy0RnUlQDhSU,8110
102
- copyparty-1.10.0.dist-info/LICENSE,sha256=gOr4h33pCsBEg9uIy9AYmb7qlocL4V9t2uPJS5wllr0,1072
103
- copyparty-1.10.0.dist-info/METADATA,sha256=ZVEqnxPQAU2TQaNxa-q6tBluezZxdr8ZUEUChBPAv0g,111776
104
- copyparty-1.10.0.dist-info/WHEEL,sha256=oiQVh_5PnQM0E3gPdiz09WCNmwiHDMaGer_elqB3coM,92
105
- copyparty-1.10.0.dist-info/entry_points.txt,sha256=4zw6a3rqASywQomiYLObjjlxybaI65LYYOTJwgKz7b0,128
106
- copyparty-1.10.0.dist-info/top_level.txt,sha256=LnYUPsDyk-8kFgM6YJLG4h820DQekn81cObKSu9g-sI,10
107
- copyparty-1.10.0.dist-info/RECORD,,
102
+ copyparty-1.10.2.dist-info/LICENSE,sha256=gOr4h33pCsBEg9uIy9AYmb7qlocL4V9t2uPJS5wllr0,1072
103
+ copyparty-1.10.2.dist-info/METADATA,sha256=VztHHVGSLDK0ZRVxlGhBN2WuBCO3fpz6rbK0iVROAYY,112378
104
+ copyparty-1.10.2.dist-info/WHEEL,sha256=oiQVh_5PnQM0E3gPdiz09WCNmwiHDMaGer_elqB3coM,92
105
+ copyparty-1.10.2.dist-info/entry_points.txt,sha256=4zw6a3rqASywQomiYLObjjlxybaI65LYYOTJwgKz7b0,128
106
+ copyparty-1.10.2.dist-info/top_level.txt,sha256=LnYUPsDyk-8kFgM6YJLG4h820DQekn81cObKSu9g-sI,10
107
+ copyparty-1.10.2.dist-info/RECORD,,