copyparty 1.16.18__py3-none-any.whl → 1.16.19__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
copyparty/__init__.py CHANGED
@@ -11,7 +11,7 @@ _=(0,0) # _____________________________________________________________________
11
11
  # fmt: on
12
12
 
13
13
  try:
14
- from typing import TYPE_CHECKING
14
+ TYPE_CHECKING = False
15
15
  except:
16
16
  TYPE_CHECKING = False
17
17
 
copyparty/__main__.py CHANGED
@@ -222,7 +222,23 @@ def init_E(EE ) :
222
222
  if E.mod.endswith("__init__"):
223
223
  E.mod = os.path.dirname(E.mod)
224
224
 
225
- if sys.platform == "win32":
225
+ try:
226
+ p = os.environ.get("XDG_CONFIG_HOME")
227
+ if not p:
228
+ raise Exception()
229
+ if p.startswith("~"):
230
+ p = os.path.expanduser(p)
231
+ p = os.path.abspath(os.path.realpath(p))
232
+ p = os.path.join(p, "copyparty")
233
+ if not os.path.isdir(p):
234
+ os.mkdir(p)
235
+ os.listdir(p)
236
+ except:
237
+ p = ""
238
+
239
+ if p:
240
+ E.cfg = p
241
+ elif sys.platform == "win32":
226
242
  bdir = os.environ.get("APPDATA") or os.environ.get("TEMP") or "."
227
243
  E.cfg = os.path.normpath(bdir + "/copyparty")
228
244
  elif sys.platform == "darwin":
@@ -1003,7 +1019,7 @@ def add_upload(ap):
1003
1019
  ap2.add_argument("--turbo", metavar="LVL", type=int, default=0, help="configure turbo-mode in up2k client; [\033[32m-1\033[0m] = forbidden/always-off, [\033[32m0\033[0m] = default-off and warn if enabled, [\033[32m1\033[0m] = default-off, [\033[32m2\033[0m] = on, [\033[32m3\033[0m] = on and disable datecheck")
1004
1020
  ap2.add_argument("--u2j", metavar="JOBS", type=int, default=2, help="web-client: number of file chunks to upload in parallel; 1 or 2 is good for low-latency (same-country) connections, 4-8 for android clients, 16 for cross-atlantic (max=64)")
1005
1021
  ap2.add_argument("--u2sz", metavar="N,N,N", type=u, default="1,64,96", help="web-client: default upload chunksize (MiB); sets \033[33mmin,default,max\033[0m in the settings gui. Each HTTP POST will aim for \033[33mdefault\033[0m, and never exceed \033[33mmax\033[0m. Cloudflare max is 96. Big values are good for cross-atlantic but may increase HDD fragmentation on some FS. Disable this optimization with [\033[32m1,1,1\033[0m]")
1006
- ap2.add_argument("--u2ow", metavar="NUM", type=int, default=0, help="web-client: default setting for when to overwrite existing files; [\033[32m0\033[0m]=never, [\033[32m1\033[0m]=if-client-newer, [\033[32m2\033[0m]=always (volflag=u2ow)")
1022
+ ap2.add_argument("--u2ow", metavar="NUM", type=int, default=0, help="web-client: default setting for when to replace/overwrite existing files; [\033[32m0\033[0m]=never, [\033[32m1\033[0m]=if-client-newer, [\033[32m2\033[0m]=always (volflag=u2ow)")
1007
1023
  ap2.add_argument("--u2sort", metavar="TXT", type=u, default="s", help="upload order; [\033[32ms\033[0m]=smallest-first, [\033[32mn\033[0m]=alphabetical, [\033[32mfs\033[0m]=force-s, [\033[32mfn\033[0m]=force-n -- alphabetical is a bit slower on fiber/LAN but makes it easier to eyeball if everything went fine")
1008
1024
  ap2.add_argument("--write-uplog", action="store_true", help="write POST reports to textfiles in working-directory")
1009
1025
 
@@ -1385,6 +1401,7 @@ def add_db_general(ap, hcores):
1385
1401
  ap2.add_argument("-e2vu", action="store_true", help="on hash mismatch: update the database with the new hash")
1386
1402
  ap2.add_argument("-e2vp", action="store_true", help="on hash mismatch: panic and quit copyparty")
1387
1403
  ap2.add_argument("--hist", metavar="PATH", type=u, default="", help="where to store volume data (db, thumbs); default is a folder named \".hist\" inside each volume (volflag=hist)")
1404
+ ap2.add_argument("--dbpath", metavar="PATH", type=u, default="", help="override where the volume databases are to be placed; default is the same as \033[33m--hist\033[0m (volflag=dbpath)")
1388
1405
  ap2.add_argument("--no-hash", metavar="PTN", type=u, default="", help="regex: disable hashing of matching absolute-filesystem-paths during e2ds folder scans (volflag=nohash)")
1389
1406
  ap2.add_argument("--no-idx", metavar="PTN", type=u, default=noidx, help="regex: disable indexing of matching absolute-filesystem-paths during e2ds folder scans (volflag=noidx)")
1390
1407
  ap2.add_argument("--no-dirsz", action="store_true", help="do not show total recursive size of folders in listings, show inode size instead; slightly faster (volflag=nodirsz)")
@@ -1423,6 +1440,7 @@ def add_db_metadata(ap):
1423
1440
 
1424
1441
  def add_txt(ap):
1425
1442
  ap2 = ap.add_argument_group('textfile options')
1443
+ ap2.add_argument("--md-hist", metavar="TXT", type=u, default="s", help="where to store old version of markdown files; [\033[32ms\033[0m]=subfolder, [\033[32mv\033[0m]=volume-histpath, [\033[32mn\033[0m]=nope/disabled (volflag=md_hist)")
1426
1444
  ap2.add_argument("-mcr", metavar="SEC", type=int, default=60, help="the textfile editor will check for serverside changes every \033[33mSEC\033[0m seconds")
1427
1445
  ap2.add_argument("-emp", action="store_true", help="enable markdown plugins -- neat but dangerous, big XSS risk")
1428
1446
  ap2.add_argument("--exp", action="store_true", help="enable textfile expansion -- replace {{self.ip}} and such; see \033[33m--help-exp\033[0m (volflag=exp)")
copyparty/__version__.py CHANGED
@@ -1,8 +1,8 @@
1
1
  # coding: utf-8
2
2
 
3
- VERSION = (1, 16, 18)
3
+ VERSION = (1, 16, 19)
4
4
  CODENAME = "COPYparty"
5
- BUILD_DT = (2025, 3, 23)
5
+ BUILD_DT = (2025, 4, 8)
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
@@ -353,6 +353,7 @@ class VFS(object):
353
353
  self.badcfg1 = False
354
354
  self.nodes = {} # child nodes
355
355
  self.histtab = {} # all realpath->histpath
356
+ self.dbpaths = {} # all realpath->dbpath
356
357
  self.dbv = None # closest full/non-jump parent
357
358
  self.lim = None # upload limits; only set for dbv
358
359
  self.shr_src = None # source vfs+rem of a share
@@ -374,12 +375,13 @@ class VFS(object):
374
375
  rp = realpath + ("" if realpath.endswith(os.sep) else os.sep)
375
376
  vp = vpath + ("/" if vpath else "")
376
377
  self.histpath = os.path.join(realpath, ".hist") # db / thumbcache
378
+ self.dbpath = self.histpath
377
379
  self.all_vols = {vpath: self} # flattened recursive
378
380
  self.all_nodes = {vpath: self} # also jumpvols/shares
379
381
  self.all_aps = [(rp, self)]
380
382
  self.all_vps = [(vp, self)]
381
383
  else:
382
- self.histpath = ""
384
+ self.histpath = self.dbpath = ""
383
385
  self.all_vols = {}
384
386
  self.all_nodes = {}
385
387
  self.all_aps = []
@@ -454,17 +456,23 @@ class VFS(object):
454
456
 
455
457
  def _copy_flags(self, name ) :
456
458
  flags = {k: v for k, v in self.flags.items()}
459
+
457
460
  hist = flags.get("hist")
458
461
  if hist and hist != "-":
459
462
  zs = "{}/{}".format(hist.rstrip("/"), name)
460
463
  flags["hist"] = os.path.expandvars(os.path.expanduser(zs))
461
464
 
465
+ dbp = flags.get("dbpath")
466
+ if dbp and dbp != "-":
467
+ zs = "{}/{}".format(dbp.rstrip("/"), name)
468
+ flags["dbpath"] = os.path.expandvars(os.path.expanduser(zs))
469
+
462
470
  return flags
463
471
 
464
472
  def bubble_flags(self) :
465
473
  if self.dbv:
466
474
  for k, v in self.dbv.flags.items():
467
- if k not in ["hist"]:
475
+ if k not in ("hist", "dbpath"):
468
476
  self.flags[k] = v
469
477
 
470
478
  for n in self.nodes.values():
@@ -1752,7 +1760,7 @@ class AuthSrv(object):
1752
1760
  pass
1753
1761
  elif vflag:
1754
1762
  vflag = os.path.expandvars(os.path.expanduser(vflag))
1755
- vol.histpath = uncyg(vflag) if WINDOWS else vflag
1763
+ vol.histpath = vol.dbpath = uncyg(vflag) if WINDOWS else vflag
1756
1764
  elif self.args.hist:
1757
1765
  for nch in range(len(hid)):
1758
1766
  hpath = os.path.join(self.args.hist, hid[: nch + 1])
@@ -1773,12 +1781,45 @@ class AuthSrv(object):
1773
1781
  with open(powner, "wb") as f:
1774
1782
  f.write(me)
1775
1783
 
1776
- vol.histpath = hpath
1784
+ vol.histpath = vol.dbpath = hpath
1777
1785
  break
1778
1786
 
1779
1787
  vol.histpath = absreal(vol.histpath)
1788
+
1789
+ for vol in vfs.all_vols.values():
1790
+ hid = self.hid_cache[vol.realpath]
1791
+ vflag = vol.flags.get("dbpath")
1792
+ if vflag == "-":
1793
+ pass
1794
+ elif vflag:
1795
+ vflag = os.path.expandvars(os.path.expanduser(vflag))
1796
+ vol.dbpath = uncyg(vflag) if WINDOWS else vflag
1797
+ elif self.args.dbpath:
1798
+ for nch in range(len(hid)):
1799
+ hpath = os.path.join(self.args.dbpath, hid[: nch + 1])
1800
+ bos.makedirs(hpath)
1801
+
1802
+ powner = os.path.join(hpath, "owner.txt")
1803
+ try:
1804
+ with open(powner, "rb") as f:
1805
+ owner = f.read().rstrip()
1806
+ except:
1807
+ owner = None
1808
+
1809
+ me = afsenc(vol.realpath).rstrip()
1810
+ if owner not in [None, me]:
1811
+ continue
1812
+
1813
+ if owner is None:
1814
+ with open(powner, "wb") as f:
1815
+ f.write(me)
1816
+
1817
+ vol.dbpath = hpath
1818
+ break
1819
+
1820
+ vol.dbpath = absreal(vol.dbpath)
1780
1821
  if vol.dbv:
1781
- if bos.path.exists(os.path.join(vol.histpath, "up2k.db")):
1822
+ if bos.path.exists(os.path.join(vol.dbpath, "up2k.db")):
1782
1823
  promote.append(vol)
1783
1824
  vol.dbv = None
1784
1825
  else:
@@ -1793,9 +1834,7 @@ class AuthSrv(object):
1793
1834
  "\n the following jump-volumes were generated to assist the vfs.\n As they contain a database (probably from v0.11.11 or older),\n they are promoted to full volumes:"
1794
1835
  ]
1795
1836
  for vol in promote:
1796
- ta.append(
1797
- " /{} ({}) ({})".format(vol.vpath, vol.realpath, vol.histpath)
1798
- )
1837
+ ta.append(" /%s (%s) (%s)" % (vol.vpath, vol.realpath, vol.dbpath))
1799
1838
 
1800
1839
  self.log("\n\n".join(ta) + "\n", c=3)
1801
1840
 
@@ -1806,13 +1845,27 @@ class AuthSrv(object):
1806
1845
  is_shr = shr and zv.vpath.split("/")[0] == shr
1807
1846
  if histp and not is_shr and histp in rhisttab:
1808
1847
  zv2 = rhisttab[histp]
1809
- t = "invalid config; multiple volumes share the same histpath (database location):\n histpath: %s\n volume 1: /%s [%s]\n volume 2: %s [%s]"
1848
+ t = "invalid config; multiple volumes share the same histpath (database+thumbnails location):\n histpath: %s\n volume 1: /%s [%s]\n volume 2: %s [%s]"
1810
1849
  t = t % (histp, zv2.vpath, zv2.realpath, zv.vpath, zv.realpath)
1811
1850
  self.log(t, 1)
1812
1851
  raise Exception(t)
1813
1852
  rhisttab[histp] = zv
1814
1853
  vfs.histtab[zv.realpath] = histp
1815
1854
 
1855
+ rdbpaths = {}
1856
+ vfs.dbpaths = {}
1857
+ for zv in vfs.all_vols.values():
1858
+ dbp = zv.dbpath
1859
+ is_shr = shr and zv.vpath.split("/")[0] == shr
1860
+ if dbp and not is_shr and dbp in rdbpaths:
1861
+ zv2 = rdbpaths[dbp]
1862
+ t = "invalid config; multiple volumes share the same dbpath (database location):\n dbpath: %s\n volume 1: /%s [%s]\n volume 2: %s [%s]"
1863
+ t = t % (dbp, zv2.vpath, zv2.realpath, zv.vpath, zv.realpath)
1864
+ self.log(t, 1)
1865
+ raise Exception(t)
1866
+ rdbpaths[dbp] = zv
1867
+ vfs.dbpaths[zv.realpath] = dbp
1868
+
1816
1869
  for vol in vfs.all_vols.values():
1817
1870
  use = False
1818
1871
  for k in ["zipmaxn", "zipmaxs"]:
copyparty/cfg.py CHANGED
@@ -83,6 +83,7 @@ def vf_vmap() :
83
83
  "md_sbf",
84
84
  "lg_sba",
85
85
  "md_sba",
86
+ "md_hist",
86
87
  "nrand",
87
88
  "u2ow",
88
89
  "og_desc",
@@ -204,6 +205,7 @@ flagcats = {
204
205
  "d2v": "disables file verification, overrides -e2v*",
205
206
  "d2d": "disables all database stuff, overrides -e2*",
206
207
  "hist=/tmp/cdb": "puts thumbnails and indexes at that location",
208
+ "dbpath=/tmp/cdb": "puts indexes at that location",
207
209
  "scan=60": "scan for new files every 60sec, same as --re-maxage",
208
210
  "nohash=\\.iso$": "skips hashing file contents if path matches *.iso",
209
211
  "noidx=\\.iso$": "fully ignores the contents at paths matching *.iso",
@@ -291,6 +293,7 @@ flagcats = {
291
293
  "og_ua": "if defined: only send OG html if useragent matches this regex",
292
294
  },
293
295
  "textfiles": {
296
+ "md_hist": "where to put markdown backups; s=subfolder, v=volHist, n=nope",
294
297
  "exp": "enable textfile expansion; see --help-exp",
295
298
  "exp_md": "placeholders to expand in markdown files; see --help",
296
299
  "exp_lg": "placeholders to expand in prologue/epilogue; see --help",
copyparty/httpcli.py CHANGED
@@ -57,6 +57,7 @@ from .util import (
57
57
  UnrecvEOF,
58
58
  WrongPostKey,
59
59
  absreal,
60
+ afsenc,
60
61
  alltrace,
61
62
  atomic_move,
62
63
  b64dec,
@@ -2983,9 +2984,6 @@ class HttpCli(object):
2983
2984
  vfs, rem = self.asrv.vfs.get(vpath, self.uname, False, True)
2984
2985
  rem = sanitize_vpath(rem, "/")
2985
2986
  fn = vfs.canonical(rem)
2986
- if not fn.startswith(vfs.realpath):
2987
- self.log("invalid mkdir %r %r" % (self.gctx, vpath), 1)
2988
- raise Pebkac(422)
2989
2987
 
2990
2988
  if not nullwrite:
2991
2989
  fdir = os.path.dirname(fn)
@@ -3484,6 +3482,7 @@ class HttpCli(object):
3484
3482
 
3485
3483
  fp = os.path.join(fp, fn)
3486
3484
  rem = "{}/{}".format(rp, fn).strip("/")
3485
+ dbv, vrem = vfs.get_dbv(rem)
3487
3486
 
3488
3487
  if not rem.endswith(".md") and not self.can_delete:
3489
3488
  raise Pebkac(400, "only markdown pls")
@@ -3538,13 +3537,27 @@ class HttpCli(object):
3538
3537
  mdir, mfile = os.path.split(fp)
3539
3538
  fname, fext = mfile.rsplit(".", 1) if "." in mfile else (mfile, "md")
3540
3539
  mfile2 = "{}.{:.3f}.{}".format(fname, srv_lastmod, fext)
3541
- try:
3540
+
3541
+ dp = ""
3542
+ hist_cfg = dbv.flags["md_hist"]
3543
+ if hist_cfg == "v":
3544
+ vrd = vsplit(vrem)[0]
3545
+ zb = hashlib.sha512(afsenc(vrd)).digest()
3546
+ zs = ub64enc(zb).decode("ascii")[:24].lower()
3547
+ dp = "%s/md/%s/%s/%s" % (dbv.histpath, zs[:2], zs[2:4], zs)
3548
+ self.log("moving old version to %s/%s" % (dp, mfile2))
3549
+ if bos.makedirs(dp):
3550
+ with open(os.path.join(dp, "dir.txt"), "wb") as f:
3551
+ f.write(afsenc(vrd))
3552
+ elif hist_cfg == "s":
3542
3553
  dp = os.path.join(mdir, ".hist")
3543
- bos.mkdir(dp)
3544
- hidedir(dp)
3545
- except:
3546
- pass
3547
- wrename(self.log, fp, os.path.join(mdir, ".hist", mfile2), vfs.flags)
3554
+ try:
3555
+ bos.mkdir(dp)
3556
+ hidedir(dp)
3557
+ except:
3558
+ pass
3559
+ if dp:
3560
+ wrename(self.log, fp, os.path.join(dp, mfile2), vfs.flags)
3548
3561
 
3549
3562
  p_field, _, p_data = next(self.parser.gen)
3550
3563
  if p_field != "body":
@@ -3616,13 +3629,12 @@ class HttpCli(object):
3616
3629
  wunlink(self.log, fp, vfs.flags)
3617
3630
  raise Pebkac(403, t)
3618
3631
 
3619
- vfs, rem = vfs.get_dbv(rem)
3620
3632
  self.conn.hsrv.broker.say(
3621
3633
  "up2k.hash_file",
3622
- vfs.realpath,
3623
- vfs.vpath,
3624
- vfs.flags,
3625
- vsplit(rem)[0],
3634
+ dbv.realpath,
3635
+ dbv.vpath,
3636
+ dbv.flags,
3637
+ vsplit(vrem)[0],
3626
3638
  fn,
3627
3639
  self.ip,
3628
3640
  new_lastmod,
@@ -4849,7 +4861,7 @@ class HttpCli(object):
4849
4861
  self.reply(pt.encode("utf-8"), status=rc)
4850
4862
  return True
4851
4863
 
4852
- if "th" in self.ouparam:
4864
+ if "th" in self.ouparam and str(self.ouparam["th"])[:1] in "jw":
4853
4865
  return self.tx_svg("e" + pt[:3])
4854
4866
 
4855
4867
  # most webdav clients will not send credentials until they
@@ -5776,7 +5788,13 @@ class HttpCli(object):
5776
5788
 
5777
5789
  thp = None
5778
5790
  if self.thumbcli and not nothumb:
5779
- thp = self.thumbcli.get(dbv, vrem, int(st.st_mtime), th_fmt)
5791
+ try:
5792
+ thp = self.thumbcli.get(dbv, vrem, int(st.st_mtime), th_fmt)
5793
+ except Pebkac as ex:
5794
+ if ex.code == 500 and th_fmt[:1] in "jw":
5795
+ self.log("failed to convert [%s]:\n%s" % (abspath, ex), 3)
5796
+ return self.tx_svg("--error--\ncheck\nserver\nlog")
5797
+ raise
5780
5798
 
5781
5799
  if thp:
5782
5800
  return self.tx_file(thp)
@@ -5998,9 +6016,11 @@ class HttpCli(object):
5998
6016
  # check for old versions of files,
5999
6017
  # [num-backups, most-recent, hist-path]
6000
6018
  hist = {}
6001
- histdir = os.path.join(fsroot, ".hist")
6002
- ptn = RE_MDV
6003
6019
  try:
6020
+ if vf["md_hist"] != "s":
6021
+ raise Exception()
6022
+ histdir = os.path.join(fsroot, ".hist")
6023
+ ptn = RE_MDV
6004
6024
  for hfn in bos.listdir(histdir):
6005
6025
  m = ptn.match(hfn)
6006
6026
  if not m:
copyparty/ico.py CHANGED
@@ -94,10 +94,21 @@ class Ico(object):
94
94
  <?xml version="1.0" encoding="UTF-8"?>
95
95
  <svg version="1.1" viewBox="0 0 100 {}" xmlns="http://www.w3.org/2000/svg"><g>
96
96
  <rect width="100%" height="100%" fill="#{}" />
97
- <text x="50%" y="50%" dominant-baseline="middle" text-anchor="middle" xml:space="preserve"
97
+ <text x="50%" y="{}" dominant-baseline="middle" text-anchor="middle" xml:space="preserve"
98
98
  fill="#{}" font-family="monospace" font-size="14px" style="letter-spacing:.5px">{}</text>
99
99
  </g></svg>
100
100
  """
101
- svg = svg.format(h, c[:6], c[6:], html_escape(ext, True))
101
+
102
+ txt = html_escape(ext, True)
103
+ if "\n" in txt:
104
+ lines = txt.split("\n")
105
+ n = len(lines)
106
+ y = "20%" if n == 2 else "10%" if n == 3 else "0"
107
+ zs = '<tspan x="50%%" dy="1.2em">%s</tspan>'
108
+ txt = "".join([zs % (x,) for x in lines])
109
+ else:
110
+ y = "50%"
111
+
112
+ svg = svg.format(h, c[:6], y, c[6:], txt)
102
113
 
103
114
  return "image/svg+xml", svg.encode("utf-8")
copyparty/pwhash.py CHANGED
@@ -15,7 +15,7 @@ try:
15
15
  raise Exception()
16
16
 
17
17
  HAVE_ARGON2 = True
18
- from argon2 import __version__ as argon2ver
18
+ from argon2 import exceptions as argon2ex
19
19
  except:
20
20
  HAVE_ARGON2 = False
21
21
 
copyparty/svchub.py CHANGED
@@ -58,6 +58,7 @@ from .util import (
58
58
  expat_ver,
59
59
  gzip,
60
60
  load_ipu,
61
+ lock_file,
61
62
  min_ex,
62
63
  mp,
63
64
  odfusion,
@@ -67,6 +68,9 @@ from .util import (
67
68
  ub64enc,
68
69
  )
69
70
 
71
+ if HAVE_SQLITE3:
72
+ import sqlite3
73
+
70
74
  if TYPE_CHECKING:
71
75
  try:
72
76
  from .mdns import MDNS
@@ -78,6 +82,10 @@ if PY2:
78
82
  range = xrange # type: ignore
79
83
 
80
84
 
85
+ VER_SESSION_DB = 1
86
+ VER_SHARES_DB = 2
87
+
88
+
81
89
  class SvcHub(object):
82
90
  """
83
91
  Hosts all services which cannot be parallelized due to reliance on monolithic resources.
@@ -180,8 +188,14 @@ class SvcHub(object):
180
188
 
181
189
  if not args.use_fpool and args.j != 1:
182
190
  args.no_fpool = True
183
- t = "multithreading enabled with -j {}, so disabling fpool -- this can reduce upload performance on some filesystems"
184
- self.log("root", t.format(args.j))
191
+ t = "multithreading enabled with -j {}, so disabling fpool -- this can reduce upload performance on some filesystems, and make some antivirus-softwares "
192
+ c = 0
193
+ if ANYWIN:
194
+ t += "(especially Microsoft Defender) stress your CPU and HDD severely during big uploads"
195
+ c = 3
196
+ else:
197
+ t += "consume more resources (CPU/HDD) than normal"
198
+ self.log("root", t.format(args.j), c)
185
199
 
186
200
  if not args.no_fpool and args.j != 1:
187
201
  t = "WARNING: ignoring --use-fpool because multithreading (-j{}) is enabled"
@@ -400,25 +414,48 @@ class SvcHub(object):
400
414
  self.log("root", t, 3)
401
415
  return
402
416
 
403
- import sqlite3
404
417
 
405
- create = True
418
+ # policy:
419
+ # the sessions-db is whatever, if something looks broken then just nuke it
420
+
406
421
  db_path = self.args.ses_db
407
- self.log("root", "opening sessions-db %s" % (db_path,))
408
- for n in range(2):
422
+ db_lock = db_path + ".lock"
423
+ try:
424
+ create = not os.path.getsize(db_path)
425
+ except:
426
+ create = True
427
+ zs = "creating new" if create else "opening"
428
+ self.log("root", "%s sessions-db %s" % (zs, db_path))
429
+
430
+ for tries in range(2):
431
+ sver = 0
409
432
  try:
410
433
  db = sqlite3.connect(db_path)
411
434
  cur = db.cursor()
412
435
  try:
436
+ zs = "select v from kv where k='sver'"
437
+ sver = cur.execute(zs).fetchall()[0][0]
438
+ if sver > VER_SESSION_DB:
439
+ zs = "this version of copyparty only understands session-db v%d and older; the db is v%d"
440
+ raise Exception(zs % (VER_SESSION_DB, sver))
441
+
413
442
  cur.execute("select count(*) from us").fetchone()
414
- create = False
415
- break
416
443
  except:
417
- pass
444
+ if sver:
445
+ raise
446
+ sver = 1
447
+ self._create_session_db(cur)
448
+ err = self._verify_session_db(cur, sver, db_path)
449
+ if err:
450
+ tries = 99
451
+ self.args.no_ses = True
452
+ self.log("root", err, 3)
453
+ break
454
+
418
455
  except Exception as ex:
419
- if n:
456
+ if tries or sver > VER_SESSION_DB:
420
457
  raise
421
- t = "sessions-db corrupt; deleting and recreating: %r"
458
+ t = "sessions-db is unusable; deleting and recreating: %r"
422
459
  self.log("root", t % (ex,), 3)
423
460
  try:
424
461
  cur.close() # type: ignore
@@ -428,8 +465,13 @@ class SvcHub(object):
428
465
  db.close() # type: ignore
429
466
  except:
430
467
  pass
468
+ try:
469
+ os.unlink(db_lock)
470
+ except:
471
+ pass
431
472
  os.unlink(db_path)
432
473
 
474
+ def _create_session_db(self, cur ) :
433
475
  sch = [
434
476
  r"create table kv (k text, v int)",
435
477
  r"create table us (un text, si text, t0 int)",
@@ -439,15 +481,44 @@ class SvcHub(object):
439
481
  r"create index us_t0 on us(t0)",
440
482
  r"insert into kv values ('sver', 1)",
441
483
  ]
484
+ for cmd in sch:
485
+ cur.execute(cmd)
486
+ self.log("root", "created new sessions-db")
442
487
 
443
- if create:
444
- for cmd in sch:
445
- cur.execute(cmd)
446
- self.log("root", "created new sessions-db")
447
- db.commit()
488
+ def _verify_session_db(self, cur , sver , db_path ) :
489
+ # ensure writable (maybe owned by other user)
490
+ db = cur.connection
491
+
492
+ try:
493
+ zil = cur.execute("select v from kv where k='pid'").fetchall()
494
+ if len(zil) > 1:
495
+ raise Exception()
496
+ owner = zil[0][0]
497
+ except:
498
+ owner = 0
499
+
500
+ if not lock_file(db_path + ".lock"):
501
+ t = "the sessions-db [%s] is already in use by another copyparty instance (pid:%d). This is not supported; please provide another database with --ses-db or give this copyparty-instance its entirely separate config-folder by setting another path in the XDG_CONFIG_HOME env-var. You can also disable this safeguard by setting env-var PRTY_NO_DB_LOCK=1. Will now disable sessions and instead use plaintext passwords in cookies."
502
+ return t % (db_path, owner)
503
+
504
+ vars = (("pid", os.getpid()), ("ts", int(time.time() * 1000)))
505
+ if owner:
506
+ # wear-estimate: 2 cells; offsets 0x10, 0x50, 0x19720
507
+ for k, v in vars:
508
+ cur.execute("update kv set v=? where k=?", (v, k))
509
+ else:
510
+ # wear-estimate: 3~4 cells; offsets 0x10, 0x50, 0x19180, 0x19710, 0x36000, 0x360b0, 0x36b90
511
+ for k, v in vars:
512
+ cur.execute("insert into kv values(?, ?)", (k, v))
448
513
 
514
+ if sver < VER_SESSION_DB:
515
+ cur.execute("delete from kv where k='sver'")
516
+ cur.execute("insert into kv values('sver',?)", (VER_SESSION_DB,))
517
+
518
+ db.commit()
449
519
  cur.close()
450
520
  db.close()
521
+ return ""
451
522
 
452
523
  def setup_share_db(self) :
453
524
  al = self.args
@@ -456,7 +527,6 @@ class SvcHub(object):
456
527
  al.shr = ""
457
528
  return
458
529
 
459
- import sqlite3
460
530
 
461
531
  al.shr = al.shr.strip("/")
462
532
  if "/" in al.shr or not al.shr:
@@ -467,34 +537,48 @@ class SvcHub(object):
467
537
  al.shr = "/%s/" % (al.shr,)
468
538
  al.shr1 = al.shr[1:]
469
539
 
470
- create = True
471
- modified = False
540
+ # policy:
541
+ # the shares-db is important, so panic if something is wrong
542
+
472
543
  db_path = self.args.shr_db
473
- self.log("root", "opening shares-db %s" % (db_path,))
474
- for n in range(2):
475
- try:
476
- db = sqlite3.connect(db_path)
477
- cur = db.cursor()
478
- try:
479
- cur.execute("select count(*) from sh").fetchone()
480
- create = False
481
- break
482
- except:
483
- pass
484
- except Exception as ex:
485
- if n:
486
- raise
487
- t = "shares-db corrupt; deleting and recreating: %r"
488
- self.log("root", t % (ex,), 3)
489
- try:
490
- cur.close() # type: ignore
491
- except:
492
- pass
493
- try:
494
- db.close() # type: ignore
495
- except:
496
- pass
497
- os.unlink(db_path)
544
+ db_lock = db_path + ".lock"
545
+ try:
546
+ create = not os.path.getsize(db_path)
547
+ except:
548
+ create = True
549
+ zs = "creating new" if create else "opening"
550
+ self.log("root", "%s shares-db %s" % (zs, db_path))
551
+
552
+ sver = 0
553
+ try:
554
+ db = sqlite3.connect(db_path)
555
+ cur = db.cursor()
556
+ if not create:
557
+ zs = "select v from kv where k='sver'"
558
+ sver = cur.execute(zs).fetchall()[0][0]
559
+ if sver > VER_SHARES_DB:
560
+ zs = "this version of copyparty only understands shares-db v%d and older; the db is v%d"
561
+ raise Exception(zs % (VER_SHARES_DB, sver))
562
+
563
+ cur.execute("select count(*) from sh").fetchone()
564
+ except Exception as ex:
565
+ t = "could not open shares-db; will now panic...\nthe following database must be repaired or deleted before you can launch copyparty:\n%s\n\nERROR: %s\n\nadditional details:\n%s\n"
566
+ self.log("root", t % (db_path, ex, min_ex()), 1)
567
+ raise
568
+
569
+ try:
570
+ zil = cur.execute("select v from kv where k='pid'").fetchall()
571
+ if len(zil) > 1:
572
+ raise Exception()
573
+ owner = zil[0][0]
574
+ except:
575
+ owner = 0
576
+
577
+ if not lock_file(db_lock):
578
+ t = "the shares-db [%s] is already in use by another copyparty instance (pid:%d). This is not supported; please provide another database with --shr-db or give this copyparty-instance its entirely separate config-folder by setting another path in the XDG_CONFIG_HOME env-var. You can also disable this safeguard by setting env-var PRTY_NO_DB_LOCK=1. Will now panic."
579
+ t = t % (db_path, owner)
580
+ self.log("root", t, 1)
581
+ raise Exception(t)
498
582
 
499
583
  sch1 = [
500
584
  r"create table kv (k text, v int)",
@@ -506,32 +590,35 @@ class SvcHub(object):
506
590
  r"create index sf_k on sf(k)",
507
591
  r"create index sh_k on sh(k)",
508
592
  r"create index sh_t1 on sh(t1)",
593
+ r"insert into kv values ('sver', 2)",
509
594
  ]
510
595
 
511
- if create:
512
- dver = 2
513
- modified = True
596
+ if not sver:
597
+ sver = VER_SHARES_DB
514
598
  for cmd in sch1 + sch2:
515
599
  cur.execute(cmd)
516
600
  self.log("root", "created new shares-db")
517
- else:
518
- (dver,) = cur.execute("select v from kv where k = 'sver'").fetchall()[0]
519
601
 
520
- if dver == 1:
521
- modified = True
602
+ if sver == 1:
522
603
  for cmd in sch2:
523
604
  cur.execute(cmd)
524
605
  cur.execute("update sh set st = 0")
525
606
  self.log("root", "shares-db schema upgrade ok")
526
607
 
527
- if modified:
528
- for cmd in [
529
- r"delete from kv where k = 'sver'",
530
- r"insert into kv values ('sver', %d)" % (2,),
531
- ]:
532
- cur.execute(cmd)
533
- db.commit()
608
+ if sver < VER_SHARES_DB:
609
+ cur.execute("delete from kv where k='sver'")
610
+ cur.execute("insert into kv values('sver',?)", (VER_SHARES_DB,))
611
+
612
+ vars = (("pid", os.getpid()), ("ts", int(time.time() * 1000)))
613
+ if owner:
614
+ # wear-estimate: same as sessions-db
615
+ for k, v in vars:
616
+ cur.execute("update kv set v=? where k=?", (v, k))
617
+ else:
618
+ for k, v in vars:
619
+ cur.execute("insert into kv values(?, ?)", (k, v))
534
620
 
621
+ db.commit()
535
622
  cur.close()
536
623
  db.close()
537
624
 
@@ -669,8 +756,8 @@ class SvcHub(object):
669
756
  t += ", "
670
757
  t += "\033[0mNG: \033[35m" + sng
671
758
 
672
- t += "\033[0m, see --deps"
673
- self.log("dependencies", t, 6)
759
+ t += "\033[0m, see --deps (this is fine btw)"
760
+ self.log("optional-dependencies", t, 6)
674
761
 
675
762
  def _check_env(self) :
676
763
  try:
@@ -753,7 +840,7 @@ class SvcHub(object):
753
840
  vl = [os.path.expandvars(os.path.expanduser(x)) for x in vl]
754
841
  setattr(al, k, vl)
755
842
 
756
- for k in "lo hist ssl_log".split(" "):
843
+ for k in "lo hist dbpath ssl_log".split(" "):
757
844
  vs = getattr(al, k)
758
845
  if vs:
759
846
  vs = os.path.expandvars(os.path.expanduser(vs))
copyparty/th_cli.py CHANGED
@@ -1,18 +1,23 @@
1
1
  # coding: utf-8
2
2
  from __future__ import print_function, unicode_literals
3
3
 
4
+ import errno
4
5
  import os
6
+ import stat
5
7
 
6
8
  from .__init__ import TYPE_CHECKING
7
9
  from .authsrv import VFS
8
10
  from .bos import bos
9
11
  from .th_srv import EXTS_AC, HAVE_WEBP, thumb_path
10
- from .util import Cooldown
12
+ from .util import Cooldown, Pebkac
11
13
 
12
14
  if TYPE_CHECKING:
13
15
  from .httpsrv import HttpSrv
14
16
 
15
17
 
18
+ IOERROR = "reading the file was denied by the server os; either due to filesystem permissions, selinux, apparmor, or similar:\n%r"
19
+
20
+
16
21
  class ThumbCli(object):
17
22
  def __init__(self, hsrv ) :
18
23
  self.broker = hsrv.broker
@@ -121,7 +126,7 @@ class ThumbCli(object):
121
126
 
122
127
  tpath = thumb_path(histpath, rem, mtime, fmt, self.fmt_ffa)
123
128
  tpaths = [tpath]
124
- if fmt == "w":
129
+ if fmt[:1] == "w":
125
130
  # also check for jpg (maybe webp is unavailable)
126
131
  tpaths.append(tpath.rsplit(".", 1)[0] + ".jpg")
127
132
 
@@ -154,8 +159,22 @@ class ThumbCli(object):
154
159
  if abort:
155
160
  return None
156
161
 
157
- if not bos.path.getsize(os.path.join(ptop, rem)):
158
- return None
162
+ ap = os.path.join(ptop, rem)
163
+ try:
164
+ st = bos.stat(ap)
165
+ if not st.st_size or not stat.S_ISREG(st.st_mode):
166
+ return None
167
+
168
+ with open(ap, "rb", 4) as f:
169
+ if not f.read(4):
170
+ raise Exception()
171
+ except OSError as ex:
172
+ if ex.errno == errno.ENOENT:
173
+ raise Pebkac(404)
174
+ else:
175
+ raise Pebkac(500, IOERROR % (ex,))
176
+ except Exception as ex:
177
+ raise Pebkac(500, IOERROR % (ex,))
159
178
 
160
179
  x = self.broker.ask("thumbsrv.get", ptop, rem, mtime, fmt)
161
180
  return x.get() # type: ignore
copyparty/th_srv.py CHANGED
@@ -4,6 +4,7 @@ from __future__ import print_function, unicode_literals
4
4
  import hashlib
5
5
  import logging
6
6
  import os
7
+ import re
7
8
  import shutil
8
9
  import subprocess as sp
9
10
  import threading
@@ -46,6 +47,9 @@ HAVE_WEBP = False
46
47
  EXTS_TH = set(["jpg", "webp", "png"])
47
48
  EXTS_AC = set(["opus", "owa", "caf", "mp3"])
48
49
 
50
+ PTN_TS = re.compile("^-?[0-9a-f]{8,10}$")
51
+
52
+
49
53
  try:
50
54
  if os.environ.get("PRTY_NO_PIL"):
51
55
  raise Exception()
@@ -382,8 +386,12 @@ class ThumbSrv(object):
382
386
  self.log(msg, c)
383
387
  if getattr(ex, "returncode", 0) != 321:
384
388
  if fun == funs[-1]:
385
- with open(ttpath, "wb") as _:
386
- pass
389
+ try:
390
+ with open(ttpath, "wb") as _:
391
+ pass
392
+ except Exception as ex:
393
+ t = "failed to create the file [%s]: %r"
394
+ self.log(t % (ttpath, ex), 3)
387
395
  else:
388
396
  # ffmpeg may spawn empty files on windows
389
397
  try:
@@ -396,7 +404,10 @@ class ThumbSrv(object):
396
404
 
397
405
  try:
398
406
  wrename(self.log, ttpath, tpath, vn.flags)
399
- except:
407
+ except Exception as ex:
408
+ if not os.path.exists(tpath):
409
+ t = "failed to move [%s] to [%s]: %r"
410
+ self.log(t % (ttpath, tpath, ex), 3)
400
411
  pass
401
412
 
402
413
  with self.mutex:
@@ -987,6 +998,8 @@ class ThumbSrv(object):
987
998
  # thumb file
988
999
  try:
989
1000
  b64, ts, ext = f.split(".")
1001
+ if len(ts) > 8 and PTN_TS.match(ts):
1002
+ ts = "yeahokay"
990
1003
  if len(b64) != 24 or len(ts) != 8 or ext not in exts:
991
1004
  raise Exception()
992
1005
  except:
copyparty/u2idx.py CHANGED
@@ -128,9 +128,9 @@ class U2idx(object):
128
128
 
129
129
 
130
130
  ptop = vn.realpath
131
- histpath = self.asrv.vfs.histtab.get(ptop)
131
+ histpath = self.asrv.vfs.dbpaths.get(ptop)
132
132
  if not histpath:
133
- self.log("no histpath for %r" % (ptop,))
133
+ self.log("no dbpath for %r" % (ptop,))
134
134
  return None
135
135
 
136
136
  db_path = os.path.join(histpath, "up2k.db")
copyparty/up2k.py CHANGED
@@ -91,7 +91,7 @@ VF_AFFECTS_INDEXING = set(zsg.split(" "))
91
91
 
92
92
  SBUSY = "cannot receive uploads right now;\nserver busy with %s.\nPlease wait; the client will retry..."
93
93
 
94
- HINT_HISTPATH = "you could try moving the database to another location (preferably an SSD or NVME drive) using either the --hist argument (global option for all volumes), or the hist volflag (just for this volume)"
94
+ HINT_HISTPATH = "you could try moving the database to another location (preferably an SSD or NVME drive) using either the --hist argument (global option for all volumes), or the hist volflag (just for this volume), or, if you want to keep the thumbnails in the current location and only move the database itself, then use --dbpath or volflag dbpath"
95
95
 
96
96
 
97
97
  NULLSTAT = os.stat_result((0, -1, -1, 0, 0, 0, 0, 0, 0, 0))
@@ -1089,9 +1089,9 @@ class Up2k(object):
1089
1089
  self, ptop , flags
1090
1090
  ) :
1091
1091
  """mutex(main,reg) me"""
1092
- histpath = self.vfs.histtab.get(ptop)
1092
+ histpath = self.vfs.dbpaths.get(ptop)
1093
1093
  if not histpath:
1094
- self.log("no histpath for %r" % (ptop,))
1094
+ self.log("no dbpath for %r" % (ptop,))
1095
1095
  return None
1096
1096
 
1097
1097
  db_path = os.path.join(histpath, "up2k.db")
@@ -1336,12 +1336,15 @@ class Up2k(object):
1336
1336
  ]
1337
1337
  excl += [absreal(x) for x in excl]
1338
1338
  excl += list(self.vfs.histtab.values())
1339
+ excl += list(self.vfs.dbpaths.values())
1339
1340
  if WINDOWS:
1340
1341
  excl = [x.replace("/", "\\") for x in excl]
1341
1342
  else:
1342
1343
  # ~/.wine/dosdevices/z:/ and such
1343
1344
  excl.extend(("/dev", "/proc", "/run", "/sys"))
1344
1345
 
1346
+ excl = list({k: 1 for k in excl})
1347
+
1345
1348
  if self.args.re_dirsz:
1346
1349
  db.c.execute("delete from ds")
1347
1350
  db.n += 1
@@ -5078,7 +5081,7 @@ class Up2k(object):
5078
5081
 
5079
5082
  def _snap_reg(self, ptop , reg ) :
5080
5083
  now = time.time()
5081
- histpath = self.vfs.histtab.get(ptop)
5084
+ histpath = self.vfs.dbpaths.get(ptop)
5082
5085
  if not histpath:
5083
5086
  return
5084
5087
 
copyparty/util.py CHANGED
@@ -114,8 +114,14 @@ IP6ALL = "0:0:0:0:0:0:0:0"
114
114
 
115
115
 
116
116
  try:
117
- import ctypes
118
117
  import fcntl
118
+
119
+ HAVE_FCNTL = True
120
+ except:
121
+ HAVE_FCNTL = False
122
+
123
+ try:
124
+ import ctypes
119
125
  import termios
120
126
  except:
121
127
  pass
@@ -225,7 +231,7 @@ SYMTIME = PY36 and os.utime in os.supports_follow_symlinks
225
231
  META_NOBOTS = '<meta name="robots" content="noindex, nofollow">\n'
226
232
 
227
233
  # smart enough to understand javascript while also ignoring rel="nofollow"
228
- BAD_BOTS = r"Barkrowler|bingbot|BLEXBot|Googlebot|GPTBot|PetalBot|SeekportBot|SemrushBot|YandexBot"
234
+ BAD_BOTS = r"Barkrowler|bingbot|BLEXBot|Googlebot|GoogleOther|GPTBot|PetalBot|SeekportBot|SemrushBot|YandexBot"
229
235
 
230
236
  FFMPEG_URL = "https://www.gyan.dev/ffmpeg/builds/ffmpeg-git-full.7z"
231
237
 
@@ -1464,6 +1470,12 @@ def vol_san(vols , txt ) :
1464
1470
  txt = txt.replace(bap.replace(b"\\", b"\\\\"), bvp)
1465
1471
  txt = txt.replace(bhp.replace(b"\\", b"\\\\"), bvph)
1466
1472
 
1473
+ if vol.histpath != vol.dbpath:
1474
+ bdp = vol.dbpath.encode("utf-8")
1475
+ bdph = b"$db(/" + bvp + b")"
1476
+ txt = txt.replace(bdp, bdph)
1477
+ txt = txt.replace(bdp.replace(b"\\", b"\\\\"), bdph)
1478
+
1467
1479
  if txt != txt0:
1468
1480
  txt += b"\r\nNOTE: filepaths sanitized; see serverlog for correct values"
1469
1481
 
@@ -3843,8 +3855,74 @@ def hidedir(dp) :
3843
3855
  pass
3844
3856
 
3845
3857
 
3858
+ _flocks = {}
3859
+
3860
+
3861
+ def _lock_file_noop(ap ) :
3862
+ return True
3863
+
3864
+
3865
+ def _lock_file_ioctl(ap ) :
3866
+ try:
3867
+ fd = _flocks.pop(ap)
3868
+ os.close(fd)
3869
+ except:
3870
+ pass
3871
+
3872
+ fd = os.open(ap, os.O_RDWR | os.O_CREAT, 438)
3873
+ # NOTE: the fcntl.lockf identifier is (pid,node);
3874
+ # the lock will be dropped if os.close(os.open(ap))
3875
+ # is performed anywhere else in this thread
3876
+
3877
+ try:
3878
+ fcntl.lockf(fd, fcntl.LOCK_EX | fcntl.LOCK_NB)
3879
+ _flocks[ap] = fd
3880
+ return True
3881
+ except Exception as ex:
3882
+ eno = getattr(ex, "errno", -1)
3883
+ try:
3884
+ os.close(fd)
3885
+ except:
3886
+ pass
3887
+ if eno in (errno.EAGAIN, errno.EACCES):
3888
+ return False
3889
+ print("WARNING: unexpected errno %d from fcntl.lockf; %r" % (eno, ex))
3890
+ return True
3891
+
3892
+
3893
+ def _lock_file_windows(ap ) :
3894
+ try:
3895
+ import msvcrt
3896
+
3897
+ try:
3898
+ fd = _flocks.pop(ap)
3899
+ os.close(fd)
3900
+ except:
3901
+ pass
3902
+
3903
+ fd = os.open(ap, os.O_RDWR | os.O_CREAT, 438)
3904
+ msvcrt.locking(fd, msvcrt.LK_NBLCK, 1)
3905
+ return True
3906
+ except Exception as ex:
3907
+ eno = getattr(ex, "errno", -1)
3908
+ if eno == errno.EACCES:
3909
+ return False
3910
+ print("WARNING: unexpected errno %d from msvcrt.locking; %r" % (eno, ex))
3911
+ return True
3912
+
3913
+
3914
+ if os.environ.get("PRTY_NO_DB_LOCK"):
3915
+ lock_file = _lock_file_noop
3916
+ elif ANYWIN:
3917
+ lock_file = _lock_file_windows
3918
+ elif HAVE_FCNTL:
3919
+ lock_file = _lock_file_ioctl
3920
+ else:
3921
+ lock_file = _lock_file_noop
3922
+
3923
+
3846
3924
  try:
3847
- if sys.version_info < (3, 10):
3925
+ if sys.version_info < (3, 10) or os.environ.get("PRTY_NO_IMPRESO"):
3848
3926
  # py3.8 doesn't have .files
3849
3927
  # py3.9 has broken .is_file
3850
3928
  raise ImportError()
Binary file
Binary file
Binary file
copyparty/web/ui.css.gz CHANGED
Binary file
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: copyparty
3
- Version: 1.16.18
3
+ Version: 1.16.19
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
@@ -339,6 +339,8 @@ small collection of user feedback
339
339
 
340
340
  `good enough`, `surprisingly correct`, `certified good software`, `just works`, `why`, `wow this is better than nextcloud`
341
341
 
342
+ * UI просто ужасно. Если буду описывать детально не смогу удержаться в рамках приличий
343
+
342
344
 
343
345
  # motivations
344
346
 
@@ -387,7 +389,8 @@ roughly sorted by chance of encounter
387
389
  * `--th-ff-jpg` may fix video thumbnails on some FFmpeg versions (macos, some linux)
388
390
  * `--th-ff-swr` may fix audio thumbnails on some FFmpeg versions
389
391
  * if the `up2k.db` (filesystem index) is on a samba-share or network disk, you'll get unpredictable behavior if the share is disconnected for a bit
390
- * use `--hist` or the `hist` volflag (`-v [...]:c,hist=/tmp/foo`) to place the db on a local disk instead
392
+ * use `--hist` or the `hist` volflag (`-v [...]:c,hist=/tmp/foo`) to place the db and thumbnails on a local disk instead
393
+ * or, if you only want to move the db (and not the thumbnails), then use `--dbpath` or the `dbpath` volflag
391
394
  * all volumes must exist / be available on startup; up2k (mtp especially) gets funky otherwise
392
395
  * probably more, pls let me know
393
396
 
@@ -440,7 +443,8 @@ same order here too
440
443
  * this is an msys2 bug, the regular windows edition of python is fine
441
444
 
442
445
  * VirtualBox: sqlite throws `Disk I/O Error` when running in a VM and the up2k database is in a vboxsf
443
- * use `--hist` or the `hist` volflag (`-v [...]:c,hist=/tmp/foo`) to place the db inside the vm instead
446
+ * use `--hist` or the `hist` volflag (`-v [...]:c,hist=/tmp/foo`) to place the db and thumbnails inside the vm instead
447
+ * or, if you only want to move the db (and not the thumbnails), then use `--dbpath` or the `dbpath` volflag
444
448
  * also happens on mergerfs, so put the db elsewhere
445
449
 
446
450
  * Ubuntu: dragging files from certain folders into firefox or chrome is impossible
@@ -862,6 +866,8 @@ if you are resuming a massive upload and want to skip hashing the files which al
862
866
 
863
867
  if the server is behind a proxy which imposes a request-size limit, you can configure up2k to sneak below the limit with server-option `--u2sz` (the default is 96 MiB to support Cloudflare)
864
868
 
869
+ if you want to replace existing files on the server with new uploads by default, run with `--u2ow 2` (only works if users have the delete-permission, and can still be disabled with `🛡️` in the UI)
870
+
865
871
 
866
872
  ### file-search
867
873
 
@@ -1650,6 +1656,8 @@ copyparty creates a subfolder named `.hist` inside each volume where it stores t
1650
1656
  this can instead be kept in a single place using the `--hist` argument, or the `hist=` volflag, or a mix of both:
1651
1657
  * `--hist ~/.cache/copyparty -v ~/music::r:c,hist=-` sets `~/.cache/copyparty` as the default place to put volume info, but `~/music` gets the regular `.hist` subfolder (`-` restores default behavior)
1652
1658
 
1659
+ by default, the per-volume `up2k.db` sqlite3-database for `-e2d` and `-e2t` is stored next to the thumbnails according to the `--hist` option, but the global-option `--dbpath` and/or volflag `dbpath` can be used to put the database somewhere else
1660
+
1653
1661
  note:
1654
1662
  * putting the hist-folders on an SSD is strongly recommended for performance
1655
1663
  * markdown edits are always stored in a local `.hist` subdirectory
@@ -2185,7 +2193,9 @@ buggy feature? rip it out by setting any of the following environment variables
2185
2193
 
2186
2194
  | env-var | what it does |
2187
2195
  | -------------------- | ------------ |
2196
+ | `PRTY_NO_DB_LOCK` | do not lock session/shares-databases for exclusive access |
2188
2197
  | `PRTY_NO_IFADDR` | disable ip/nic discovery by poking into your OS with ctypes |
2198
+ | `PRTY_NO_IMPRESO` | do not try to load js/css files using `importlib.resources` |
2189
2199
  | `PRTY_NO_IPV6` | disable some ipv6 support (should not be necessary since windows 2000) |
2190
2200
  | `PRTY_NO_LZMA` | disable streaming xz compression of incoming uploads |
2191
2201
  | `PRTY_NO_MP` | disable all use of the python `multiprocessing` module (actual multithreading, cpu-count for parsers/thumbnailers) |
@@ -2503,6 +2513,8 @@ below are some tweaks roughly ordered by usefulness:
2503
2513
  * `--no-hash .` when indexing a network-disk if you don't care about the actual filehashes and only want the names/tags searchable
2504
2514
  * if your volumes are on a network-disk such as NFS / SMB / s3, specifying larger values for `--iobuf` and/or `--s-rd-sz` and/or `--s-wr-sz` may help; try setting all of them to `524288` or `1048576` or `4194304`
2505
2515
  * `--no-htp --hash-mt=0 --mtag-mt=1 --th-mt=1` minimizes the number of threads; can help in some eccentric environments (like the vscode debugger)
2516
+ * when running on AlpineLinux or other musl-based distro, try mimalloc for higher performance (and twice as much RAM usage); `apk add mimalloc2` and run copyparty with env-var `LD_PRELOAD=/usr/lib/libmimalloc-secure.so.2`
2517
+ * note that mimalloc requires special care when combined with prisonparty and/or bubbleparty/bubblewrap; you must give it access to `/proc` and `/sys` otherwise you'll encounter issues with FFmpeg (audio transcoding, thumbnails)
2506
2518
  * `-j0` enables multiprocessing (actual multithreading), can reduce latency to `20+80/numCores` percent and generally improve performance in cpu-intensive workloads, for example:
2507
2519
  * lots of connections (many users or heavy clients)
2508
2520
  * simultaneous downloads and uploads saturating a 20gbps connection
@@ -1,38 +1,38 @@
1
- copyparty/__init__.py,sha256=VR6ZZhB9IxaK5TDXDTBM_OIP5ydkrdbaEnstktLM__s,2649
2
- copyparty/__main__.py,sha256=uViBDvQpkCCi0qjolJD80zDc8loFhhEHjYwhH0PGP-E,118299
3
- copyparty/__version__.py,sha256=78j3OrDBpMZNsMwQSRDHMqr_rVUvNjxpxYHjQjQPJuE,252
4
- copyparty/authsrv.py,sha256=nZ27CC3HxwVbbCLPMEO9v6NZV7pLYP3euwrQ9qwT3gg,111163
1
+ copyparty/__init__.py,sha256=TnFSStmHlwlRIClWW8jSHxZpt3dl_kN6_pEnqBqh3mE,2638
2
+ copyparty/__main__.py,sha256=RuEnJf0a2d7ZbV2bOBj43iNkdBIG_zDUh0DbVUgsc50,119139
3
+ copyparty/__version__.py,sha256=M7ZuH7-ojW4ae8FrXsuVGdN6xOoyyx4SciZROZSQbUg,251
4
+ copyparty/authsrv.py,sha256=S4HrUq-C6wpe_IT3KN75yo9GJw9Uzj5y558ooYCQqng,113286
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=921wcbj05-dLLM9AWDavqFfcp4-aVRUlFlwir44W_Eo,14106
10
+ copyparty/cfg.py,sha256=EilhOIMLopgvn5j72MaRFS0q78K9Xf0NU0I7EAs3YAQ,14269
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=NeHP9wqO998OJ3zKzAMZQ50rMaq6eXBC7M0fvRs3BDc,221105
14
+ copyparty/httpcli.py,sha256=pRZtGqx13FDCKpjxc4vlnqEuqVFLaXX7Ylx029bkCIw,222050
15
15
  copyparty/httpconn.py,sha256=mQSgljh0Q-jyWjF4tQLrHbRKRe9WKl19kGqsGMsJpWo,6880
16
16
  copyparty/httpsrv.py,sha256=pxH_Eh8ElBLvOEDejgpP9Bvk65HNEou-03aYIcgXhrs,18090
17
- copyparty/ico.py,sha256=eWSxEae4wOCfheHl-m-wchYvFRAR_97kJDb4NGaB-Z8,3561
17
+ copyparty/ico.py,sha256=-7QjF_jIxnPo4Vr0oUPksQ_U_Ef0HRsSPm3s71idOz8,3879
18
18
  copyparty/mdns.py,sha256=G73OWWg1copda47LgayCRK7qjVrk6cnUGpMR5ugmi7o,18315
19
19
  copyparty/metrics.py,sha256=1dim0ShnsD5cfspRbeN9Mt14wOIxPRtxCZY2IScikKw,8974
20
20
  copyparty/mtag.py,sha256=7lINRFc1Vrc-ebP2hU3dBHH3BRFOnEmEZ0jzlpYb8Jg,19910
21
21
  copyparty/multicast.py,sha256=Me4XEEJijvvK2lMRwmGU2hsaI5_E9AEpCjIC4b9UefA,12393
22
- copyparty/pwhash.py,sha256=X87RWeay8IhzGVZLDKE5LctF9YVUcYxPAJ1BX6r_9CU,4248
22
+ 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
26
  copyparty/sutil.py,sha256=6zEEGl4hRe6bTB83Y_RtnBGxr2JcUa__GdiAMqNJZnY,3208
27
- copyparty/svchub.py,sha256=-4GjPLFytUdALrfaAvsQHm_HKYoQkteTXJFKxrF3Q1Y,41574
27
+ copyparty/svchub.py,sha256=A2yxlg9hzilOypMKPlZOn80YzJ3A6W5pHWWLSAvji0I,45782
28
28
  copyparty/szip.py,sha256=7_RVwHCGk7ON79sAB5HAIUbTEbjDDtn99141aGaI_MA,8596
29
29
  copyparty/tcpsrv.py,sha256=L8jsmFhs6GdJYA58gnsUM1RDakFzHSQGlDcsaeTq-8w,20472
30
30
  copyparty/tftpd.py,sha256=HAXNbIM7I3yFng_a4ubLWGQ4trRTineAZsUPTZDWNQs,14001
31
- copyparty/th_cli.py,sha256=PxDAmUvO_8Vm5edXiWtsCft0Fw69QL9rCHf9zLmUNeA,4800
32
- copyparty/th_srv.py,sha256=tHbh_Ve3v8tYclWH2thLs5oFufeXgJi1duUMveKIx9k,30725
33
- copyparty/u2idx.py,sha256=G6MDbD4I_sJSOwaNFZ6XLTQhnEDrB12pVKuKhzQ_leE,13676
34
- copyparty/up2k.py,sha256=RRC58ac9IpK0CyBJIs0Ri1Fk0IsGlQRp1bvB-agDQko,177450
35
- copyparty/util.py,sha256=wjBrdCEBlPhCL3N9Ls4dRKIjYpDHK7W1DzPpLEIvgjQ,101114
31
+ copyparty/th_cli.py,sha256=IEX5tCb0gw9Z2aRIDL9bwdvJ6g5jhWZ8OEAAz16_xN4,5426
32
+ copyparty/th_srv.py,sha256=-V4eNfAYzl-NkJrAjLGqU1cS1dUDS9QLK3iYiXoUz1U,31277
33
+ copyparty/u2idx.py,sha256=4Y5OOPyVkc-pS0z6e3p4StXAMnjHobSOMmMsvNUTD34,13674
34
+ copyparty/up2k.py,sha256=0Fj2yT2XnJ4MhwdEcDRs1T-gsdxVhdIxdaZSjGyGKBA,177682
35
+ copyparty/util.py,sha256=qkPD9LFPgLhpmCic_IBvutS2extujDiRrG75PnLMY90,102987
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=_HiFW5vPUusWadoqdY8ZihuWizY9UECAc5nIamBPRi4,11654
58
+ copyparty/web/browser.css.gz,sha256=kPN35ttg9KAACfAB-aZnxFX1GQGqRnK0cLCZXBAu_lc,11654
59
59
  copyparty/web/browser.html,sha256=auvhLVE_t0aIN0q-nk0zOWFqITgDhroMAAviBNLoFfc,4788
60
- copyparty/web/browser.js.gz,sha256=48fDqIqQKCkrH0VsYVj03sDOx9gEZ-DfiHDaqEUuyr0,92341
60
+ copyparty/web/browser.js.gz,sha256=zxztzuGQuEr4SFTy_i_OWLJFe2ZnGWqz7hx0QeXPw8Y,92425
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
@@ -82,7 +82,7 @@ copyparty/web/splash.html,sha256=QEnTH9QZXFmAuyVtgqOuuHKBtIdi7uclpRqe0ZMewj4,624
82
82
  copyparty/web/splash.js.gz,sha256=4VqNznN10-bT33IJm3VWzBEJ1s08XZyxFB1TYPUkuAo,2739
83
83
  copyparty/web/svcs.html,sha256=dnE1fG15zOpq7u0GYt8ij6BUv_LTwsiipFeneVYlMsM,14140
84
84
  copyparty/web/svcs.js.gz,sha256=lMXEP9W-VlXyANlva4q0ASSxvvHYlE2CrmxGgZXZop0,713
85
- copyparty/web/ui.css.gz,sha256=0sHIwGsL3_xH8Uu6N0Ag3bKBTjf-e_yfFbKynEZXAnk,2800
85
+ copyparty/web/ui.css.gz,sha256=iDjrmq32aDN6l2S5AjCQdKjD6bxmzP6ji2WjM1FjKiU,2819
86
86
  copyparty/web/up2k.js.gz,sha256=VQVWBXK2gEz1b8if_ujXHNHnfBO7cdrKoSjqX397VUI,24519
87
87
  copyparty/web/util.js.gz,sha256=rD9iLfVLKRhxC8hmakal-s18xN_rs6GuOqyRPii6HQ8,15110
88
88
  copyparty/web/w.hash.js.gz,sha256=JhJagnqIkcKng_hs6otEgzcuQE7keToG_r5dd2o3EfU,1108
@@ -100,7 +100,7 @@ copyparty/web/deps/busy.mp3.gz,sha256=EVphk1_HYyRKJmtpeK99vbAstF7ub1f9ndu020H8PQ
100
100
  copyparty/web/deps/easymde.css.gz,sha256=vWxfueI64rPikuqFj69wJBtGisqf93AheQtOZqgUI_c,3041
101
101
  copyparty/web/deps/easymde.js.gz,sha256=rHBs4XWQe2bmv7ZzDIk43oxnTwrwpq5laYHhV5sKQQo,77014
102
102
  copyparty/web/deps/fuse.py,sha256=6j4Zy3VpQg629pwwIW77v2LJ1hy-qlyrxwhXfKl9B7I,33426
103
- copyparty/web/deps/marked.js.gz,sha256=M6FwmQujGDX33fpo32JZRbxqtSdPzmh7WcaHK6SpZlc,22650
103
+ copyparty/web/deps/marked.js.gz,sha256=6GrdpSikQ-tt9GrcAl7wA7mp_Il_2T2ZvBeACVX1j1k,22665
104
104
  copyparty/web/deps/mini-fa.css.gz,sha256=CTPrNaH8OTVmxajrGP88E2MkjadY9_81TBVnd9sw9Y8,572
105
105
  copyparty/web/deps/mini-fa.woff,sha256=L9DNncV2TIyvsrspMbJouvnnt7F068Hbn7YZYvN76AU,2784
106
106
  copyparty/web/deps/prism.css.gz,sha256=Z_A6rJ3MN5KWnjvXaV787aTW_5DT-xjFd0YZ7_W-Krk,1468
@@ -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.18.dist-info/licenses/LICENSE,sha256=gOr4h33pCsBEg9uIy9AYmb7qlocL4V9t2uPJS5wllr0,1072
113
- copyparty-1.16.18.dist-info/METADATA,sha256=VyOufoEysGMjSNtqLmv-7SFthPGqw-tk_G0Vjt2aBUs,158674
114
- copyparty-1.16.18.dist-info/WHEEL,sha256=1tXe9gY0PYatrMPMDd6jXqjfpz_B-Wqm32CPfRC58XU,91
115
- copyparty-1.16.18.dist-info/entry_points.txt,sha256=4zw6a3rqASywQomiYLObjjlxybaI65LYYOTJwgKz7b0,128
116
- copyparty-1.16.18.dist-info/top_level.txt,sha256=LnYUPsDyk-8kFgM6YJLG4h820DQekn81cObKSu9g-sI,10
117
- copyparty-1.16.18.dist-info/RECORD,,
112
+ copyparty-1.16.19.dist-info/licenses/LICENSE,sha256=gOr4h33pCsBEg9uIy9AYmb7qlocL4V9t2uPJS5wllr0,1072
113
+ copyparty-1.16.19.dist-info/METADATA,sha256=-QmQvp2dZJ8RVVHlm6Pf4B6WUpiNJNnBUXCwmyMExAI,160161
114
+ copyparty-1.16.19.dist-info/WHEEL,sha256=CmyFI0kx5cdEMTLiONQRbGQwjIoR1aIYB7eCAQ4KPJ0,91
115
+ copyparty-1.16.19.dist-info/entry_points.txt,sha256=4zw6a3rqASywQomiYLObjjlxybaI65LYYOTJwgKz7b0,128
116
+ copyparty-1.16.19.dist-info/top_level.txt,sha256=LnYUPsDyk-8kFgM6YJLG4h820DQekn81cObKSu9g-sI,10
117
+ copyparty-1.16.19.dist-info/RECORD,,
@@ -1,5 +1,5 @@
1
1
  Wheel-Version: 1.0
2
- Generator: setuptools (77.0.3)
2
+ Generator: setuptools (78.1.0)
3
3
  Root-Is-Purelib: true
4
4
  Tag: py3-none-any
5
5