copyparty 1.17.0__py3-none-any.whl → 1.17.1__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
@@ -995,6 +995,9 @@ def add_upload(ap):
995
995
  ap2 = ap.add_argument_group('upload options')
996
996
  ap2.add_argument("--dotpart", action="store_true", help="dotfile incomplete uploads, hiding them from clients unless \033[33m-ed\033[0m")
997
997
  ap2.add_argument("--plain-ip", action="store_true", help="when avoiding filename collisions by appending the uploader's ip to the filename: append the plaintext ip instead of salting and hashing the ip")
998
+ ap2.add_argument("--put-name", metavar="TXT", type=u, default="put-{now.6f}-{cip}.bin", help="filename for nameless uploads (when uploader doesn't provide a name); default is [\033[32mput-UNIXTIME-IP.bin\033[0m] (the \033[32m.6f\033[0m means six decimal places) (volflag=put_name)")
999
+ ap2.add_argument("--put-ck", metavar="ALG", type=u, default="sha512", help="default checksum-hasher for PUT/WebDAV uploads: no / md5 / sha1 / sha256 / sha512 / b2 / blake2 / b2s / blake2s (volflag=put_ck)")
1000
+ ap2.add_argument("--bup-ck", metavar="ALG", type=u, default="sha512", help="default checksum-hasher for bup/basic-uploader: no / md5 / sha1 / sha256 / sha512 / b2 / blake2 / b2s / blake2s (volflag=bup_ck)")
998
1001
  ap2.add_argument("--unpost", metavar="SEC", type=int, default=3600*12, help="grace period where uploads can be deleted by the uploader, even without delete permissions; 0=disabled, default=12h")
999
1002
  ap2.add_argument("--u2abort", metavar="NUM", type=int, default=1, help="clients can abort incomplete uploads by using the unpost tab (requires \033[33m-e2d\033[0m). [\033[32m0\033[0m] = never allowed (disable feature), [\033[32m1\033[0m] = allow if client has the same IP as the upload AND is using the same account, [\033[32m2\033[0m] = just check the IP, [\033[32m3\033[0m] = just check account-name (volflag=u2abort)")
1000
1003
  ap2.add_argument("--blank-wt", metavar="SEC", type=int, default=300, help="file write grace period (any client can write to a blank file last-modified more recently than \033[33mSEC\033[0m seconds ago)")
@@ -1371,8 +1374,8 @@ def add_thumbnail(ap):
1371
1374
  ap2.add_argument("--th-r-vips", metavar="T,T", type=u, default="avif,exr,fit,fits,fts,gif,hdr,heic,jp2,jpeg,jpg,jpx,jxl,nii,pfm,pgm,png,ppm,svg,tif,tiff,webp", help="image formats to decode using pyvips")
1372
1375
  ap2.add_argument("--th-r-ffi", metavar="T,T", type=u, default="apng,avif,avifs,bmp,cbz,dds,dib,fit,fits,fts,gif,hdr,heic,heics,heif,heifs,icns,ico,jp2,jpeg,jpg,jpx,jxl,pbm,pcx,pfm,pgm,png,pnm,ppm,psd,qoi,sgi,tga,tif,tiff,webp,xbm,xpm", help="image formats to decode using ffmpeg")
1373
1376
  ap2.add_argument("--th-r-ffv", metavar="T,T", type=u, default="3gp,asf,av1,avc,avi,flv,h264,h265,hevc,m4v,mjpeg,mjpg,mkv,mov,mp4,mpeg,mpeg2,mpegts,mpg,mpg2,mts,nut,ogm,ogv,rm,ts,vob,webm,wmv", help="video formats to decode using ffmpeg")
1374
- ap2.add_argument("--th-r-ffa", metavar="T,T", type=u, default="aac,ac3,aif,aiff,alac,alaw,amr,apac,ape,au,bonk,dfpwm,dts,flac,gsm,ilbc,it,itgz,itxz,itz,m4a,mdgz,mdxz,mdz,mo3,mod,mp2,mp3,mpc,mptm,mt2,mulaw,ogg,okt,opus,ra,s3m,s3gz,s3xz,s3z,tak,tta,ulaw,wav,wma,wv,xm,xmgz,xmxz,xmz,xpk", help="audio formats to decode using ffmpeg")
1375
- ap2.add_argument("--th-spec-cnv", metavar="T,T", type=u, default="it,itgz,itxz,itz,mdgz,mdxz,mdz,mo3,mod,s3m,s3gz,s3xz,s3z,xm,xmgz,xmxz,xmz,xpk", help="audio formats which provoke https://trac.ffmpeg.org/ticket/10797 (huge ram usage for s3xmodit spectrograms)")
1377
+ ap2.add_argument("--th-r-ffa", metavar="T,T", type=u, default="aac,ac3,aif,aiff,alac,alaw,amr,apac,ape,au,bonk,dfpwm,dts,flac,gsm,ilbc,it,itgz,itxz,itz,m4a,mdgz,mdxz,mdz,mo3,mod,mp2,mp3,mpc,mptm,mt2,mulaw,oga,ogg,okt,opus,ra,s3m,s3gz,s3xz,s3z,tak,tta,ulaw,wav,wma,wv,xm,xmgz,xmxz,xmz,xpk", help="audio formats to decode using ffmpeg")
1378
+ ap2.add_argument("--th-spec-cnv", metavar="T", type=u, default="it,itgz,itxz,itz,mdgz,mdxz,mdz,mo3,mod,s3m,s3gz,s3xz,s3z,xm,xmgz,xmxz,xmz,xpk", help="audio formats which provoke https://trac.ffmpeg.org/ticket/10797 (huge ram usage for s3xmodit spectrograms)")
1376
1379
  ap2.add_argument("--au-unpk", metavar="E=F.C", type=u, default="mdz=mod.zip, mdgz=mod.gz, mdxz=mod.xz, s3z=s3m.zip, s3gz=s3m.gz, s3xz=s3m.xz, xmz=xm.zip, xmgz=xm.gz, xmxz=xm.xz, itz=it.zip, itgz=it.gz, itxz=it.xz, cbz=jpg.cbz", help="audio/image formats to decompress before passing to ffmpeg")
1377
1380
 
1378
1381
 
copyparty/__version__.py CHANGED
@@ -1,8 +1,8 @@
1
1
  # coding: utf-8
2
2
 
3
- VERSION = (1, 17, 0)
3
+ VERSION = (1, 17, 1)
4
4
  CODENAME = "mixtape.m3u"
5
- BUILD_DT = (2025, 4, 26)
5
+ BUILD_DT = (2025, 5, 18)
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
@@ -2067,6 +2067,10 @@ class AuthSrv(object):
2067
2067
  if len(zs) == 3: # fc5 => ffcc55
2068
2068
  vol.flags["tcolor"] = "".join([x * 2 for x in zs])
2069
2069
 
2070
+ # volflag syntax currently doesn't allow for ':' in value
2071
+ zs = vol.flags["put_name"]
2072
+ vol.flags["put_name2"] = zs.replace("{now.", "{now:.")
2073
+
2070
2074
  if vol.flags.get("neversymlink"):
2071
2075
  vol.flags["hardlinkonly"] = True # was renamed
2072
2076
  if vol.flags.get("hardlinkonly"):
copyparty/cert.py CHANGED
@@ -1,13 +1,11 @@
1
1
  import calendar
2
2
  import errno
3
- import filecmp
4
3
  import json
5
4
  import os
6
- import shutil
7
5
  import time
8
6
 
9
7
  from .__init__ import ANYWIN
10
- from .util import Netdev, load_resource, runcmd, wrename, wunlink
8
+ from .util import Netdev, atomic_move, load_resource, runcmd, wunlink
11
9
 
12
10
  HAVE_CFSSL = not os.environ.get("PRTY_NO_CFSSL")
13
11
 
@@ -118,7 +116,7 @@ def _gen_ca(log , args):
118
116
  wunlink(nlog, bname + ".key", VF)
119
117
  except:
120
118
  pass
121
- wrename(nlog, bname + "-key.pem", bname + ".key", VF)
119
+ atomic_move(nlog, bname + "-key.pem", bname + ".key", VF)
122
120
  wunlink(nlog, bname + ".csr", VF)
123
121
 
124
122
  log("cert", "new ca OK", 2)
@@ -211,7 +209,7 @@ def _gen_srv(log , args, netdevs ):
211
209
  wunlink(nlog, bname + ".key", VF)
212
210
  except:
213
211
  pass
214
- wrename(nlog, bname + "-key.pem", bname + ".key", VF)
212
+ atomic_move(nlog, bname + "-key.pem", bname + ".key", VF)
215
213
  wunlink(nlog, bname + ".csr", VF)
216
214
 
217
215
  with open(os.path.join(args.crt_dir, "ca.pem"), "rb") as f:
copyparty/cfg.py CHANGED
@@ -75,6 +75,7 @@ def vf_vmap() :
75
75
  "th_x3": "th3x",
76
76
  }
77
77
  for k in (
78
+ "bup_ck",
78
79
  "dbd",
79
80
  "forget_ip",
80
81
  "hsortn",
@@ -95,6 +96,8 @@ def vf_vmap() :
95
96
  "og_title_i",
96
97
  "og_tpl",
97
98
  "og_ua",
99
+ "put_ck",
100
+ "put_name",
98
101
  "mv_retry",
99
102
  "rm_retry",
100
103
  "sort",
@@ -165,6 +168,9 @@ flagcats = {
165
168
  "daw": "enable full WebDAV write support (dangerous);\nPUT-operations will now \033[1;31mOVERWRITE\033[0;35m existing files",
166
169
  "nosub": "forces all uploads into the top folder of the vfs",
167
170
  "magic": "enables filetype detection for nameless uploads",
171
+ "put_name": "fallback filename for nameless uploads",
172
+ "put_ck": "default checksum-hasher for PUT/WebDAV uploads",
173
+ "bup_ck": "default checksum-hasher for bup/basic uploads",
168
174
  "gz": "allows server-side gzip compression of uploads with ?gz",
169
175
  "xz": "allows server-side lzma compression of uploads with ?xz",
170
176
  "pk": "forces server-side compression, optional arg: xz,9",
copyparty/httpcli.py CHANGED
@@ -113,7 +113,6 @@ from .util import (
113
113
  vol_san,
114
114
  vroots,
115
115
  vsplit,
116
- wrename,
117
116
  wunlink,
118
117
  yieldfile,
119
118
  )
@@ -1408,7 +1407,7 @@ class HttpCli(object):
1408
1407
  desc = "%s - %s" % (tag_a, tag_t) if tag_t and tag_a else (tag_t or tag_a)
1409
1408
  desc = html_escape(desc, True, True) if desc else title
1410
1409
  mime = html_escape(guess_mime(title))
1411
- lmod = formatdate(i["ts"])
1410
+ lmod = formatdate(max(0, i["ts"]))
1412
1411
  zsa = (iurl, iurl, title, desc, lmod, iurl, mime, i["sz"])
1413
1412
  zs = (
1414
1413
  """\
@@ -1565,7 +1564,7 @@ class HttpCli(object):
1565
1564
  for x in fgen:
1566
1565
  rp = vjoin(vtop, x["vp"])
1567
1566
  st = x["st"]
1568
- mtime = st.st_mtime
1567
+ mtime = max(0, st.st_mtime)
1569
1568
  if stat.S_ISLNK(st.st_mode):
1570
1569
  try:
1571
1570
  st = bos.stat(os.path.join(tap, x["vp"]))
@@ -2091,8 +2090,7 @@ class HttpCli(object):
2091
2090
  suffix = "-{:.6f}-{}".format(time.time(), self.dip())
2092
2091
  nameless = not fn
2093
2092
  if nameless:
2094
- suffix += ".bin"
2095
- fn = "put" + suffix
2093
+ fn = vfs.flags["put_name2"].format(now=time.time(), cip=self.dip())
2096
2094
 
2097
2095
  params = {"suffix": suffix, "fdir": fdir}
2098
2096
  if self.args.nw:
@@ -2171,28 +2169,26 @@ class HttpCli(object):
2171
2169
  # small toctou, but better than clobbering a hardlink
2172
2170
  wunlink(self.log, path, vfs.flags)
2173
2171
 
2174
- halg = "sha512"
2175
2172
  hasher = None
2176
2173
  copier = hashcopy
2177
- if "ck" in self.ouparam or "ck" in self.headers:
2178
- halg = zs = self.ouparam.get("ck") or self.headers.get("ck") or ""
2179
- if not zs or zs == "no":
2180
- copier = justcopy
2181
- halg = ""
2182
- elif zs == "md5":
2183
- hasher = hashlib.md5(**USED4SEC)
2184
- elif zs == "sha1":
2185
- hasher = hashlib.sha1(**USED4SEC)
2186
- elif zs == "sha256":
2187
- hasher = hashlib.sha256(**USED4SEC)
2188
- elif zs in ("blake2", "b2"):
2189
- hasher = hashlib.blake2b(**USED4SEC)
2190
- elif zs in ("blake2s", "b2s"):
2191
- hasher = hashlib.blake2s(**USED4SEC)
2192
- elif zs == "sha512":
2193
- pass
2194
- else:
2195
- raise Pebkac(500, "unknown hash alg")
2174
+ halg = self.ouparam.get("ck") or self.headers.get("ck") or vfs.flags["put_ck"]
2175
+ if halg == "sha512":
2176
+ pass
2177
+ elif halg == "no":
2178
+ copier = justcopy
2179
+ halg = ""
2180
+ elif halg == "md5":
2181
+ hasher = hashlib.md5(**USED4SEC)
2182
+ elif halg == "sha1":
2183
+ hasher = hashlib.sha1(**USED4SEC)
2184
+ elif halg == "sha256":
2185
+ hasher = hashlib.sha256(**USED4SEC)
2186
+ elif halg in ("blake2", "b2"):
2187
+ hasher = hashlib.blake2b(**USED4SEC)
2188
+ elif halg in ("blake2s", "b2s"):
2189
+ hasher = hashlib.blake2s(**USED4SEC)
2190
+ else:
2191
+ raise Pebkac(500, "unknown hash alg")
2196
2192
 
2197
2193
  f, fn = ren_open(fn, *open_a, **params)
2198
2194
  try:
@@ -2917,7 +2913,8 @@ class HttpCli(object):
2917
2913
  self.parser.drop()
2918
2914
 
2919
2915
  self.log("logout " + self.uname)
2920
- self.asrv.forget_session(self.conn.hsrv.broker, self.uname)
2916
+ if not self.uname.startswith("s_"):
2917
+ self.asrv.forget_session(self.conn.hsrv.broker, self.uname)
2921
2918
  self.get_pwd_cookie("x")
2922
2919
 
2923
2920
  dst = self.args.SRS + "?h"
@@ -3068,15 +3065,18 @@ class HttpCli(object):
3068
3065
  vfs, rem = self.asrv.vfs.get(self.vpath, self.uname, False, True)
3069
3066
  self._assert_safe_rem(rem)
3070
3067
 
3071
- halg = "sha512"
3072
3068
  hasher = None
3073
- copier = hashcopy
3074
3069
  if nohash:
3075
3070
  halg = ""
3076
3071
  copier = justcopy
3077
- elif "ck" in self.ouparam or "ck" in self.headers:
3078
- halg = self.ouparam.get("ck") or self.headers.get("ck") or ""
3079
- if not halg or halg == "no":
3072
+ else:
3073
+ copier = hashcopy
3074
+ halg = (
3075
+ self.ouparam.get("ck") or self.headers.get("ck") or vfs.flags["bup_ck"]
3076
+ )
3077
+ if halg == "sha512":
3078
+ pass
3079
+ elif halg == "no":
3080
3080
  copier = justcopy
3081
3081
  halg = ""
3082
3082
  elif halg == "md5":
@@ -3089,8 +3089,6 @@ class HttpCli(object):
3089
3089
  hasher = hashlib.blake2b(**USED4SEC)
3090
3090
  elif halg in ("blake2s", "b2s"):
3091
3091
  hasher = hashlib.blake2s(**USED4SEC)
3092
- elif halg == "sha512":
3093
- pass
3094
3092
  else:
3095
3093
  raise Pebkac(500, "unknown hash alg")
3096
3094
 
@@ -3552,7 +3550,7 @@ class HttpCli(object):
3552
3550
  except:
3553
3551
  pass
3554
3552
  if dp:
3555
- wrename(self.log, fp, os.path.join(dp, mfile2), vfs.flags)
3553
+ atomic_move(self.log, fp, os.path.join(dp, mfile2), vfs.flags)
3556
3554
 
3557
3555
  p_field, _, p_data = next(self.parser.gen)
3558
3556
  if p_field != "body":
@@ -3961,7 +3959,7 @@ class HttpCli(object):
3961
3959
  for ext in ("", ".gz"):
3962
3960
  if ptop is not None:
3963
3961
  sz = job["size"]
3964
- file_ts = job["lmod"]
3962
+ file_ts = max(0, job["lmod"])
3965
3963
  editions["plain"] = (ap_data, sz)
3966
3964
  break
3967
3965
 
@@ -5482,6 +5480,7 @@ class HttpCli(object):
5482
5480
  raise Pebkac(400, "selected file not found on disk: [%s]" % (fn,))
5483
5481
 
5484
5482
  pw = req.get("pw") or ""
5483
+ pw = self.asrv.ah.hash(pw)
5485
5484
  now = int(time.time())
5486
5485
  sexp = req["exp"]
5487
5486
  exp = int(sexp) if sexp else 0
@@ -6099,7 +6098,7 @@ class HttpCli(object):
6099
6098
  margin = "-"
6100
6099
 
6101
6100
  sz = inf.st_size
6102
- zd = datetime.fromtimestamp(linf.st_mtime, UTC)
6101
+ zd = datetime.fromtimestamp(max(0, linf.st_mtime), UTC)
6103
6102
  dt = "%04d-%02d-%02d %02d:%02d:%02d" % (
6104
6103
  zd.year,
6105
6104
  zd.month,
copyparty/sutil.py CHANGED
@@ -11,6 +11,9 @@ from .bos import bos
11
11
  from .th_cli import ThumbCli
12
12
  from .util import UTC, vjoin, vol_san
13
13
 
14
+ TAR_NO_OPUS = set("aac|m4a|mp3|oga|ogg|opus|wma".split("|"))
15
+
16
+
14
17
  class StreamArc(object):
15
18
  def __init__(
16
19
  self,
@@ -76,9 +79,7 @@ def enthumb(
76
79
  ) :
77
80
  rem = f["vp"]
78
81
  ext = rem.rsplit(".", 1)[-1].lower()
79
- if (fmt == "mp3" and ext == "mp3") or (
80
- fmt == "opus" and ext in "aac|m4a|mp3|ogg|opus|wma".split("|")
81
- ):
82
+ if (fmt == "mp3" and ext == "mp3") or (fmt == "opus" and ext in TAR_NO_OPUS):
82
83
  raise Exception()
83
84
 
84
85
  vp = vjoin(vtop, rem.split("/", 1)[1])
copyparty/tftpd.py CHANGED
@@ -281,6 +281,7 @@ class Tftpd(object):
281
281
  if not ptn or not ptn.match(fn.lower()):
282
282
  return None
283
283
 
284
+ tsdt = datetime.fromtimestamp
284
285
  vn, rem = self.asrv.vfs.get(vpath, "*", True, False)
285
286
  fsroot, vfs_ls, vfs_virt = vn.ls(
286
287
  rem,
@@ -293,7 +294,7 @@ class Tftpd(object):
293
294
  dirs1 = [(v.st_mtime, v.st_size, k + "/") for k, v in vfs_ls if k in dnames]
294
295
  fils1 = [(v.st_mtime, v.st_size, k) for k, v in vfs_ls if k not in dnames]
295
296
  real1 = dirs1 + fils1
296
- realt = [(datetime.fromtimestamp(mt, UTC), sz, fn) for mt, sz, fn in real1]
297
+ realt = [(tsdt(max(0, mt), UTC), sz, fn) for mt, sz, fn in real1]
297
298
  reals = [
298
299
  (
299
300
  "%04d-%02d-%02d %02d:%02d:%02d"
copyparty/th_srv.py CHANGED
@@ -24,13 +24,13 @@ from .util import (
24
24
  Cooldown,
25
25
  Daemon,
26
26
  afsenc,
27
+ atomic_move,
27
28
  fsenc,
28
29
  min_ex,
29
30
  runcmd,
30
31
  statdir,
31
32
  ub64enc,
32
33
  vsplit,
33
- wrename,
34
34
  wunlink,
35
35
  )
36
36
 
@@ -409,7 +409,7 @@ class ThumbSrv(object):
409
409
  wunlink(self.log, ap_unpk, vn.flags)
410
410
 
411
411
  try:
412
- wrename(self.log, ttpath, tpath, vn.flags)
412
+ atomic_move(self.log, ttpath, tpath, vn.flags)
413
413
  except Exception as ex:
414
414
  if not os.path.exists(tpath):
415
415
  t = "failed to move [%s] to [%s]: %r"
@@ -673,7 +673,7 @@ class ThumbSrv(object):
673
673
  except:
674
674
  pass
675
675
  else:
676
- wrename(self.log, wtpath, tpath, vn.flags)
676
+ atomic_move(self.log, wtpath, tpath, vn.flags)
677
677
 
678
678
  def conv_spec(self, abspath , tpath , fmt , vn ) :
679
679
  ret, _ = ffprobe(abspath, int(vn.flags["convt"] / 2))
copyparty/up2k.py CHANGED
@@ -1112,7 +1112,7 @@ class Up2k(object):
1112
1112
  ft = "\033[0;32m{}{:.0}"
1113
1113
  ff = "\033[0;35m{}{:.0}"
1114
1114
  fv = "\033[0;36m{}:\033[90m{}"
1115
- zs = "ext_th_d html_head mv_re_r mv_re_t rm_re_r rm_re_t srch_re_dots srch_re_nodot zipmax zipmaxn_v zipmaxs_v"
1115
+ zs = "ext_th_d html_head put_name2 mv_re_r mv_re_t rm_re_r rm_re_t srch_re_dots srch_re_nodot zipmax zipmaxn_v zipmaxs_v"
1116
1116
  fx = set(zs.split())
1117
1117
  fd = vf_bmap()
1118
1118
  fd.update(vf_cmap())
@@ -2110,11 +2110,12 @@ class Up2k(object):
2110
2110
  return -1
2111
2111
 
2112
2112
  w = bw[:-1].decode("ascii")
2113
+ w16 = w[:16]
2113
2114
 
2114
2115
  with self.mutex:
2115
2116
  try:
2116
2117
  q = "select rd, fn, ip, at from up where substr(w,1,16)=? and +w=?"
2117
- rd, fn, ip, at = cur.execute(q, (w[:16], w)).fetchone()
2118
+ rd, fn, ip, at = cur.execute(q, (w16, w)).fetchone()
2118
2119
  except:
2119
2120
  # file modified/deleted since spooling
2120
2121
  continue
@@ -2123,8 +2124,12 @@ class Up2k(object):
2123
2124
  rd, fn = s3dec(rd, fn)
2124
2125
 
2125
2126
  if "mtp" in flags:
2127
+ q = "select 1 from mt where w=? and +k='t:mtp' limit 1"
2128
+ if cur.execute(q, (w16,)).fetchone():
2129
+ continue
2130
+
2126
2131
  q = "insert into mt values (?,'t:mtp','a')"
2127
- cur.execute(q, (w[:16],))
2132
+ cur.execute(q, (w16,))
2128
2133
 
2129
2134
  abspath = djoin(ptop, rd, fn)
2130
2135
  self.pp.msg = "c%d %s" % (nq, abspath)
@@ -2180,7 +2185,7 @@ class Up2k(object):
2180
2185
  return tf, -1
2181
2186
 
2182
2187
  if flt == 1:
2183
- q = "select w from mt where w = ?"
2188
+ q = "select 1 from mt where w=? and +k != 't:mtp'"
2184
2189
  if c2.execute(q, (row[0][:16],)).fetchone():
2185
2190
  continue
2186
2191
 
@@ -3215,7 +3220,7 @@ class Up2k(object):
3215
3220
  if hr.get("reloc"):
3216
3221
  x = pathmod(self.vfs, dst, vp, hr["reloc"])
3217
3222
  if x:
3218
- zvfs = vfs
3223
+ ud1 = (vfs.vpath, job["prel"], job["name"])
3219
3224
  pdir, _, job["name"], (vfs, rem) = x
3220
3225
  dst = os.path.join(pdir, job["name"])
3221
3226
  job["vcfg"] = vfs.flags
@@ -3223,7 +3228,8 @@ class Up2k(object):
3223
3228
  job["vtop"] = vfs.vpath
3224
3229
  job["prel"] = rem
3225
3230
  job["name"] = sanitize_fn(job["name"], "")
3226
- if zvfs.vpath != vfs.vpath:
3231
+ ud2 = (vfs.vpath, job["prel"], job["name"])
3232
+ if ud1 != ud2:
3227
3233
  # print(json.dumps(job, sort_keys=True, indent=4))
3228
3234
  job["hash"] = cj["hash"]
3229
3235
  self.log("xbu reloc1:%d..." % (depth,), 6)
@@ -4970,14 +4976,15 @@ class Up2k(object):
4970
4976
  if hr.get("reloc"):
4971
4977
  x = pathmod(self.vfs, ap_chk, vp_chk, hr["reloc"])
4972
4978
  if x:
4973
- zvfs = vfs
4979
+ ud1 = (vfs.vpath, job["prel"], job["name"])
4974
4980
  pdir, _, job["name"], (vfs, rem) = x
4975
4981
  job["vcfg"] = vf = vfs.flags
4976
4982
  job["ptop"] = vfs.realpath
4977
4983
  job["vtop"] = vfs.vpath
4978
4984
  job["prel"] = rem
4979
4985
  job["name"] = sanitize_fn(job["name"], "")
4980
- if zvfs.vpath != vfs.vpath:
4986
+ ud2 = (vfs.vpath, job["prel"], job["name"])
4987
+ if ud1 != ud2:
4981
4988
  self.log("xbu reloc2:%d..." % (depth,), 6)
4982
4989
  return self._handle_json(job, depth + 1)
4983
4990
 
copyparty/util.py CHANGED
@@ -2497,6 +2497,11 @@ def _fs_mvrm(
2497
2497
  now = time.time()
2498
2498
  if ex.errno == errno.ENOENT:
2499
2499
  return False
2500
+ if not attempt and ex.errno == errno.EXDEV:
2501
+ t = "using copy+delete (%s)\n %s\n %s"
2502
+ log(t % (ex.strerror, src, dst))
2503
+ osfun = shutil.move
2504
+ continue
2500
2505
  if now - t0 > maxtime or attempt == 90209:
2501
2506
  raise
2502
2507
  if not attempt:
@@ -2521,15 +2526,18 @@ def atomic_move(log , src , dst , flags ) :
2521
2526
  elif flags.get("mv_re_t"):
2522
2527
  _fs_mvrm(log, src, dst, True, flags)
2523
2528
  else:
2524
- os.replace(bsrc, bdst)
2525
-
2526
-
2527
- def wrename(log , src , dst , flags ) :
2528
- if not flags.get("mv_re_t"):
2529
- os.rename(fsenc(src), fsenc(dst))
2530
- return True
2531
-
2532
- return _fs_mvrm(log, src, dst, False, flags)
2529
+ try:
2530
+ os.replace(bsrc, bdst)
2531
+ except OSError as ex:
2532
+ if ex.errno != errno.EXDEV:
2533
+ raise
2534
+ t = "using copy+delete (%s);\n %s\n %s"
2535
+ log(t % (ex.strerror, src, dst))
2536
+ try:
2537
+ os.unlink(bdst)
2538
+ except:
2539
+ pass
2540
+ shutil.move(bsrc, bdst)
2533
2541
 
2534
2542
 
2535
2543
  def wunlink(log , abspath , flags ) :
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 = "2.10"
5
- S_BUILD_DT = "2025-02-19"
4
+ S_VERSION = "2.11"
5
+ S_BUILD_DT = "2025-05-18"
6
6
 
7
7
  """
8
8
  u2c.py: upload to copyparty
@@ -1287,7 +1287,7 @@ class Ctl(object):
1287
1287
  if self.ar.jw:
1288
1288
  print("%s %s" % (wark, vp))
1289
1289
  else:
1290
- zd = datetime.datetime.fromtimestamp(file.lmod, UTC)
1290
+ zd = datetime.datetime.fromtimestamp(max(0, file.lmod), UTC)
1291
1291
  dt = "%04d-%02d-%02d %02d:%02d:%02d" % (
1292
1292
  zd.year,
1293
1293
  zd.month,
Binary file
Binary file
copyparty/web/svcs.html CHANGED
@@ -101,6 +101,7 @@
101
101
  gio mount -a dav{{ s }}://{{ ep }}/{{ rvp }}
102
102
  {%- endif %}
103
103
  </pre>
104
+ <p>on KDE Dolphin, use <code>webdav{{ s }}://{{ ep }}/{{ rvp }}</code></p>
104
105
  </div>
105
106
 
106
107
  <div class="os mac">
copyparty/web/util.js.gz CHANGED
Binary file
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: copyparty
3
- Version: 1.17.0
3
+ Version: 1.17.1
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
@@ -162,7 +162,7 @@ turn almost any device into a file server with resumable uploads/downloads using
162
162
  * [feature chickenbits](#feature-chickenbits) - buggy feature? rip it out
163
163
  * [feature beefybits](#feature-beefybits) - force-enable features with known issues on your OS/env
164
164
  * [packages](#packages) - the party might be closer than you think
165
- * [arch package](#arch-package) - now [available on aur](https://aur.archlinux.org/packages/copyparty) maintained by [@icxes](https://github.com/icxes)
165
+ * [arch package](#arch-package) - `pacman -S copyparty` (in [arch linux extra](https://archlinux.org/packages/extra/any/copyparty/))
166
166
  * [fedora package](#fedora-package) - does not exist yet
167
167
  * [nix package](#nix-package) - `nix profile install github:9001/copyparty`
168
168
  * [nixos module](#nixos-module)
@@ -475,6 +475,9 @@ upgrade notes
475
475
 
476
476
  "frequently" asked questions
477
477
 
478
+ * CopyParty?
479
+ * nope! the name is either copyparty (all-lowercase) or Copyparty -- it's [one word](https://en.wiktionary.org/wiki/copyparty) after all :>
480
+
478
481
  * can I change the 🌲 spinning pine-tree loading animation?
479
482
  * [yeah...](https://github.com/9001/copyparty/tree/hovudstraum/docs/rice#boring-loader-spinner) :-(
480
483
 
@@ -973,6 +976,7 @@ semi-intentional limitations:
973
976
 
974
977
  * cleanup of expired shares only works when global option `e2d` is set, and/or at least one volume on the server has volflag `e2d`
975
978
  * only folders from the same volume are shared; if you are sharing a folder which contains other volumes, then the contents of those volumes will not be available
979
+ * if you change [password hashing](#password-hashing) settings after creating a password-protected share, then that share will stop working
976
980
  * related to [IdP volumes being forgotten on shutdown](https://github.com/9001/copyparty/blob/hovudstraum/docs/idp.md#idp-volumes-are-forgotten-on-shutdown), any shares pointing into a user's IdP volume will be unavailable until that user makes their first request after a restart
977
981
  * no option to "delete after first access" because tricky
978
982
  * when linking something to discord (for example) it'll get accessed by their scraper and that would count as a hit
@@ -2261,10 +2265,14 @@ if your distro/OS is not mentioned below, there might be some hints in the [«on
2261
2265
 
2262
2266
  ## arch package
2263
2267
 
2264
- now [available on aur](https://aur.archlinux.org/packages/copyparty) maintained by [@icxes](https://github.com/icxes)
2268
+ `pacman -S copyparty` (in [arch linux extra](https://archlinux.org/packages/extra/any/copyparty/))
2265
2269
 
2266
2270
  it comes with a [systemd service](./contrib/package/arch/copyparty.service) and expects to find one or more [config files](./docs/example.conf) in `/etc/copyparty.d/`
2267
2271
 
2272
+ after installing it, you may want to `cp /usr/lib/systemd/system/copyparty.service /etc/systemd/system/` and then `vim /etc/systemd/system/copyparty.service` to change what user/group it is running as (you only need to do this once)
2273
+
2274
+ NOTE: there used to be an aur package; this evaporated when copyparty was adopted by the official archlinux repos. If you're still using the aur package, please move
2275
+
2268
2276
 
2269
2277
  ## fedora package
2270
2278
 
@@ -1,17 +1,17 @@
1
1
  copyparty/__init__.py,sha256=TnFSStmHlwlRIClWW8jSHxZpt3dl_kN6_pEnqBqh3mE,2638
2
- copyparty/__main__.py,sha256=dJaY-3WD04xo1rf2YsEhTsQ5ywg4qttImmL5PdO58Bo,120099
3
- copyparty/__version__.py,sha256=SayyDzASpFZQ7quvxJVu76GJKarGmrfz_Lfx8aEzc84,253
4
- copyparty/authsrv.py,sha256=S4HrUq-C6wpe_IT3KN75yo9GJw9Uzj5y558ooYCQqng,113286
2
+ copyparty/__main__.py,sha256=FZs3IMHTFyeR_-Ftx-M-61_gOwmQCM3bD5b9jo6nrbs,120810
3
+ copyparty/__version__.py,sha256=OCUlop4ig08TTAg6PnNAKigP9eAnhhG-82RUqzvbE-w,253
4
+ copyparty/authsrv.py,sha256=Z7VH7nO-Yd2X33d8qdMemb6bsAGZqgtaRPFgSAF7rl0,113463
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
- copyparty/cert.py,sha256=0ZAPeXeMR164vWn9GQU3JDKooYXEq_NOQkDeg543ivg,8009
10
- copyparty/cfg.py,sha256=EilhOIMLopgvn5j72MaRFS0q78K9Xf0NU0I7EAs3YAQ,14269
9
+ copyparty/cert.py,sha256=pSSeVYticrDsnsrdRtfpUQN-8WRObsqrYtSRroXmgxo,7992
10
+ copyparty/cfg.py,sha256=H2p2bHRWk2e4rOx5NZWUVHUcoETy3dtzDkeC8TdosRY,14522
11
11
  copyparty/dxml.py,sha256=vu5uZQtwvwoqnFHbULs2Zh_y2DETu0T-ENpMZ1i2CV4,2505
12
12
  copyparty/fsutil.py,sha256=NC_CJC4TDag399vVDH9_uQfdfpTMwRFLNxERSWhlVvs,4594
13
13
  copyparty/ftpd.py,sha256=G7PApVIFeSzRo4-D-9uRb8NxYgz6nFwTEbrOk1ErYCU,17969
14
- copyparty/httpcli.py,sha256=2EEQfjIa2rDwcFslTKPAkPCS14a8Hj7TJj6c-khRIFk,222000
14
+ copyparty/httpcli.py,sha256=9yCKMqUD3J2m1tmvx9VIF_RkGi2LvbBHkUx4xxBzVL4,221960
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=-7QjF_jIxnPo4Vr0oUPksQ_U_Ef0HRsSPm3s71idOz8,3879
@@ -23,16 +23,16 @@ copyparty/pwhash.py,sha256=zHoz9FHGkFBxoRvSfG1XyjN3ibww_h5GE6_m5yS-fws,4246
23
23
  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
- copyparty/sutil.py,sha256=6zEEGl4hRe6bTB83Y_RtnBGxr2JcUa__GdiAMqNJZnY,3208
26
+ copyparty/sutil.py,sha256=E65jAaOzHlJYnqsOKDMPVT8kALPUVexpkSStWBdItkY,3231
27
27
  copyparty/svchub.py,sha256=leERN449te_yBbyChcOenjY_nuNPoFJpncLHfDMvDYE,46618
28
28
  copyparty/szip.py,sha256=9srQzjsTBrBadf6QMh4YRAL70rkZLevAOHqXWK5jvr8,8846
29
29
  copyparty/tcpsrv.py,sha256=F5K4Qr4eBLfhdLT_39yDf6ftrhWuGTrd6DSqqp_6e-Q,20480
30
- copyparty/tftpd.py,sha256=HAXNbIM7I3yFng_a4ubLWGQ4trRTineAZsUPTZDWNQs,14001
30
+ copyparty/tftpd.py,sha256=tbnxUsilwyusrAUCVVjJUZnR9TIHDkE-99WLsUxAIGA,14029
31
31
  copyparty/th_cli.py,sha256=IEX5tCb0gw9Z2aRIDL9bwdvJ6g5jhWZ8OEAAz16_xN4,5426
32
- copyparty/th_srv.py,sha256=ibEQL9LSYcwo7YXSaW5hNmH1SZex8Pud7-dsSkBRQ-A,32544
32
+ copyparty/th_srv.py,sha256=2omGprnKGWim6U7fjJAXI41UCZBSxRwZfS0rCDsUDps,32556
33
33
  copyparty/u2idx.py,sha256=4Y5OOPyVkc-pS0z6e3p4StXAMnjHobSOMmMsvNUTD34,13674
34
- copyparty/up2k.py,sha256=0Fj2yT2XnJ4MhwdEcDRs1T-gsdxVhdIxdaZSjGyGKBA,177682
35
- copyparty/util.py,sha256=SjG1pBPhv6XYa6uPbiOU8HRB6dDZqwjCRwUe22fLhdg,103117
34
+ copyparty/up2k.py,sha256=nnR_ZKaopSNBuAjSN3Q5G_UVe6GmYD1NkxJt5QQf02o,178079
35
+ copyparty/util.py,sha256=Keb-mlTq4rtWjv3utaGsKqwujHYPZLkaBZOeivnGxKc,103485
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
@@ -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=bPS2U1pXtSBNjXFM9nZkg_exWP4N8kgYe4grEBOX1t8,11706
58
+ copyparty/web/browser.css.gz,sha256=-w-OUbKy0UdL9vYKAC95ayl4MesDAFjqrq60uAxPj4U,11728
59
59
  copyparty/web/browser.html,sha256=auvhLVE_t0aIN0q-nk0zOWFqITgDhroMAAviBNLoFfc,4788
60
- copyparty/web/browser.js.gz,sha256=RYVZRBltqZwUyDfBOrOz8S1iS-Bf0AKeI0lloXHrPs0,94249
60
+ copyparty/web/browser.js.gz,sha256=wdxJBHlMCo1Oh9v1YZsj6rucul7qhQts1-iVfmFPjr8,94639
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
@@ -80,15 +80,15 @@ copyparty/web/shares.js.gz,sha256=emeY2-wjkh8x1JgaW6ny5fcC7XpZzZzfE1f-sEyntQ4,94
80
80
  copyparty/web/splash.css.gz,sha256=S8_A7JJl71xACRBYGzafeaD82OacW6Fa7oKPiNyrhAs,1087
81
81
  copyparty/web/splash.html,sha256=ouFB1P9g0K-S3LZQtOLfNz3GLXIRjyETkxo9aJZhiYk,6249
82
82
  copyparty/web/splash.js.gz,sha256=4VqNznN10-bT33IJm3VWzBEJ1s08XZyxFB1TYPUkuAo,2739
83
- copyparty/web/svcs.html,sha256=dnE1fG15zOpq7u0GYt8ij6BUv_LTwsiipFeneVYlMsM,14140
83
+ copyparty/web/svcs.html,sha256=cxgrhX9wD0Z_kvidry3aS9ubuGXYDj2f4ehq1X8T1EA,14227
84
84
  copyparty/web/svcs.js.gz,sha256=lMXEP9W-VlXyANlva4q0ASSxvvHYlE2CrmxGgZXZop0,713
85
85
  copyparty/web/ui.css.gz,sha256=iDjrmq32aDN6l2S5AjCQdKjD6bxmzP6ji2WjM1FjKiU,2819
86
86
  copyparty/web/up2k.js.gz,sha256=7AKmoJOtFh9tx3Ha7w2F-z69-XZo_LzyR3ilWnBO_D8,24524
87
- copyparty/web/util.js.gz,sha256=Sa7oyIKW0HtEQegmhUcrDwgty0lzqsMU0qn67pVvaZY,15231
87
+ copyparty/web/util.js.gz,sha256=Ha2u-RG4HAYOL_QZnt426man0BdhucVNYOQjG5v69AA,15254
88
88
  copyparty/web/w.hash.js.gz,sha256=JhJagnqIkcKng_hs6otEgzcuQE7keToG_r5dd2o3EfU,1108
89
89
  copyparty/web/a/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
90
90
  copyparty/web/a/partyfuse.py,sha256=9p5Hpg_IBiSimv7j9kmPhCGpy-FLXSRUOYnLjJ5JifU,28049
91
- copyparty/web/a/u2c.py,sha256=oRKEApPuaXiOkmUyA-WGCDjtWsn8FirSW6nCJtx9sgk,53157
91
+ copyparty/web/a/u2c.py,sha256=auXzLj04dt_lw4H70PhNUK0GjrQEThrybo2-77SLsUg,53165
92
92
  copyparty/web/a/webdav-cfg.bat,sha256=Y4NoGZlksAIg4cBMb7KdJrpKC6Nx97onaTl6yMjaimk,1449
93
93
  copyparty/web/dd/2.png,sha256=gJ14XFPzaw95L6z92fSq9eMPikSQyu-03P1lgiGe0_I,258
94
94
  copyparty/web/dd/3.png,sha256=4lho8Koz5tV7jJ4ODo6GMTScZfkqsT05yp48EDFIlyg,252
@@ -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.17.0.dist-info/licenses/LICENSE,sha256=gOr4h33pCsBEg9uIy9AYmb7qlocL4V9t2uPJS5wllr0,1072
113
- copyparty-1.17.0.dist-info/METADATA,sha256=nKWqseMoVudhVi1HaLux7M1zJt1QLdpD4bXSUL4_eb4,162654
114
- copyparty-1.17.0.dist-info/WHEEL,sha256=SmOxYU7pzNKBqASvQJ7DjX3XGUF92lrGhMb3R6_iiqI,91
115
- copyparty-1.17.0.dist-info/entry_points.txt,sha256=4zw6a3rqASywQomiYLObjjlxybaI65LYYOTJwgKz7b0,128
116
- copyparty-1.17.0.dist-info/top_level.txt,sha256=LnYUPsDyk-8kFgM6YJLG4h820DQekn81cObKSu9g-sI,10
117
- copyparty-1.17.0.dist-info/RECORD,,
112
+ copyparty-1.17.1.dist-info/licenses/LICENSE,sha256=gOr4h33pCsBEg9uIy9AYmb7qlocL4V9t2uPJS5wllr0,1072
113
+ copyparty-1.17.1.dist-info/METADATA,sha256=ApeqUFduyJZInDL2s46CSphR3JT7XsAmrTvoOlLCa20,163313
114
+ copyparty-1.17.1.dist-info/WHEEL,sha256=Nw36Djuh_5VDukK0H78QzOX-_FQEo6V37m3nkm96gtU,91
115
+ copyparty-1.17.1.dist-info/entry_points.txt,sha256=4zw6a3rqASywQomiYLObjjlxybaI65LYYOTJwgKz7b0,128
116
+ copyparty-1.17.1.dist-info/top_level.txt,sha256=LnYUPsDyk-8kFgM6YJLG4h820DQekn81cObKSu9g-sI,10
117
+ copyparty-1.17.1.dist-info/RECORD,,
@@ -1,5 +1,5 @@
1
1
  Wheel-Version: 1.0
2
- Generator: setuptools (79.0.1)
2
+ Generator: setuptools (80.7.1)
3
3
  Root-Is-Purelib: true
4
4
  Tag: py3-none-any
5
5