copyparty 1.15.10__py3-none-any.whl → 1.16.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/__init__.py CHANGED
@@ -77,6 +77,7 @@ web/deps/prismd.css
77
77
  web/deps/scp.woff2
78
78
  web/deps/sha512.ac.js
79
79
  web/deps/sha512.hw.js
80
+ web/iiam.gif
80
81
  web/md.css
81
82
  web/md.html
82
83
  web/md.js
copyparty/__main__.py CHANGED
@@ -50,6 +50,8 @@ from .util import (
50
50
  PARTFTPY_VER,
51
51
  PY_DESC,
52
52
  PYFTPD_VER,
53
+ RAM_AVAIL,
54
+ RAM_TOTAL,
53
55
  SQLITE_VER,
54
56
  UNPLICATIONS,
55
57
  Daemon,
@@ -676,6 +678,8 @@ def get_sects():
676
678
  \033[36mxbu\033[35m executes CMD before a file upload starts
677
679
  \033[36mxau\033[35m executes CMD after a file upload finishes
678
680
  \033[36mxiu\033[35m executes CMD after all uploads finish and volume is idle
681
+ \033[36mxbc\033[35m executes CMD before a file copy
682
+ \033[36mxac\033[35m executes CMD after a file copy
679
683
  \033[36mxbr\033[35m executes CMD before a file rename/move
680
684
  \033[36mxar\033[35m executes CMD after a file rename/move
681
685
  \033[36mxbd\033[35m executes CMD before a file delete
@@ -866,8 +870,9 @@ def get_sects():
866
870
  use argon2id with timecost 3, 256 MiB, 4 threads, version 19 (0x13/v1.3)
867
871
 
868
872
  \033[36m--ah-alg scrypt\033[0m # which is the same as:
869
- \033[36m--ah-alg scrypt,13,2,8,4\033[0m
870
- use scrypt with cost 2**13, 2 iterations, blocksize 8, 4 threads
873
+ \033[36m--ah-alg scrypt,13,2,8,4,32\033[0m
874
+ use scrypt with cost 2**13, 2 iterations, blocksize 8, 4 threads,
875
+ and allow using up to 32 MiB RAM (ram=cost*blksz roughly)
871
876
 
872
877
  \033[36m--ah-alg sha2\033[0m # which is the same as:
873
878
  \033[36m--ah-alg sha2,424242\033[0m
@@ -1193,6 +1198,8 @@ def add_hooks(ap):
1193
1198
  ap2.add_argument("--xbu", metavar="CMD", type=u, action="append", help="execute \033[33mCMD\033[0m before a file upload starts")
1194
1199
  ap2.add_argument("--xau", metavar="CMD", type=u, action="append", help="execute \033[33mCMD\033[0m after a file upload finishes")
1195
1200
  ap2.add_argument("--xiu", metavar="CMD", type=u, action="append", help="execute \033[33mCMD\033[0m after all uploads finish and volume is idle")
1201
+ ap2.add_argument("--xbc", metavar="CMD", type=u, action="append", help="execute \033[33mCMD\033[0m before a file copy")
1202
+ ap2.add_argument("--xac", metavar="CMD", type=u, action="append", help="execute \033[33mCMD\033[0m after a file copy")
1196
1203
  ap2.add_argument("--xbr", metavar="CMD", type=u, action="append", help="execute \033[33mCMD\033[0m before a file move/rename")
1197
1204
  ap2.add_argument("--xar", metavar="CMD", type=u, action="append", help="execute \033[33mCMD\033[0m after a file move/rename")
1198
1205
  ap2.add_argument("--xbd", metavar="CMD", type=u, action="append", help="execute \033[33mCMD\033[0m before a file delete")
@@ -1225,6 +1232,7 @@ def add_optouts(ap):
1225
1232
  ap2.add_argument("--no-dav", action="store_true", help="disable webdav support")
1226
1233
  ap2.add_argument("--no-del", action="store_true", help="disable delete operations")
1227
1234
  ap2.add_argument("--no-mv", action="store_true", help="disable move/rename operations")
1235
+ ap2.add_argument("--no-cp", action="store_true", help="disable copy operations")
1228
1236
  ap2.add_argument("-nth", action="store_true", help="no title hostname; don't show \033[33m--name\033[0m in <title>")
1229
1237
  ap2.add_argument("-nih", action="store_true", help="no info hostname -- don't show in UI")
1230
1238
  ap2.add_argument("-nid", action="store_true", help="no info disk-usage -- don't show in UI")
@@ -1308,9 +1316,12 @@ def add_admin(ap):
1308
1316
  ap2.add_argument("--no-reload", action="store_true", help="disable ?reload=cfg (reload users/volumes/volflags from config file)")
1309
1317
  ap2.add_argument("--no-rescan", action="store_true", help="disable ?scan (volume reindexing)")
1310
1318
  ap2.add_argument("--no-stack", action="store_true", help="disable ?stack (list all stacks)")
1319
+ ap2.add_argument("--dl-list", metavar="LVL", type=int, default=2, help="who can see active downloads in the controlpanel? [\033[32m0\033[0m]=nobody, [\033[32m1\033[0m]=admins, [\033[32m2\033[0m]=everyone")
1311
1320
 
1312
1321
 
1313
1322
  def add_thumbnail(ap):
1323
+ th_ram = (RAM_AVAIL or RAM_TOTAL or 9) * 0.6
1324
+ th_ram = int(max(min(th_ram, 6), 1) * 10) / 10
1314
1325
  ap2 = ap.add_argument_group('thumbnail options')
1315
1326
  ap2.add_argument("--no-thumb", action="store_true", help="disable all thumbnails (volflag=dthumb)")
1316
1327
  ap2.add_argument("--no-vthumb", action="store_true", help="disable video thumbnails (volflag=dvthumb)")
@@ -1318,7 +1329,7 @@ def add_thumbnail(ap):
1318
1329
  ap2.add_argument("--th-size", metavar="WxH", default="320x256", help="thumbnail res (volflag=thsize)")
1319
1330
  ap2.add_argument("--th-mt", metavar="CORES", type=int, default=CORES, help="num cpu cores to use for generating thumbnails")
1320
1331
  ap2.add_argument("--th-convt", metavar="SEC", type=float, default=60.0, help="conversion timeout in seconds (volflag=convt)")
1321
- ap2.add_argument("--th-ram-max", metavar="GB", type=float, default=6.0, help="max memory usage (GiB) permitted by thumbnailer; not very accurate")
1332
+ ap2.add_argument("--th-ram-max", metavar="GB", type=float, default=th_ram, help="max memory usage (GiB) permitted by thumbnailer; not very accurate")
1322
1333
  ap2.add_argument("--th-crop", metavar="TXT", type=u, default="y", help="crop thumbnails to 4:3 or keep dynamic height; client can override in UI unless force. [\033[32my\033[0m]=crop, [\033[32mn\033[0m]=nocrop, [\033[32mfy\033[0m]=force-y, [\033[32mfn\033[0m]=force-n (volflag=crop)")
1323
1334
  ap2.add_argument("--th-x3", metavar="TXT", type=u, default="n", help="show thumbs at 3x resolution; client can override in UI unless force. [\033[32my\033[0m]=yes, [\033[32mn\033[0m]=no, [\033[32mfy\033[0m]=force-yes, [\033[32mfn\033[0m]=force-no (volflag=th3x)")
1324
1335
  ap2.add_argument("--th-dec", metavar="LIBS", default="vips,pil,ff", help="image decoders, in order of preference")
@@ -1333,12 +1344,12 @@ def add_thumbnail(ap):
1333
1344
  # https://pillow.readthedocs.io/en/stable/handbook/image-file-formats.html
1334
1345
  # https://github.com/libvips/libvips
1335
1346
  # ffmpeg -hide_banner -demuxers | awk '/^ D /{print$2}' | while IFS= read -r x; do ffmpeg -hide_banner -h demuxer=$x; done | grep -E '^Demuxer |extensions:'
1336
- ap2.add_argument("--th-r-pil", metavar="T,T", type=u, default="avif,avifs,blp,bmp,dcx,dds,dib,emf,eps,fits,flc,fli,fpx,gif,heic,heics,heif,heifs,icns,ico,im,j2p,j2k,jp2,jpeg,jpg,jpx,pbm,pcx,pgm,png,pnm,ppm,psd,qoi,sgi,spi,tga,tif,tiff,webp,wmf,xbm,xpm", help="image formats to decode using pillow")
1347
+ ap2.add_argument("--th-r-pil", metavar="T,T", type=u, default="avif,avifs,blp,bmp,cbz,dcx,dds,dib,emf,eps,fits,flc,fli,fpx,gif,heic,heics,heif,heifs,icns,ico,im,j2p,j2k,jp2,jpeg,jpg,jpx,pbm,pcx,pgm,png,pnm,ppm,psd,qoi,sgi,spi,tga,tif,tiff,webp,wmf,xbm,xpm", help="image formats to decode using pillow")
1337
1348
  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")
1338
- ap2.add_argument("--th-r-ffi", metavar="T,T", type=u, default="apng,avif,avifs,bmp,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")
1349
+ 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")
1339
1350
  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")
1340
1351
  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")
1341
- 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", help="audio formats to decompress before passing to ffmpeg")
1352
+ 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")
1342
1353
 
1343
1354
 
1344
1355
  def add_transcoding(ap):
copyparty/__version__.py CHANGED
@@ -1,8 +1,8 @@
1
1
  # coding: utf-8
2
2
 
3
- VERSION = (1, 15, 10)
4
- CODENAME = "fill the drives"
5
- BUILD_DT = (2024, 10, 26)
3
+ VERSION = (1, 16, 1)
4
+ CODENAME = "COPYparty"
5
+ BUILD_DT = (2024, 11, 15)
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
@@ -360,18 +360,19 @@ class VFS(object):
360
360
  self.ahtml = {}
361
361
  self.aadmin = {}
362
362
  self.adot = {}
363
- self.all_vols = {}
364
363
 
365
364
  if realpath:
366
365
  rp = realpath + ("" if realpath.endswith(os.sep) else os.sep)
367
366
  vp = vpath + ("/" if vpath else "")
368
367
  self.histpath = os.path.join(realpath, ".hist") # db / thumbcache
369
368
  self.all_vols = {vpath: self} # flattened recursive
369
+ self.all_nodes = {vpath: self} # also jumpvols
370
370
  self.all_aps = [(rp, self)]
371
371
  self.all_vps = [(vp, self)]
372
372
  else:
373
373
  self.histpath = ""
374
374
  self.all_vols = {}
375
+ self.all_nodes = {}
375
376
  self.all_aps = []
376
377
  self.all_vps = []
377
378
 
@@ -389,9 +390,11 @@ class VFS(object):
389
390
  def get_all_vols(
390
391
  self,
391
392
  vols ,
393
+ nodes ,
392
394
  aps ,
393
395
  vps ,
394
396
  ) :
397
+ nodes[self.vpath] = self
395
398
  if self.realpath:
396
399
  vols[self.vpath] = self
397
400
  rp = self.realpath
@@ -401,7 +404,7 @@ class VFS(object):
401
404
  vps.append((vp, self))
402
405
 
403
406
  for v in self.nodes.values():
404
- v.get_all_vols(vols, aps, vps)
407
+ v.get_all_vols(vols, nodes, aps, vps)
405
408
 
406
409
  def add(self, src , dst ) :
407
410
  """get existing, or add new path to the vfs"""
@@ -584,10 +587,11 @@ class VFS(object):
584
587
  scandir ,
585
588
  permsets ,
586
589
  lstat = False,
590
+ throw = False,
587
591
  ) :
588
592
  """replaces _ls for certain shares (single-file, or file selection)"""
589
593
  vn, rem = self.shr_src # type: ignore
590
- abspath, real, _ = vn.ls(rem, "\n", scandir, permsets, lstat)
594
+ abspath, real, _ = vn.ls(rem, "\n", scandir, permsets, lstat, throw)
591
595
  real = [x for x in real if os.path.basename(x[0]) in self.shr_files]
592
596
  return abspath, real, {}
593
597
 
@@ -598,11 +602,12 @@ class VFS(object):
598
602
  scandir ,
599
603
  permsets ,
600
604
  lstat = False,
605
+ throw = False,
601
606
  ) :
602
607
  """return user-readable [fsdir,real,virt] items at vpath"""
603
608
  virt_vis = {} # nodes readable by user
604
609
  abspath = self.canonical(rem)
605
- real = list(statdir(self.log, scandir, lstat, abspath))
610
+ real = list(statdir(self.log, scandir, lstat, abspath, throw))
606
611
  real.sort()
607
612
  if not rem:
608
613
  # no vfs nodes in the list of real inodes
@@ -664,6 +669,10 @@ class VFS(object):
664
669
  """
665
670
  recursively yields from ./rem;
666
671
  rel is a unix-style user-defined vpath (not vfs-related)
672
+
673
+ NOTE: don't invoke this function from a dbv; subvols are only
674
+ descended into if rem is blank due to the _ls `if not rem:`
675
+ which intention is to prevent unintended access to subvols
667
676
  """
668
677
 
669
678
  fsroot, vfs_ls, vfs_virt = self.ls(rem, uname, scandir, permsets, lstat=lstat)
@@ -904,7 +913,7 @@ class AuthSrv(object):
904
913
  self._reload()
905
914
  return True
906
915
 
907
- broker.ask("_reload_blocking", False).get()
916
+ broker.ask("reload", False, True).get()
908
917
  return True
909
918
 
910
919
  def _map_volume_idp(
@@ -1374,7 +1383,7 @@ class AuthSrv(object):
1374
1383
  flags[name] = True
1375
1384
  return
1376
1385
 
1377
- zs = "mtp on403 on404 xbu xau xiu xbr xar xbd xad xm xban"
1386
+ zs = "mtp on403 on404 xbu xau xiu xbc xac xbr xar xbd xad xm xban"
1378
1387
  if name not in zs.split():
1379
1388
  if value is True:
1380
1389
  t = "└─add volflag [{}] = {} ({})"
@@ -1522,10 +1531,11 @@ class AuthSrv(object):
1522
1531
 
1523
1532
  assert vfs # type: ignore
1524
1533
  vfs.all_vols = {}
1534
+ vfs.all_nodes = {}
1525
1535
  vfs.all_aps = []
1526
1536
  vfs.all_vps = []
1527
- vfs.get_all_vols(vfs.all_vols, vfs.all_aps, vfs.all_vps)
1528
- for vol in vfs.all_vols.values():
1537
+ vfs.get_all_vols(vfs.all_vols, vfs.all_nodes, vfs.all_aps, vfs.all_vps)
1538
+ for vol in vfs.all_nodes.values():
1529
1539
  vol.all_aps.sort(key=lambda x: len(x[0]), reverse=True)
1530
1540
  vol.all_vps.sort(key=lambda x: len(x[0]), reverse=True)
1531
1541
  vol.root = vfs
@@ -1576,7 +1586,7 @@ class AuthSrv(object):
1576
1586
 
1577
1587
  vfs.nodes[shr] = vfs.all_vols[shr] = shv
1578
1588
  for vol in shv.nodes.values():
1579
- vfs.all_vols[vol.vpath] = vol
1589
+ vfs.all_vols[vol.vpath] = vfs.all_nodes[vol.vpath] = vol
1580
1590
  vol.get_dbv = vol._get_share_src
1581
1591
  vol.ls = vol._ls_nope
1582
1592
 
@@ -1719,7 +1729,19 @@ class AuthSrv(object):
1719
1729
 
1720
1730
  self.log("\n\n".join(ta) + "\n", c=3)
1721
1731
 
1722
- vfs.histtab = {zv.realpath: zv.histpath for zv in vfs.all_vols.values()}
1732
+ rhisttab = {}
1733
+ vfs.histtab = {}
1734
+ for zv in vfs.all_vols.values():
1735
+ histp = zv.histpath
1736
+ is_shr = shr and zv.vpath.split("/")[0] == shr
1737
+ if histp and not is_shr and histp in rhisttab:
1738
+ zv2 = rhisttab[histp]
1739
+ t = "invalid config; multiple volumes share the same histpath (database location):\n histpath: %s\n volume 1: /%s [%s]\n volume 2: %s [%s]"
1740
+ t = t % (histp, zv2.vpath, zv2.realpath, zv.vpath, zv.realpath)
1741
+ self.log(t, 1)
1742
+ raise Exception(t)
1743
+ rhisttab[histp] = zv
1744
+ vfs.histtab[zv.realpath] = histp
1723
1745
 
1724
1746
  for vol in vfs.all_vols.values():
1725
1747
  lim = Lim(self.log_func)
@@ -1778,12 +1800,12 @@ class AuthSrv(object):
1778
1800
  vol.lim = lim
1779
1801
 
1780
1802
  if self.args.no_robots:
1781
- for vol in vfs.all_vols.values():
1803
+ for vol in vfs.all_nodes.values():
1782
1804
  # volflag "robots" overrides global "norobots", allowing indexing by search engines for this vol
1783
1805
  if not vol.flags.get("robots"):
1784
1806
  vol.flags["norobots"] = True
1785
1807
 
1786
- for vol in vfs.all_vols.values():
1808
+ for vol in vfs.all_nodes.values():
1787
1809
  if self.args.no_vthumb:
1788
1810
  vol.flags["dvthumb"] = True
1789
1811
  if self.args.no_athumb:
@@ -1795,7 +1817,7 @@ class AuthSrv(object):
1795
1817
  vol.flags["dithumb"] = True
1796
1818
 
1797
1819
  have_fk = False
1798
- for vol in vfs.all_vols.values():
1820
+ for vol in vfs.all_nodes.values():
1799
1821
  fk = vol.flags.get("fk")
1800
1822
  fka = vol.flags.get("fka")
1801
1823
  if fka and not fk:
@@ -1827,7 +1849,7 @@ class AuthSrv(object):
1827
1849
  zs = os.path.join(E.cfg, "fk-salt.txt")
1828
1850
  self.log(t % (fk_len, 16, zs), 3)
1829
1851
 
1830
- for vol in vfs.all_vols.values():
1852
+ for vol in vfs.all_nodes.values():
1831
1853
  if "pk" in vol.flags and "gz" not in vol.flags and "xz" not in vol.flags:
1832
1854
  vol.flags["gz"] = False # def.pk
1833
1855
 
@@ -1838,7 +1860,7 @@ class AuthSrv(object):
1838
1860
 
1839
1861
  all_mte = {}
1840
1862
  errors = False
1841
- for vol in vfs.all_vols.values():
1863
+ for vol in vfs.all_nodes.values():
1842
1864
  if (self.args.e2ds and vol.axs.uwrite) or self.args.e2dsa:
1843
1865
  vol.flags["e2ds"] = True
1844
1866
 
@@ -1929,7 +1951,7 @@ class AuthSrv(object):
1929
1951
  vol.flags[k] = odfusion(getattr(self.args, k), vol.flags[k])
1930
1952
 
1931
1953
  # append additive args from argv to volflags
1932
- hooks = "xbu xau xiu xbr xar xbd xad xm xban".split()
1954
+ hooks = "xbu xau xiu xbc xac xbr xar xbd xad xm xban".split()
1933
1955
  for name in "mtp on404 on403".split() + hooks:
1934
1956
  self._read_volflag(vol.flags, name, getattr(self.args, name), True)
1935
1957
 
@@ -2056,7 +2078,7 @@ class AuthSrv(object):
2056
2078
  errors = True
2057
2079
 
2058
2080
  have_daw = False
2059
- for vol in vfs.all_vols.values():
2081
+ for vol in vfs.all_nodes.values():
2060
2082
  daw = vol.flags.get("daw") or self.args.daw
2061
2083
  if daw:
2062
2084
  vol.flags["daw"] = True
@@ -2071,13 +2093,12 @@ class AuthSrv(object):
2071
2093
  self.log("--smb can only be used when --ah-alg is none", 1)
2072
2094
  errors = True
2073
2095
 
2074
- for vol in vfs.all_vols.values():
2096
+ for vol in vfs.all_nodes.values():
2075
2097
  for k in list(vol.flags.keys()):
2076
2098
  if re.match("^-[^-]+$", k):
2077
2099
  vol.flags.pop(k[1:], None)
2078
2100
  vol.flags.pop(k)
2079
2101
 
2080
- for vol in vfs.all_vols.values():
2081
2102
  if vol.flags.get("dots"):
2082
2103
  for name in vol.axs.uread:
2083
2104
  vol.axs.udot.add(name)
@@ -2219,6 +2240,11 @@ class AuthSrv(object):
2219
2240
  for x, y in vfs.all_vols.items()
2220
2241
  if x != shr and not x.startswith(shrs)
2221
2242
  }
2243
+ vfs.all_nodes = {
2244
+ x: y
2245
+ for x, y in vfs.all_nodes.items()
2246
+ if x != shr and not x.startswith(shrs)
2247
+ }
2222
2248
 
2223
2249
  assert db and cur and cur2 and shv # type: ignore
2224
2250
  for row in cur.execute("select * from sh"):
@@ -2380,7 +2406,7 @@ class AuthSrv(object):
2380
2406
  self._reload()
2381
2407
  return True, "new password OK"
2382
2408
 
2383
- broker.ask("_reload_blocking", False, False).get()
2409
+ broker.ask("reload", False, False).get()
2384
2410
  return True, "new password OK"
2385
2411
 
2386
2412
  def setup_chpw(self, acct ) :
@@ -2632,7 +2658,7 @@ class AuthSrv(object):
2632
2658
  ]
2633
2659
 
2634
2660
  csv = set("i p th_covers zm_on zm_off zs_on zs_off".split())
2635
- zs = "c ihead ohead mtm mtp on403 on404 xad xar xau xiu xban xbd xbr xbu xm"
2661
+ zs = "c ihead ohead mtm mtp on403 on404 xac xad xar xau xiu xban xbc xbd xbr xbu xm"
2636
2662
  lst = set(zs.split())
2637
2663
  askip = set("a v c vc cgen exp_lg exp_md theme".split())
2638
2664
  fskip = set("exp_lg exp_md mv_re_r mv_re_t rm_re_r rm_re_t".split())
copyparty/broker_mp.py CHANGED
@@ -39,6 +39,9 @@ class BrokerMp(object):
39
39
  self.procs = []
40
40
  self.mutex = threading.Lock()
41
41
 
42
+ self.retpend = {}
43
+ self.retpend_mutex = threading.Lock()
44
+
42
45
  self.num_workers = self.args.j or CORES
43
46
  self.log("broker", "booting {} subprocesses".format(self.num_workers))
44
47
  for n in range(1, self.num_workers + 1):
@@ -50,6 +53,8 @@ class BrokerMp(object):
50
53
  self.procs.append(proc)
51
54
  proc.start()
52
55
 
56
+ Daemon(self.periodic, "mp-periodic")
57
+
53
58
  def shutdown(self) :
54
59
  self.log("broker", "shutting down")
55
60
  for n, proc in enumerate(self.procs):
@@ -86,8 +91,10 @@ class BrokerMp(object):
86
91
  self.log(*args)
87
92
 
88
93
  elif dest == "retq":
89
- # response from previous ipc call
90
- raise Exception("invalid broker_mp usage")
94
+ with self.retpend_mutex:
95
+ retq = self.retpend.pop(retq_id)
96
+
97
+ retq.put(args[0])
91
98
 
92
99
  else:
93
100
  # new ipc invoking managed service in hub
@@ -105,7 +112,6 @@ class BrokerMp(object):
105
112
  proc.q_pend.put((retq_id, "retq", rv))
106
113
 
107
114
  def ask(self, dest , *args ) :
108
-
109
115
  # new non-ipc invoking managed service in hub
110
116
  obj = self.hub
111
117
  for node in dest.split("."):
@@ -117,17 +123,30 @@ class BrokerMp(object):
117
123
  retq.put(rv)
118
124
  return retq
119
125
 
126
+ def wask(self, dest , *args ) :
127
+ # call from hub to workers
128
+ ret = []
129
+ for p in self.procs:
130
+ retq = ExceptionalQueue(1)
131
+ retq_id = id(retq)
132
+ with self.retpend_mutex:
133
+ self.retpend[retq_id] = retq
134
+
135
+ p.q_pend.put((retq_id, dest, list(args)))
136
+ ret.append(retq)
137
+ return ret
138
+
120
139
  def say(self, dest , *args ) :
121
140
  """
122
141
  send message to non-hub component in other process,
123
142
  returns a Queue object which eventually contains the response if want_retval
124
143
  (not-impl here since nothing uses it yet)
125
144
  """
126
- if dest == "listen":
145
+ if dest == "httpsrv.listen":
127
146
  for p in self.procs:
128
147
  p.q_pend.put((0, dest, [args[0], len(self.procs)]))
129
148
 
130
- elif dest == "set_netdevs":
149
+ elif dest == "httpsrv.set_netdevs":
131
150
  for p in self.procs:
132
151
  p.q_pend.put((0, dest, list(args)))
133
152
 
@@ -136,3 +155,19 @@ class BrokerMp(object):
136
155
 
137
156
  else:
138
157
  raise Exception("what is " + str(dest))
158
+
159
+ def periodic(self) :
160
+ while True:
161
+ time.sleep(1)
162
+
163
+ tdli = {}
164
+ tdls = {}
165
+ qs = self.wask("httpsrv.read_dls")
166
+ for q in qs:
167
+ qr = q.get()
168
+ dli, dls = qr
169
+ tdli.update(dli)
170
+ tdls.update(dls)
171
+ tdl = (tdli, tdls)
172
+ for p in self.procs:
173
+ p.q_pend.put((0, "httpsrv.write_dls", tdl))
copyparty/broker_mpw.py CHANGED
@@ -76,37 +76,38 @@ class MpWorker(BrokerCli):
76
76
  while True:
77
77
  retq_id, dest, args = self.q_pend.get()
78
78
 
79
- # self.logw("work: [{}]".format(d[0]))
79
+ if dest == "retq":
80
+ # response from previous ipc call
81
+ with self.retpend_mutex:
82
+ retq = self.retpend.pop(retq_id)
83
+
84
+ retq.put(args)
85
+ continue
86
+
80
87
  if dest == "shutdown":
81
88
  self.httpsrv.shutdown()
82
89
  self.logw("ok bye")
83
90
  sys.exit(0)
84
91
  return
85
92
 
86
- elif dest == "reload":
93
+ if dest == "reload":
87
94
  self.logw("mpw.asrv reloading")
88
95
  self.asrv.reload()
89
96
  self.logw("mpw.asrv reloaded")
97
+ continue
90
98
 
91
- elif dest == "reload_sessions":
99
+ if dest == "reload_sessions":
92
100
  with self.asrv.mutex:
93
101
  self.asrv.load_sessions()
102
+ continue
94
103
 
95
- elif dest == "listen":
96
- self.httpsrv.listen(args[0], args[1])
97
-
98
- elif dest == "set_netdevs":
99
- self.httpsrv.set_netdevs(args[0])
104
+ obj = self
105
+ for node in dest.split("."):
106
+ obj = getattr(obj, node)
100
107
 
101
- elif dest == "retq":
102
- # response from previous ipc call
103
- with self.retpend_mutex:
104
- retq = self.retpend.pop(retq_id)
105
-
106
- retq.put(args)
107
-
108
- else:
109
- raise Exception("what is " + str(dest))
108
+ rv = obj(*args) # type: ignore
109
+ if retq_id:
110
+ self.say("retq", rv, retq_id=retq_id)
110
111
 
111
112
  def ask(self, dest , *args ) :
112
113
  retq = ExceptionalQueue(1)
@@ -117,5 +118,5 @@ class MpWorker(BrokerCli):
117
118
  self.q_yield.put((retq_id, dest, list(args)))
118
119
  return retq
119
120
 
120
- def say(self, dest , *args ) :
121
- self.q_yield.put((0, dest, list(args)))
121
+ def say(self, dest , *args , retq_id=0) :
122
+ self.q_yield.put((retq_id, dest, list(args)))
copyparty/broker_thr.py CHANGED
@@ -49,11 +49,11 @@ class BrokerThr(BrokerCli):
49
49
  return NotExQueue(obj(*args)) # type: ignore
50
50
 
51
51
  def say(self, dest , *args ) :
52
- if dest == "listen":
52
+ if dest == "httpsrv.listen":
53
53
  self.httpsrv.listen(args[0], 1)
54
54
  return
55
55
 
56
- if dest == "set_netdevs":
56
+ if dest == "httpsrv.set_netdevs":
57
57
  self.httpsrv.set_netdevs(args[0])
58
58
  return
59
59
 
copyparty/cfg.py CHANGED
@@ -103,10 +103,12 @@ def vf_cmap() :
103
103
  "mte",
104
104
  "mth",
105
105
  "mtp",
106
+ "xac",
106
107
  "xad",
107
108
  "xar",
108
109
  "xau",
109
110
  "xban",
111
+ "xbc",
110
112
  "xbd",
111
113
  "xbr",
112
114
  "xbu",
@@ -212,6 +214,8 @@ flagcats = {
212
214
  "xbu=CMD": "execute CMD before a file upload starts",
213
215
  "xau=CMD": "execute CMD after a file upload finishes",
214
216
  "xiu=CMD": "execute CMD after all uploads finish and volume is idle",
217
+ "xbc=CMD": "execute CMD before a file copy",
218
+ "xac=CMD": "execute CMD after a file copy",
215
219
  "xbr=CMD": "execute CMD before a file rename/move",
216
220
  "xar=CMD": "execute CMD after a file rename/move",
217
221
  "xbd=CMD": "execute CMD before a file delete",
copyparty/ftpd.py CHANGED
@@ -292,6 +292,7 @@ class FtpFs(AbstractedFS):
292
292
  self.uname,
293
293
  not self.args.no_scandir,
294
294
  [[True, False], [False, True]],
295
+ throw=True,
295
296
  )
296
297
  vfs_ls = [x[0] for x in vfs_ls1]
297
298
  vfs_ls.extend(vfs_virt.keys())