copyparty 1.10.0__tar.gz → 1.10.2__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 (117) hide show
  1. {copyparty-1.10.0 → copyparty-1.10.2}/PKG-INFO +14 -7
  2. {copyparty-1.10.0 → copyparty-1.10.2}/README.md +12 -5
  3. {copyparty-1.10.0 → copyparty-1.10.2}/copyparty/__main__.py +4 -1
  4. {copyparty-1.10.0 → copyparty-1.10.2}/copyparty/__version__.py +2 -2
  5. {copyparty-1.10.0 → copyparty-1.10.2}/copyparty/authsrv.py +1 -1
  6. {copyparty-1.10.0 → copyparty-1.10.2}/copyparty/cfg.py +4 -2
  7. {copyparty-1.10.0 → copyparty-1.10.2}/copyparty/ftpd.py +3 -0
  8. {copyparty-1.10.0 → copyparty-1.10.2}/copyparty/httpcli.py +24 -9
  9. {copyparty-1.10.0 → copyparty-1.10.2}/copyparty/ico.py +3 -4
  10. {copyparty-1.10.0 → copyparty-1.10.2}/copyparty/tftpd.py +129 -9
  11. {copyparty-1.10.0 → copyparty-1.10.2}/copyparty/th_cli.py +27 -9
  12. {copyparty-1.10.0 → copyparty-1.10.2}/copyparty/th_srv.py +17 -12
  13. {copyparty-1.10.0 → copyparty-1.10.2}/copyparty/up2k.py +18 -11
  14. {copyparty-1.10.0 → copyparty-1.10.2}/copyparty/util.py +1 -1
  15. {copyparty-1.10.0 → copyparty-1.10.2}/copyparty/web/a/u2c.py +7 -7
  16. copyparty-1.10.2/copyparty/web/baguettebox.js.gz +0 -0
  17. copyparty-1.10.2/copyparty/web/browser.css.gz +0 -0
  18. {copyparty-1.10.0 → copyparty-1.10.2}/copyparty/web/browser.html +1 -0
  19. copyparty-1.10.2/copyparty/web/browser.js.gz +0 -0
  20. {copyparty-1.10.0 → copyparty-1.10.2}/copyparty/web/browser2.html +1 -0
  21. {copyparty-1.10.0 → copyparty-1.10.2}/copyparty/web/cf.html +1 -0
  22. copyparty-1.10.2/copyparty/web/deps/marked.js.gz +0 -0
  23. {copyparty-1.10.0 → copyparty-1.10.2}/copyparty/web/md.html +1 -0
  24. {copyparty-1.10.0 → copyparty-1.10.2}/copyparty/web/mde.html +1 -0
  25. {copyparty-1.10.0 → copyparty-1.10.2}/copyparty/web/msg.html +2 -1
  26. {copyparty-1.10.0 → copyparty-1.10.2}/copyparty/web/splash.html +1 -0
  27. {copyparty-1.10.0 → copyparty-1.10.2}/copyparty/web/svcs.html +1 -0
  28. copyparty-1.10.2/copyparty/web/ui.css.gz +0 -0
  29. {copyparty-1.10.0 → copyparty-1.10.2}/copyparty.egg-info/PKG-INFO +14 -7
  30. {copyparty-1.10.0 → copyparty-1.10.2}/copyparty.egg-info/requires.txt +1 -1
  31. {copyparty-1.10.0 → copyparty-1.10.2}/pyproject.toml +1 -1
  32. copyparty-1.10.0/copyparty/web/baguettebox.js.gz +0 -0
  33. copyparty-1.10.0/copyparty/web/browser.css.gz +0 -0
  34. copyparty-1.10.0/copyparty/web/browser.js.gz +0 -0
  35. copyparty-1.10.0/copyparty/web/deps/marked.js.gz +0 -0
  36. copyparty-1.10.0/copyparty/web/ui.css.gz +0 -0
  37. {copyparty-1.10.0 → copyparty-1.10.2}/LICENSE +0 -0
  38. {copyparty-1.10.0 → copyparty-1.10.2}/copyparty/__init__.py +0 -0
  39. {copyparty-1.10.0 → copyparty-1.10.2}/copyparty/bos/__init__.py +0 -0
  40. {copyparty-1.10.0 → copyparty-1.10.2}/copyparty/bos/bos.py +0 -0
  41. {copyparty-1.10.0 → copyparty-1.10.2}/copyparty/bos/path.py +0 -0
  42. {copyparty-1.10.0 → copyparty-1.10.2}/copyparty/broker_mp.py +0 -0
  43. {copyparty-1.10.0 → copyparty-1.10.2}/copyparty/broker_mpw.py +0 -0
  44. {copyparty-1.10.0 → copyparty-1.10.2}/copyparty/broker_thr.py +0 -0
  45. {copyparty-1.10.0 → copyparty-1.10.2}/copyparty/broker_util.py +0 -0
  46. {copyparty-1.10.0 → copyparty-1.10.2}/copyparty/cert.py +0 -0
  47. {copyparty-1.10.0 → copyparty-1.10.2}/copyparty/dxml.py +0 -0
  48. {copyparty-1.10.0 → copyparty-1.10.2}/copyparty/fsutil.py +0 -0
  49. {copyparty-1.10.0 → copyparty-1.10.2}/copyparty/httpconn.py +0 -0
  50. {copyparty-1.10.0 → copyparty-1.10.2}/copyparty/httpsrv.py +0 -0
  51. {copyparty-1.10.0 → copyparty-1.10.2}/copyparty/mdns.py +0 -0
  52. {copyparty-1.10.0 → copyparty-1.10.2}/copyparty/metrics.py +0 -0
  53. {copyparty-1.10.0 → copyparty-1.10.2}/copyparty/mtag.py +0 -0
  54. {copyparty-1.10.0 → copyparty-1.10.2}/copyparty/multicast.py +0 -0
  55. {copyparty-1.10.0 → copyparty-1.10.2}/copyparty/pwhash.py +0 -0
  56. {copyparty-1.10.0 → copyparty-1.10.2}/copyparty/res/COPYING.txt +0 -0
  57. {copyparty-1.10.0 → copyparty-1.10.2}/copyparty/res/__init__.py +0 -0
  58. {copyparty-1.10.0 → copyparty-1.10.2}/copyparty/res/insecure.pem +0 -0
  59. {copyparty-1.10.0 → copyparty-1.10.2}/copyparty/smbd.py +0 -0
  60. {copyparty-1.10.0 → copyparty-1.10.2}/copyparty/ssdp.py +0 -0
  61. {copyparty-1.10.0 → copyparty-1.10.2}/copyparty/star.py +0 -0
  62. {copyparty-1.10.0 → copyparty-1.10.2}/copyparty/stolen/__init__.py +0 -0
  63. {copyparty-1.10.0 → copyparty-1.10.2}/copyparty/stolen/dnslib/__init__.py +0 -0
  64. {copyparty-1.10.0 → copyparty-1.10.2}/copyparty/stolen/dnslib/bimap.py +0 -0
  65. {copyparty-1.10.0 → copyparty-1.10.2}/copyparty/stolen/dnslib/bit.py +0 -0
  66. {copyparty-1.10.0 → copyparty-1.10.2}/copyparty/stolen/dnslib/buffer.py +0 -0
  67. {copyparty-1.10.0 → copyparty-1.10.2}/copyparty/stolen/dnslib/dns.py +0 -0
  68. {copyparty-1.10.0 → copyparty-1.10.2}/copyparty/stolen/dnslib/label.py +0 -0
  69. {copyparty-1.10.0 → copyparty-1.10.2}/copyparty/stolen/dnslib/lex.py +0 -0
  70. {copyparty-1.10.0 → copyparty-1.10.2}/copyparty/stolen/dnslib/ranges.py +0 -0
  71. {copyparty-1.10.0 → copyparty-1.10.2}/copyparty/stolen/ifaddr/__init__.py +0 -0
  72. {copyparty-1.10.0 → copyparty-1.10.2}/copyparty/stolen/ifaddr/_posix.py +0 -0
  73. {copyparty-1.10.0 → copyparty-1.10.2}/copyparty/stolen/ifaddr/_shared.py +0 -0
  74. {copyparty-1.10.0 → copyparty-1.10.2}/copyparty/stolen/ifaddr/_win32.py +0 -0
  75. {copyparty-1.10.0 → copyparty-1.10.2}/copyparty/stolen/qrcodegen.py +0 -0
  76. {copyparty-1.10.0 → copyparty-1.10.2}/copyparty/stolen/surrogateescape.py +0 -0
  77. {copyparty-1.10.0 → copyparty-1.10.2}/copyparty/sutil.py +0 -0
  78. {copyparty-1.10.0 → copyparty-1.10.2}/copyparty/svchub.py +0 -0
  79. {copyparty-1.10.0 → copyparty-1.10.2}/copyparty/szip.py +0 -0
  80. {copyparty-1.10.0 → copyparty-1.10.2}/copyparty/tcpsrv.py +0 -0
  81. {copyparty-1.10.0 → copyparty-1.10.2}/copyparty/u2idx.py +0 -0
  82. {copyparty-1.10.0 → copyparty-1.10.2}/copyparty/web/a/__init__.py +0 -0
  83. {copyparty-1.10.0 → copyparty-1.10.2}/copyparty/web/a/partyfuse.py +0 -0
  84. {copyparty-1.10.0 → copyparty-1.10.2}/copyparty/web/a/webdav-cfg.bat +0 -0
  85. {copyparty-1.10.0 → copyparty-1.10.2}/copyparty/web/dbg-audio.js.gz +0 -0
  86. {copyparty-1.10.0 → copyparty-1.10.2}/copyparty/web/dd/2.png +0 -0
  87. {copyparty-1.10.0 → copyparty-1.10.2}/copyparty/web/dd/3.png +0 -0
  88. {copyparty-1.10.0 → copyparty-1.10.2}/copyparty/web/dd/4.png +0 -0
  89. {copyparty-1.10.0 → copyparty-1.10.2}/copyparty/web/dd/5.png +0 -0
  90. {copyparty-1.10.0 → copyparty-1.10.2}/copyparty/web/dd/__init__.py +0 -0
  91. {copyparty-1.10.0 → copyparty-1.10.2}/copyparty/web/deps/__init__.py +0 -0
  92. {copyparty-1.10.0 → copyparty-1.10.2}/copyparty/web/deps/mini-fa.css.gz +0 -0
  93. {copyparty-1.10.0 → copyparty-1.10.2}/copyparty/web/deps/mini-fa.woff +0 -0
  94. {copyparty-1.10.0 → copyparty-1.10.2}/copyparty/web/deps/prism.css.gz +0 -0
  95. {copyparty-1.10.0 → copyparty-1.10.2}/copyparty/web/deps/prism.js.gz +0 -0
  96. {copyparty-1.10.0 → copyparty-1.10.2}/copyparty/web/deps/prismd.css.gz +0 -0
  97. {copyparty-1.10.0 → copyparty-1.10.2}/copyparty/web/deps/scp.woff2 +0 -0
  98. {copyparty-1.10.0 → copyparty-1.10.2}/copyparty/web/deps/sha512.ac.js.gz +0 -0
  99. {copyparty-1.10.0 → copyparty-1.10.2}/copyparty/web/deps/sha512.hw.js.gz +0 -0
  100. {copyparty-1.10.0 → copyparty-1.10.2}/copyparty/web/md.css.gz +0 -0
  101. {copyparty-1.10.0 → copyparty-1.10.2}/copyparty/web/md.js.gz +0 -0
  102. {copyparty-1.10.0 → copyparty-1.10.2}/copyparty/web/md2.css.gz +0 -0
  103. {copyparty-1.10.0 → copyparty-1.10.2}/copyparty/web/md2.js.gz +0 -0
  104. {copyparty-1.10.0 → copyparty-1.10.2}/copyparty/web/mde.css.gz +0 -0
  105. {copyparty-1.10.0 → copyparty-1.10.2}/copyparty/web/mde.js.gz +0 -0
  106. {copyparty-1.10.0 → copyparty-1.10.2}/copyparty/web/msg.css.gz +0 -0
  107. {copyparty-1.10.0 → copyparty-1.10.2}/copyparty/web/splash.css.gz +0 -0
  108. {copyparty-1.10.0 → copyparty-1.10.2}/copyparty/web/splash.js.gz +0 -0
  109. {copyparty-1.10.0 → copyparty-1.10.2}/copyparty/web/svcs.js.gz +0 -0
  110. {copyparty-1.10.0 → copyparty-1.10.2}/copyparty/web/up2k.js.gz +0 -0
  111. {copyparty-1.10.0 → copyparty-1.10.2}/copyparty/web/util.js.gz +0 -0
  112. {copyparty-1.10.0 → copyparty-1.10.2}/copyparty/web/w.hash.js.gz +0 -0
  113. {copyparty-1.10.0 → copyparty-1.10.2}/copyparty.egg-info/SOURCES.txt +0 -0
  114. {copyparty-1.10.0 → copyparty-1.10.2}/copyparty.egg-info/dependency_links.txt +0 -0
  115. {copyparty-1.10.0 → copyparty-1.10.2}/copyparty.egg-info/entry_points.txt +0 -0
  116. {copyparty-1.10.0 → copyparty-1.10.2}/copyparty.egg-info/top_level.txt +0 -0
  117. {copyparty-1.10.0 → copyparty-1.10.2}/setup.cfg +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: copyparty
3
- Version: 1.10.0
3
+ Version: 1.10.2
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
@@ -48,7 +48,7 @@ Provides-Extra: ftps
48
48
  Requires-Dist: pyftpdlib; extra == "ftps"
49
49
  Requires-Dist: pyopenssl; extra == "ftps"
50
50
  Provides-Extra: tftpd
51
- Requires-Dist: partftpy>=0.2.0; extra == "tftpd"
51
+ Requires-Dist: partftpy>=0.3.0; extra == "tftpd"
52
52
  Provides-Extra: pwhash
53
53
  Requires-Dist: argon2-cffi; extra == "pwhash"
54
54
 
@@ -1008,17 +1008,24 @@ a TFTP server (read/write) can be started using `--tftp 3969` (you probably wan
1008
1008
  * based on [partftpy](https://github.com/9001/partftpy)
1009
1009
  * no accounts; read from world-readable folders, write to world-writable, overwrite in world-deletable
1010
1010
  * needs a dedicated port (cannot share with the HTTP/HTTPS API)
1011
- * run as root to use the spec-recommended port `69` (nice)
1011
+ * run as root (or see below) to use the spec-recommended port `69` (nice)
1012
1012
  * can reply from a predefined portrange (good for firewalls)
1013
1013
  * only supports the binary/octet/image transfer mode (no netascii)
1014
1014
  * [RFC 7440](https://datatracker.ietf.org/doc/html/rfc7440) is **not** supported, so will be extremely slow over WAN
1015
- * expect 1100 KiB/s over 1000BASE-T, 400-500 KiB/s over wifi, 200 on bad wifi
1015
+ * assuming default blksize (512), expect 1100 KiB/s over 100BASE-T, 400-500 KiB/s over wifi, 200 on bad wifi
1016
+
1017
+ most clients expect to find TFTP on port 69, but on linux and macos you need to be root to listen on that. Alternatively, listen on 3969 and use NAT on the server to forward 69 to that port;
1018
+ * on linux: `iptables -t nat -A PREROUTING -i eth0 -p udp --dport 69 -j REDIRECT --to-port 3969`
1016
1019
 
1017
1020
  some recommended TFTP clients:
1021
+ * curl (cross-platform, read/write)
1022
+ * get: `curl --tftp-blksize 1428 tftp://127.0.0.1:3969/firmware.bin`
1023
+ * put: `curl --tftp-blksize 1428 -T firmware.bin tftp://127.0.0.1:3969/`
1018
1024
  * windows: `tftp.exe` (you probably already have it)
1025
+ * `tftp -i 127.0.0.1 put firmware.bin`
1019
1026
  * linux: `tftp-hpa`, `atftp`
1020
- * `tftp 127.0.0.1 3969 -v -m binary -c put firmware.bin`
1021
- * `curl tftp://127.0.0.1:3969/firmware.bin` (read-only)
1027
+ * `atftp --option "blksize 1428" 127.0.0.1 3969 -p -l firmware.bin -r firmware.bin`
1028
+ * `tftp -v -m binary 127.0.0.1 3969 -c put firmware.bin`
1022
1029
 
1023
1030
 
1024
1031
  ## smb server
@@ -1051,7 +1058,7 @@ known client bugs:
1051
1058
  * however smb1 is buggy and is not enabled by default on win10 onwards
1052
1059
  * windows cannot access folders which contain filenames with invalid unicode or forbidden characters (`<>:"/\|?*`), or names ending with `.`
1053
1060
 
1054
- the smb protocol listens on TCP port 445, which is a privileged port on linux and macos, which would require running copyparty as root. However, this can be avoided by listening on another port using `--smb-port 3945` and then using NAT to forward the traffic from 445 to there;
1061
+ the smb protocol listens on TCP port 445, which is a privileged port on linux and macos, which would require running copyparty as root. However, this can be avoided by listening on another port using `--smb-port 3945` and then using NAT on the server to forward the traffic from 445 to there;
1055
1062
  * on linux: `iptables -t nat -A PREROUTING -i eth0 -p tcp --dport 445 -j REDIRECT --to-port 3945`
1056
1063
 
1057
1064
  authenticate with one of the following:
@@ -954,17 +954,24 @@ a TFTP server (read/write) can be started using `--tftp 3969` (you probably wan
954
954
  * based on [partftpy](https://github.com/9001/partftpy)
955
955
  * no accounts; read from world-readable folders, write to world-writable, overwrite in world-deletable
956
956
  * needs a dedicated port (cannot share with the HTTP/HTTPS API)
957
- * run as root to use the spec-recommended port `69` (nice)
957
+ * run as root (or see below) to use the spec-recommended port `69` (nice)
958
958
  * can reply from a predefined portrange (good for firewalls)
959
959
  * only supports the binary/octet/image transfer mode (no netascii)
960
960
  * [RFC 7440](https://datatracker.ietf.org/doc/html/rfc7440) is **not** supported, so will be extremely slow over WAN
961
- * expect 1100 KiB/s over 1000BASE-T, 400-500 KiB/s over wifi, 200 on bad wifi
961
+ * assuming default blksize (512), expect 1100 KiB/s over 100BASE-T, 400-500 KiB/s over wifi, 200 on bad wifi
962
+
963
+ most clients expect to find TFTP on port 69, but on linux and macos you need to be root to listen on that. Alternatively, listen on 3969 and use NAT on the server to forward 69 to that port;
964
+ * on linux: `iptables -t nat -A PREROUTING -i eth0 -p udp --dport 69 -j REDIRECT --to-port 3969`
962
965
 
963
966
  some recommended TFTP clients:
967
+ * curl (cross-platform, read/write)
968
+ * get: `curl --tftp-blksize 1428 tftp://127.0.0.1:3969/firmware.bin`
969
+ * put: `curl --tftp-blksize 1428 -T firmware.bin tftp://127.0.0.1:3969/`
964
970
  * windows: `tftp.exe` (you probably already have it)
971
+ * `tftp -i 127.0.0.1 put firmware.bin`
965
972
  * linux: `tftp-hpa`, `atftp`
966
- * `tftp 127.0.0.1 3969 -v -m binary -c put firmware.bin`
967
- * `curl tftp://127.0.0.1:3969/firmware.bin` (read-only)
973
+ * `atftp --option "blksize 1428" 127.0.0.1 3969 -p -l firmware.bin -r firmware.bin`
974
+ * `tftp -v -m binary 127.0.0.1 3969 -c put firmware.bin`
968
975
 
969
976
 
970
977
  ## smb server
@@ -997,7 +1004,7 @@ known client bugs:
997
1004
  * however smb1 is buggy and is not enabled by default on win10 onwards
998
1005
  * windows cannot access folders which contain filenames with invalid unicode or forbidden characters (`<>:"/\|?*`), or names ending with `.`
999
1006
 
1000
- the smb protocol listens on TCP port 445, which is a privileged port on linux and macos, which would require running copyparty as root. However, this can be avoided by listening on another port using `--smb-port 3945` and then using NAT to forward the traffic from 445 to there;
1007
+ the smb protocol listens on TCP port 445, which is a privileged port on linux and macos, which would require running copyparty as root. However, this can be avoided by listening on another port using `--smb-port 3945` and then using NAT on the server to forward the traffic from 445 to there;
1001
1008
  * on linux: `iptables -t nat -A PREROUTING -i eth0 -p tcp --dport 445 -j REDIRECT --to-port 3945`
1002
1009
 
1003
1010
  authenticate with one of the following:
@@ -1013,6 +1013,7 @@ def add_tftp(ap):
1013
1013
  ap2.add_argument("--tftp", metavar="PORT", type=int, help="enable TFTP server on \033[33mPORT\033[0m, for example \033[32m69 \033[0mor \033[32m3969")
1014
1014
  ap2.add_argument("--tftpv", action="store_true", help="verbose")
1015
1015
  ap2.add_argument("--tftpvv", action="store_true", help="verboser")
1016
+ ap2.add_argument("--tftp-no-fast", action="store_true", help="debug: disable optimizations")
1016
1017
  ap2.add_argument("--tftp-lsf", metavar="PTN", type=u, default="\\.?(dir|ls)(\\.txt)?", help="return a directory listing if a file with this name is requested and it does not exist; defaults matches .ls, dir, .dir.txt, ls.txt, ...")
1017
1018
  ap2.add_argument("--tftp-nols", action="store_true", help="if someone tries to download a directory, return an error instead of showing its directory listing")
1018
1019
  ap2.add_argument("--tftp-ipa", metavar="PFX", type=u, default="", help="only accept connections from IP-addresses starting with \033[33mPFX\033[0m; specify [\033[32many\033[0m] to disable inheriting \033[33m--ipa\033[0m. Example: [\033[32m127., 10.89., 192.168.\033[0m]")
@@ -1163,7 +1164,8 @@ def add_thumbnail(ap):
1163
1164
  ap2.add_argument("--th-mt", metavar="CORES", type=int, default=CORES, help="num cpu cores to use for generating thumbnails")
1164
1165
  ap2.add_argument("--th-convt", metavar="SEC", type=float, default=60, help="conversion timeout in seconds (volflag=convt)")
1165
1166
  ap2.add_argument("--th-ram-max", metavar="GB", type=float, default=6, help="max memory usage (GiB) permitted by thumbnailer; not very accurate")
1166
- ap2.add_argument("--th-no-crop", action="store_true", help="dynamic height; show full image by default (client can override in UI) (volflag=nocrop)")
1167
+ 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[32mfy\033[0m]=crop, [\033[32mfn\033[0m]=nocrop, [\033[32mfy\033[0m]=force-y, [\033[32mfn\033[0m]=force-n (volflag=crop)")
1168
+ 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[32mfy\033[0m]=yes, [\033[32mfn\033[0m]=no, [\033[32mfy\033[0m]=force-yes, [\033[32mfn\033[0m]=force-no (volflag=th3x)")
1167
1169
  ap2.add_argument("--th-dec", metavar="LIBS", default="vips,pil,ff", help="image decoders, in order of preference")
1168
1170
  ap2.add_argument("--th-no-jpg", action="store_true", help="disable jpg output")
1169
1171
  ap2.add_argument("--th-no-webp", action="store_true", help="disable webp output")
@@ -1423,6 +1425,7 @@ def main(argv = None) :
1423
1425
  deprecated = [
1424
1426
  ("--salt", "--warksalt"),
1425
1427
  ("--hdr-au-usr", "--idp-h-usr"),
1428
+ ("--th-no-crop", "--th-crop=n"),
1426
1429
  ]
1427
1430
  for dk, nk in deprecated:
1428
1431
  idx = -1
@@ -1,8 +1,8 @@
1
1
  # coding: utf-8
2
2
 
3
- VERSION = (1, 10, 0)
3
+ VERSION = (1, 10, 2)
4
4
  CODENAME = "tftp"
5
- BUILD_DT = (2024, 2, 15)
5
+ BUILD_DT = (2024, 2, 21)
6
6
 
7
7
  S_VERSION = ".".join(map(str, VERSION))
8
8
  S_BUILD_DT = "{0:04d}-{1:02d}-{2:02d}".format(*BUILD_DT)
@@ -186,7 +186,7 @@ class Lim(object):
186
186
  self.dft = int(time.time()) + 300
187
187
  self.dfv = get_df(abspath)[0] or 0
188
188
  for j in list(self.reg.values()) if self.reg else []:
189
- self.dfv -= int(j["size"] / len(j["hash"]) * len(j["need"]))
189
+ self.dfv -= int(j["size"] / (len(j["hash"]) or 999) * len(j["need"]))
190
190
 
191
191
  if already_written:
192
192
  sz = 0
@@ -20,7 +20,6 @@ def vf_bmap() :
20
20
  "no_thumb": "dthumb",
21
21
  "no_vthumb": "dvthumb",
22
22
  "no_athumb": "dathumb",
23
- "th_no_crop": "nocrop",
24
23
  }
25
24
  for k in (
26
25
  "dotsrch",
@@ -56,6 +55,8 @@ def vf_vmap() :
56
55
  "re_maxage": "scan",
57
56
  "th_convt": "convt",
58
57
  "th_size": "thsize",
58
+ "th_crop": "crop",
59
+ "th_x3": "th3x",
59
60
  }
60
61
  for k in (
61
62
  "dbd",
@@ -172,7 +173,8 @@ flagcats = {
172
173
  "dathumb": "disables audio thumbnails (spectrograms)",
173
174
  "dithumb": "disables image thumbnails",
174
175
  "thsize": "thumbnail res; WxH",
175
- "nocrop": "disable center-cropping by default",
176
+ "crop": "center-cropping (y/n/fy/fn)",
177
+ "th3x": "3x resolution (y/n/fy/fn)",
176
178
  "convt": "conversion timeout in seconds",
177
179
  },
178
180
  "handlers\n(better explained in --help-handlers)": {
@@ -20,6 +20,7 @@ from .authsrv import VFS
20
20
  from .bos import bos
21
21
  from .util import (
22
22
  Daemon,
23
+ ODict,
23
24
  Pebkac,
24
25
  exclude_dotfiles,
25
26
  fsenc,
@@ -540,6 +541,8 @@ class Ftpd(object):
540
541
  if self.args.ftp4:
541
542
  ips = [x for x in ips if ":" not in x]
542
543
 
544
+ ips = list(ODict.fromkeys(ips)) # dedup
545
+
543
546
  ioloop = IOLoop()
544
547
  for ip in ips:
545
548
  for h, lp in hs:
@@ -3135,11 +3135,15 @@ class HttpCli(object):
3135
3135
 
3136
3136
  ext = ext.rstrip(".") or "unk"
3137
3137
  if len(ext) > 11:
3138
- ext = "" + ext[-9:]
3138
+ ext = "~" + ext[-9:]
3139
3139
 
3140
+ return self.tx_svg(ext, exact)
3141
+
3142
+ def tx_svg(self, txt , small = False) :
3140
3143
  # chrome cannot handle more than ~2000 unique SVGs
3141
- chrome = " rv:" not in self.ua
3142
- mime, ico = self.ico.get(ext, not exact, chrome)
3144
+ # so url-param "raster" returns a png/webp instead
3145
+ # (useragent-sniffing kinshi due to caching proxies)
3146
+ mime, ico = self.ico.get(txt, not small, "raster" in self.uparam)
3143
3147
 
3144
3148
  lm = formatdate(self.E.t0, usegmt=True)
3145
3149
  self.reply(ico, mime=mime, headers={"Last-Modified": lm})
@@ -3403,6 +3407,9 @@ class HttpCli(object):
3403
3407
  self.reply(pt.encode("utf-8"), status=rc)
3404
3408
  return True
3405
3409
 
3410
+ if "th" in self.ouparam:
3411
+ return self.tx_svg("e" + pt[:3])
3412
+
3406
3413
  t = t.format(self.args.SR)
3407
3414
  qv = quotep(self.vpaths) + self.ourlq()
3408
3415
  html = self.j2s("splash", this=self, qvpath=qv, msg=t)
@@ -3783,12 +3790,15 @@ class HttpCli(object):
3783
3790
  if idx and hasattr(idx, "p_end"):
3784
3791
  icur = idx.get_cur(dbv.realpath)
3785
3792
 
3793
+ th_fmt = self.uparam.get("th")
3786
3794
  if self.can_read:
3787
- th_fmt = self.uparam.get("th")
3788
3795
  if th_fmt is not None:
3796
+ nothumb = "dthumb" in dbv.flags
3789
3797
  if is_dir:
3790
3798
  vrem = vrem.rstrip("/")
3791
- if icur and vrem:
3799
+ if nothumb:
3800
+ pass
3801
+ elif icur and vrem:
3792
3802
  q = "select fn from cv where rd=? and dn=?"
3793
3803
  crd, cdn = vrem.rsplit("/", 1) if "/" in vrem else ("", vrem)
3794
3804
  # no mojibake support:
@@ -3811,10 +3821,10 @@ class HttpCli(object):
3811
3821
  break
3812
3822
 
3813
3823
  if is_dir:
3814
- return self.tx_ico("a.folder")
3824
+ return self.tx_svg("folder")
3815
3825
 
3816
3826
  thp = None
3817
- if self.thumbcli:
3827
+ if self.thumbcli and not nothumb:
3818
3828
  thp = self.thumbcli.get(dbv, vrem, int(st.st_mtime), th_fmt)
3819
3829
 
3820
3830
  if thp:
@@ -3825,6 +3835,9 @@ class HttpCli(object):
3825
3835
 
3826
3836
  return self.tx_ico(rem)
3827
3837
 
3838
+ elif self.can_write and th_fmt is not None:
3839
+ return self.tx_svg("upload\nonly")
3840
+
3828
3841
  elif self.can_get and self.avn:
3829
3842
  axs = self.avn.axs
3830
3843
  if self.uname not in axs.uhtml:
@@ -3969,7 +3982,8 @@ class HttpCli(object):
3969
3982
  "idx": e2d,
3970
3983
  "itag": e2t,
3971
3984
  "dsort": vf["sort"],
3972
- "dfull": "nocrop" in vf,
3985
+ "dcrop": vf["crop"],
3986
+ "dth3x": vf["th3x"],
3973
3987
  "u2ts": vf["u2ts"],
3974
3988
  "lifetime": vn.flags.get("lifetime") or 0,
3975
3989
  "frand": bool(vn.flags.get("rand")),
@@ -3996,8 +4010,9 @@ class HttpCli(object):
3996
4010
  "sb_md": "" if "no_sb_md" in vf else (vf.get("md_sbf") or "y"),
3997
4011
  "readme": readme,
3998
4012
  "dgrid": "grid" in vf,
3999
- "dfull": "nocrop" in vf,
4000
4013
  "dsort": vf["sort"],
4014
+ "dcrop": vf["crop"],
4015
+ "dth3x": vf["th3x"],
4001
4016
  "themes": self.args.themes,
4002
4017
  "turbolvl": self.args.turbo,
4003
4018
  "u2j": self.args.u2j,
@@ -8,7 +8,7 @@ import re
8
8
 
9
9
  from .__init__ import PY2
10
10
  from .th_srv import HAVE_PIL, HAVE_PILF
11
- from .util import BytesIO # type: ignore
11
+ from .util import BytesIO, html_escape # type: ignore
12
12
 
13
13
 
14
14
  class Ico(object):
@@ -31,10 +31,9 @@ class Ico(object):
31
31
 
32
32
  w = 100
33
33
  h = 30
34
- if not self.args.th_no_crop and as_thumb:
34
+ if as_thumb:
35
35
  sw, sh = self.args.th_size.split("x")
36
36
  h = int(100.0 / (float(sw) / float(sh)))
37
- w = 100
38
37
 
39
38
  if chrome:
40
39
  # cannot handle more than ~2000 unique SVGs
@@ -99,6 +98,6 @@ class Ico(object):
99
98
  fill="#{}" font-family="monospace" font-size="14px" style="letter-spacing:.5px">{}</text>
100
99
  </g></svg>
101
100
  """
102
- svg = svg.format(h, c[:6], c[6:], ext)
101
+ svg = svg.format(h, c[:6], c[6:], html_escape(ext, True))
103
102
 
104
103
  return "image/svg+xml", svg.encode("utf-8")
@@ -10,19 +10,33 @@ except:
10
10
  self.__dict__.update(attr)
11
11
 
12
12
 
13
- import inspect
14
13
  import logging
15
14
  import os
15
+ import re
16
+ import socket
16
17
  import stat
18
+ import threading
19
+ import time
17
20
  from datetime import datetime
18
21
 
19
- from partftpy import TftpContexts, TftpServer, TftpStates
22
+ try:
23
+ import inspect
24
+ except:
25
+ pass
26
+
27
+ from partftpy import (
28
+ TftpContexts,
29
+ TftpPacketFactory,
30
+ TftpPacketTypes,
31
+ TftpServer,
32
+ TftpStates,
33
+ )
20
34
  from partftpy.TftpShared import TftpException
21
35
 
22
- from .__init__ import PY2, TYPE_CHECKING
36
+ from .__init__ import EXE, TYPE_CHECKING
23
37
  from .authsrv import VFS
24
38
  from .bos import bos
25
- from .util import BytesIO, Daemon, exclude_dotfiles, runhook, undot
39
+ from .util import BytesIO, Daemon, ODict, exclude_dotfiles, min_ex, runhook, undot
26
40
 
27
41
  if TYPE_CHECKING:
28
42
  from .svchub import SvcHub
@@ -32,6 +46,10 @@ lg = logging.getLogger("tftp")
32
46
  debug, info, warning, error = (lg.debug, lg.info, lg.warning, lg.error)
33
47
 
34
48
 
49
+ def noop(*a, **ka) :
50
+ pass
51
+
52
+
35
53
  def _serverInitial(self, pkt , raddress , rport ) :
36
54
  info("connection from %s:%s", raddress, rport)
37
55
  ret = _orig_serverInitial(self, pkt, raddress, rport)
@@ -53,6 +71,7 @@ class Tftpd(object):
53
71
  self.args = hub.args
54
72
  self.asrv = hub.asrv
55
73
  self.log = hub.log
74
+ self.mutex = threading.Lock()
56
75
 
57
76
  _hub[:] = []
58
77
  _hub.append(hub)
@@ -62,6 +81,38 @@ class Tftpd(object):
62
81
  lgr = logging.getLogger(x)
63
82
  lgr.setLevel(logging.DEBUG if self.args.tftpv else logging.INFO)
64
83
 
84
+ if not self.args.tftpv and not self.args.tftpvv:
85
+ # contexts -> states -> packettypes -> shared
86
+ # contexts -> packetfactory
87
+ # packetfactory -> packettypes
88
+ Cs = [
89
+ TftpPacketTypes,
90
+ TftpPacketFactory,
91
+ TftpStates,
92
+ TftpContexts,
93
+ TftpServer,
94
+ ]
95
+ cbak = []
96
+ if not self.args.tftp_no_fast and not EXE:
97
+ try:
98
+ import inspect
99
+
100
+ ptn = re.compile(r"(^\s*)log\.debug\(.*\)$")
101
+ for C in Cs:
102
+ cbak.append(C.__dict__)
103
+ src1 = inspect.getsource(C).split("\n")
104
+ src2 = "\n".join([ptn.sub("\\1pass", ln) for ln in src1])
105
+ cfn = C.__spec__.origin
106
+ exec (compile(src2, filename=cfn, mode="exec"), C.__dict__)
107
+ except Exception:
108
+ t = "failed to optimize tftp code; run with --tftp-noopt if there are issues:\n"
109
+ self.log("tftp", t + min_ex(), 3)
110
+ for n, zd in enumerate(cbak):
111
+ Cs[n].__dict__ = zd
112
+
113
+ for C in Cs:
114
+ C.log.debug = noop
115
+
65
116
  # patch vfs into partftpy
66
117
  TftpContexts.open = self._open
67
118
  TftpStates.open = self._open
@@ -99,21 +150,90 @@ class Tftpd(object):
99
150
  self.log("tftp", "IPv6 not supported for tftp; listening on 0.0.0.0", 3)
100
151
  ip = "0.0.0.0"
101
152
 
102
- self.ip = ip
103
153
  self.port = int(self.args.tftp)
104
- self.srv = TftpServer.TftpServer("/", self._ls)
105
- self.stop = self.srv.stop
154
+ self.srv = []
155
+ self.ips = []
106
156
 
107
157
  ports = []
108
158
  if self.args.tftp_pr:
109
159
  p1, p2 = [int(x) for x in self.args.tftp_pr.split("-")]
110
160
  ports = list(range(p1, p2 + 1))
111
161
 
112
- Daemon(self.srv.listen, "tftp", [self.ip, self.port], ka={"ports": ports})
162
+ ips = self.args.i
163
+ if "::" in ips:
164
+ ips.append("0.0.0.0")
165
+
166
+ if self.args.ftp4:
167
+ ips = [x for x in ips if ":" not in x]
168
+
169
+ ips = list(ODict.fromkeys(ips)) # dedup
170
+
171
+ for ip in ips:
172
+ name = "tftp_%s" % (ip,)
173
+ Daemon(self._start, name, [ip, ports])
174
+ time.sleep(0.2) # give dualstack a chance
113
175
 
114
176
  def nlog(self, msg , c = 0) :
115
177
  self.log("tftp", msg, c)
116
178
 
179
+ def _start(self, ip, ports):
180
+ fam = socket.AF_INET6 if ":" in ip else socket.AF_INET
181
+ have_been_alive = False
182
+ while True:
183
+ srv = TftpServer.TftpServer("/", self._ls)
184
+ with self.mutex:
185
+ self.srv.append(srv)
186
+ self.ips.append(ip)
187
+
188
+ try:
189
+ # this is the listen loop; it should block forever
190
+ srv.listen(ip, self.port, af_family=fam, ports=ports)
191
+ except:
192
+ with self.mutex:
193
+ self.srv.remove(srv)
194
+ self.ips.remove(ip)
195
+
196
+ try:
197
+ srv.sock.close()
198
+ except:
199
+ pass
200
+
201
+ try:
202
+ bound = bool(srv.listenport)
203
+ except:
204
+ bound = False
205
+
206
+ if bound:
207
+ # this instance has managed to bind at least once
208
+ have_been_alive = True
209
+
210
+ if have_been_alive:
211
+ t = "tftp server [%s]:%d crashed; restarting in 3 sec:\n%s"
212
+ error(t, ip, self.port, min_ex())
213
+ time.sleep(3)
214
+ continue
215
+
216
+ # server failed to start; could be due to dualstack (ipv6 managed to bind and this is ipv4)
217
+ if ip != "0.0.0.0" or "::" not in self.ips:
218
+ # nope, it's fatal
219
+ t = "tftp server [%s]:%d failed to start:\n%s"
220
+ error(t, ip, self.port, min_ex())
221
+
222
+ # yep; ignore
223
+ # (TODO: move the "listening @ ..." infolog in partftpy to
224
+ # after the bind attempt so it doesn't print twice)
225
+ return
226
+
227
+ info("tftp server [%s]:%d terminated", ip, self.port)
228
+ break
229
+
230
+ def stop(self):
231
+ with self.mutex:
232
+ srvs = self.srv[:]
233
+
234
+ for srv in srvs:
235
+ srv.stop()
236
+
117
237
  def _v2a(self, caller , vpath , perms , *a ) :
118
238
  vpath = vpath.replace("\\", "/").lstrip("/")
119
239
  if not perms:
@@ -187,7 +307,7 @@ class Tftpd(object):
187
307
  retl = ["# permissions: %s" % (", ".join(perms),)]
188
308
  retl += [fmt.format(*x) for x in ls]
189
309
  ret = "\n".join(retl).encode("utf-8", "replace")
190
- return BytesIO(ret)
310
+ return BytesIO(ret + b"\n")
191
311
 
192
312
  def _open(self, vpath , mode , *a , **ka ) :
193
313
  rd = wr = False
@@ -75,16 +75,34 @@ class ThumbCli(object):
75
75
  if rem.startswith(".hist/th/") and rem.split(".")[-1] in ["webp", "jpg", "png"]:
76
76
  return os.path.join(ptop, rem)
77
77
 
78
- if fmt == "j" and self.args.th_no_jpg:
79
- fmt = "w"
78
+ if fmt[:1] in "jw":
79
+ sfmt = fmt[:1]
80
80
 
81
- if fmt == "w":
82
- if (
83
- self.args.th_no_webp
84
- or (is_img and not self.can_webp)
85
- or (self.args.th_ff_jpg and (not is_img or preferred == "ff"))
86
- ):
87
- fmt = "j"
81
+ if sfmt == "j" and self.args.th_no_jpg:
82
+ sfmt = "w"
83
+
84
+ if sfmt == "w":
85
+ if (
86
+ self.args.th_no_webp
87
+ or (is_img and not self.can_webp)
88
+ or (self.args.th_ff_jpg and (not is_img or preferred == "ff"))
89
+ ):
90
+ sfmt = "j"
91
+
92
+ vf_crop = dbv.flags["crop"]
93
+ vf_th3x = dbv.flags["th3x"]
94
+
95
+ if "f" in vf_crop:
96
+ sfmt += "f" if "n" in vf_crop else ""
97
+ else:
98
+ sfmt += "f" if "f" in fmt else ""
99
+
100
+ if "f" in vf_th3x:
101
+ sfmt += "3" if "y" in vf_th3x else ""
102
+ else:
103
+ sfmt += "3" if "3" in fmt else ""
104
+
105
+ fmt = sfmt
88
106
 
89
107
  histpath = self.asrv.vfs.histtab.get(ptop)
90
108
  if not histpath:
@@ -94,8 +94,8 @@ def thumb_path(histpath , rem , mtime , fmt , ffa ) :
94
94
 
95
95
  # spectrograms are never cropped; strip fullsize flag
96
96
  ext = rem.split(".")[-1].lower()
97
- if ext in ffa and fmt in ("wf", "jf"):
98
- fmt = fmt[:1]
97
+ if ext in ffa and fmt[:2] in ("wf", "jf"):
98
+ fmt = fmt.replace("f", "")
99
99
 
100
100
  rd += "\n" + fmt
101
101
  h = hashlib.sha512(afsenc(rd)).digest()
@@ -197,9 +197,10 @@ class ThumbSrv(object):
197
197
  with self.mutex:
198
198
  return not self.nthr
199
199
 
200
- def getres(self, vn ) :
200
+ def getres(self, vn , fmt ) :
201
+ mul = 3 if "3" in fmt else 1
201
202
  w, h = vn.flags["thsize"].split("x")
202
- return int(w), int(h)
203
+ return int(w) * mul, int(h) * mul
203
204
 
204
205
  def get(self, ptop , rem , mtime , fmt ) :
205
206
  histpath = self.asrv.vfs.histtab.get(ptop)
@@ -361,7 +362,7 @@ class ThumbSrv(object):
361
362
 
362
363
  def fancy_pillow(self, im , fmt , vn ) :
363
364
  # exif_transpose is expensive (loads full image + unconditional copy)
364
- res = self.getres(vn)
365
+ res = self.getres(vn, fmt)
365
366
  r = max(*res) * 2
366
367
  im.thumbnail((r, r), resample=Image.LANCZOS)
367
368
  try:
@@ -376,7 +377,7 @@ class ThumbSrv(object):
376
377
  if rot in rots:
377
378
  im = im.transpose(rots[rot])
378
379
 
379
- if fmt.endswith("f"):
380
+ if "f" in fmt:
380
381
  im.thumbnail(res, resample=Image.LANCZOS)
381
382
  else:
382
383
  iw, ih = im.size
@@ -393,7 +394,7 @@ class ThumbSrv(object):
393
394
  im = self.fancy_pillow(im, fmt, vn)
394
395
  except Exception as ex:
395
396
  self.log("fancy_pillow {}".format(ex), "90")
396
- im.thumbnail(self.getres(vn))
397
+ im.thumbnail(self.getres(vn, fmt))
397
398
 
398
399
  fmts = ["RGB", "L"]
399
400
  args = {"quality": 40}
@@ -419,10 +420,10 @@ class ThumbSrv(object):
419
420
  def conv_vips(self, abspath , tpath , fmt , vn ) :
420
421
  self.wait4ram(0.2, tpath)
421
422
  crops = ["centre", "none"]
422
- if fmt.endswith("f"):
423
+ if "f" in fmt:
423
424
  crops = ["none"]
424
425
 
425
- w, h = self.getres(vn)
426
+ w, h = self.getres(vn, fmt)
426
427
  kw = {"height": h, "size": "down", "intent": "relative"}
427
428
 
428
429
  for c in crops:
@@ -451,12 +452,12 @@ class ThumbSrv(object):
451
452
  seek = [b"-ss", "{:.0f}".format(dur / 3).encode("utf-8")]
452
453
 
453
454
  scale = "scale={0}:{1}:force_original_aspect_ratio="
454
- if fmt.endswith("f"):
455
+ if "f" in fmt:
455
456
  scale += "decrease,setsar=1:1"
456
457
  else:
457
458
  scale += "increase,crop={0}:{1},setsar=1:1"
458
459
 
459
- res = self.getres(vn)
460
+ res = self.getres(vn, fmt)
460
461
  bscale = scale.format(*list(res)).encode("utf-8")
461
462
  # fmt: off
462
463
  cmd = [
@@ -591,7 +592,11 @@ class ThumbSrv(object):
591
592
  need = 0.2 + dur / coeff
592
593
  self.wait4ram(need, tpath)
593
594
 
594
- fc = "[0:a:0]aresample=48000{},showspectrumpic=s=640x512,crop=780:544:70:50[o]"
595
+ fc = "[0:a:0]aresample=48000{},showspectrumpic=s="
596
+ if "3" in fmt:
597
+ fc += "1280x1024,crop=1420:1056:70:48[o]"
598
+ else:
599
+ fc += "640x512,crop=780:544:70:48[o]"
595
600
 
596
601
  if self.args.th_ff_swr:
597
602
  fco = ":filter_size=128:cutoff=0.877"