copyparty 1.19.16__tar.gz → 1.19.18__tar.gz

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.
Files changed (148) hide show
  1. {copyparty-1.19.16 → copyparty-1.19.18}/PKG-INFO +26 -4
  2. {copyparty-1.19.16 → copyparty-1.19.18}/README.md +25 -3
  3. {copyparty-1.19.16 → copyparty-1.19.18}/copyparty/__init__.py +25 -1
  4. {copyparty-1.19.16 → copyparty-1.19.18}/copyparty/__main__.py +32 -9
  5. {copyparty-1.19.16 → copyparty-1.19.18}/copyparty/__version__.py +2 -2
  6. {copyparty-1.19.16 → copyparty-1.19.18}/copyparty/authsrv.py +78 -31
  7. {copyparty-1.19.16 → copyparty-1.19.18}/copyparty/bos/bos.py +5 -1
  8. {copyparty-1.19.16 → copyparty-1.19.18}/copyparty/cfg.py +20 -0
  9. {copyparty-1.19.16 → copyparty-1.19.18}/copyparty/ftpd.py +6 -4
  10. {copyparty-1.19.16 → copyparty-1.19.18}/copyparty/httpcli.py +166 -45
  11. {copyparty-1.19.16 → copyparty-1.19.18}/copyparty/httpsrv.py +2 -2
  12. {copyparty-1.19.16 → copyparty-1.19.18}/copyparty/smbd.py +1 -1
  13. {copyparty-1.19.16 → copyparty-1.19.18}/copyparty/svchub.py +3 -0
  14. {copyparty-1.19.16 → copyparty-1.19.18}/copyparty/tftpd.py +1 -1
  15. {copyparty-1.19.16 → copyparty-1.19.18}/copyparty/up2k.py +39 -15
  16. {copyparty-1.19.16 → copyparty-1.19.18}/copyparty/util.py +15 -5
  17. copyparty-1.19.18/copyparty/web/a/partyfuse.py.gz +0 -0
  18. copyparty-1.19.18/copyparty/web/a/u2c.py.gz +0 -0
  19. copyparty-1.19.18/copyparty/web/a/webdav-cfg.txt.gz +0 -0
  20. copyparty-1.19.18/copyparty/web/baguettebox.js.gz +0 -0
  21. copyparty-1.19.18/copyparty/web/browser.css.gz +0 -0
  22. {copyparty-1.19.16 → copyparty-1.19.18}/copyparty/web/browser.html +3 -0
  23. copyparty-1.19.18/copyparty/web/browser.js.gz +0 -0
  24. {copyparty-1.19.16 → copyparty-1.19.18}/copyparty/web/splash.html +3 -0
  25. copyparty-1.19.18/copyparty/web/splash.js.gz +0 -0
  26. {copyparty-1.19.16 → copyparty-1.19.18}/copyparty/web/svcs.html +1 -1
  27. copyparty-1.19.18/copyparty/web/tl/chi.js.gz +0 -0
  28. copyparty-1.19.18/copyparty/web/tl/cze.js.gz +0 -0
  29. copyparty-1.19.18/copyparty/web/tl/deu.js.gz +0 -0
  30. copyparty-1.19.18/copyparty/web/tl/epo.js.gz +0 -0
  31. copyparty-1.19.18/copyparty/web/tl/fin.js.gz +0 -0
  32. copyparty-1.19.18/copyparty/web/tl/fra.js.gz +0 -0
  33. copyparty-1.19.18/copyparty/web/tl/grc.js.gz +0 -0
  34. copyparty-1.19.18/copyparty/web/tl/ita.js.gz +0 -0
  35. copyparty-1.19.18/copyparty/web/tl/kor.js.gz +0 -0
  36. copyparty-1.19.18/copyparty/web/tl/nld.js.gz +0 -0
  37. copyparty-1.19.18/copyparty/web/tl/nno.js.gz +0 -0
  38. copyparty-1.19.18/copyparty/web/tl/nor.js.gz +0 -0
  39. copyparty-1.19.18/copyparty/web/tl/pol.js.gz +0 -0
  40. copyparty-1.19.18/copyparty/web/tl/por.js.gz +0 -0
  41. copyparty-1.19.18/copyparty/web/tl/rus.js.gz +0 -0
  42. copyparty-1.19.18/copyparty/web/tl/spa.js.gz +0 -0
  43. copyparty-1.19.18/copyparty/web/tl/swe.js.gz +0 -0
  44. copyparty-1.19.18/copyparty/web/tl/tur.js.gz +0 -0
  45. copyparty-1.19.18/copyparty/web/tl/ukr.js.gz +0 -0
  46. copyparty-1.19.18/copyparty/web/up2k.js.gz +0 -0
  47. copyparty-1.19.18/copyparty/web/util.js.gz +0 -0
  48. {copyparty-1.19.16 → copyparty-1.19.18}/copyparty.egg-info/PKG-INFO +26 -4
  49. {copyparty-1.19.16 → copyparty-1.19.18}/copyparty.egg-info/SOURCES.txt +23 -4
  50. {copyparty-1.19.16 → copyparty-1.19.18}/pyproject.toml +4 -1
  51. copyparty-1.19.16/copyparty/web/a/partyfuse.py +0 -947
  52. copyparty-1.19.16/copyparty/web/a/u2c.py +0 -1718
  53. copyparty-1.19.16/copyparty/web/a/webdav-cfg.bat +0 -45
  54. copyparty-1.19.16/copyparty/web/baguettebox.js.gz +0 -0
  55. copyparty-1.19.16/copyparty/web/browser.css.gz +0 -0
  56. copyparty-1.19.16/copyparty/web/browser.js.gz +0 -0
  57. copyparty-1.19.16/copyparty/web/splash.js.gz +0 -0
  58. copyparty-1.19.16/copyparty/web/up2k.js.gz +0 -0
  59. copyparty-1.19.16/copyparty/web/util.js.gz +0 -0
  60. {copyparty-1.19.16 → copyparty-1.19.18}/LICENSE +0 -0
  61. {copyparty-1.19.16 → copyparty-1.19.18}/copyparty/bos/__init__.py +0 -0
  62. {copyparty-1.19.16 → copyparty-1.19.18}/copyparty/bos/path.py +0 -0
  63. {copyparty-1.19.16 → copyparty-1.19.18}/copyparty/broker_mp.py +0 -0
  64. {copyparty-1.19.16 → copyparty-1.19.18}/copyparty/broker_mpw.py +0 -0
  65. {copyparty-1.19.16 → copyparty-1.19.18}/copyparty/broker_thr.py +0 -0
  66. {copyparty-1.19.16 → copyparty-1.19.18}/copyparty/broker_util.py +0 -0
  67. {copyparty-1.19.16 → copyparty-1.19.18}/copyparty/cert.py +0 -0
  68. {copyparty-1.19.16 → copyparty-1.19.18}/copyparty/dxml.py +0 -0
  69. {copyparty-1.19.16 → copyparty-1.19.18}/copyparty/fsutil.py +0 -0
  70. {copyparty-1.19.16 → copyparty-1.19.18}/copyparty/httpconn.py +0 -0
  71. {copyparty-1.19.16 → copyparty-1.19.18}/copyparty/ico.py +0 -0
  72. {copyparty-1.19.16 → copyparty-1.19.18}/copyparty/mdns.py +0 -0
  73. {copyparty-1.19.16 → copyparty-1.19.18}/copyparty/metrics.py +0 -0
  74. {copyparty-1.19.16 → copyparty-1.19.18}/copyparty/mtag.py +2 -2
  75. {copyparty-1.19.16 → copyparty-1.19.18}/copyparty/multicast.py +0 -0
  76. {copyparty-1.19.16 → copyparty-1.19.18}/copyparty/pwhash.py +0 -0
  77. {copyparty-1.19.16 → copyparty-1.19.18}/copyparty/qrkode.py +0 -0
  78. {copyparty-1.19.16 → copyparty-1.19.18}/copyparty/res/COPYING.txt +0 -0
  79. {copyparty-1.19.16 → copyparty-1.19.18}/copyparty/res/__init__.py +0 -0
  80. {copyparty-1.19.16 → copyparty-1.19.18}/copyparty/res/insecure.pem +0 -0
  81. {copyparty-1.19.16 → copyparty-1.19.18}/copyparty/ssdp.py +0 -0
  82. {copyparty-1.19.16 → copyparty-1.19.18}/copyparty/star.py +0 -0
  83. {copyparty-1.19.16 → copyparty-1.19.18}/copyparty/stolen/__init__.py +0 -0
  84. {copyparty-1.19.16 → copyparty-1.19.18}/copyparty/stolen/dnslib/__init__.py +0 -0
  85. {copyparty-1.19.16 → copyparty-1.19.18}/copyparty/stolen/dnslib/bimap.py +0 -0
  86. {copyparty-1.19.16 → copyparty-1.19.18}/copyparty/stolen/dnslib/bit.py +0 -0
  87. {copyparty-1.19.16 → copyparty-1.19.18}/copyparty/stolen/dnslib/buffer.py +0 -0
  88. {copyparty-1.19.16 → copyparty-1.19.18}/copyparty/stolen/dnslib/dns.py +0 -0
  89. {copyparty-1.19.16 → copyparty-1.19.18}/copyparty/stolen/dnslib/label.py +0 -0
  90. {copyparty-1.19.16 → copyparty-1.19.18}/copyparty/stolen/dnslib/lex.py +0 -0
  91. {copyparty-1.19.16 → copyparty-1.19.18}/copyparty/stolen/dnslib/ranges.py +0 -0
  92. {copyparty-1.19.16 → copyparty-1.19.18}/copyparty/stolen/ifaddr/__init__.py +0 -0
  93. {copyparty-1.19.16 → copyparty-1.19.18}/copyparty/stolen/ifaddr/_posix.py +0 -0
  94. {copyparty-1.19.16 → copyparty-1.19.18}/copyparty/stolen/ifaddr/_shared.py +0 -0
  95. {copyparty-1.19.16 → copyparty-1.19.18}/copyparty/stolen/ifaddr/_win32.py +0 -0
  96. {copyparty-1.19.16 → copyparty-1.19.18}/copyparty/stolen/qrcodegen.py +0 -0
  97. {copyparty-1.19.16 → copyparty-1.19.18}/copyparty/stolen/surrogateescape.py +0 -0
  98. {copyparty-1.19.16 → copyparty-1.19.18}/copyparty/sutil.py +0 -0
  99. {copyparty-1.19.16 → copyparty-1.19.18}/copyparty/szip.py +0 -0
  100. {copyparty-1.19.16 → copyparty-1.19.18}/copyparty/tcpsrv.py +0 -0
  101. {copyparty-1.19.16 → copyparty-1.19.18}/copyparty/th_cli.py +0 -0
  102. {copyparty-1.19.16 → copyparty-1.19.18}/copyparty/th_srv.py +0 -0
  103. {copyparty-1.19.16 → copyparty-1.19.18}/copyparty/u2idx.py +0 -0
  104. {copyparty-1.19.16 → copyparty-1.19.18}/copyparty/web/a/__init__.py +0 -0
  105. {copyparty-1.19.16 → copyparty-1.19.18}/copyparty/web/browser2.html +0 -0
  106. {copyparty-1.19.16 → copyparty-1.19.18}/copyparty/web/cf.html +0 -0
  107. {copyparty-1.19.16 → copyparty-1.19.18}/copyparty/web/dbg-audio.js.gz +0 -0
  108. {copyparty-1.19.16 → copyparty-1.19.18}/copyparty/web/deps/__init__.py +0 -0
  109. {copyparty-1.19.16 → copyparty-1.19.18}/copyparty/web/deps/busy.mp3.gz +0 -0
  110. {copyparty-1.19.16 → copyparty-1.19.18}/copyparty/web/deps/easymde.css.gz +0 -0
  111. {copyparty-1.19.16 → copyparty-1.19.18}/copyparty/web/deps/easymde.js.gz +0 -0
  112. {copyparty-1.19.16 → copyparty-1.19.18}/copyparty/web/deps/fuse.py +0 -0
  113. {copyparty-1.19.16 → copyparty-1.19.18}/copyparty/web/deps/marked.js.gz +0 -0
  114. {copyparty-1.19.16 → copyparty-1.19.18}/copyparty/web/deps/mini-fa.css.gz +0 -0
  115. {copyparty-1.19.16 → copyparty-1.19.18}/copyparty/web/deps/mini-fa.woff +0 -0
  116. {copyparty-1.19.16 → copyparty-1.19.18}/copyparty/web/deps/prism.css.gz +0 -0
  117. {copyparty-1.19.16 → copyparty-1.19.18}/copyparty/web/deps/prism.js.gz +0 -0
  118. {copyparty-1.19.16 → copyparty-1.19.18}/copyparty/web/deps/prismd.css.gz +0 -0
  119. {copyparty-1.19.16 → copyparty-1.19.18}/copyparty/web/deps/scp.woff2 +0 -0
  120. {copyparty-1.19.16 → copyparty-1.19.18}/copyparty/web/deps/sha512.ac.js.gz +0 -0
  121. {copyparty-1.19.16 → copyparty-1.19.18}/copyparty/web/deps/sha512.hw.js.gz +0 -0
  122. {copyparty-1.19.16 → copyparty-1.19.18}/copyparty/web/idp.html +0 -0
  123. {copyparty-1.19.16 → copyparty-1.19.18}/copyparty/web/md.css.gz +0 -0
  124. {copyparty-1.19.16 → copyparty-1.19.18}/copyparty/web/md.html +0 -0
  125. {copyparty-1.19.16 → copyparty-1.19.18}/copyparty/web/md.js.gz +0 -0
  126. {copyparty-1.19.16 → copyparty-1.19.18}/copyparty/web/md2.css.gz +0 -0
  127. {copyparty-1.19.16 → copyparty-1.19.18}/copyparty/web/md2.js.gz +0 -0
  128. {copyparty-1.19.16 → copyparty-1.19.18}/copyparty/web/mde.css.gz +0 -0
  129. {copyparty-1.19.16 → copyparty-1.19.18}/copyparty/web/mde.html +0 -0
  130. {copyparty-1.19.16 → copyparty-1.19.18}/copyparty/web/mde.js.gz +0 -0
  131. {copyparty-1.19.16 → copyparty-1.19.18}/copyparty/web/msg.css.gz +0 -0
  132. {copyparty-1.19.16 → copyparty-1.19.18}/copyparty/web/msg.html +0 -0
  133. {copyparty-1.19.16 → copyparty-1.19.18}/copyparty/web/opds.xml +0 -0
  134. {copyparty-1.19.16 → copyparty-1.19.18}/copyparty/web/rups.css.gz +0 -0
  135. {copyparty-1.19.16 → copyparty-1.19.18}/copyparty/web/rups.html +0 -0
  136. {copyparty-1.19.16 → copyparty-1.19.18}/copyparty/web/rups.js.gz +0 -0
  137. {copyparty-1.19.16 → copyparty-1.19.18}/copyparty/web/shares.css.gz +0 -0
  138. {copyparty-1.19.16 → copyparty-1.19.18}/copyparty/web/shares.html +0 -0
  139. {copyparty-1.19.16 → copyparty-1.19.18}/copyparty/web/shares.js.gz +0 -0
  140. {copyparty-1.19.16 → copyparty-1.19.18}/copyparty/web/splash.css.gz +0 -0
  141. {copyparty-1.19.16 → copyparty-1.19.18}/copyparty/web/svcs.js.gz +0 -0
  142. {copyparty-1.19.16 → copyparty-1.19.18}/copyparty/web/ui.css.gz +0 -0
  143. {copyparty-1.19.16 → copyparty-1.19.18}/copyparty/web/w.hash.js.gz +0 -0
  144. {copyparty-1.19.16 → copyparty-1.19.18}/copyparty.egg-info/dependency_links.txt +0 -0
  145. {copyparty-1.19.16 → copyparty-1.19.18}/copyparty.egg-info/entry_points.txt +0 -0
  146. {copyparty-1.19.16 → copyparty-1.19.18}/copyparty.egg-info/requires.txt +0 -0
  147. {copyparty-1.19.16 → copyparty-1.19.18}/copyparty.egg-info/top_level.txt +0 -0
  148. {copyparty-1.19.16 → copyparty-1.19.18}/setup.cfg +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: copyparty
3
- Version: 1.19.16
3
+ Version: 1.19.18
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
@@ -86,6 +86,7 @@ made in Norway 🇳🇴
86
86
 
87
87
  * top
88
88
  * [quickstart](#quickstart) - just run **[copyparty-sfx.py](https://github.com/9001/copyparty/releases/latest/download/copyparty-sfx.py)** -- that's it! 🎉
89
+ * [mirrors](#mirrors) - other places to download copyparty from
89
90
  * [at home](#at-home) - make it accessible over the internet
90
91
  * [on servers](#on-servers) - you may also want these, especially on servers
91
92
  * [features](#features) - also see [comparison to similar software](./docs/versus.md)
@@ -222,7 +223,7 @@ just run **[copyparty-sfx.py](https://github.com/9001/copyparty/releases/latest/
222
223
 
223
224
  * or install through [pypi](https://pypi.org/project/copyparty/): `python3 -m pip install --user -U copyparty`
224
225
  * or if you cannot install python, you can use [copyparty.exe](#copypartyexe) instead
225
- * or install [on arch](#arch-package) ╱ [on NixOS](#nixos-module) ╱ [through nix](#nix-package)
226
+ * or install [on arch](#arch-package) / [homebrew](#homebrew-formulae) ╱ [on NixOS](#nixos-module) ╱ [through nix](#nix-package)
226
227
  * or if you are on android, [install copyparty in termux](#install-on-android)
227
228
  * or maybe an iPhone or iPad? [install in a-Shell on iOS](#install-on-iOS)
228
229
  * or maybe you have a [synology nas / dsm](./docs/synology-dsm.md)
@@ -260,6 +261,18 @@ some recommended options:
260
261
  * see [accounts and volumes](#accounts-and-volumes) (or `--help-accounts`) for the syntax and other permissions
261
262
 
262
263
 
264
+ ### mirrors
265
+
266
+ other places to download copyparty from (non-github links):
267
+
268
+ * https://copyparty.eu/ (hetzner, finland, official mirror):
269
+ * https://copyparty.eu/py = https://copyparty.eu/copyparty-sfx.py = the sfx
270
+ * https://copyparty.eu/en = https://copyparty.eu/copyparty-en.py = the english-only sfx
271
+ * https://copyparty.eu/pyz = https://copyparty.eu/copyparty.pyz = the zipapp
272
+ * https://copyparty.eu/enz = https://copyparty.eu/copyparty-en.pyz = the enterprise pyz
273
+ * https://copyparty.eu/cli = online cli helptext
274
+
275
+
263
276
  ### at home
264
277
 
265
278
  make it accessible over the internet by starting a [cloudflare quicktunnel](https://developers.cloudflare.com/cloudflare-one/connections/connect-networks/do-more-with-tunnels/trycloudflare/) like so:
@@ -588,6 +601,9 @@ examples:
588
601
 
589
602
  if you want to grant access to all users who are logged in, the group `acct` will always contain all known users, so for example `-v /mnt/music:music:r,@acct`
590
603
 
604
+ * to do the opposite, granting access to everyone who is NOT logged in. `*,-@acct` does the trick, for example `-v /srv/welcome:welcome:r,*,-@acct`
605
+ * single users can also be subtracted from a group: `@admins,-james`
606
+
591
607
  anyone trying to bruteforce a password gets banned according to `--ban-pw`; default is 24h ban for 9 failed attempts in 1 hour
592
608
 
593
609
  and if you want to use config files instead of commandline args (good!) then here's the same examples as a configfile; save it as `foobar.conf` and use it like this: `python copyparty-sfx.py -c foobar.conf`
@@ -1558,7 +1574,12 @@ tweaking the ui
1558
1574
  * to sort in music order (album, track, artist, title) with filename as fallback, you could `--sort tags/Circle,tags/.tn,tags/Artist,tags/Title,href`
1559
1575
  * to sort by upload date, first enable showing the upload date in the listing with `-e2d -mte +.up_at` and then `--sort tags/.up_at`
1560
1576
 
1561
- see [./docs/rice](./docs/rice) for more, including how to add stuff (css/`<meta>`/...) to the html `<head>` tag, or to add your own translation
1577
+ see [./docs/rice](./docs/rice) for more, including:
1578
+ * how to [hide ui-elements](./docs/rice/README.md#hide-ui-elements)
1579
+ * [custom fonts](./docs/rice/README.md#custom-fonts)
1580
+ * [custom loading-spinner](./docs/rice/README.md#boring-loader-spinner)
1581
+ * adding stuff (css/`<meta>`/...) [to the html `<head>` tag](./docs/rice/README.md#head)
1582
+ * [adding your own translation](./docs/rice/README.md#translations)
1562
1583
 
1563
1584
 
1564
1585
  ## opengraph
@@ -3123,7 +3144,8 @@ another emergency alternative, [copyparty.pyz](https://github.com/9001/copyparty
3123
3144
  run it by doubleclicking it, or try typing `python copyparty.pyz` in your terminal/console/commandline/telex if that fails
3124
3145
 
3125
3146
  it is a python [zipapp](https://docs.python.org/3/library/zipapp.html) meaning it doesn't have to unpack its own python code anywhere to run, so if the filesystem is busted it has a better chance of getting somewhere
3126
- * but note that it currently still needs to extract the web-resources somewhere (they'll land in the default TEMP-folder of your OS)
3147
+
3148
+ > there is also [copyparty-en.pyz](https://github.com/9001/copyparty/releases/latest/download/copyparty-en.pyz), english-only and without smb support (enterprise-friendly)
3127
3149
 
3128
3150
 
3129
3151
  # install on android
@@ -21,6 +21,7 @@ made in Norway 🇳🇴
21
21
 
22
22
  * top
23
23
  * [quickstart](#quickstart) - just run **[copyparty-sfx.py](https://github.com/9001/copyparty/releases/latest/download/copyparty-sfx.py)** -- that's it! 🎉
24
+ * [mirrors](#mirrors) - other places to download copyparty from
24
25
  * [at home](#at-home) - make it accessible over the internet
25
26
  * [on servers](#on-servers) - you may also want these, especially on servers
26
27
  * [features](#features) - also see [comparison to similar software](./docs/versus.md)
@@ -157,7 +158,7 @@ just run **[copyparty-sfx.py](https://github.com/9001/copyparty/releases/latest/
157
158
 
158
159
  * or install through [pypi](https://pypi.org/project/copyparty/): `python3 -m pip install --user -U copyparty`
159
160
  * or if you cannot install python, you can use [copyparty.exe](#copypartyexe) instead
160
- * or install [on arch](#arch-package) ╱ [on NixOS](#nixos-module) ╱ [through nix](#nix-package)
161
+ * or install [on arch](#arch-package) / [homebrew](#homebrew-formulae) ╱ [on NixOS](#nixos-module) ╱ [through nix](#nix-package)
161
162
  * or if you are on android, [install copyparty in termux](#install-on-android)
162
163
  * or maybe an iPhone or iPad? [install in a-Shell on iOS](#install-on-iOS)
163
164
  * or maybe you have a [synology nas / dsm](./docs/synology-dsm.md)
@@ -195,6 +196,18 @@ some recommended options:
195
196
  * see [accounts and volumes](#accounts-and-volumes) (or `--help-accounts`) for the syntax and other permissions
196
197
 
197
198
 
199
+ ### mirrors
200
+
201
+ other places to download copyparty from (non-github links):
202
+
203
+ * https://copyparty.eu/ (hetzner, finland, official mirror):
204
+ * https://copyparty.eu/py = https://copyparty.eu/copyparty-sfx.py = the sfx
205
+ * https://copyparty.eu/en = https://copyparty.eu/copyparty-en.py = the english-only sfx
206
+ * https://copyparty.eu/pyz = https://copyparty.eu/copyparty.pyz = the zipapp
207
+ * https://copyparty.eu/enz = https://copyparty.eu/copyparty-en.pyz = the enterprise pyz
208
+ * https://copyparty.eu/cli = online cli helptext
209
+
210
+
198
211
  ### at home
199
212
 
200
213
  make it accessible over the internet by starting a [cloudflare quicktunnel](https://developers.cloudflare.com/cloudflare-one/connections/connect-networks/do-more-with-tunnels/trycloudflare/) like so:
@@ -523,6 +536,9 @@ examples:
523
536
 
524
537
  if you want to grant access to all users who are logged in, the group `acct` will always contain all known users, so for example `-v /mnt/music:music:r,@acct`
525
538
 
539
+ * to do the opposite, granting access to everyone who is NOT logged in. `*,-@acct` does the trick, for example `-v /srv/welcome:welcome:r,*,-@acct`
540
+ * single users can also be subtracted from a group: `@admins,-james`
541
+
526
542
  anyone trying to bruteforce a password gets banned according to `--ban-pw`; default is 24h ban for 9 failed attempts in 1 hour
527
543
 
528
544
  and if you want to use config files instead of commandline args (good!) then here's the same examples as a configfile; save it as `foobar.conf` and use it like this: `python copyparty-sfx.py -c foobar.conf`
@@ -1493,7 +1509,12 @@ tweaking the ui
1493
1509
  * to sort in music order (album, track, artist, title) with filename as fallback, you could `--sort tags/Circle,tags/.tn,tags/Artist,tags/Title,href`
1494
1510
  * to sort by upload date, first enable showing the upload date in the listing with `-e2d -mte +.up_at` and then `--sort tags/.up_at`
1495
1511
 
1496
- see [./docs/rice](./docs/rice) for more, including how to add stuff (css/`<meta>`/...) to the html `<head>` tag, or to add your own translation
1512
+ see [./docs/rice](./docs/rice) for more, including:
1513
+ * how to [hide ui-elements](./docs/rice/README.md#hide-ui-elements)
1514
+ * [custom fonts](./docs/rice/README.md#custom-fonts)
1515
+ * [custom loading-spinner](./docs/rice/README.md#boring-loader-spinner)
1516
+ * adding stuff (css/`<meta>`/...) [to the html `<head>` tag](./docs/rice/README.md#head)
1517
+ * [adding your own translation](./docs/rice/README.md#translations)
1497
1518
 
1498
1519
 
1499
1520
  ## opengraph
@@ -3058,7 +3079,8 @@ another emergency alternative, [copyparty.pyz](https://github.com/9001/copyparty
3058
3079
  run it by doubleclicking it, or try typing `python copyparty.pyz` in your terminal/console/commandline/telex if that fails
3059
3080
 
3060
3081
  it is a python [zipapp](https://docs.python.org/3/library/zipapp.html) meaning it doesn't have to unpack its own python code anywhere to run, so if the filesystem is busted it has a better chance of getting somewhere
3061
- * but note that it currently still needs to extract the web-resources somewhere (they'll land in the default TEMP-folder of your OS)
3082
+
3083
+ > there is also [copyparty-en.pyz](https://github.com/9001/copyparty/releases/latest/download/copyparty-en.pyz), english-only and without smb support (enterprise-friendly)
3062
3084
 
3063
3085
 
3064
3086
  # install on android
@@ -52,7 +52,7 @@ except:
52
52
  zs = """
53
53
  web/a/partyfuse.py
54
54
  web/a/u2c.py
55
- web/a/webdav-cfg.bat
55
+ web/a/webdav-cfg.txt
56
56
  web/baguettebox.js
57
57
  web/browser.css
58
58
  web/browser.html
@@ -97,12 +97,36 @@ web/splash.html
97
97
  web/splash.js
98
98
  web/svcs.html
99
99
  web/svcs.js
100
+ web/tl/chi.js
101
+ web/tl/cze.js
102
+ web/tl/deu.js
103
+ web/tl/epo.js
104
+ web/tl/fin.js
105
+ web/tl/fra.js
106
+ web/tl/grc.js
107
+ web/tl/ita.js
108
+ web/tl/kor.js
109
+ web/tl/nld.js
110
+ web/tl/nno.js
111
+ web/tl/nor.js
112
+ web/tl/pol.js
113
+ web/tl/por.js
114
+ web/tl/rus.js
115
+ web/tl/spa.js
116
+ web/tl/swe.js
117
+ web/tl/tur.js
118
+ web/tl/ukr.js
100
119
  web/ui.css
101
120
  web/up2k.js
102
121
  web/util.js
103
122
  web/w.hash.js
104
123
  """
105
124
  RES = set(zs.strip().split("\n"))
125
+ RESM = {
126
+ "web/a/partyfuse.txt": "web/a/partyfuse.py",
127
+ "web/a/u2c.txt": "web/a/u2c.py",
128
+ "web/a/webdav-cfg.bat": "web/a/webdav-cfg.txt",
129
+ }
106
130
 
107
131
 
108
132
  class EnvParams(object):
@@ -641,8 +641,11 @@ def get_sects():
641
641
  if no accounts or volumes are configured,
642
642
  current folder will be read/write for everyone
643
643
 
644
- the group @acct will always have every user with an account
645
- (the name of that group can be changed with --grp-all)
644
+ the group \033[33m@acct\033[0m will always have every user with an account
645
+ (the name of that group can be changed with \033[32m--grp-all\033[0m)
646
+
647
+ to hide a volume from authenticated users, specify \033[33m*,-@acct\033[0m
648
+ to subtract \033[33m@acct\033[0m from \033[33m*\033[0m (can subtract users from groups too)
646
649
 
647
650
  consider the config file for more flexible account/volume management,
648
651
  including dynamic reload at runtime (and being more readable w)
@@ -664,12 +667,12 @@ def get_sects():
664
667
 
665
668
  send the password in the '\033[36mPW\033[0m' http-header:
666
669
  \033[36mPW: \033[35mhunter2\033[0m
667
- or if you have \033[33m--accounts\033[0m enabled,
670
+ or if you have \033[33m--usernames\033[0m enabled,
668
671
  \033[36mPW: \033[35med:hunter2\033[0m
669
672
 
670
673
  send the password in the URL itself:
671
674
  \033[36mhttp://127.0.0.1:3923/\033[35m?pw=hunter2\033[0m
672
- or if you have \033[33m--accounts\033[0m enabled,
675
+ or if you have \033[33m--usernames\033[0m enabled,
673
676
  \033[36mhttp://127.0.0.1:3923/\033[35m?pw=ed:hunter2\033[0m
674
677
 
675
678
  use basic-authentication:
@@ -1166,6 +1169,7 @@ def add_general(ap, nc, srvname):
1166
1169
  ap2.add_argument("--rmagic", action="store_true", help="do expensive analysis to improve accuracy of returned mimetypes; will make file-downloads, rss, and webdav slower (volflag=rmagic)")
1167
1170
  ap2.add_argument("--license", action="store_true", help="show licenses and exit")
1168
1171
  ap2.add_argument("--version", action="store_true", help="show versions and exit")
1172
+ ap2.add_argument("--versionb", action="store_true", help="show version and exit")
1169
1173
 
1170
1174
 
1171
1175
  def add_qr(ap, tty):
@@ -1233,6 +1237,7 @@ def add_upload(ap):
1233
1237
  ap2.add_argument("--hardlink-only", action="store_true", help="do not fallback to symlinks when a hardlink cannot be made (volflag=hardlinkonly)")
1234
1238
  ap2.add_argument("--reflink", action="store_true", help="enable reflink-based dedup; will fallback on full copies when that is impossible (non-CoW filesystem) (volflag=reflink)")
1235
1239
  ap2.add_argument("--no-dupe", action="store_true", help="reject duplicate files during upload; only matches within the same volume (volflag=nodupe)")
1240
+ ap2.add_argument("--no-dupe-m", action="store_true", help="also reject dupes when moving a file into another volume (volflag=nodupem)")
1236
1241
  ap2.add_argument("--no-clone", action="store_true", help="do not use existing data on disk to satisfy dupe uploads; reduces server HDD reads in exchange for much more network load (volflag=noclone)")
1237
1242
  ap2.add_argument("--no-snap", action="store_true", help="disable snapshots -- forget unfinished uploads on shutdown; don't create .hist/up2k.snap files -- abandoned/interrupted uploads must be cleaned up manually")
1238
1243
  ap2.add_argument("--snap-wri", metavar="SEC", type=int, default=300, help="write upload state to ./hist/up2k.snap every \033[33mSEC\033[0m seconds; allows resuming incomplete uploads after a server crash")
@@ -1520,6 +1525,7 @@ def add_optouts(ap):
1520
1525
  ap2.add_argument("--no-pipe", action="store_true", help="disable race-the-beam (lockstep download of files which are currently being uploaded) (volflag=nopipe)")
1521
1526
  ap2.add_argument("--no-tail", action="store_true", help="disable streaming a growing files with ?tail (volflag=notail)")
1522
1527
  ap2.add_argument("--no-db-ip", action="store_true", help="do not write uploader-IP into the database; will also disable unpost, you may want \033[32m--forget-ip\033[0m instead (volflag=no_db_ip)")
1528
+ ap2.add_argument("--no-zls", action="store_true", help="disable browsing the contents of zip/cbz files, does not affect thumbnails")
1523
1529
 
1524
1530
 
1525
1531
  def add_safety(ap):
@@ -1538,6 +1544,7 @@ def add_safety(ap):
1538
1544
  ap2.add_argument("--force-js", action="store_true", help="don't send folder listings as HTML, force clients to use the embedded json instead -- slight protection against misbehaving search engines which ignore \033[33m--no-robots\033[0m")
1539
1545
  ap2.add_argument("--no-robots", action="store_true", help="adds http and html headers asking search engines to not index anything (volflag=norobots)")
1540
1546
  ap2.add_argument("--logout", metavar="H", type=float, default=8086.0, help="logout clients after \033[33mH\033[0m hours of inactivity; [\033[32m0.0028\033[0m]=10sec, [\033[32m0.1\033[0m]=6min, [\033[32m24\033[0m]=day, [\033[32m168\033[0m]=week, [\033[32m720\033[0m]=month, [\033[32m8760\033[0m]=year)")
1547
+ ap2.add_argument("--dont-ban", metavar="TXT", type=u, default="no", help="anyone at this accesslevel or above will not get banned: [\033[32mav\033[0m]=admin-in-volume, [\033[32maa\033[0m]=has-admin-anywhere, [\033[32mrw\033[0m]=read-write, [\033[32mauth\033[0m]=authenticated, [\033[32many\033[0m]=disable-all-bans, [\033[32mno\033[0m]=anyone-can-get-banned")
1541
1548
  ap2.add_argument("--ban-pw", metavar="N,W,B", type=u, default="9,60,1440", help="more than \033[33mN\033[0m wrong passwords in \033[33mW\033[0m minutes = ban for \033[33mB\033[0m minutes; disable with [\033[32mno\033[0m]")
1542
1549
  ap2.add_argument("--ban-pwc", metavar="N,W,B", type=u, default="5,60,1440", help="more than \033[33mN\033[0m password-changes in \033[33mW\033[0m minutes = ban for \033[33mB\033[0m minutes; disable with [\033[32mno\033[0m]")
1543
1550
  ap2.add_argument("--ban-404", metavar="N,W,B", type=u, default="50,60,1440", help="hitting more than \033[33mN\033[0m 404's in \033[33mW\033[0m minutes = ban for \033[33mB\033[0m minutes; only affects users who cannot see directory listings because their access is either g/G/h")
@@ -1602,12 +1609,14 @@ def add_admin(ap):
1602
1609
  ap2 = ap.add_argument_group("admin panel options")
1603
1610
  ap2.add_argument("--no-reload", action="store_true", help="disable ?reload=cfg (reload users/volumes/volflags from config file)")
1604
1611
  ap2.add_argument("--no-rescan", action="store_true", help="disable ?scan (volume reindexing)")
1605
- ap2.add_argument("--no-stack", action="store_true", help="disable ?stack (list all stacks)")
1612
+ ap2.add_argument("--no-stack", action="store_true", help="disable ?stack (list all stacks); same as --stack-who=no")
1606
1613
  ap2.add_argument("--no-ups-page", action="store_true", help="disable ?ru (list of recent uploads)")
1607
1614
  ap2.add_argument("--no-up-list", action="store_true", help="don't show list of incoming files in controlpanel")
1608
1615
  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")
1609
1616
  ap2.add_argument("--ups-who", metavar="LVL", type=int, default=2, help="who can see recent uploads on the ?ru page? [\033[32m0\033[0m]=nobody, [\033[32m1\033[0m]=admins, [\033[32m2\033[0m]=everyone (volflag=ups_who)")
1610
1617
  ap2.add_argument("--ups-when", action="store_true", help="let everyone see upload timestamps on the ?ru page, not just admins")
1618
+ ap2.add_argument("--stack-who", metavar="LVL", type=u, default="a", help="who can see the ?stack page (list of threads)? [\033[32mno\033[0m]=nobody, [\033[32ma\033[0m]=admins, [\033[32mrw\033[0m]=read+write, [\033[32mall\033[0m]=everyone")
1619
+ ap2.add_argument("--stack-v", action="store_true", help="verbose ?stack")
1611
1620
 
1612
1621
 
1613
1622
  def add_thumbnail(ap):
@@ -1779,6 +1788,7 @@ def add_ui(ap, retry ):
1779
1788
  ap2.add_argument("--ufavico", metavar="TXT", type=u, default="", help="URL to .ico/png/gif/svg file; \033[33m--favico\033[0m takes precedence unless disabled (volflag=ufavico)")
1780
1789
  ap2.add_argument("--ext-th", metavar="E=VP", type=u, action="append", help="\033[34mREPEATABLE:\033[0m use thumbnail-image \033[33mVP\033[0m for file-extension \033[33mE\033[0m, example: [\033[32mexe=/.res/exe.png\033[0m] (volflag=ext_th)")
1781
1790
  ap2.add_argument("--mpmc", type=u, default="", help=argparse.SUPPRESS)
1791
+ ap2.add_argument("--notooltips", action="store_true", help="tooltips disabled as default")
1782
1792
  ap2.add_argument("--spinner", metavar="TXT", type=u, default="🌲", help="\033[33memoji\033[0m or \033[33memoji,css\033[0m Example: [\033[32m🥖,padding:0\033[0m]")
1783
1793
  ap2.add_argument("--css-browser", metavar="L", type=u, default="", help="URL to additional CSS to include in the filebrowser html")
1784
1794
  ap2.add_argument("--js-browser", metavar="L", type=u, default="", help="URL to additional JS to include in the filebrowser html")
@@ -1805,6 +1815,15 @@ def add_ui(ap, retry ):
1805
1815
  ap2.add_argument("--lg-sba", metavar="TXT", type=u, default="", help="the value of the iframe 'allow' attribute for prologue/epilogue docs (volflag=lg_sba); see https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Permissions-Policy#iframes")
1806
1816
  ap2.add_argument("--no-sb-md", action="store_true", help="don't sandbox README/PREADME.md documents (volflags: no_sb_md | sb_md)")
1807
1817
  ap2.add_argument("--no-sb-lg", action="store_true", help="don't sandbox prologue/epilogue docs (volflags: no_sb_lg | sb_lg); enables non-js support")
1818
+ ap2.add_argument("--ui-nombar", action="store_true", help="hide top-menu in the UI (volflag=ui_nombar)")
1819
+ ap2.add_argument("--ui-noacci", action="store_true", help="hide account-info in the UI (volflag=ui_noacci)")
1820
+ ap2.add_argument("--ui-nosrvi", action="store_true", help="hide server-info in the UI (volflag=ui_nosrvi)")
1821
+ ap2.add_argument("--ui-nonav", action="store_true", help="hide navpane+breadcrumbs (volflag=ui_nonav)")
1822
+ ap2.add_argument("--ui-notree", action="store_true", help="hide navpane in the UI (volflag=ui_nonav)")
1823
+ ap2.add_argument("--ui-nocpla", action="store_true", help="hide cpanel-link in the UI (volflag=ui_nocpla)")
1824
+ ap2.add_argument("--ui-nolbar", action="store_true", help="hide link-bar in the UI (volflag=ui_nolbar)")
1825
+ ap2.add_argument("--ui-noctxb", action="store_true", help="hide context-buttons in the UI (volflag=ui_noctxb)")
1826
+ ap2.add_argument("--ui-norepl", action="store_true", help="hide repl-button in the UI (volflag=ui_norepl)")
1808
1827
  ap2.add_argument("--have-unlistc", action="store_true", help=argparse.SUPPRESS)
1809
1828
 
1810
1829
 
@@ -1932,15 +1951,19 @@ def run_argparse(
1932
1951
 
1933
1952
 
1934
1953
  def main(argv = None) :
1954
+ if argv is None:
1955
+ argv = sys.argv
1956
+
1957
+ if "--versionb" in argv:
1958
+ print(S_VERSION)
1959
+ sys.exit(0)
1960
+
1935
1961
  time.strptime("19970815", "%Y%m%d") # python#7980
1936
1962
  if WINDOWS:
1937
1963
  os.system("rem") # enables colors
1938
1964
 
1939
1965
  init_E(E)
1940
1966
 
1941
- if argv is None:
1942
- argv = sys.argv
1943
-
1944
1967
  f = '\033[36mcopyparty v{} "\033[35m{}\033[36m" ({})\n{}\033[0;36m\n sqlite {} | jinja {} | pyftpd {} | tftp {}\n\033[0m'
1945
1968
  f = f.format(
1946
1969
  S_VERSION,
@@ -2067,7 +2090,7 @@ def main(argv = None) :
2067
2090
 
2068
2091
  # propagate implications
2069
2092
  for k1, k2 in IMPLICATIONS:
2070
- if getattr(al, k1):
2093
+ if getattr(al, k1, None):
2071
2094
  setattr(al, k2, True)
2072
2095
 
2073
2096
  # propagate unplications
@@ -1,8 +1,8 @@
1
1
  # coding: utf-8
2
2
 
3
- VERSION = (1, 19, 16)
3
+ VERSION = (1, 19, 18)
4
4
  CODENAME = "usernames"
5
- BUILD_DT = (2025, 10, 5)
5
+ BUILD_DT = (2025, 10, 25)
6
6
 
7
7
  S_VERSION = ".".join(map(str, VERSION))
8
8
  S_BUILD_DT = "{0:04d}-{1:02d}-{2:02d}".format(*BUILD_DT)
@@ -391,6 +391,9 @@ class VFS(object):
391
391
  self.vpath = vpath # absolute path in the virtual filesystem
392
392
  self.vpath0 = vpath0 # original vpath (before idp expansion)
393
393
  self.axs = axs
394
+ self.uaxs = {}
395
+
396
+
394
397
  self.flags = flags # config options
395
398
  self.root = self
396
399
  self.dev = 0 # st_dev
@@ -548,29 +551,19 @@ class VFS(object):
548
551
 
549
552
  def can_access(
550
553
  self, vpath , uname
551
- ) :
552
- """can Read,Write,Move,Delete,Get,Upget,Admin,Dot"""
554
+ ) :
555
+ """can Read,Write,Move,Delete,Get,Upget,Html,Admin,Dot"""
556
+ # NOTE: only used by get_perms, which is only used by hooks; the lowest of fruits
553
557
  if vpath:
554
558
  vn, _ = self._find(undot(vpath))
555
559
  else:
556
560
  vn = self
557
561
 
558
- c = vn.axs
559
- return (
560
- uname in c.uread,
561
- uname in c.uwrite,
562
- uname in c.umove,
563
- uname in c.udel,
564
- uname in c.uget,
565
- uname in c.upget,
566
- uname in c.uadmin,
567
- uname in c.udot,
568
- )
569
- # skip uhtml because it's rarely needed
562
+ return vn.uaxs[uname]
570
563
 
571
564
  def get_perms(self, vpath , uname ) :
572
565
  zbl = self.can_access(vpath, uname)
573
- ret = "".join(ch for ch, ok in zip("rwmdgGa.", zbl) if ok)
566
+ ret = "".join(ch for ch, ok in zip("rwmdgGha.", zbl) if ok)
574
567
  if "rwmd" in ret and "a." in ret:
575
568
  ret += "A"
576
569
  return ret
@@ -763,20 +756,17 @@ class VFS(object):
763
756
  virt_vis[name] = vn2
764
757
  continue
765
758
 
766
- ok = False
767
- zx = vn2.axs
768
- axs = [zx.uread, zx.uwrite, zx.umove, zx.udel, zx.uget]
759
+ u_has = vn2.uaxs.get(uname) or [False] * 9
769
760
  for pset in permsets:
770
761
  ok = True
771
- for req, lst in zip(pset, axs):
772
- if req and uname not in lst:
762
+ for req, zb in zip(pset, u_has):
763
+ if req and not zb:
773
764
  ok = False
765
+ break
774
766
  if ok:
767
+ virt_vis[name] = vn2
775
768
  break
776
769
 
777
- if ok:
778
- virt_vis[name] = vn2
779
-
780
770
  if ".hist" in abspath:
781
771
  p = abspath.replace("\\", "/") if WINDOWS else abspath
782
772
  if p.endswith("/.hist"):
@@ -1961,9 +1951,18 @@ class AuthSrv(object):
1961
1951
  axs_key = "u" + perm
1962
1952
  for vp, vol in vfs.all_vols.items():
1963
1953
  zx = getattr(vol.axs, axs_key)
1964
- if "*" in zx:
1954
+ if "*" in zx and "-@acct" not in zx:
1965
1955
  for usr in unames:
1966
1956
  zx.add(usr)
1957
+ for zs in list(zx):
1958
+ if zs.startswith("-"):
1959
+ zx.discard(zs)
1960
+ zs = zs[1:]
1961
+ zx.discard(zs)
1962
+ if zs.startswith("@"):
1963
+ zs = zs[1:]
1964
+ for zs in grps.get(zs) or []:
1965
+ zx.discard(zs)
1967
1966
 
1968
1967
  # aread,... = dict[uname, list[volnames] or []]
1969
1968
  umap = {x: [] for x in unames}
@@ -1975,6 +1974,23 @@ class AuthSrv(object):
1975
1974
  umap[usr].sort()
1976
1975
  setattr(vfs, "a" + perm, umap)
1977
1976
 
1977
+ for vol in vfs.all_nodes.values():
1978
+ za = vol.axs
1979
+ vol.uaxs = {
1980
+ un: (
1981
+ un in za.uread,
1982
+ un in za.uwrite,
1983
+ un in za.umove,
1984
+ un in za.udel,
1985
+ un in za.uget,
1986
+ un in za.upget,
1987
+ un in za.uhtml,
1988
+ un in za.uadmin,
1989
+ un in za.udot,
1990
+ )
1991
+ for un in unames
1992
+ }
1993
+
1978
1994
  all_users = {}
1979
1995
  missing_users = {}
1980
1996
  associated_users = {}
@@ -2308,6 +2324,10 @@ class AuthSrv(object):
2308
2324
  free_umask = False
2309
2325
  have_reflink = False
2310
2326
  for vol in vfs.all_nodes.values():
2327
+ if os.path.isfile(vol.realpath):
2328
+ vol.flags["is_file"] = True
2329
+ vol.flags["d2d"] = True
2330
+
2311
2331
  if (self.args.e2ds and vol.axs.uwrite) or self.args.e2dsa:
2312
2332
  vol.flags["e2ds"] = True
2313
2333
 
@@ -2577,6 +2597,15 @@ class AuthSrv(object):
2577
2597
  for x in drop:
2578
2598
  vol.flags.pop(x)
2579
2599
 
2600
+ zi = vol.flags.get("lifetime") or 0
2601
+ zi2 = time.time() // (86400 * 365)
2602
+ zi3 = zi2 * 86400 * 365
2603
+ if zi < 0 or zi > zi3:
2604
+ t = "the lifetime of volume [/%s] (%d) exceeds max value (%d years; %d)"
2605
+ t = t % (vol.vpath, zi, zi2, zi3)
2606
+ self.log(t, 1)
2607
+ raise Exception(t)
2608
+
2580
2609
  # verify tags mentioned by -mt[mp] are used by -mte
2581
2610
  local_mtp = {}
2582
2611
  local_only_mtp = {}
@@ -2613,7 +2642,14 @@ class AuthSrv(object):
2613
2642
  errors = True
2614
2643
 
2615
2644
  for vol in vfs.all_nodes.values():
2616
- if not vol.realpath or os.path.isfile(vol.realpath):
2645
+ if not vol.flags.get("is_file"):
2646
+ continue
2647
+ zs = "og opds xlink"
2648
+ for zs in zs.split():
2649
+ vol.flags.pop(zs, None)
2650
+
2651
+ for vol in vfs.all_nodes.values():
2652
+ if not vol.realpath or vol.flags.get("is_file"):
2617
2653
  continue
2618
2654
  ccs = vol.flags["casechk"][:1].lower()
2619
2655
  if ccs in ("y", "n"):
@@ -2743,9 +2779,13 @@ class AuthSrv(object):
2743
2779
  ["uadmin", "uadmin"],
2744
2780
  ]:
2745
2781
  u = list(sorted(getattr(zv.axs, attr)))
2746
- u = ["*"] if "*" in u else u
2747
- u = ", ".join("\033[35meverybody\033[0m" if x == "*" else x for x in u)
2748
- u = u if u else "\033[36m--none--\033[0m"
2782
+ if u == ["*"] and acct:
2783
+ u = ["\033[35monly-anonymous\033[0m"]
2784
+ elif "*" in u:
2785
+ u = ["\033[35meverybody\033[0m"]
2786
+ if not u:
2787
+ u = ["\033[36m--none--\033[0m"]
2788
+ u = ", ".join(u)
2749
2789
  t += "\n| {}: {}".format(txt, u)
2750
2790
 
2751
2791
  if "e2d" in zv.flags:
@@ -2857,8 +2897,6 @@ class AuthSrv(object):
2857
2897
 
2858
2898
  if have_reflink:
2859
2899
  t = "WARNING: Reflink-based dedup was requested, but %s. This will not work; files will be full copies instead."
2860
- if sys.version_info < (3, 14):
2861
- self.log(t % "your python version is not new enough", 1)
2862
2900
  if not sys.platform.startswith("linux"):
2863
2901
  self.log(t % "your OS is not Linux", 1)
2864
2902
 
@@ -3016,6 +3054,7 @@ class AuthSrv(object):
3016
3054
  "have_shr": self.args.shr,
3017
3055
  "shr_who": vf["shr_who"],
3018
3056
  "have_zip": not self.args.no_zip,
3057
+ "have_zls": not self.args.no_zls,
3019
3058
  "have_mv": not self.args.no_mv,
3020
3059
  "have_del": not self.args.no_del,
3021
3060
  "have_unpost": int(self.args.unpost),
@@ -3052,6 +3091,14 @@ class AuthSrv(object):
3052
3091
  "lifetime": vn.js_ls["lifetime"],
3053
3092
  "u2sort": self.args.u2sort,
3054
3093
  }
3094
+ zs = "ui_noacci ui_nocpla ui_noctxb ui_nolbar ui_nombar ui_nonav ui_notree ui_norepl ui_nosrvi"
3095
+ for zs in zs.split():
3096
+ if vf.get(zs):
3097
+ js_htm[zs] = 1
3098
+ zs = "notooltips"
3099
+ for zs in zs.split():
3100
+ if getattr(self.args, zs, False):
3101
+ js_htm[zs] = 1
3055
3102
  vn.js_htm = json_hesc(json.dumps(js_htm))
3056
3103
 
3057
3104
  vols = list(vfs.all_nodes.values())
@@ -3396,7 +3443,7 @@ class AuthSrv(object):
3396
3443
  raise Exception("volume not found: " + zs)
3397
3444
 
3398
3445
  self.log(str({"users": users, "vols": vols, "flags": flags}))
3399
- t = "/{}: read({}) write({}) move({}) del({}) dots({}) get({}) upGet({}) uadmin({})"
3446
+ t = "/{}: read({}) write({}) move({}) del({}) dots({}) get({}) upGet({}) html({}) uadmin({})"
3400
3447
  for k, zv in self.vfs.all_vols.items():
3401
3448
  vc = zv.axs
3402
3449
  vs = [
@@ -99,7 +99,11 @@ def utime(
99
99
 
100
100
 
101
101
  def utime_c(
102
- log , p , ts , follow_symlinks = True, throw = False
102
+ log ,
103
+ p ,
104
+ ts ,
105
+ follow_symlinks = True,
106
+ throw = False,
103
107
  ) :
104
108
  clamp = 0
105
109
  ov = ts
@@ -19,6 +19,7 @@ def vf_bmap() :
19
19
  "no_clone": "noclone",
20
20
  "no_dirsz": "nodirsz",
21
21
  "no_dupe": "nodupe",
22
+ "no_dupe_m": "nodupem",
22
23
  "no_forget": "noforget",
23
24
  "no_pipe": "nopipe",
24
25
  "no_robots": "norobots",
@@ -58,6 +59,15 @@ def vf_bmap() :
58
59
  "rm_partial",
59
60
  "rmagic",
60
61
  "rss",
62
+ "ui_noacci",
63
+ "ui_nocpla",
64
+ "ui_nolbar",
65
+ "ui_nombar",
66
+ "ui_nonav",
67
+ "ui_notree",
68
+ "ui_norepl",
69
+ "ui_nosrvi",
70
+ "ui_noctxb",
61
71
  "wo_up_readme",
62
72
  "wram",
63
73
  "xdev",
@@ -189,6 +199,7 @@ flagcats = {
189
199
  "safededup": "verify on-disk data before using it for dedup",
190
200
  "noclone": "take dupe data from clients, even if available on HDD",
191
201
  "nodupe": "rejects existing files (instead of linking/cloning them)",
202
+ "nodupem": "rejects existing files during moves as well",
192
203
  "chmod_d=755": "unix-permission for new dirs/folders",
193
204
  "chmod_f=644": "unix-permission for new files",
194
205
  "uid=573": "change owner of new files/folders to unix-user 573",
@@ -325,6 +336,15 @@ flagcats = {
325
336
  "md_sba": "value of iframe allow-prop for markdown-sandbox",
326
337
  "lg_sba": "value of iframe allow-prop for *logue-sandbox",
327
338
  "nohtml": "return html and markdown as text/html",
339
+ "ui_noacci": "hide account-info in the UI",
340
+ "ui_nocpla": "hide cpanel-link in the UI",
341
+ "ui_nolbar": "hide link-bar in the UI",
342
+ "ui_nombar": "hide top-menu in the UI",
343
+ "ui_nonav": "hide navpane+breadcrumbs in the UI",
344
+ "ui_notree": "hide navpane in the UI",
345
+ "ui_norepl": "hide repl-button in the UI",
346
+ "ui_nosrvi": "hide server-info in the UI",
347
+ "ui_noctxb": "hide context-buttons in the UI",
328
348
  },
329
349
  "opengraph (discord embeds)": {
330
350
  "og": "enable OG (disables hotlinking)",
@@ -194,7 +194,7 @@ class FtpFs(AbstractedFS):
194
194
  if not avfs:
195
195
  raise FSE(t.format(vpath), 1)
196
196
 
197
- cr, cw, cm, cd, _, _, _, _ = avfs.can_access("", self.h.uname)
197
+ cr, cw, cm, cd, _, _, _, _, _ = avfs.uaxs[self.h.uname]
198
198
  if r and not cr or w and not cw or m and not cm or d and not cd:
199
199
  raise FSE(t.format(vpath), 1)
200
200
 
@@ -247,7 +247,7 @@ class FtpFs(AbstractedFS):
247
247
 
248
248
  if w and need_unlink:
249
249
  if td >= -1 and td <= self.args.ftp_wt:
250
- # within permitted timeframe; unlink and accept
250
+ # within permitted timeframe; allow overwrite or resume
251
251
  do_it = True
252
252
  elif self.args.no_del or self.args.ftp_no_ow:
253
253
  # file too old, or overwrite not allowed; reject
@@ -264,7 +264,9 @@ class FtpFs(AbstractedFS):
264
264
  if not do_it:
265
265
  raise FSE("File already exists")
266
266
 
267
- wunlink(self.log, ap, VF_CAREFUL)
267
+ # Don't unlink file for append mode
268
+ elif "a" not in mode:
269
+ wunlink(self.log, ap, VF_CAREFUL)
268
270
 
269
271
  ret = open(fsenc(ap), mode, self.args.iobuf)
270
272
  if w and "fperms" in vfs.flags:
@@ -505,7 +507,7 @@ class FtpHandler(FTPHandler):
505
507
  0,
506
508
  self.cli_ip,
507
509
  time.time(),
508
- "",
510
+ None,
509
511
  )
510
512
  t = hr.get("rejectmsg") or ""
511
513
  if t or not hr: