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.
- {copyparty-1.10.0 → copyparty-1.10.2}/PKG-INFO +14 -7
- {copyparty-1.10.0 → copyparty-1.10.2}/README.md +12 -5
- {copyparty-1.10.0 → copyparty-1.10.2}/copyparty/__main__.py +4 -1
- {copyparty-1.10.0 → copyparty-1.10.2}/copyparty/__version__.py +2 -2
- {copyparty-1.10.0 → copyparty-1.10.2}/copyparty/authsrv.py +1 -1
- {copyparty-1.10.0 → copyparty-1.10.2}/copyparty/cfg.py +4 -2
- {copyparty-1.10.0 → copyparty-1.10.2}/copyparty/ftpd.py +3 -0
- {copyparty-1.10.0 → copyparty-1.10.2}/copyparty/httpcli.py +24 -9
- {copyparty-1.10.0 → copyparty-1.10.2}/copyparty/ico.py +3 -4
- {copyparty-1.10.0 → copyparty-1.10.2}/copyparty/tftpd.py +129 -9
- {copyparty-1.10.0 → copyparty-1.10.2}/copyparty/th_cli.py +27 -9
- {copyparty-1.10.0 → copyparty-1.10.2}/copyparty/th_srv.py +17 -12
- {copyparty-1.10.0 → copyparty-1.10.2}/copyparty/up2k.py +18 -11
- {copyparty-1.10.0 → copyparty-1.10.2}/copyparty/util.py +1 -1
- {copyparty-1.10.0 → copyparty-1.10.2}/copyparty/web/a/u2c.py +7 -7
- copyparty-1.10.2/copyparty/web/baguettebox.js.gz +0 -0
- copyparty-1.10.2/copyparty/web/browser.css.gz +0 -0
- {copyparty-1.10.0 → copyparty-1.10.2}/copyparty/web/browser.html +1 -0
- copyparty-1.10.2/copyparty/web/browser.js.gz +0 -0
- {copyparty-1.10.0 → copyparty-1.10.2}/copyparty/web/browser2.html +1 -0
- {copyparty-1.10.0 → copyparty-1.10.2}/copyparty/web/cf.html +1 -0
- copyparty-1.10.2/copyparty/web/deps/marked.js.gz +0 -0
- {copyparty-1.10.0 → copyparty-1.10.2}/copyparty/web/md.html +1 -0
- {copyparty-1.10.0 → copyparty-1.10.2}/copyparty/web/mde.html +1 -0
- {copyparty-1.10.0 → copyparty-1.10.2}/copyparty/web/msg.html +2 -1
- {copyparty-1.10.0 → copyparty-1.10.2}/copyparty/web/splash.html +1 -0
- {copyparty-1.10.0 → copyparty-1.10.2}/copyparty/web/svcs.html +1 -0
- copyparty-1.10.2/copyparty/web/ui.css.gz +0 -0
- {copyparty-1.10.0 → copyparty-1.10.2}/copyparty.egg-info/PKG-INFO +14 -7
- {copyparty-1.10.0 → copyparty-1.10.2}/copyparty.egg-info/requires.txt +1 -1
- {copyparty-1.10.0 → copyparty-1.10.2}/pyproject.toml +1 -1
- copyparty-1.10.0/copyparty/web/baguettebox.js.gz +0 -0
- copyparty-1.10.0/copyparty/web/browser.css.gz +0 -0
- copyparty-1.10.0/copyparty/web/browser.js.gz +0 -0
- copyparty-1.10.0/copyparty/web/deps/marked.js.gz +0 -0
- copyparty-1.10.0/copyparty/web/ui.css.gz +0 -0
- {copyparty-1.10.0 → copyparty-1.10.2}/LICENSE +0 -0
- {copyparty-1.10.0 → copyparty-1.10.2}/copyparty/__init__.py +0 -0
- {copyparty-1.10.0 → copyparty-1.10.2}/copyparty/bos/__init__.py +0 -0
- {copyparty-1.10.0 → copyparty-1.10.2}/copyparty/bos/bos.py +0 -0
- {copyparty-1.10.0 → copyparty-1.10.2}/copyparty/bos/path.py +0 -0
- {copyparty-1.10.0 → copyparty-1.10.2}/copyparty/broker_mp.py +0 -0
- {copyparty-1.10.0 → copyparty-1.10.2}/copyparty/broker_mpw.py +0 -0
- {copyparty-1.10.0 → copyparty-1.10.2}/copyparty/broker_thr.py +0 -0
- {copyparty-1.10.0 → copyparty-1.10.2}/copyparty/broker_util.py +0 -0
- {copyparty-1.10.0 → copyparty-1.10.2}/copyparty/cert.py +0 -0
- {copyparty-1.10.0 → copyparty-1.10.2}/copyparty/dxml.py +0 -0
- {copyparty-1.10.0 → copyparty-1.10.2}/copyparty/fsutil.py +0 -0
- {copyparty-1.10.0 → copyparty-1.10.2}/copyparty/httpconn.py +0 -0
- {copyparty-1.10.0 → copyparty-1.10.2}/copyparty/httpsrv.py +0 -0
- {copyparty-1.10.0 → copyparty-1.10.2}/copyparty/mdns.py +0 -0
- {copyparty-1.10.0 → copyparty-1.10.2}/copyparty/metrics.py +0 -0
- {copyparty-1.10.0 → copyparty-1.10.2}/copyparty/mtag.py +0 -0
- {copyparty-1.10.0 → copyparty-1.10.2}/copyparty/multicast.py +0 -0
- {copyparty-1.10.0 → copyparty-1.10.2}/copyparty/pwhash.py +0 -0
- {copyparty-1.10.0 → copyparty-1.10.2}/copyparty/res/COPYING.txt +0 -0
- {copyparty-1.10.0 → copyparty-1.10.2}/copyparty/res/__init__.py +0 -0
- {copyparty-1.10.0 → copyparty-1.10.2}/copyparty/res/insecure.pem +0 -0
- {copyparty-1.10.0 → copyparty-1.10.2}/copyparty/smbd.py +0 -0
- {copyparty-1.10.0 → copyparty-1.10.2}/copyparty/ssdp.py +0 -0
- {copyparty-1.10.0 → copyparty-1.10.2}/copyparty/star.py +0 -0
- {copyparty-1.10.0 → copyparty-1.10.2}/copyparty/stolen/__init__.py +0 -0
- {copyparty-1.10.0 → copyparty-1.10.2}/copyparty/stolen/dnslib/__init__.py +0 -0
- {copyparty-1.10.0 → copyparty-1.10.2}/copyparty/stolen/dnslib/bimap.py +0 -0
- {copyparty-1.10.0 → copyparty-1.10.2}/copyparty/stolen/dnslib/bit.py +0 -0
- {copyparty-1.10.0 → copyparty-1.10.2}/copyparty/stolen/dnslib/buffer.py +0 -0
- {copyparty-1.10.0 → copyparty-1.10.2}/copyparty/stolen/dnslib/dns.py +0 -0
- {copyparty-1.10.0 → copyparty-1.10.2}/copyparty/stolen/dnslib/label.py +0 -0
- {copyparty-1.10.0 → copyparty-1.10.2}/copyparty/stolen/dnslib/lex.py +0 -0
- {copyparty-1.10.0 → copyparty-1.10.2}/copyparty/stolen/dnslib/ranges.py +0 -0
- {copyparty-1.10.0 → copyparty-1.10.2}/copyparty/stolen/ifaddr/__init__.py +0 -0
- {copyparty-1.10.0 → copyparty-1.10.2}/copyparty/stolen/ifaddr/_posix.py +0 -0
- {copyparty-1.10.0 → copyparty-1.10.2}/copyparty/stolen/ifaddr/_shared.py +0 -0
- {copyparty-1.10.0 → copyparty-1.10.2}/copyparty/stolen/ifaddr/_win32.py +0 -0
- {copyparty-1.10.0 → copyparty-1.10.2}/copyparty/stolen/qrcodegen.py +0 -0
- {copyparty-1.10.0 → copyparty-1.10.2}/copyparty/stolen/surrogateescape.py +0 -0
- {copyparty-1.10.0 → copyparty-1.10.2}/copyparty/sutil.py +0 -0
- {copyparty-1.10.0 → copyparty-1.10.2}/copyparty/svchub.py +0 -0
- {copyparty-1.10.0 → copyparty-1.10.2}/copyparty/szip.py +0 -0
- {copyparty-1.10.0 → copyparty-1.10.2}/copyparty/tcpsrv.py +0 -0
- {copyparty-1.10.0 → copyparty-1.10.2}/copyparty/u2idx.py +0 -0
- {copyparty-1.10.0 → copyparty-1.10.2}/copyparty/web/a/__init__.py +0 -0
- {copyparty-1.10.0 → copyparty-1.10.2}/copyparty/web/a/partyfuse.py +0 -0
- {copyparty-1.10.0 → copyparty-1.10.2}/copyparty/web/a/webdav-cfg.bat +0 -0
- {copyparty-1.10.0 → copyparty-1.10.2}/copyparty/web/dbg-audio.js.gz +0 -0
- {copyparty-1.10.0 → copyparty-1.10.2}/copyparty/web/dd/2.png +0 -0
- {copyparty-1.10.0 → copyparty-1.10.2}/copyparty/web/dd/3.png +0 -0
- {copyparty-1.10.0 → copyparty-1.10.2}/copyparty/web/dd/4.png +0 -0
- {copyparty-1.10.0 → copyparty-1.10.2}/copyparty/web/dd/5.png +0 -0
- {copyparty-1.10.0 → copyparty-1.10.2}/copyparty/web/dd/__init__.py +0 -0
- {copyparty-1.10.0 → copyparty-1.10.2}/copyparty/web/deps/__init__.py +0 -0
- {copyparty-1.10.0 → copyparty-1.10.2}/copyparty/web/deps/mini-fa.css.gz +0 -0
- {copyparty-1.10.0 → copyparty-1.10.2}/copyparty/web/deps/mini-fa.woff +0 -0
- {copyparty-1.10.0 → copyparty-1.10.2}/copyparty/web/deps/prism.css.gz +0 -0
- {copyparty-1.10.0 → copyparty-1.10.2}/copyparty/web/deps/prism.js.gz +0 -0
- {copyparty-1.10.0 → copyparty-1.10.2}/copyparty/web/deps/prismd.css.gz +0 -0
- {copyparty-1.10.0 → copyparty-1.10.2}/copyparty/web/deps/scp.woff2 +0 -0
- {copyparty-1.10.0 → copyparty-1.10.2}/copyparty/web/deps/sha512.ac.js.gz +0 -0
- {copyparty-1.10.0 → copyparty-1.10.2}/copyparty/web/deps/sha512.hw.js.gz +0 -0
- {copyparty-1.10.0 → copyparty-1.10.2}/copyparty/web/md.css.gz +0 -0
- {copyparty-1.10.0 → copyparty-1.10.2}/copyparty/web/md.js.gz +0 -0
- {copyparty-1.10.0 → copyparty-1.10.2}/copyparty/web/md2.css.gz +0 -0
- {copyparty-1.10.0 → copyparty-1.10.2}/copyparty/web/md2.js.gz +0 -0
- {copyparty-1.10.0 → copyparty-1.10.2}/copyparty/web/mde.css.gz +0 -0
- {copyparty-1.10.0 → copyparty-1.10.2}/copyparty/web/mde.js.gz +0 -0
- {copyparty-1.10.0 → copyparty-1.10.2}/copyparty/web/msg.css.gz +0 -0
- {copyparty-1.10.0 → copyparty-1.10.2}/copyparty/web/splash.css.gz +0 -0
- {copyparty-1.10.0 → copyparty-1.10.2}/copyparty/web/splash.js.gz +0 -0
- {copyparty-1.10.0 → copyparty-1.10.2}/copyparty/web/svcs.js.gz +0 -0
- {copyparty-1.10.0 → copyparty-1.10.2}/copyparty/web/up2k.js.gz +0 -0
- {copyparty-1.10.0 → copyparty-1.10.2}/copyparty/web/util.js.gz +0 -0
- {copyparty-1.10.0 → copyparty-1.10.2}/copyparty/web/w.hash.js.gz +0 -0
- {copyparty-1.10.0 → copyparty-1.10.2}/copyparty.egg-info/SOURCES.txt +0 -0
- {copyparty-1.10.0 → copyparty-1.10.2}/copyparty.egg-info/dependency_links.txt +0 -0
- {copyparty-1.10.0 → copyparty-1.10.2}/copyparty.egg-info/entry_points.txt +0 -0
- {copyparty-1.10.0 → copyparty-1.10.2}/copyparty.egg-info/top_level.txt +0 -0
- {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.
|
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.
|
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
|
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
|
-
* `
|
1021
|
-
* `
|
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
|
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
|
-
* `
|
967
|
-
* `
|
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-
|
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
|
@@ -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
|
-
"
|
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 = "
|
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
|
-
|
3142
|
-
|
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
|
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.
|
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
|
-
"
|
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
|
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
|
-
|
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
|
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 =
|
105
|
-
self.
|
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
|
-
|
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
|
79
|
-
|
78
|
+
if fmt[:1] in "jw":
|
79
|
+
sfmt = fmt[:1]
|
80
80
|
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
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
|
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
|
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
|
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
|
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=
|
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"
|