copyparty 1.16.12__py3-none-any.whl → 1.16.13__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
@@ -1175,6 +1175,7 @@ def add_webdav(ap):
1175
1175
  ap2.add_argument("--dav-mac", action="store_true", help="disable apple-garbage filter -- allow macos to create junk files (._* and .DS_Store, .Spotlight-*, .fseventsd, .Trashes, .AppleDouble, __MACOS)")
1176
1176
  ap2.add_argument("--dav-rt", action="store_true", help="show symlink-destination's lastmodified instead of the link itself; always enabled for recursive listings (volflag=davrt)")
1177
1177
  ap2.add_argument("--dav-auth", action="store_true", help="force auth for all folders (required by davfs2 when only some folders are world-readable) (volflag=davauth)")
1178
+ ap2.add_argument("--dav-ua1", metavar="PTN", type=u, default=r" kioworker/", help="regex of tricky user-agents which expect 401 from GET requests; disable with [\033[32mno\033[0m] or blank")
1178
1179
 
1179
1180
 
1180
1181
  def add_tftp(ap):
@@ -1387,7 +1388,7 @@ def add_transcoding(ap):
1387
1388
 
1388
1389
  def add_rss(ap):
1389
1390
  ap2 = ap.add_argument_group('RSS options')
1390
- ap2.add_argument("--rss", action="store_true", help="enable RSS output (experimental)")
1391
+ ap2.add_argument("--rss", action="store_true", help="enable RSS output (experimental) (volflag=rss)")
1391
1392
  ap2.add_argument("--rss-nf", metavar="HITS", type=int, default=250, help="default number of files to return (url-param 'nf')")
1392
1393
  ap2.add_argument("--rss-fext", metavar="E,E", type=u, default="", help="default list of file extensions to include (url-param 'fext'); blank=all")
1393
1394
  ap2.add_argument("--rss-sort", metavar="ORD", type=u, default="m", help="default sort order (url-param 'sort'); [\033[32mm\033[0m]=last-modified [\033[32mu\033[0m]=upload-time [\033[32mn\033[0m]=filename [\033[32ms\033[0m]=filesize; Uppercase=oldest-first. Note that upload-time is 0 for non-uploaded files")
@@ -1478,7 +1479,9 @@ def add_ui(ap, retry):
1478
1479
  ap2.add_argument("--hsortn", metavar="N", type=int, default=2, help="number of sorting rules to include in media URLs by default (volflag=hsortn)")
1479
1480
  ap2.add_argument("--unlist", metavar="REGEX", type=u, default="", help="don't show files matching \033[33mREGEX\033[0m in file list. Purely cosmetic! Does not affect API calls, just the browser. Example: [\033[32m\\.(js|css)$\033[0m] (volflag=unlist)")
1480
1481
  ap2.add_argument("--favico", metavar="TXT", type=u, default="c 000 none" if retry else "🎉 000 none", help="\033[33mfavicon-text\033[0m [ \033[33mforeground\033[0m [ \033[33mbackground\033[0m ] ], set blank to disable")
1482
+ ap2.add_argument("--ext-th", metavar="E=VP", type=u, action="append", help="use thumbnail-image \033[33mVP\033[0m for file-extension \033[33mE\033[0m, example: [\033[32mexe=/.res/exe.png\033[0m] (volflag=ext_th)")
1481
1483
  ap2.add_argument("--mpmc", metavar="URL", type=u, default="", help="change the mediaplayer-toggle mouse cursor; URL to a folder with {2..5}.png inside (or disable with [\033[32m.\033[0m])")
1484
+ ap2.add_argument("--spinner", metavar="TXT", type=u, default="🌲", help="\033[33memoji\033[0m or \033[33memoji,css\033[0m Example: [\033[32m🥖,padding:0\033[0m]")
1482
1485
  ap2.add_argument("--css-browser", metavar="L", type=u, default="", help="URL to additional CSS to include in the filebrowser html")
1483
1486
  ap2.add_argument("--js-browser", metavar="L", type=u, default="", help="URL to additional JS to include in the filebrowser html")
1484
1487
  ap2.add_argument("--js-other", metavar="L", type=u, default="", help="URL to additional JS to include in all other pages")
copyparty/__version__.py CHANGED
@@ -1,8 +1,8 @@
1
1
  # coding: utf-8
2
2
 
3
- VERSION = (1, 16, 12)
3
+ VERSION = (1, 16, 13)
4
4
  CODENAME = "COPYparty"
5
- BUILD_DT = (2025, 2, 9)
5
+ BUILD_DT = (2025, 2, 13)
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
@@ -1282,10 +1282,10 @@ class AuthSrv(object):
1282
1282
  # one or more bools before the final flag; eat them
1283
1283
  n1, uname = uname.split(",", 1)
1284
1284
  for _, vp, _, _ in vols:
1285
- self._read_volflag(flags[vp], n1, True, False)
1285
+ self._read_volflag(vp, flags[vp], n1, True, False)
1286
1286
 
1287
1287
  for _, vp, _, _ in vols:
1288
- self._read_volflag(flags[vp], uname, cval, False)
1288
+ self._read_volflag(vp, flags[vp], uname, cval, False)
1289
1289
 
1290
1290
  return
1291
1291
 
@@ -1372,20 +1372,34 @@ class AuthSrv(object):
1372
1372
 
1373
1373
  def _read_volflag(
1374
1374
  self,
1375
+ vpath ,
1375
1376
  flags ,
1376
1377
  name ,
1377
1378
  value ,
1378
1379
  is_list ,
1379
1380
  ) :
1381
+ if name not in flagdescs:
1382
+ name = name.lower()
1383
+
1384
+ # volflags are snake_case, but a leading dash is the removal operator
1385
+ if name not in flagdescs and "-" in name[1:]:
1386
+ name = name[:1] + name[1:].replace("-", "_")
1387
+
1380
1388
  desc = flagdescs.get(name.lstrip("-"), "?").replace("\n", " ")
1381
1389
 
1390
+ if not name:
1391
+ self._e("└─unreadable-line")
1392
+ t = "WARNING: the config for volume [/%s] indicated that a volflag was to be defined, but the volflag name was blank"
1393
+ self.log(t % (vpath,), 3)
1394
+ return
1395
+
1382
1396
  if re.match("^-[^-]+$", name):
1383
1397
  t = "└─unset volflag [{}] ({})"
1384
1398
  self._e(t.format(name[1:], desc))
1385
1399
  flags[name] = True
1386
1400
  return
1387
1401
 
1388
- zs = "mtp on403 on404 xbu xau xiu xbc xac xbr xar xbd xad xm xban"
1402
+ zs = "ext_th mtp on403 on404 xbu xau xiu xbc xac xbr xar xbd xad xm xban"
1389
1403
  if name not in zs.split():
1390
1404
  if value is True:
1391
1405
  t = "└─add volflag [{}] = {} ({})"
@@ -1550,6 +1564,17 @@ class AuthSrv(object):
1550
1564
  vol.all_vps.sort(key=lambda x: len(x[0]), reverse=True)
1551
1565
  vol.root = vfs
1552
1566
 
1567
+ zs = "neversymlink"
1568
+ k_ign = set(zs.split())
1569
+ for vol in vfs.all_vols.values():
1570
+ unknown_flags = set()
1571
+ for k, v in vol.flags.items():
1572
+ if k not in flagdescs and k not in k_ign:
1573
+ unknown_flags.add(k)
1574
+ if unknown_flags:
1575
+ t = "WARNING: the config for volume [/%s] has unrecognized volflags; will ignore: '%s'"
1576
+ self.log(t % (vol.vpath, "', '".join(unknown_flags)), 3)
1577
+
1553
1578
  enshare = self.args.shr
1554
1579
  shr = enshare[1:-1]
1555
1580
  shrs = enshare[1:]
@@ -1967,8 +1992,10 @@ class AuthSrv(object):
1967
1992
 
1968
1993
  # append additive args from argv to volflags
1969
1994
  hooks = "xbu xau xiu xbc xac xbr xar xbd xad xm xban".split()
1970
- for name in "mtp on404 on403".split() + hooks:
1971
- self._read_volflag(vol.flags, name, getattr(self.args, name), True)
1995
+ for name in "ext_th mtp on404 on403".split() + hooks:
1996
+ self._read_volflag(
1997
+ vol.vpath, vol.flags, name, getattr(self.args, name), True
1998
+ )
1972
1999
 
1973
2000
  for hn in hooks:
1974
2001
  cmds = vol.flags.get(hn)
@@ -1996,6 +2023,16 @@ class AuthSrv(object):
1996
2023
  ncmds.append(ocmd)
1997
2024
  vol.flags[hn] = ncmds
1998
2025
 
2026
+ ext_th = vol.flags["ext_th_d"] = {}
2027
+ etv = "(?)"
2028
+ try:
2029
+ for etv in vol.flags.get("ext_th") or []:
2030
+ k, v = etv.split("=")
2031
+ ext_th[k] = v
2032
+ except:
2033
+ t = "WARNING: volume [/%s]: invalid value specified for ext-th: %s"
2034
+ self.log(t % (vol.vpath, etv), 3)
2035
+
1999
2036
  # d2d drops all database features for a volume
2000
2037
  for grp, rm in [["d2d", "e2d"], ["d2t", "e2t"], ["d2d", "e2v"]]:
2001
2038
  if not vol.flags.get(grp, False):
@@ -2347,6 +2384,7 @@ class AuthSrv(object):
2347
2384
  "sb_lg": "" if "no_sb_lg" in vf else (vf.get("lg_sbf") or "y"),
2348
2385
  }
2349
2386
  js_htm = {
2387
+ "SPINNER": self.args.spinner,
2350
2388
  "s_name": self.args.bname,
2351
2389
  "have_up2k_idx": "e2d" in vf,
2352
2390
  "have_acode": not self.args.no_acode,
@@ -2356,6 +2394,7 @@ class AuthSrv(object):
2356
2394
  "have_del": not self.args.no_del,
2357
2395
  "have_unpost": int(self.args.unpost),
2358
2396
  "have_emp": self.args.emp,
2397
+ "ext_th": vf.get("ext_th_d") or {},
2359
2398
  "sb_md": "" if "no_sb_md" in vf else (vf.get("md_sbf") or "y"),
2360
2399
  "sba_md": vf.get("md_sba") or "",
2361
2400
  "sba_lg": vf.get("lg_sba") or "",
@@ -2759,7 +2798,9 @@ class AuthSrv(object):
2759
2798
  zs = "c ihead ohead mtm mtp on403 on404 xac xad xar xau xiu xban xbc xbd xbr xbu xm"
2760
2799
  lst = set(zs.split())
2761
2800
  askip = set("a v c vc cgen exp_lg exp_md theme".split())
2762
- fskip = set("exp_lg exp_md mv_re_r mv_re_t rm_re_r rm_re_t".split())
2801
+
2802
+ t = "exp_lg exp_md ext_th_d mv_re_r mv_re_t rm_re_r rm_re_t srch_re_dots srch_re_nodot"
2803
+ fskip = set(t.split())
2763
2804
 
2764
2805
  # keymap from argv to vflag
2765
2806
  amap = vf_bmap()
copyparty/cfg.py CHANGED
@@ -5,6 +5,9 @@ from __future__ import print_function, unicode_literals
5
5
  zs = "a c e2d e2ds e2dsa e2t e2ts e2tsr e2v e2vp e2vu ed emp i j lo mcr mte mth mtm mtp nb nc nid nih nth nw p q s ss sss v z zv"
6
6
  onedash = set(zs.split())
7
7
 
8
+ # verify that all volflags are documented here:
9
+ # grep volflag= __main__.py | sed -r 's/.*volflag=//;s/\).*//' | sort | uniq | while IFS= read -r x; do grep -E "\"$x(=[^ \"]+)?\": \"" cfg.py || printf '%s\n' "$x"; done
10
+
8
11
 
9
12
  def vf_bmap() :
10
13
  """argv-to-volflag: simple bools"""
@@ -106,6 +109,7 @@ def vf_cmap() :
106
109
  for k in (
107
110
  "exp_lg",
108
111
  "exp_md",
112
+ "ext_th",
109
113
  "mte",
110
114
  "mth",
111
115
  "mtp",
@@ -179,8 +183,11 @@ flagcats = {
179
183
  "e2dsa": "scans all folders for new files on startup; also sets -e2d",
180
184
  "e2t": "enable multimedia indexing; makes it possible to search for tags",
181
185
  "e2ts": "scan existing files for tags on startup; also sets -e2t",
182
- "e2tsa": "delete all metadata from DB (full rescan); also sets -e2ts",
186
+ "e2tsr": "delete all metadata from DB (full rescan); also sets -e2ts",
183
187
  "d2ts": "disables metadata collection for existing files",
188
+ "e2v": "verify integrity on startup by hashing files and comparing to db",
189
+ "e2vu": "when e2v fails, update the db (assume on-disk files are good)",
190
+ "e2vp": "when e2v fails, panic and quit copyparty",
184
191
  "d2ds": "disables onboot indexing, overrides -e2ds*",
185
192
  "d2t": "disables metadata collection, overrides -e2t*",
186
193
  "d2v": "disables file verification, overrides -e2v*",
@@ -200,6 +207,8 @@ flagcats = {
200
207
  "srch_excl": "exclude search results with URL matching this regex",
201
208
  },
202
209
  'database, audio tags\n"mte", "mth", "mtp", "mtm" all work the same as -mte, -mth, ...': {
210
+ "mte=artist,title": "media-tags to index/display",
211
+ "mth=fmt,res,ac": "media-tags to hide by default",
203
212
  "mtp=.bpm=f,audio-bpm.py": 'uses the "audio-bpm.py" program to\ngenerate ".bpm" tags from uploads (f = overwrite tags)',
204
213
  "mtp=ahash,vhash=media-hash.py": "collects two tags at once",
205
214
  },
@@ -213,6 +222,7 @@ flagcats = {
213
222
  "crop": "center-cropping (y/n/fy/fn)",
214
223
  "th3x": "3x resolution (y/n/fy/fn)",
215
224
  "convt": "conversion timeout in seconds",
225
+ "ext_th=s=/b.png": "use /b.png as thumbnail for file-extension s",
216
226
  },
217
227
  "handlers\n(better explained in --help-handlers)": {
218
228
  "on404=PY": "handle 404s by executing PY file",
@@ -235,8 +245,12 @@ flagcats = {
235
245
  "grid": "show grid/thumbnails by default",
236
246
  "gsel": "select files in grid by ctrl-click",
237
247
  "sort": "default sort order",
248
+ "nsort": "natural-sort of leading digits in filenames",
249
+ "hsortn": "number of sort-rules to add to media URLs",
238
250
  "unlist": "dont list files matching REGEX",
239
251
  "html_head=TXT": "includes TXT in the <head>, or @PATH for file at PATH",
252
+ "tcolor=#fc0": "theme color (a hint for webbrowsers, discord, etc.)",
253
+ "nodirsz": "don't show total folder size",
240
254
  "robots": "allows indexing by search engines (default)",
241
255
  "norobots": "kindly asks search engines to leave",
242
256
  "no_sb_md": "disable js sandbox for markdown files",
@@ -249,12 +263,33 @@ flagcats = {
249
263
  "lg_sba": "value of iframe allow-prop for *logue-sandbox",
250
264
  "nohtml": "return html and markdown as text/html",
251
265
  },
266
+ "opengraph (discord embeds)": {
267
+ "og": "enable OG (disables hotlinking)",
268
+ "og_site": "sitename; defaults to --name, disable with '-'",
269
+ "og_desc": "description text for all files; disable with '-'",
270
+ "og_th=jf": "thumbnail format; j / jf / jf3 / w / w3 / ...",
271
+ "og_title_a": "audio title format; default: {{ artist }} - {{ title }}",
272
+ "og_title_v": "video title format; default: {{ title }}",
273
+ "og_title_i": "image title format; default: {{ title }}",
274
+ "og_title=foo": "fallback title if there's nothing in the db",
275
+ "og_s_title": "force default title; do not read from tags",
276
+ "og_tpl": "custom html; see --og-tpl in --help",
277
+ "og_no_head": "you want to add tags manually with og_tpl",
278
+ "og_ua": "if defined: only send OG html if useragent matches this regex",
279
+ },
280
+ "textfiles": {
281
+ "exp": "enable textfile expansion; see --help-exp",
282
+ "exp_md": "placeholders to expand in markdown files; see --help",
283
+ "exp_lg": "placeholders to expand in prologue/epilogue; see --help",
284
+ },
252
285
  "others": {
253
286
  "dots": "allow all users with read-access to\nenable the option to show dotfiles in listings",
254
287
  "fk=8": 'generates per-file accesskeys,\nwhich are then required at the "g" permission;\nkeys are invalidated if filesize or inode changes',
255
288
  "fka=8": 'generates slightly weaker per-file accesskeys,\nwhich are then required at the "g" permission;\nnot affected by filesize or inode numbers',
289
+ "rss": "allow '?rss' URL suffix (experimental)",
256
290
  "ups_who=2": "restrict viewing the list of recent uploads",
257
291
  "zip_who=2": "restrict access to download-as-zip/tar",
292
+ "nopipe": "disable race-the-beam (download unfinished uploads)",
258
293
  "mv_retry": "ms-windows: timeout for renaming busy files",
259
294
  "rm_retry": "ms-windows: timeout for deleting busy files",
260
295
  "davauth": "ask webdav clients to login for all folders",
@@ -264,3 +299,4 @@ flagcats = {
264
299
 
265
300
 
266
301
  flagdescs = {k.split("=")[0]: v for tab in flagcats.values() for k, v in tab.items()}
302
+
copyparty/httpcli.py CHANGED
@@ -186,7 +186,7 @@ class HttpCli(object):
186
186
  self.is_vproxied = False
187
187
  self.in_hdr_recv = True
188
188
  self.headers = {}
189
- self.mode = " "
189
+ self.mode = " " # http verb
190
190
  self.req = " "
191
191
  self.http_ver = ""
192
192
  self.hint = ""
@@ -726,10 +726,10 @@ class HttpCli(object):
726
726
  return self.handle_unlock() and self.keepalive
727
727
  elif self.mode == "MKCOL":
728
728
  return self.handle_mkcol() and self.keepalive
729
- elif self.mode == "MOVE":
730
- return self.handle_move() and self.keepalive
729
+ elif self.mode in ("MOVE", "COPY"):
730
+ return self.handle_cpmv() and self.keepalive
731
731
  else:
732
- raise Pebkac(400, 'invalid HTTP mode "{0}"'.format(self.mode))
732
+ raise Pebkac(400, 'invalid HTTP verb "{0}"'.format(self.mode))
733
733
 
734
734
  except Exception as ex:
735
735
  if not isinstance(ex, Pebkac):
@@ -1768,6 +1768,12 @@ class HttpCli(object):
1768
1768
  if "%" in self.req:
1769
1769
  self.log(" `-- %r" % (self.vpath,))
1770
1770
 
1771
+ if self.args.no_dav:
1772
+ raise Pebkac(405, "WebDAV is disabled in server config")
1773
+
1774
+ if not self.can_write:
1775
+ raise Pebkac(401, "authenticate")
1776
+
1771
1777
  try:
1772
1778
  return self._mkdir(self.vpath, True)
1773
1779
  except Pebkac as ex:
@@ -1777,14 +1783,35 @@ class HttpCli(object):
1777
1783
  self.reply(b"", ex.code)
1778
1784
  return True
1779
1785
 
1780
- def handle_move(self) :
1786
+ def handle_cpmv(self) :
1781
1787
  dst = self.headers["destination"]
1782
- dst = re.sub("^https?://[^/]+", "", dst).lstrip()
1788
+
1789
+ # dolphin (kioworker/6.10) "webdav://127.0.0.1:3923/a/b.txt"
1790
+ dst = re.sub("^[a-zA-Z]+://[^/]+", "", dst).lstrip()
1791
+
1792
+ if self.is_vproxied and dst.startswith(self.args.SRS):
1793
+ dst = dst[len(self.args.RS) :]
1794
+
1795
+ if self.do_log:
1796
+ self.log("%s %s --//> %s @%s" % (self.mode, self.req, dst, self.uname))
1797
+ if "%" in self.req:
1798
+ self.log(" `-- %r" % (self.vpath,))
1799
+
1800
+ if self.args.no_dav:
1801
+ raise Pebkac(405, "WebDAV is disabled in server config")
1802
+
1783
1803
  dst = unquotep(dst)
1784
- if not self._mv(self.vpath, dst.lstrip("/")):
1785
- return False
1786
1804
 
1787
- return True
1805
+ # overwrite=True is default; rfc4918 9.8.4
1806
+ overwrite = self.headers.get("overwrite", "").lower() != "f"
1807
+
1808
+ try:
1809
+ fun = self._cp if self.mode == "COPY" else self._mv
1810
+ return fun(self.vpath, dst.lstrip("/"), overwrite)
1811
+ except Pebkac as ex:
1812
+ if ex.code == 403:
1813
+ ex.code = 401
1814
+ raise
1788
1815
 
1789
1816
  def _applesan(self) :
1790
1817
  if self.args.dav_mac or "Darwin/" not in self.ua:
@@ -4788,9 +4815,12 @@ class HttpCli(object):
4788
4815
  # that the client is not a graphical browser
4789
4816
  if (
4790
4817
  rc == 403
4791
- and not self.pw
4792
- and not self.ua.startswith("Mozilla/")
4818
+ and self.uname == "*"
4793
4819
  and "sec-fetch-site" not in self.headers
4820
+ and (
4821
+ not self.ua.startswith("Mozilla/")
4822
+ or (self.args.dav_ua1 and self.args.dav_ua1.search(self.ua))
4823
+ )
4794
4824
  ):
4795
4825
  rc = 401
4796
4826
  self.out_headers["WWW-Authenticate"] = 'Basic realm="a"'
@@ -5419,6 +5449,8 @@ class HttpCli(object):
5419
5449
 
5420
5450
  def handle_rm(self, req ) :
5421
5451
  if not req and not self.can_delete:
5452
+ if self.mode == "DELETE" and self.uname == "*":
5453
+ raise Pebkac(401, "authenticate") # webdav
5422
5454
  raise Pebkac(403, "'delete' not allowed for user " + self.uname)
5423
5455
 
5424
5456
  if self.args.no_del:
@@ -5453,14 +5485,22 @@ class HttpCli(object):
5453
5485
  if not dst:
5454
5486
  raise Pebkac(400, "need dst vpath")
5455
5487
 
5456
- return self._mv(self.vpath, dst.lstrip("/"))
5488
+ return self._mv(self.vpath, dst.lstrip("/"), False)
5457
5489
 
5458
- def _mv(self, vsrc , vdst ) :
5490
+ def _mv(self, vsrc , vdst , overwrite ) :
5459
5491
  if self.args.no_mv:
5460
5492
  raise Pebkac(403, "the rename/move feature is disabled in server config")
5461
5493
 
5462
- self.asrv.vfs.get(vsrc, self.uname, True, False, True)
5463
- self.asrv.vfs.get(vdst, self.uname, False, True)
5494
+ # `handle_cpmv` will catch 403 from these and raise 401
5495
+ svn, srem = self.asrv.vfs.get(vsrc, self.uname, True, False, True)
5496
+ dvn, drem = self.asrv.vfs.get(vdst, self.uname, False, True)
5497
+
5498
+ if overwrite:
5499
+ dabs = dvn.canonical(drem)
5500
+ if bos.path.exists(dabs):
5501
+ self.log("overwriting %s" % (dabs,))
5502
+ self.asrv.vfs.get(vdst, self.uname, False, True, False, True)
5503
+ wunlink(self.log, dabs, dvn.flags)
5464
5504
 
5465
5505
  x = self.conn.hsrv.broker.ask("up2k.handle_mv", self.uname, self.ip, vsrc, vdst)
5466
5506
  self.loud_reply(x.get(), status=201)
@@ -5476,14 +5516,21 @@ class HttpCli(object):
5476
5516
  if not dst:
5477
5517
  raise Pebkac(400, "need dst vpath")
5478
5518
 
5479
- return self._cp(self.vpath, dst.lstrip("/"))
5519
+ return self._cp(self.vpath, dst.lstrip("/"), False)
5480
5520
 
5481
- def _cp(self, vsrc , vdst ) :
5521
+ def _cp(self, vsrc , vdst , overwrite ) :
5482
5522
  if self.args.no_cp:
5483
5523
  raise Pebkac(403, "the copy feature is disabled in server config")
5484
5524
 
5485
- self.asrv.vfs.get(vsrc, self.uname, True, False)
5486
- self.asrv.vfs.get(vdst, self.uname, False, True)
5525
+ svn, srem = self.asrv.vfs.get(vsrc, self.uname, True, False)
5526
+ dvn, drem = self.asrv.vfs.get(vdst, self.uname, False, True)
5527
+
5528
+ if overwrite:
5529
+ dabs = dvn.canonical(drem)
5530
+ if bos.path.exists(dabs):
5531
+ self.log("overwriting %s" % (dabs,))
5532
+ self.asrv.vfs.get(vdst, self.uname, False, True, False, True)
5533
+ wunlink(self.log, dabs, dvn.flags)
5487
5534
 
5488
5535
  x = self.conn.hsrv.broker.ask("up2k.handle_cp", self.uname, self.ip, vsrc, vdst)
5489
5536
  self.loud_reply(x.get(), status=201)
copyparty/svchub.py CHANGED
@@ -759,7 +759,7 @@ class SvcHub(object):
759
759
  vs = os.path.expandvars(os.path.expanduser(vs))
760
760
  setattr(al, k, vs)
761
761
 
762
- for k in "sus_urls nonsus_urls".split(" "):
762
+ for k in "dav_ua1 sus_urls nonsus_urls".split(" "):
763
763
  vs = getattr(al, k)
764
764
  if not vs or vs == "no":
765
765
  setattr(al, k, None)
copyparty/up2k.py CHANGED
@@ -4610,7 +4610,7 @@ class Up2k(object):
4610
4610
 
4611
4611
  cur = self.cur.get(ptop)
4612
4612
  if not cur:
4613
- return None, None, None, None, None, None
4613
+ return None, None, None, None, "", None
4614
4614
 
4615
4615
  rd, fn = vsplit(vrem)
4616
4616
  q = "select w, mt, sz, ip, at from up where rd=? and fn=? limit 1"
@@ -4623,7 +4623,7 @@ class Up2k(object):
4623
4623
  if hit:
4624
4624
  wark, ftime, fsize, ip, at = hit
4625
4625
  return cur, wark, ftime, fsize, ip, at
4626
- return cur, None, None, None, None, None
4626
+ return cur, None, None, None, "", None
4627
4627
 
4628
4628
  def _forget_file(
4629
4629
  self,
Binary file
Binary file
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.2
2
2
  Name: copyparty
3
- Version: 1.16.12
3
+ Version: 1.16.13
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
@@ -721,6 +721,8 @@ enabling `multiselect` lets you click files to select them, and then shift-click
721
721
  * `multiselect` is mostly intended for phones/tablets, but the `sel` option in the `[⚙️] settings` tab is better suited for desktop use, allowing selection by CTRL-clicking and range-selection with SHIFT-click, all without affecting regular clicking
722
722
  * the `sel` option can be made default globally with `--gsel` or per-volume with volflag `gsel`
723
723
 
724
+ to show `/icons/exe.png` as the thumbnail for all .exe files, `--ext-th=exe=/icons/exe.png` (optionally as a volflag)
725
+
724
726
  config file example:
725
727
 
726
728
  ```yaml
@@ -735,6 +737,7 @@ config file example:
735
737
  flags:
736
738
  dthumb # disable ALL thumbnails and audio transcoding
737
739
  dvthumb # only disable video thumbnails
740
+ ext-th: exe=/ico/exe.png # /ico/exe.png is the thumbnail of *.exe
738
741
  th-covers: folder.png,folder.jpg,cover.png,cover.jpg # the default
739
742
  ```
740
743
 
@@ -1,17 +1,17 @@
1
1
  copyparty/__init__.py,sha256=VR6ZZhB9IxaK5TDXDTBM_OIP5ydkrdbaEnstktLM__s,2649
2
- copyparty/__main__.py,sha256=ATbSyFWmocvOlarT6d-nglHBf3O4KmX4zrSwW-Bz1WQ,115851
3
- copyparty/__version__.py,sha256=hb9GoVB8qCxT8TBRr_QaRRAT3xQiXkZaGN_wbrwbd-w,251
4
- copyparty/authsrv.py,sha256=SJoNXQmE2yx-3DvLiHWCfrcW67uF2b9v3mzD0gFNaWY,105412
2
+ copyparty/__main__.py,sha256=Tcvi7uIGA37yGVXJbbhn-Cr0OBtt81aRF2Or0ZHXz6U,116449
3
+ copyparty/__version__.py,sha256=Kh8nmhFwgLz9KpLvVPB-drOjIKb4uSXTYfW7c0xGr3E,252
4
+ copyparty/authsrv.py,sha256=-oy1PtcMONLcrMllSY2aPuyHWQmE0Ot_EQTX7UJ5tuc,107035
5
5
  copyparty/broker_mp.py,sha256=QdOXXvV2Xn6J0CysEqyY3GZbqxQMyWnTpnba-a5lMc0,4987
6
6
  copyparty/broker_mpw.py,sha256=PpSS4SK3pItlpfD8OwVr3QmJEPKlUgaf2nuMOozixgU,3347
7
7
  copyparty/broker_thr.py,sha256=fjoYtpSscUA7-nMl4r1n2R7UK3J9lrvLS3rUZ-iJzKQ,1721
8
8
  copyparty/broker_util.py,sha256=76mfnFOpX1gUUvtjm8UQI7jpTIaVINX10QonM-B7ggc,1680
9
9
  copyparty/cert.py,sha256=0ZAPeXeMR164vWn9GQU3JDKooYXEq_NOQkDeg543ivg,8009
10
- copyparty/cfg.py,sha256=n6mZmlcvT7spUIyhyKfUfd3V6ZX8mX-8QpdeMvYqb9w,10788
10
+ copyparty/cfg.py,sha256=-Cbva1shfXQVGFl8Xbo-bkUYjkpU6amPZpTyPJcE7M0,12926
11
11
  copyparty/dxml.py,sha256=vu5uZQtwvwoqnFHbULs2Zh_y2DETu0T-ENpMZ1i2CV4,2505
12
12
  copyparty/fsutil.py,sha256=IVOFG8zBQPMQDDv7RIStSJHwHiAnVNROZS37O5k465A,4524
13
13
  copyparty/ftpd.py,sha256=T97SFS7JFtvRLbJX8C4fJSYwe13vhN3-E6emtlVmqLA,17608
14
- copyparty/httpcli.py,sha256=zIzrI9BRTDiBxj8ptDjPCzB3mK46JrRrbmz2K4dygJM,217236
14
+ copyparty/httpcli.py,sha256=Dr776ra7_wTCghymLOymy_Bpv8A15Qmgt9ERe-8kZ5A,219131
15
15
  copyparty/httpconn.py,sha256=mQSgljh0Q-jyWjF4tQLrHbRKRe9WKl19kGqsGMsJpWo,6880
16
16
  copyparty/httpsrv.py,sha256=pxH_Eh8ElBLvOEDejgpP9Bvk65HNEou-03aYIcgXhrs,18090
17
17
  copyparty/ico.py,sha256=eWSxEae4wOCfheHl-m-wchYvFRAR_97kJDb4NGaB-Z8,3561
@@ -24,14 +24,14 @@ copyparty/smbd.py,sha256=dixFl2wlWymq_Cycc8a4cVB4gY8RSg2e3tE7Xr-aDa0,14614
24
24
  copyparty/ssdp.py,sha256=R1Z61GZOxBMF2Sk4RTxKWMOemogmcjEWG-CvLihd45k,7023
25
25
  copyparty/star.py,sha256=tV5BbX6AiQ7N4UU8DYtSTckNYeoeey4DBqq4LjfymbY,3818
26
26
  copyparty/sutil.py,sha256=6zEEGl4hRe6bTB83Y_RtnBGxr2JcUa__GdiAMqNJZnY,3208
27
- copyparty/svchub.py,sha256=mgZ2UndrMqWuUywecsCgVz488WoS0EFHR94VvWZjeT0,41454
27
+ copyparty/svchub.py,sha256=gBp7x1hGF4b6_nanW5QUQcz8UmMCad6DzdE2KD5cLvE,41462
28
28
  copyparty/szip.py,sha256=HFtnwOiBgx0HMLUf-h_T84zSlRijPxmhRo5PM613kRA,8602
29
29
  copyparty/tcpsrv.py,sha256=2q18dGR8jnezA4SMfUXa-wrGRGX3nHIwkxkWvkTzF2A,19889
30
30
  copyparty/tftpd.py,sha256=PXgG4rTmiaU_TavSyZWD5cFphdfChs9YvNY21qfExt8,13611
31
31
  copyparty/th_cli.py,sha256=PxDAmUvO_8Vm5edXiWtsCft0Fw69QL9rCHf9zLmUNeA,4800
32
32
  copyparty/th_srv.py,sha256=tHbh_Ve3v8tYclWH2thLs5oFufeXgJi1duUMveKIx9k,30725
33
33
  copyparty/u2idx.py,sha256=G6MDbD4I_sJSOwaNFZ6XLTQhnEDrB12pVKuKhzQ_leE,13676
34
- copyparty/up2k.py,sha256=alObSL1EVyPedi_24s7DBFlBzA0ZMkr9_jOtzdRV3vk,175427
34
+ copyparty/up2k.py,sha256=yli2ALT61o1sPta4ckJu4v7hwUrV7IAJSsJo1-OEZWY,175423
35
35
  copyparty/util.py,sha256=Y_znSn3hBNYaaduwcCB7mmBYsi6vv9CYC1zJ9rq9yeQ,99435
36
36
  copyparty/bos/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
37
37
  copyparty/bos/bos.py,sha256=Wb7eWsXJgR5AFlBR9ZOyKrLTwy-Kct9RrGiOu4Jo37Y,1622
@@ -55,9 +55,9 @@ copyparty/stolen/ifaddr/_posix.py,sha256=-67NdfGrCktfQPakT2fLbjl2U00QMvyBGkSvrUu
55
55
  copyparty/stolen/ifaddr/_shared.py,sha256=uNC4SdEIgdSLKvuUzsf1aM-H1Xrc_9mpLoOT43YukGs,6206
56
56
  copyparty/stolen/ifaddr/_win32.py,sha256=EE-QyoBgeB7lYQ6z62VjXNaRozaYfCkaJBHGNA8QtZM,4026
57
57
  copyparty/web/baguettebox.js.gz,sha256=r2c_hOZV_RTyl4CqWWX14FDWP8nnDVwGkDl4Sfk0rU4,8239
58
- copyparty/web/browser.css.gz,sha256=WwVIXAbrqU2TlNEoDg5D7XJU1o7iAogJoS8aCfSvuOo,11645
58
+ copyparty/web/browser.css.gz,sha256=A44DddZBf-PEEMOj-u5YF_JrSk5DZYf-8f_J5jqC2is,11651
59
59
  copyparty/web/browser.html,sha256=auvhLVE_t0aIN0q-nk0zOWFqITgDhroMAAviBNLoFfc,4788
60
- copyparty/web/browser.js.gz,sha256=XmTA7KN35cL_Av3nHJy6L8zkg2H307spGoagnBXKg3g,91462
60
+ copyparty/web/browser.js.gz,sha256=Ec5tzZutoI8daxzHdfRfDP7L9ovDTY10j2g-hkeRlmA,91598
61
61
  copyparty/web/browser2.html,sha256=NRUZ08GH-e2YcGXcoz0UjYg6JIVF42u4IMX4HHwWTmg,1587
62
62
  copyparty/web/cf.html,sha256=lJThtNFNAQT1ClCHHlivAkDGE0LutedwopXD62Z8Nys,589
63
63
  copyparty/web/dbg-audio.js.gz,sha256=Ma-KZtK8LnmiwNvNKFKXMPYl_Nn_3U7GsJ6-DRWC2HE,688
@@ -109,9 +109,9 @@ copyparty/web/deps/prismd.css.gz,sha256=ObUlksQVr-OuYlTz-I4B23TeBg2QDVVGRnWBz8cV
109
109
  copyparty/web/deps/scp.woff2,sha256=w99BDU5i8MukkMEL-iW0YO9H4vFFZSPWxbkH70ytaAg,8612
110
110
  copyparty/web/deps/sha512.ac.js.gz,sha256=lFZaCLumgWxrvEuDr4bqdKHsqjX82AbVAb7_F45Yk88,7033
111
111
  copyparty/web/deps/sha512.hw.js.gz,sha256=UAed2_ocklZCnIzcSYz2h4P1ycztlCLj-ewsRTud2lU,7939
112
- copyparty-1.16.12.dist-info/LICENSE,sha256=gOr4h33pCsBEg9uIy9AYmb7qlocL4V9t2uPJS5wllr0,1072
113
- copyparty-1.16.12.dist-info/METADATA,sha256=VzmaUTc62j97TcHYADjryHTMmKHBa1GhobpWJdtI9pk,153975
114
- copyparty-1.16.12.dist-info/WHEEL,sha256=In9FTNxeP60KnTkGw7wk6mJPYd_dQSjEZmXdBdMCI-8,91
115
- copyparty-1.16.12.dist-info/entry_points.txt,sha256=4zw6a3rqASywQomiYLObjjlxybaI65LYYOTJwgKz7b0,128
116
- copyparty-1.16.12.dist-info/top_level.txt,sha256=LnYUPsDyk-8kFgM6YJLG4h820DQekn81cObKSu9g-sI,10
117
- copyparty-1.16.12.dist-info/RECORD,,
112
+ copyparty-1.16.13.dist-info/LICENSE,sha256=gOr4h33pCsBEg9uIy9AYmb7qlocL4V9t2uPJS5wllr0,1072
113
+ copyparty-1.16.13.dist-info/METADATA,sha256=hm4IR2Xqc3bnZLZuEE3MPuNqwi73-IXzd_WqnVo_2NI,154166
114
+ copyparty-1.16.13.dist-info/WHEEL,sha256=In9FTNxeP60KnTkGw7wk6mJPYd_dQSjEZmXdBdMCI-8,91
115
+ copyparty-1.16.13.dist-info/entry_points.txt,sha256=4zw6a3rqASywQomiYLObjjlxybaI65LYYOTJwgKz7b0,128
116
+ copyparty-1.16.13.dist-info/top_level.txt,sha256=LnYUPsDyk-8kFgM6YJLG4h820DQekn81cObKSu9g-sI,10
117
+ copyparty-1.16.13.dist-info/RECORD,,