mirror of
https://github.com/9001/copyparty.git
synced 2025-08-17 09:02:15 -06:00
Merge branch 'hovudstraum' into idp
This commit is contained in:
commit
1b52ef1f8a
3
.vscode/launch.json
vendored
3
.vscode/launch.json
vendored
|
@ -19,8 +19,7 @@
|
||||||
"-emp",
|
"-emp",
|
||||||
"-e2dsa",
|
"-e2dsa",
|
||||||
"-e2ts",
|
"-e2ts",
|
||||||
"-mtp",
|
"-mtp=.bpm=f,bin/mtag/audio-bpm.py",
|
||||||
".bpm=f,bin/mtag/audio-bpm.py",
|
|
||||||
"-aed:wark",
|
"-aed:wark",
|
||||||
"-vsrv::r:rw,ed:c,dupe",
|
"-vsrv::r:rw,ed:c,dupe",
|
||||||
"-vdist:dist:r"
|
"-vdist:dist:r"
|
||||||
|
|
46
README.md
46
README.md
|
@ -3,7 +3,7 @@
|
||||||
turn almost any device into a file server with resumable uploads/downloads using [*any*](#browser-support) web browser
|
turn almost any device into a file server with resumable uploads/downloads using [*any*](#browser-support) web browser
|
||||||
|
|
||||||
* server only needs Python (2 or 3), all dependencies optional
|
* server only needs Python (2 or 3), all dependencies optional
|
||||||
* 🔌 protocols: [http](#the-browser) // [ftp](#ftp-server) // [webdav](#webdav-server) // [smb/cifs](#smb-server)
|
* 🔌 protocols: [http](#the-browser) // [webdav](#webdav-server) // [ftp](#ftp-server) // [tftp](#tftp-server) // [smb/cifs](#smb-server)
|
||||||
* 📱 [android app](#android-app) // [iPhone shortcuts](#ios-shortcuts)
|
* 📱 [android app](#android-app) // [iPhone shortcuts](#ios-shortcuts)
|
||||||
|
|
||||||
👉 **[Get started](#quickstart)!** or visit the **[read-only demo server](https://a.ocv.me/pub/demo/)** 👀 running from a basement in finland
|
👉 **[Get started](#quickstart)!** or visit the **[read-only demo server](https://a.ocv.me/pub/demo/)** 👀 running from a basement in finland
|
||||||
|
@ -53,6 +53,7 @@ turn almost any device into a file server with resumable uploads/downloads using
|
||||||
* [ftp server](#ftp-server) - an FTP server can be started using `--ftp 3921`
|
* [ftp server](#ftp-server) - an FTP server can be started using `--ftp 3921`
|
||||||
* [webdav server](#webdav-server) - with read-write support
|
* [webdav server](#webdav-server) - with read-write support
|
||||||
* [connecting to webdav from windows](#connecting-to-webdav-from-windows) - using the GUI
|
* [connecting to webdav from windows](#connecting-to-webdav-from-windows) - using the GUI
|
||||||
|
* [tftp server](#tftp-server) - a TFTP server (read/write) can be started using `--tftp 3969`
|
||||||
* [smb server](#smb-server) - unsafe, slow, not recommended for wan
|
* [smb server](#smb-server) - unsafe, slow, not recommended for wan
|
||||||
* [browser ux](#browser-ux) - tweaking the ui
|
* [browser ux](#browser-ux) - tweaking the ui
|
||||||
* [file indexing](#file-indexing) - enables dedup and music search ++
|
* [file indexing](#file-indexing) - enables dedup and music search ++
|
||||||
|
@ -157,11 +158,11 @@ you may also want these, especially on servers:
|
||||||
and remember to open the ports you want; here's a complete example including every feature copyparty has to offer:
|
and remember to open the ports you want; here's a complete example including every feature copyparty has to offer:
|
||||||
```
|
```
|
||||||
firewall-cmd --permanent --add-port={80,443,3921,3923,3945,3990}/tcp # --zone=libvirt
|
firewall-cmd --permanent --add-port={80,443,3921,3923,3945,3990}/tcp # --zone=libvirt
|
||||||
firewall-cmd --permanent --add-port=12000-12099/tcp --permanent # --zone=libvirt
|
firewall-cmd --permanent --add-port=12000-12099/tcp # --zone=libvirt
|
||||||
firewall-cmd --permanent --add-port={1900,5353}/udp # --zone=libvirt
|
firewall-cmd --permanent --add-port={69,1900,3969,5353}/udp # --zone=libvirt
|
||||||
firewall-cmd --reload
|
firewall-cmd --reload
|
||||||
```
|
```
|
||||||
(1900:ssdp, 3921:ftp, 3923:http/https, 3945:smb, 3990:ftps, 5353:mdns, 12000:passive-ftp)
|
(69:tftp, 1900:ssdp, 3921:ftp, 3923:http/https, 3945:smb, 3969:tftp, 3990:ftps, 5353:mdns, 12000:passive-ftp)
|
||||||
|
|
||||||
|
|
||||||
## features
|
## features
|
||||||
|
@ -172,6 +173,7 @@ firewall-cmd --reload
|
||||||
* ☑ volumes (mountpoints)
|
* ☑ volumes (mountpoints)
|
||||||
* ☑ [accounts](#accounts-and-volumes)
|
* ☑ [accounts](#accounts-and-volumes)
|
||||||
* ☑ [ftp server](#ftp-server)
|
* ☑ [ftp server](#ftp-server)
|
||||||
|
* ☑ [tftp server](#tftp-server)
|
||||||
* ☑ [webdav server](#webdav-server)
|
* ☑ [webdav server](#webdav-server)
|
||||||
* ☑ [smb/cifs server](#smb-server)
|
* ☑ [smb/cifs server](#smb-server)
|
||||||
* ☑ [qr-code](#qr-code) for quick access
|
* ☑ [qr-code](#qr-code) for quick access
|
||||||
|
@ -943,6 +945,35 @@ known client bugs:
|
||||||
* latin-1 is fine, hiragana is not (not even as shift-jis on japanese xp)
|
* latin-1 is fine, hiragana is not (not even as shift-jis on japanese xp)
|
||||||
|
|
||||||
|
|
||||||
|
## tftp server
|
||||||
|
|
||||||
|
a TFTP server (read/write) can be started using `--tftp 3969` (you probably want [ftp](#ftp-server) instead unless you are *actually* communicating with hardware from the 90s (in which case we should definitely hang some time))
|
||||||
|
|
||||||
|
> that makes this the first RTX DECT Base that has been updated using copyparty 🎉
|
||||||
|
|
||||||
|
* based on [partftpy](https://github.com/9001/partftpy)
|
||||||
|
* no accounts; read from world-readable folders, write to world-writable, overwrite in world-deletable
|
||||||
|
* needs a dedicated port (cannot share with the HTTP/HTTPS API)
|
||||||
|
* run as root (or see below) to use the spec-recommended port `69` (nice)
|
||||||
|
* can reply from a predefined portrange (good for firewalls)
|
||||||
|
* only supports the binary/octet/image transfer mode (no netascii)
|
||||||
|
* [RFC 7440](https://datatracker.ietf.org/doc/html/rfc7440) is **not** supported, so will be extremely slow over WAN
|
||||||
|
* assuming default blksize (512), expect 1100 KiB/s over 100BASE-T, 400-500 KiB/s over wifi, 200 on bad wifi
|
||||||
|
|
||||||
|
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;
|
||||||
|
* on linux: `iptables -t nat -A PREROUTING -i eth0 -p udp --dport 69 -j REDIRECT --to-port 3969`
|
||||||
|
|
||||||
|
some recommended TFTP clients:
|
||||||
|
* curl (cross-platform, read/write)
|
||||||
|
* get: `curl --tftp-blksize 1428 tftp://127.0.0.1:3969/firmware.bin`
|
||||||
|
* put: `curl --tftp-blksize 1428 -T firmware.bin tftp://127.0.0.1:3969/`
|
||||||
|
* windows: `tftp.exe` (you probably already have it)
|
||||||
|
* `tftp -i 127.0.0.1 put firmware.bin`
|
||||||
|
* linux: `tftp-hpa`, `atftp`
|
||||||
|
* `atftp --option "blksize 1428" 127.0.0.1 3969 -p -l firmware.bin -r firmware.bin`
|
||||||
|
* `tftp -v -m binary 127.0.0.1 3969 -c put firmware.bin`
|
||||||
|
|
||||||
|
|
||||||
## smb server
|
## smb server
|
||||||
|
|
||||||
unsafe, slow, not recommended for wan, enable with `--smb` for read-only or `--smbw` for read-write
|
unsafe, slow, not recommended for wan, enable with `--smb` for read-only or `--smbw` for read-write
|
||||||
|
@ -973,7 +1004,7 @@ known client bugs:
|
||||||
* however smb1 is buggy and is not enabled by default on win10 onwards
|
* however smb1 is buggy and is not enabled by default on win10 onwards
|
||||||
* windows cannot access folders which contain filenames with invalid unicode or forbidden characters (`<>:"/\|?*`), or names ending with `.`
|
* windows cannot access folders which contain filenames with invalid unicode or forbidden characters (`<>:"/\|?*`), or names ending with `.`
|
||||||
|
|
||||||
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;
|
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;
|
||||||
* on linux: `iptables -t nat -A PREROUTING -i eth0 -p tcp --dport 445 -j REDIRECT --to-port 3945`
|
* on linux: `iptables -t nat -A PREROUTING -i eth0 -p tcp --dport 445 -j REDIRECT --to-port 3945`
|
||||||
|
|
||||||
authenticate with one of the following:
|
authenticate with one of the following:
|
||||||
|
@ -1673,6 +1704,7 @@ below are some tweaks roughly ordered by usefulness:
|
||||||
|
|
||||||
* `-q` disables logging and can help a bunch, even when combined with `-lo` to redirect logs to file
|
* `-q` disables logging and can help a bunch, even when combined with `-lo` to redirect logs to file
|
||||||
* `--hist` pointing to a fast location (ssd) will make directory listings and searches faster when `-e2d` or `-e2t` is set
|
* `--hist` pointing to a fast location (ssd) will make directory listings and searches faster when `-e2d` or `-e2t` is set
|
||||||
|
* and also makes thumbnails load faster, regardless of e2d/e2t
|
||||||
* `--no-hash .` when indexing a network-disk if you don't care about the actual filehashes and only want the names/tags searchable
|
* `--no-hash .` when indexing a network-disk if you don't care about the actual filehashes and only want the names/tags searchable
|
||||||
* `--no-htp --hash-mt=0 --mtag-mt=1 --th-mt=1` minimizes the number of threads; can help in some eccentric environments (like the vscode debugger)
|
* `--no-htp --hash-mt=0 --mtag-mt=1 --th-mt=1` minimizes the number of threads; can help in some eccentric environments (like the vscode debugger)
|
||||||
* `-j0` enables multiprocessing (actual multithreading), can reduce latency to `20+80/numCores` percent and generally improve performance in cpu-intensive workloads, for example:
|
* `-j0` enables multiprocessing (actual multithreading), can reduce latency to `20+80/numCores` percent and generally improve performance in cpu-intensive workloads, for example:
|
||||||
|
@ -1680,7 +1712,7 @@ below are some tweaks roughly ordered by usefulness:
|
||||||
* simultaneous downloads and uploads saturating a 20gbps connection
|
* simultaneous downloads and uploads saturating a 20gbps connection
|
||||||
* if `-e2d` is enabled, `-j2` gives 4x performance for directory listings; `-j4` gives 16x
|
* if `-e2d` is enabled, `-j2` gives 4x performance for directory listings; `-j4` gives 16x
|
||||||
|
|
||||||
...however it adds an overhead to internal communication so it might be a net loss, see if it works 4 u
|
...however it also increases the server/filesystem/HDD load during uploads, and adds an overhead to internal communication, so it is usually a better idea to don't
|
||||||
* using [pypy](https://www.pypy.org/) instead of [cpython](https://www.python.org/) *can* be 70% faster for some workloads, but slower for many others
|
* using [pypy](https://www.pypy.org/) instead of [cpython](https://www.python.org/) *can* be 70% faster for some workloads, but slower for many others
|
||||||
* and pypy can sometimes crash on startup with `-j0` (TODO make issue)
|
* and pypy can sometimes crash on startup with `-j0` (TODO make issue)
|
||||||
|
|
||||||
|
@ -1689,7 +1721,7 @@ below are some tweaks roughly ordered by usefulness:
|
||||||
|
|
||||||
when uploading files,
|
when uploading files,
|
||||||
|
|
||||||
* chrome is recommended, at least compared to firefox:
|
* chrome is recommended (unfortunately), at least compared to firefox:
|
||||||
* up to 90% faster when hashing, especially on SSDs
|
* up to 90% faster when hashing, especially on SSDs
|
||||||
* up to 40% faster when uploading over extremely fast internets
|
* up to 40% faster when uploading over extremely fast internets
|
||||||
* but [u2c.py](https://github.com/9001/copyparty/blob/hovudstraum/bin/u2c.py) can be 40% faster than chrome again
|
* but [u2c.py](https://github.com/9001/copyparty/blob/hovudstraum/bin/u2c.py) can be 40% faster than chrome again
|
||||||
|
|
14
bin/u2c.py
14
bin/u2c.py
|
@ -1,8 +1,8 @@
|
||||||
#!/usr/bin/env python3
|
#!/usr/bin/env python3
|
||||||
from __future__ import print_function, unicode_literals
|
from __future__ import print_function, unicode_literals
|
||||||
|
|
||||||
S_VERSION = "1.14"
|
S_VERSION = "1.15"
|
||||||
S_BUILD_DT = "2024-01-27"
|
S_BUILD_DT = "2024-02-18"
|
||||||
|
|
||||||
"""
|
"""
|
||||||
u2c.py: upload to copyparty
|
u2c.py: upload to copyparty
|
||||||
|
@ -29,7 +29,7 @@ import platform
|
||||||
import threading
|
import threading
|
||||||
import datetime
|
import datetime
|
||||||
|
|
||||||
EXE = sys.executable.endswith("exe")
|
EXE = bool(getattr(sys, "frozen", False))
|
||||||
|
|
||||||
try:
|
try:
|
||||||
import argparse
|
import argparse
|
||||||
|
@ -846,12 +846,12 @@ class Ctl(object):
|
||||||
txt = " "
|
txt = " "
|
||||||
|
|
||||||
if not self.up_br:
|
if not self.up_br:
|
||||||
spd = self.hash_b / (time.time() - self.t0)
|
spd = self.hash_b / ((time.time() - self.t0) or 1)
|
||||||
eta = (self.nbytes - self.hash_b) / (spd + 1)
|
eta = (self.nbytes - self.hash_b) / (spd or 1)
|
||||||
else:
|
else:
|
||||||
spd = self.up_br / (time.time() - self.t0_up)
|
spd = self.up_br / ((time.time() - self.t0_up) or 1)
|
||||||
spd = self.spd = (self.spd or spd) * 0.9 + spd * 0.1
|
spd = self.spd = (self.spd or spd) * 0.9 + spd * 0.1
|
||||||
eta = (self.nbytes - self.up_b) / (spd + 1)
|
eta = (self.nbytes - self.up_b) / (spd or 1)
|
||||||
|
|
||||||
spd = humansize(spd)
|
spd = humansize(spd)
|
||||||
self.eta = str(datetime.timedelta(seconds=int(eta)))
|
self.eta = str(datetime.timedelta(seconds=int(eta)))
|
||||||
|
|
|
@ -17,11 +17,6 @@
|
||||||
* `RequestURL`: full URL to the target folder
|
* `RequestURL`: full URL to the target folder
|
||||||
* `pw`: password (remove the `pw` line if anon-write)
|
* `pw`: password (remove the `pw` line if anon-write)
|
||||||
|
|
||||||
however if your copyparty is behind a reverse-proxy, you may want to use [`sharex-html.sxcu`](sharex-html.sxcu) instead:
|
|
||||||
* `RequestURL`: full URL to the target folder
|
|
||||||
* `URL`: full URL to the root folder (with trailing slash) followed by `$regex:1|1$`
|
|
||||||
* `pw`: password (remove `Parameters` if anon-write)
|
|
||||||
|
|
||||||
### [`send-to-cpp.contextlet.json`](send-to-cpp.contextlet.json)
|
### [`send-to-cpp.contextlet.json`](send-to-cpp.contextlet.json)
|
||||||
* browser integration, kind of? custom rightclick actions and stuff
|
* browser integration, kind of? custom rightclick actions and stuff
|
||||||
* rightclick a pic and send it to copyparty straight from your browser
|
* rightclick a pic and send it to copyparty straight from your browser
|
||||||
|
|
|
@ -1,8 +1,8 @@
|
||||||
# Maintainer: icxes <dev.null@need.moe>
|
# Maintainer: icxes <dev.null@need.moe>
|
||||||
pkgname=copyparty
|
pkgname=copyparty
|
||||||
pkgver="1.9.31"
|
pkgver="1.10.2"
|
||||||
pkgrel=1
|
pkgrel=1
|
||||||
pkgdesc="File server with accelerated resumable uploads, dedup, WebDAV, FTP, zeroconf, media indexer, thumbnails++"
|
pkgdesc="File server with accelerated resumable uploads, dedup, WebDAV, FTP, TFTP, zeroconf, media indexer, thumbnails++"
|
||||||
arch=("any")
|
arch=("any")
|
||||||
url="https://github.com/9001/${pkgname}"
|
url="https://github.com/9001/${pkgname}"
|
||||||
license=('MIT')
|
license=('MIT')
|
||||||
|
@ -21,7 +21,7 @@ optdepends=("ffmpeg: thumbnails for videos, images (slower) and audio, music tag
|
||||||
)
|
)
|
||||||
source=("https://github.com/9001/${pkgname}/releases/download/v${pkgver}/${pkgname}-${pkgver}.tar.gz")
|
source=("https://github.com/9001/${pkgname}/releases/download/v${pkgver}/${pkgname}-${pkgver}.tar.gz")
|
||||||
backup=("etc/${pkgname}.d/init" )
|
backup=("etc/${pkgname}.d/init" )
|
||||||
sha256sums=("a8ec1faf8cb224515355226882fdb2d1ab1de42d96ff78e148b930318867a71e")
|
sha256sums=("001be979a0fdd8ace7d48cab79a137c13b87b78be35fc9633430f45a2831c3ed")
|
||||||
|
|
||||||
build() {
|
build() {
|
||||||
cd "${srcdir}/${pkgname}-${pkgver}"
|
cd "${srcdir}/${pkgname}-${pkgver}"
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
{
|
{
|
||||||
"url": "https://github.com/9001/copyparty/releases/download/v1.9.31/copyparty-sfx.py",
|
"url": "https://github.com/9001/copyparty/releases/download/v1.10.2/copyparty-sfx.py",
|
||||||
"version": "1.9.31",
|
"version": "1.10.2",
|
||||||
"hash": "sha256-yp7qoiW5yzm2M7qVmYY7R+SyhZXlqL+JxsXV22aS+MM="
|
"hash": "sha256-O9lkN30gy3kwIH+39O4dN7agZPkuH36BDTk8mEsQCVg="
|
||||||
}
|
}
|
|
@ -1,19 +0,0 @@
|
||||||
{
|
|
||||||
"Version": "13.5.0",
|
|
||||||
"Name": "copyparty-html",
|
|
||||||
"DestinationType": "ImageUploader",
|
|
||||||
"RequestMethod": "POST",
|
|
||||||
"RequestURL": "http://127.0.0.1:3923/sharex",
|
|
||||||
"Parameters": {
|
|
||||||
"pw": "wark"
|
|
||||||
},
|
|
||||||
"Body": "MultipartFormData",
|
|
||||||
"Arguments": {
|
|
||||||
"act": "bput"
|
|
||||||
},
|
|
||||||
"FileFormName": "f",
|
|
||||||
"RegexList": [
|
|
||||||
"bytes // <a href=\"/([^\"]+)\""
|
|
||||||
],
|
|
||||||
"URL": "http://127.0.0.1:3923/$regex:1|1$"
|
|
||||||
}
|
|
|
@ -1,17 +1,19 @@
|
||||||
{
|
{
|
||||||
"Version": "13.5.0",
|
"Version": "15.0.0",
|
||||||
"Name": "copyparty",
|
"Name": "copyparty",
|
||||||
"DestinationType": "ImageUploader",
|
"DestinationType": "ImageUploader",
|
||||||
"RequestMethod": "POST",
|
"RequestMethod": "POST",
|
||||||
"RequestURL": "http://127.0.0.1:3923/sharex",
|
"RequestURL": "http://127.0.0.1:3923/sharex",
|
||||||
"Parameters": {
|
"Parameters": {
|
||||||
"pw": "wark",
|
|
||||||
"j": null
|
"j": null
|
||||||
},
|
},
|
||||||
|
"Headers": {
|
||||||
|
"pw": "PUT_YOUR_PASSWORD_HERE_MY_DUDE"
|
||||||
|
},
|
||||||
"Body": "MultipartFormData",
|
"Body": "MultipartFormData",
|
||||||
"Arguments": {
|
"Arguments": {
|
||||||
"act": "bput"
|
"act": "bput"
|
||||||
},
|
},
|
||||||
"FileFormName": "f",
|
"FileFormName": "f",
|
||||||
"URL": "$json:files[0].url$"
|
"URL": "{json:files[0].url}"
|
||||||
}
|
}
|
||||||
|
|
|
@ -43,6 +43,7 @@ from .util import (
|
||||||
DEF_MTH,
|
DEF_MTH,
|
||||||
IMPLICATIONS,
|
IMPLICATIONS,
|
||||||
JINJA_VER,
|
JINJA_VER,
|
||||||
|
PARTFTPY_VER,
|
||||||
PY_DESC,
|
PY_DESC,
|
||||||
PYFTPD_VER,
|
PYFTPD_VER,
|
||||||
SQLITE_VER,
|
SQLITE_VER,
|
||||||
|
@ -998,7 +999,7 @@ def add_zc_ssdp(ap):
|
||||||
|
|
||||||
|
|
||||||
def add_ftp(ap):
|
def add_ftp(ap):
|
||||||
ap2 = ap.add_argument_group('FTP options')
|
ap2 = ap.add_argument_group('FTP options (TCP only)')
|
||||||
ap2.add_argument("--ftp", metavar="PORT", type=int, help="enable FTP server on \033[33mPORT\033[0m, for example \033[32m3921")
|
ap2.add_argument("--ftp", metavar="PORT", type=int, help="enable FTP server on \033[33mPORT\033[0m, for example \033[32m3921")
|
||||||
ap2.add_argument("--ftps", metavar="PORT", type=int, help="enable FTPS server on \033[33mPORT\033[0m, for example \033[32m3990")
|
ap2.add_argument("--ftps", metavar="PORT", type=int, help="enable FTPS server on \033[33mPORT\033[0m, for example \033[32m3990")
|
||||||
ap2.add_argument("--ftpv", action="store_true", help="verbose")
|
ap2.add_argument("--ftpv", action="store_true", help="verbose")
|
||||||
|
@ -1018,6 +1019,18 @@ def add_webdav(ap):
|
||||||
ap2.add_argument("--dav-auth", action="store_true", help="force auth for all folders (required by davfs2 when only some folders are world-readable) (volflag=davauth)")
|
ap2.add_argument("--dav-auth", action="store_true", help="force auth for all folders (required by davfs2 when only some folders are world-readable) (volflag=davauth)")
|
||||||
|
|
||||||
|
|
||||||
|
def add_tftp(ap):
|
||||||
|
ap2 = ap.add_argument_group('TFTP options (UDP only)')
|
||||||
|
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")
|
||||||
|
ap2.add_argument("--tftpv", action="store_true", help="verbose")
|
||||||
|
ap2.add_argument("--tftpvv", action="store_true", help="verboser")
|
||||||
|
ap2.add_argument("--tftp-no-fast", action="store_true", help="debug: disable optimizations")
|
||||||
|
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, ...")
|
||||||
|
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")
|
||||||
|
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]")
|
||||||
|
ap2.add_argument("--tftp-pr", metavar="P-P", type=u, help="the range of UDP ports to use for data transfer, for example \033[32m12000-13000")
|
||||||
|
|
||||||
|
|
||||||
def add_smb(ap):
|
def add_smb(ap):
|
||||||
ap2 = ap.add_argument_group('SMB/CIFS options')
|
ap2 = ap.add_argument_group('SMB/CIFS options')
|
||||||
ap2.add_argument("--smb", action="store_true", help="enable smb (read-only) -- this requires running copyparty as root on linux and macos unless \033[33m--smb-port\033[0m is set above 1024 and your OS does port-forwarding from 445 to that.\n\033[1;31mWARNING:\033[0m this protocol is DANGEROUS and buggy! Never expose to the internet!")
|
ap2.add_argument("--smb", action="store_true", help="enable smb (read-only) -- this requires running copyparty as root on linux and macos unless \033[33m--smb-port\033[0m is set above 1024 and your OS does port-forwarding from 445 to that.\n\033[1;31mWARNING:\033[0m this protocol is DANGEROUS and buggy! Never expose to the internet!")
|
||||||
|
@ -1162,7 +1175,8 @@ def add_thumbnail(ap):
|
||||||
ap2.add_argument("--th-mt", metavar="CORES", type=int, default=CORES, help="num cpu cores to use for generating thumbnails")
|
ap2.add_argument("--th-mt", metavar="CORES", type=int, default=CORES, help="num cpu cores to use for generating thumbnails")
|
||||||
ap2.add_argument("--th-convt", metavar="SEC", type=float, default=60, help="conversion timeout in seconds (volflag=convt)")
|
ap2.add_argument("--th-convt", metavar="SEC", type=float, default=60, help="conversion timeout in seconds (volflag=convt)")
|
||||||
ap2.add_argument("--th-ram-max", metavar="GB", type=float, default=6, help="max memory usage (GiB) permitted by thumbnailer; not very accurate")
|
ap2.add_argument("--th-ram-max", metavar="GB", type=float, default=6, help="max memory usage (GiB) permitted by thumbnailer; not very accurate")
|
||||||
ap2.add_argument("--th-no-crop", action="store_true", help="dynamic height; show full image by default (client can override in UI) (volflag=nocrop)")
|
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)")
|
||||||
|
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)")
|
||||||
ap2.add_argument("--th-dec", metavar="LIBS", default="vips,pil,ff", help="image decoders, in order of preference")
|
ap2.add_argument("--th-dec", metavar="LIBS", default="vips,pil,ff", help="image decoders, in order of preference")
|
||||||
ap2.add_argument("--th-no-jpg", action="store_true", help="disable jpg output")
|
ap2.add_argument("--th-no-jpg", action="store_true", help="disable jpg output")
|
||||||
ap2.add_argument("--th-no-webp", action="store_true", help="disable webp output")
|
ap2.add_argument("--th-no-webp", action="store_true", help="disable webp output")
|
||||||
|
@ -1327,6 +1341,7 @@ def run_argparse(
|
||||||
add_transcoding(ap)
|
add_transcoding(ap)
|
||||||
add_ftp(ap)
|
add_ftp(ap)
|
||||||
add_webdav(ap)
|
add_webdav(ap)
|
||||||
|
add_tftp(ap)
|
||||||
add_smb(ap)
|
add_smb(ap)
|
||||||
add_safety(ap)
|
add_safety(ap)
|
||||||
add_salt(ap, fk_salt, ah_salt)
|
add_salt(ap, fk_salt, ah_salt)
|
||||||
|
@ -1380,7 +1395,7 @@ def main(argv: Optional[list[str]] = None) -> None:
|
||||||
if argv is None:
|
if argv is None:
|
||||||
argv = sys.argv
|
argv = sys.argv
|
||||||
|
|
||||||
f = '\033[36mcopyparty v{} "\033[35m{}\033[36m" ({})\n{}\033[0;36m\n sqlite v{} | jinja2 v{} | pyftpd v{}\n\033[0m'
|
f = '\033[36mcopyparty v{} "\033[35m{}\033[36m" ({})\n{}\033[0;36m\n sqlite {} | jinja {} | pyftpd {} | tftp {}\n\033[0m'
|
||||||
f = f.format(
|
f = f.format(
|
||||||
S_VERSION,
|
S_VERSION,
|
||||||
CODENAME,
|
CODENAME,
|
||||||
|
@ -1389,6 +1404,7 @@ def main(argv: Optional[list[str]] = None) -> None:
|
||||||
SQLITE_VER,
|
SQLITE_VER,
|
||||||
JINJA_VER,
|
JINJA_VER,
|
||||||
PYFTPD_VER,
|
PYFTPD_VER,
|
||||||
|
PARTFTPY_VER,
|
||||||
)
|
)
|
||||||
lprint(f)
|
lprint(f)
|
||||||
|
|
||||||
|
@ -1420,6 +1436,7 @@ def main(argv: Optional[list[str]] = None) -> None:
|
||||||
deprecated: list[tuple[str, str]] = [
|
deprecated: list[tuple[str, str]] = [
|
||||||
("--salt", "--warksalt"),
|
("--salt", "--warksalt"),
|
||||||
("--hdr-au-usr", "--idp-h-usr"),
|
("--hdr-au-usr", "--idp-h-usr"),
|
||||||
|
("--th-no-crop", "--th-crop=n"),
|
||||||
]
|
]
|
||||||
for dk, nk in deprecated:
|
for dk, nk in deprecated:
|
||||||
idx = -1
|
idx = -1
|
||||||
|
|
|
@ -1,8 +1,8 @@
|
||||||
# coding: utf-8
|
# coding: utf-8
|
||||||
|
|
||||||
VERSION = (1, 9, 31)
|
VERSION = (1, 10, 2)
|
||||||
CODENAME = "prometheable"
|
CODENAME = "tftp"
|
||||||
BUILD_DT = (2024, 2, 3)
|
BUILD_DT = (2024, 2, 21)
|
||||||
|
|
||||||
S_VERSION = ".".join(map(str, VERSION))
|
S_VERSION = ".".join(map(str, VERSION))
|
||||||
S_BUILD_DT = "{0:04d}-{1:02d}-{2:02d}".format(*BUILD_DT)
|
S_BUILD_DT = "{0:04d}-{1:02d}-{2:02d}".format(*BUILD_DT)
|
||||||
|
|
|
@ -197,7 +197,7 @@ class Lim(object):
|
||||||
self.dft = int(time.time()) + 300
|
self.dft = int(time.time()) + 300
|
||||||
self.dfv = get_df(abspath)[0] or 0
|
self.dfv = get_df(abspath)[0] or 0
|
||||||
for j in list(self.reg.values()) if self.reg else []:
|
for j in list(self.reg.values()) if self.reg else []:
|
||||||
self.dfv -= int(j["size"] / len(j["hash"]) * len(j["need"]))
|
self.dfv -= int(j["size"] / (len(j["hash"]) or 999) * len(j["need"]))
|
||||||
|
|
||||||
if already_written:
|
if already_written:
|
||||||
sz = 0
|
sz = 0
|
||||||
|
|
|
@ -20,7 +20,6 @@ def vf_bmap() -> dict[str, str]:
|
||||||
"no_thumb": "dthumb",
|
"no_thumb": "dthumb",
|
||||||
"no_vthumb": "dvthumb",
|
"no_vthumb": "dvthumb",
|
||||||
"no_athumb": "dathumb",
|
"no_athumb": "dathumb",
|
||||||
"th_no_crop": "nocrop",
|
|
||||||
}
|
}
|
||||||
for k in (
|
for k in (
|
||||||
"dotsrch",
|
"dotsrch",
|
||||||
|
@ -56,6 +55,8 @@ def vf_vmap() -> dict[str, str]:
|
||||||
"re_maxage": "scan",
|
"re_maxage": "scan",
|
||||||
"th_convt": "convt",
|
"th_convt": "convt",
|
||||||
"th_size": "thsize",
|
"th_size": "thsize",
|
||||||
|
"th_crop": "crop",
|
||||||
|
"th_x3": "th3x",
|
||||||
}
|
}
|
||||||
for k in (
|
for k in (
|
||||||
"dbd",
|
"dbd",
|
||||||
|
@ -172,7 +173,8 @@ flagcats = {
|
||||||
"dathumb": "disables audio thumbnails (spectrograms)",
|
"dathumb": "disables audio thumbnails (spectrograms)",
|
||||||
"dithumb": "disables image thumbnails",
|
"dithumb": "disables image thumbnails",
|
||||||
"thsize": "thumbnail res; WxH",
|
"thsize": "thumbnail res; WxH",
|
||||||
"nocrop": "disable center-cropping by default",
|
"crop": "center-cropping (y/n/fy/fn)",
|
||||||
|
"th3x": "3x resolution (y/n/fy/fn)",
|
||||||
"convt": "conversion timeout in seconds",
|
"convt": "conversion timeout in seconds",
|
||||||
},
|
},
|
||||||
"handlers\n(better explained in --help-handlers)": {
|
"handlers\n(better explained in --help-handlers)": {
|
||||||
|
|
|
@ -20,6 +20,7 @@ from .authsrv import VFS
|
||||||
from .bos import bos
|
from .bos import bos
|
||||||
from .util import (
|
from .util import (
|
||||||
Daemon,
|
Daemon,
|
||||||
|
ODict,
|
||||||
Pebkac,
|
Pebkac,
|
||||||
exclude_dotfiles,
|
exclude_dotfiles,
|
||||||
fsenc,
|
fsenc,
|
||||||
|
@ -545,6 +546,8 @@ class Ftpd(object):
|
||||||
if self.args.ftp4:
|
if self.args.ftp4:
|
||||||
ips = [x for x in ips if ":" not in x]
|
ips = [x for x in ips if ":" not in x]
|
||||||
|
|
||||||
|
ips = list(ODict.fromkeys(ips)) # dedup
|
||||||
|
|
||||||
ioloop = IOLoop()
|
ioloop = IOLoop()
|
||||||
for ip in ips:
|
for ip in ips:
|
||||||
for h, lp in hs:
|
for h, lp in hs:
|
||||||
|
|
|
@ -115,7 +115,7 @@ class HttpCli(object):
|
||||||
|
|
||||||
self.t0 = time.time()
|
self.t0 = time.time()
|
||||||
self.conn = conn
|
self.conn = conn
|
||||||
self.mutex = conn.mutex # mypy404
|
self.u2mutex = conn.u2mutex # mypy404
|
||||||
self.s = conn.s
|
self.s = conn.s
|
||||||
self.sr = conn.sr
|
self.sr = conn.sr
|
||||||
self.ip = conn.addr[0]
|
self.ip = conn.addr[0]
|
||||||
|
@ -1999,8 +1999,11 @@ class HttpCli(object):
|
||||||
except:
|
except:
|
||||||
raise Pebkac(500, min_ex())
|
raise Pebkac(500, min_ex())
|
||||||
|
|
||||||
x = self.conn.hsrv.broker.ask("up2k.handle_json", body, self.u2fh.aps)
|
# not to protect u2fh, but to prevent handshakes while files are closing
|
||||||
ret = x.get()
|
with self.u2mutex:
|
||||||
|
x = self.conn.hsrv.broker.ask("up2k.handle_json", body, self.u2fh.aps)
|
||||||
|
ret = x.get()
|
||||||
|
|
||||||
if self.is_vproxied:
|
if self.is_vproxied:
|
||||||
if "purl" in ret:
|
if "purl" in ret:
|
||||||
ret["purl"] = self.args.SR + ret["purl"]
|
ret["purl"] = self.args.SR + ret["purl"]
|
||||||
|
@ -2105,7 +2108,7 @@ class HttpCli(object):
|
||||||
f = None
|
f = None
|
||||||
fpool = not self.args.no_fpool and sprs
|
fpool = not self.args.no_fpool and sprs
|
||||||
if fpool:
|
if fpool:
|
||||||
with self.mutex:
|
with self.u2mutex:
|
||||||
try:
|
try:
|
||||||
f = self.u2fh.pop(path)
|
f = self.u2fh.pop(path)
|
||||||
except:
|
except:
|
||||||
|
@ -2148,7 +2151,7 @@ class HttpCli(object):
|
||||||
if not fpool:
|
if not fpool:
|
||||||
f.close()
|
f.close()
|
||||||
else:
|
else:
|
||||||
with self.mutex:
|
with self.u2mutex:
|
||||||
self.u2fh.put(path, f)
|
self.u2fh.put(path, f)
|
||||||
except:
|
except:
|
||||||
# maybe busted handle (eg. disk went full)
|
# maybe busted handle (eg. disk went full)
|
||||||
|
@ -2167,7 +2170,7 @@ class HttpCli(object):
|
||||||
return False
|
return False
|
||||||
|
|
||||||
if not num_left and fpool:
|
if not num_left and fpool:
|
||||||
with self.mutex:
|
with self.u2mutex:
|
||||||
self.u2fh.close(path)
|
self.u2fh.close(path)
|
||||||
|
|
||||||
if not num_left and not self.args.nw:
|
if not num_left and not self.args.nw:
|
||||||
|
@ -3147,11 +3150,15 @@ class HttpCli(object):
|
||||||
|
|
||||||
ext = ext.rstrip(".") or "unk"
|
ext = ext.rstrip(".") or "unk"
|
||||||
if len(ext) > 11:
|
if len(ext) > 11:
|
||||||
ext = "⋯" + ext[-9:]
|
ext = "~" + ext[-9:]
|
||||||
|
|
||||||
|
return self.tx_svg(ext, exact)
|
||||||
|
|
||||||
|
def tx_svg(self, txt: str, small: bool = False) -> bool:
|
||||||
# chrome cannot handle more than ~2000 unique SVGs
|
# chrome cannot handle more than ~2000 unique SVGs
|
||||||
chrome = " rv:" not in self.ua
|
# so url-param "raster" returns a png/webp instead
|
||||||
mime, ico = self.ico.get(ext, not exact, chrome)
|
# (useragent-sniffing kinshi due to caching proxies)
|
||||||
|
mime, ico = self.ico.get(txt, not small, "raster" in self.uparam)
|
||||||
|
|
||||||
lm = formatdate(self.E.t0, usegmt=True)
|
lm = formatdate(self.E.t0, usegmt=True)
|
||||||
self.reply(ico, mime=mime, headers={"Last-Modified": lm})
|
self.reply(ico, mime=mime, headers={"Last-Modified": lm})
|
||||||
|
@ -3415,6 +3422,9 @@ class HttpCli(object):
|
||||||
self.reply(pt.encode("utf-8"), status=rc)
|
self.reply(pt.encode("utf-8"), status=rc)
|
||||||
return True
|
return True
|
||||||
|
|
||||||
|
if "th" in self.ouparam:
|
||||||
|
return self.tx_svg("e" + pt[:3])
|
||||||
|
|
||||||
t = t.format(self.args.SR)
|
t = t.format(self.args.SR)
|
||||||
qv = quotep(self.vpaths) + self.ourlq()
|
qv = quotep(self.vpaths) + self.ourlq()
|
||||||
html = self.j2s("splash", this=self, qvpath=qv, msg=t)
|
html = self.j2s("splash", this=self, qvpath=qv, msg=t)
|
||||||
|
@ -3795,12 +3805,15 @@ class HttpCli(object):
|
||||||
if idx and hasattr(idx, "p_end"):
|
if idx and hasattr(idx, "p_end"):
|
||||||
icur = idx.get_cur(dbv.realpath)
|
icur = idx.get_cur(dbv.realpath)
|
||||||
|
|
||||||
|
th_fmt = self.uparam.get("th")
|
||||||
if self.can_read:
|
if self.can_read:
|
||||||
th_fmt = self.uparam.get("th")
|
|
||||||
if th_fmt is not None:
|
if th_fmt is not None:
|
||||||
|
nothumb = "dthumb" in dbv.flags
|
||||||
if is_dir:
|
if is_dir:
|
||||||
vrem = vrem.rstrip("/")
|
vrem = vrem.rstrip("/")
|
||||||
if icur and vrem:
|
if nothumb:
|
||||||
|
pass
|
||||||
|
elif icur and vrem:
|
||||||
q = "select fn from cv where rd=? and dn=?"
|
q = "select fn from cv where rd=? and dn=?"
|
||||||
crd, cdn = vrem.rsplit("/", 1) if "/" in vrem else ("", vrem)
|
crd, cdn = vrem.rsplit("/", 1) if "/" in vrem else ("", vrem)
|
||||||
# no mojibake support:
|
# no mojibake support:
|
||||||
|
@ -3823,10 +3836,10 @@ class HttpCli(object):
|
||||||
break
|
break
|
||||||
|
|
||||||
if is_dir:
|
if is_dir:
|
||||||
return self.tx_ico("a.folder")
|
return self.tx_svg("folder")
|
||||||
|
|
||||||
thp = None
|
thp = None
|
||||||
if self.thumbcli:
|
if self.thumbcli and not nothumb:
|
||||||
thp = self.thumbcli.get(dbv, vrem, int(st.st_mtime), th_fmt)
|
thp = self.thumbcli.get(dbv, vrem, int(st.st_mtime), th_fmt)
|
||||||
|
|
||||||
if thp:
|
if thp:
|
||||||
|
@ -3837,6 +3850,9 @@ class HttpCli(object):
|
||||||
|
|
||||||
return self.tx_ico(rem)
|
return self.tx_ico(rem)
|
||||||
|
|
||||||
|
elif self.can_write and th_fmt is not None:
|
||||||
|
return self.tx_svg("upload\nonly")
|
||||||
|
|
||||||
elif self.can_get and self.avn:
|
elif self.can_get and self.avn:
|
||||||
axs = self.avn.axs
|
axs = self.avn.axs
|
||||||
if self.uname not in axs.uhtml:
|
if self.uname not in axs.uhtml:
|
||||||
|
@ -3981,7 +3997,8 @@ class HttpCli(object):
|
||||||
"idx": e2d,
|
"idx": e2d,
|
||||||
"itag": e2t,
|
"itag": e2t,
|
||||||
"dsort": vf["sort"],
|
"dsort": vf["sort"],
|
||||||
"dfull": "nocrop" in vf,
|
"dcrop": vf["crop"],
|
||||||
|
"dth3x": vf["th3x"],
|
||||||
"u2ts": vf["u2ts"],
|
"u2ts": vf["u2ts"],
|
||||||
"lifetime": vn.flags.get("lifetime") or 0,
|
"lifetime": vn.flags.get("lifetime") or 0,
|
||||||
"frand": bool(vn.flags.get("rand")),
|
"frand": bool(vn.flags.get("rand")),
|
||||||
|
@ -4008,8 +4025,9 @@ class HttpCli(object):
|
||||||
"sb_md": "" if "no_sb_md" in vf else (vf.get("md_sbf") or "y"),
|
"sb_md": "" if "no_sb_md" in vf else (vf.get("md_sbf") or "y"),
|
||||||
"readme": readme,
|
"readme": readme,
|
||||||
"dgrid": "grid" in vf,
|
"dgrid": "grid" in vf,
|
||||||
"dfull": "nocrop" in vf,
|
|
||||||
"dsort": vf["sort"],
|
"dsort": vf["sort"],
|
||||||
|
"dcrop": vf["crop"],
|
||||||
|
"dth3x": vf["th3x"],
|
||||||
"themes": self.args.themes,
|
"themes": self.args.themes,
|
||||||
"turbolvl": self.args.turbo,
|
"turbolvl": self.args.turbo,
|
||||||
"u2j": self.args.u2j,
|
"u2j": self.args.u2j,
|
||||||
|
|
|
@ -50,7 +50,7 @@ class HttpConn(object):
|
||||||
self.addr = addr
|
self.addr = addr
|
||||||
self.hsrv = hsrv
|
self.hsrv = hsrv
|
||||||
|
|
||||||
self.mutex: threading.Lock = hsrv.mutex # mypy404
|
self.u2mutex: threading.Lock = hsrv.u2mutex # mypy404
|
||||||
self.args: argparse.Namespace = hsrv.args # mypy404
|
self.args: argparse.Namespace = hsrv.args # mypy404
|
||||||
self.E: EnvParams = self.args.E
|
self.E: EnvParams = self.args.E
|
||||||
self.asrv: AuthSrv = hsrv.asrv # mypy404
|
self.asrv: AuthSrv = hsrv.asrv # mypy404
|
||||||
|
|
|
@ -117,6 +117,7 @@ class HttpSrv(object):
|
||||||
self.bound: set[tuple[str, int]] = set()
|
self.bound: set[tuple[str, int]] = set()
|
||||||
self.name = "hsrv" + nsuf
|
self.name = "hsrv" + nsuf
|
||||||
self.mutex = threading.Lock()
|
self.mutex = threading.Lock()
|
||||||
|
self.u2mutex = threading.Lock()
|
||||||
self.stopping = False
|
self.stopping = False
|
||||||
|
|
||||||
self.tp_nthr = 0 # actual
|
self.tp_nthr = 0 # actual
|
||||||
|
@ -220,7 +221,7 @@ class HttpSrv(object):
|
||||||
def periodic(self) -> None:
|
def periodic(self) -> None:
|
||||||
while True:
|
while True:
|
||||||
time.sleep(2 if self.tp_ncli or self.ncli else 10)
|
time.sleep(2 if self.tp_ncli or self.ncli else 10)
|
||||||
with self.mutex:
|
with self.u2mutex, self.mutex:
|
||||||
self.u2fh.clean()
|
self.u2fh.clean()
|
||||||
if self.tp_q:
|
if self.tp_q:
|
||||||
self.tp_ncli = max(self.ncli, self.tp_ncli - 2)
|
self.tp_ncli = max(self.ncli, self.tp_ncli - 2)
|
||||||
|
|
|
@ -8,7 +8,7 @@ import re
|
||||||
|
|
||||||
from .__init__ import PY2
|
from .__init__ import PY2
|
||||||
from .th_srv import HAVE_PIL, HAVE_PILF
|
from .th_srv import HAVE_PIL, HAVE_PILF
|
||||||
from .util import BytesIO # type: ignore
|
from .util import BytesIO, html_escape # type: ignore
|
||||||
|
|
||||||
|
|
||||||
class Ico(object):
|
class Ico(object):
|
||||||
|
@ -31,10 +31,9 @@ class Ico(object):
|
||||||
|
|
||||||
w = 100
|
w = 100
|
||||||
h = 30
|
h = 30
|
||||||
if not self.args.th_no_crop and as_thumb:
|
if as_thumb:
|
||||||
sw, sh = self.args.th_size.split("x")
|
sw, sh = self.args.th_size.split("x")
|
||||||
h = int(100.0 / (float(sw) / float(sh)))
|
h = int(100.0 / (float(sw) / float(sh)))
|
||||||
w = 100
|
|
||||||
|
|
||||||
if chrome:
|
if chrome:
|
||||||
# cannot handle more than ~2000 unique SVGs
|
# cannot handle more than ~2000 unique SVGs
|
||||||
|
@ -99,6 +98,6 @@ class Ico(object):
|
||||||
fill="#{}" font-family="monospace" font-size="14px" style="letter-spacing:.5px">{}</text>
|
fill="#{}" font-family="monospace" font-size="14px" style="letter-spacing:.5px">{}</text>
|
||||||
</g></svg>
|
</g></svg>
|
||||||
"""
|
"""
|
||||||
svg = svg.format(h, c[:6], c[6:], ext)
|
svg = svg.format(h, c[:6], c[6:], html_escape(ext, True))
|
||||||
|
|
||||||
return "image/svg+xml", svg.encode("utf-8")
|
return "image/svg+xml", svg.encode("utf-8")
|
||||||
|
|
|
@ -133,7 +133,7 @@ class SvcHub(object):
|
||||||
if not self._process_config():
|
if not self._process_config():
|
||||||
raise Exception(BAD_CFG)
|
raise Exception(BAD_CFG)
|
||||||
|
|
||||||
# for non-http clients (ftp)
|
# for non-http clients (ftp, tftp)
|
||||||
self.bans: dict[str, int] = {}
|
self.bans: dict[str, int] = {}
|
||||||
self.gpwd = Garda(self.args.ban_pw)
|
self.gpwd = Garda(self.args.ban_pw)
|
||||||
self.g404 = Garda(self.args.ban_404)
|
self.g404 = Garda(self.args.ban_404)
|
||||||
|
@ -268,6 +268,12 @@ class SvcHub(object):
|
||||||
Daemon(self.start_ftpd, "start_ftpd")
|
Daemon(self.start_ftpd, "start_ftpd")
|
||||||
zms += "f" if args.ftp else "F"
|
zms += "f" if args.ftp else "F"
|
||||||
|
|
||||||
|
if args.tftp:
|
||||||
|
from .tftpd import Tftpd
|
||||||
|
|
||||||
|
self.tftpd: Optional[Tftpd] = None
|
||||||
|
Daemon(self.start_ftpd, "start_tftpd")
|
||||||
|
|
||||||
if args.smb:
|
if args.smb:
|
||||||
# impacket.dcerpc is noisy about listen timeouts
|
# impacket.dcerpc is noisy about listen timeouts
|
||||||
sto = socket.getdefaulttimeout()
|
sto = socket.getdefaulttimeout()
|
||||||
|
@ -297,10 +303,12 @@ class SvcHub(object):
|
||||||
|
|
||||||
def start_ftpd(self) -> None:
|
def start_ftpd(self) -> None:
|
||||||
time.sleep(30)
|
time.sleep(30)
|
||||||
if self.ftpd:
|
|
||||||
return
|
|
||||||
|
|
||||||
self.restart_ftpd()
|
if hasattr(self, "ftpd") and not self.ftpd:
|
||||||
|
self.restart_ftpd()
|
||||||
|
|
||||||
|
if hasattr(self, "tftpd") and not self.tftpd:
|
||||||
|
self.restart_tftpd()
|
||||||
|
|
||||||
def restart_ftpd(self) -> None:
|
def restart_ftpd(self) -> None:
|
||||||
if not hasattr(self, "ftpd"):
|
if not hasattr(self, "ftpd"):
|
||||||
|
@ -317,6 +325,17 @@ class SvcHub(object):
|
||||||
self.ftpd = Ftpd(self)
|
self.ftpd = Ftpd(self)
|
||||||
self.log("root", "started FTPd")
|
self.log("root", "started FTPd")
|
||||||
|
|
||||||
|
def restart_tftpd(self) -> None:
|
||||||
|
if not hasattr(self, "tftpd"):
|
||||||
|
return
|
||||||
|
|
||||||
|
from .tftpd import Tftpd
|
||||||
|
|
||||||
|
if self.tftpd:
|
||||||
|
return # todo
|
||||||
|
|
||||||
|
self.tftpd = Tftpd(self)
|
||||||
|
|
||||||
def thr_httpsrv_up(self) -> None:
|
def thr_httpsrv_up(self) -> None:
|
||||||
time.sleep(1 if self.args.ign_ebind_all else 5)
|
time.sleep(1 if self.args.ign_ebind_all else 5)
|
||||||
expected = self.broker.num_workers * self.tcpsrv.nsrv
|
expected = self.broker.num_workers * self.tcpsrv.nsrv
|
||||||
|
@ -432,6 +451,13 @@ class SvcHub(object):
|
||||||
else:
|
else:
|
||||||
setattr(al, k, re.compile(vs))
|
setattr(al, k, re.compile(vs))
|
||||||
|
|
||||||
|
for k in "tftp_lsf".split(" "):
|
||||||
|
vs = getattr(al, k)
|
||||||
|
if not vs or vs == "no":
|
||||||
|
setattr(al, k, None)
|
||||||
|
else:
|
||||||
|
setattr(al, k, re.compile("^" + vs + "$"))
|
||||||
|
|
||||||
if not al.sus_urls:
|
if not al.sus_urls:
|
||||||
al.ban_url = "no"
|
al.ban_url = "no"
|
||||||
elif al.ban_url == "no":
|
elif al.ban_url == "no":
|
||||||
|
@ -444,6 +470,7 @@ class SvcHub(object):
|
||||||
al.xff_re = self._ipa2re(al.xff_src)
|
al.xff_re = self._ipa2re(al.xff_src)
|
||||||
al.ipa_re = self._ipa2re(al.ipa)
|
al.ipa_re = self._ipa2re(al.ipa)
|
||||||
al.ftp_ipa_re = self._ipa2re(al.ftp_ipa or al.ipa)
|
al.ftp_ipa_re = self._ipa2re(al.ftp_ipa or al.ipa)
|
||||||
|
al.tftp_ipa_re = self._ipa2re(al.tftp_ipa or al.ipa)
|
||||||
|
|
||||||
mte = ODict.fromkeys(DEF_MTE.split(","), True)
|
mte = ODict.fromkeys(DEF_MTE.split(","), True)
|
||||||
al.mte = odfusion(mte, al.mte)
|
al.mte = odfusion(mte, al.mte)
|
||||||
|
|
|
@ -309,6 +309,7 @@ class TcpSrv(object):
|
||||||
self.hub.start_zeroconf()
|
self.hub.start_zeroconf()
|
||||||
gencert(self.log, self.args, self.netdevs)
|
gencert(self.log, self.args, self.netdevs)
|
||||||
self.hub.restart_ftpd()
|
self.hub.restart_ftpd()
|
||||||
|
self.hub.restart_tftpd()
|
||||||
|
|
||||||
def shutdown(self) -> None:
|
def shutdown(self) -> None:
|
||||||
self.stopping = True
|
self.stopping = True
|
||||||
|
|
429
copyparty/tftpd.py
Normal file
429
copyparty/tftpd.py
Normal file
|
@ -0,0 +1,429 @@
|
||||||
|
# coding: utf-8
|
||||||
|
from __future__ import print_function, unicode_literals
|
||||||
|
|
||||||
|
try:
|
||||||
|
from types import SimpleNamespace
|
||||||
|
except:
|
||||||
|
|
||||||
|
class SimpleNamespace(object):
|
||||||
|
def __init__(self, **attr):
|
||||||
|
self.__dict__.update(attr)
|
||||||
|
|
||||||
|
|
||||||
|
import logging
|
||||||
|
import os
|
||||||
|
import re
|
||||||
|
import socket
|
||||||
|
import stat
|
||||||
|
import threading
|
||||||
|
import time
|
||||||
|
from datetime import datetime
|
||||||
|
|
||||||
|
try:
|
||||||
|
import inspect
|
||||||
|
except:
|
||||||
|
pass
|
||||||
|
|
||||||
|
from partftpy import (
|
||||||
|
TftpContexts,
|
||||||
|
TftpPacketFactory,
|
||||||
|
TftpPacketTypes,
|
||||||
|
TftpServer,
|
||||||
|
TftpStates,
|
||||||
|
)
|
||||||
|
from partftpy.TftpShared import TftpException
|
||||||
|
|
||||||
|
from .__init__ import EXE, TYPE_CHECKING
|
||||||
|
from .authsrv import VFS
|
||||||
|
from .bos import bos
|
||||||
|
from .util import BytesIO, Daemon, ODict, exclude_dotfiles, min_ex, runhook, undot
|
||||||
|
|
||||||
|
if True: # pylint: disable=using-constant-test
|
||||||
|
from typing import Any, Union
|
||||||
|
|
||||||
|
if TYPE_CHECKING:
|
||||||
|
from .svchub import SvcHub
|
||||||
|
|
||||||
|
|
||||||
|
lg = logging.getLogger("tftp")
|
||||||
|
debug, info, warning, error = (lg.debug, lg.info, lg.warning, lg.error)
|
||||||
|
|
||||||
|
|
||||||
|
def noop(*a, **ka) -> None:
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
def _serverInitial(self, pkt: Any, raddress: str, rport: int) -> bool:
|
||||||
|
info("connection from %s:%s", raddress, rport)
|
||||||
|
ret = _orig_serverInitial(self, pkt, raddress, rport)
|
||||||
|
ptn = _hub[0].args.tftp_ipa_re
|
||||||
|
if ptn and not ptn.match(raddress):
|
||||||
|
yeet("client rejected (--tftp-ipa): %s" % (raddress,))
|
||||||
|
return ret
|
||||||
|
|
||||||
|
|
||||||
|
# patch ipa-check into partftpd
|
||||||
|
_hub: list["SvcHub"] = []
|
||||||
|
_orig_serverInitial = TftpStates.TftpServerState.serverInitial
|
||||||
|
TftpStates.TftpServerState.serverInitial = _serverInitial
|
||||||
|
|
||||||
|
|
||||||
|
class Tftpd(object):
|
||||||
|
def __init__(self, hub: "SvcHub") -> None:
|
||||||
|
self.hub = hub
|
||||||
|
self.args = hub.args
|
||||||
|
self.asrv = hub.asrv
|
||||||
|
self.log = hub.log
|
||||||
|
self.mutex = threading.Lock()
|
||||||
|
|
||||||
|
_hub[:] = []
|
||||||
|
_hub.append(hub)
|
||||||
|
|
||||||
|
lg.setLevel(logging.DEBUG if self.args.tftpv else logging.INFO)
|
||||||
|
for x in ["partftpy", "partftpy.TftpStates", "partftpy.TftpServer"]:
|
||||||
|
lgr = logging.getLogger(x)
|
||||||
|
lgr.setLevel(logging.DEBUG if self.args.tftpv else logging.INFO)
|
||||||
|
|
||||||
|
if not self.args.tftpv and not self.args.tftpvv:
|
||||||
|
# contexts -> states -> packettypes -> shared
|
||||||
|
# contexts -> packetfactory
|
||||||
|
# packetfactory -> packettypes
|
||||||
|
Cs = [
|
||||||
|
TftpPacketTypes,
|
||||||
|
TftpPacketFactory,
|
||||||
|
TftpStates,
|
||||||
|
TftpContexts,
|
||||||
|
TftpServer,
|
||||||
|
]
|
||||||
|
cbak = []
|
||||||
|
if not self.args.tftp_no_fast and not EXE:
|
||||||
|
try:
|
||||||
|
import inspect
|
||||||
|
|
||||||
|
ptn = re.compile(r"(^\s*)log\.debug\(.*\)$")
|
||||||
|
for C in Cs:
|
||||||
|
cbak.append(C.__dict__)
|
||||||
|
src1 = inspect.getsource(C).split("\n")
|
||||||
|
src2 = "\n".join([ptn.sub("\\1pass", ln) for ln in src1])
|
||||||
|
cfn = C.__spec__.origin
|
||||||
|
exec (compile(src2, filename=cfn, mode="exec"), C.__dict__)
|
||||||
|
except Exception:
|
||||||
|
t = "failed to optimize tftp code; run with --tftp-noopt if there are issues:\n"
|
||||||
|
self.log("tftp", t + min_ex(), 3)
|
||||||
|
for n, zd in enumerate(cbak):
|
||||||
|
Cs[n].__dict__ = zd
|
||||||
|
|
||||||
|
for C in Cs:
|
||||||
|
C.log.debug = noop
|
||||||
|
|
||||||
|
# patch vfs into partftpy
|
||||||
|
TftpContexts.open = self._open
|
||||||
|
TftpStates.open = self._open
|
||||||
|
|
||||||
|
fos = SimpleNamespace()
|
||||||
|
for k in os.__dict__:
|
||||||
|
try:
|
||||||
|
setattr(fos, k, getattr(os, k))
|
||||||
|
except:
|
||||||
|
pass
|
||||||
|
fos.access = self._access
|
||||||
|
fos.mkdir = self._mkdir
|
||||||
|
fos.unlink = self._unlink
|
||||||
|
fos.sep = "/"
|
||||||
|
TftpContexts.os = fos
|
||||||
|
TftpServer.os = fos
|
||||||
|
TftpStates.os = fos
|
||||||
|
|
||||||
|
fop = SimpleNamespace()
|
||||||
|
for k in os.path.__dict__:
|
||||||
|
try:
|
||||||
|
setattr(fop, k, getattr(os.path, k))
|
||||||
|
except:
|
||||||
|
pass
|
||||||
|
fop.abspath = self._p_abspath
|
||||||
|
fop.exists = self._p_exists
|
||||||
|
fop.isdir = self._p_isdir
|
||||||
|
fop.normpath = self._p_normpath
|
||||||
|
fos.path = fop
|
||||||
|
|
||||||
|
self._disarm(fos)
|
||||||
|
|
||||||
|
ip = next((x for x in self.args.i if ":" not in x), None)
|
||||||
|
if not ip:
|
||||||
|
self.log("tftp", "IPv6 not supported for tftp; listening on 0.0.0.0", 3)
|
||||||
|
ip = "0.0.0.0"
|
||||||
|
|
||||||
|
self.port = int(self.args.tftp)
|
||||||
|
self.srv = []
|
||||||
|
self.ips = []
|
||||||
|
|
||||||
|
ports = []
|
||||||
|
if self.args.tftp_pr:
|
||||||
|
p1, p2 = [int(x) for x in self.args.tftp_pr.split("-")]
|
||||||
|
ports = list(range(p1, p2 + 1))
|
||||||
|
|
||||||
|
ips = self.args.i
|
||||||
|
if "::" in ips:
|
||||||
|
ips.append("0.0.0.0")
|
||||||
|
|
||||||
|
if self.args.ftp4:
|
||||||
|
ips = [x for x in ips if ":" not in x]
|
||||||
|
|
||||||
|
ips = list(ODict.fromkeys(ips)) # dedup
|
||||||
|
|
||||||
|
for ip in ips:
|
||||||
|
name = "tftp_%s" % (ip,)
|
||||||
|
Daemon(self._start, name, [ip, ports])
|
||||||
|
time.sleep(0.2) # give dualstack a chance
|
||||||
|
|
||||||
|
def nlog(self, msg: str, c: Union[int, str] = 0) -> None:
|
||||||
|
self.log("tftp", msg, c)
|
||||||
|
|
||||||
|
def _start(self, ip, ports):
|
||||||
|
fam = socket.AF_INET6 if ":" in ip else socket.AF_INET
|
||||||
|
have_been_alive = False
|
||||||
|
while True:
|
||||||
|
srv = TftpServer.TftpServer("/", self._ls)
|
||||||
|
with self.mutex:
|
||||||
|
self.srv.append(srv)
|
||||||
|
self.ips.append(ip)
|
||||||
|
|
||||||
|
try:
|
||||||
|
# this is the listen loop; it should block forever
|
||||||
|
srv.listen(ip, self.port, af_family=fam, ports=ports)
|
||||||
|
except:
|
||||||
|
with self.mutex:
|
||||||
|
self.srv.remove(srv)
|
||||||
|
self.ips.remove(ip)
|
||||||
|
|
||||||
|
try:
|
||||||
|
srv.sock.close()
|
||||||
|
except:
|
||||||
|
pass
|
||||||
|
|
||||||
|
try:
|
||||||
|
bound = bool(srv.listenport)
|
||||||
|
except:
|
||||||
|
bound = False
|
||||||
|
|
||||||
|
if bound:
|
||||||
|
# this instance has managed to bind at least once
|
||||||
|
have_been_alive = True
|
||||||
|
|
||||||
|
if have_been_alive:
|
||||||
|
t = "tftp server [%s]:%d crashed; restarting in 3 sec:\n%s"
|
||||||
|
error(t, ip, self.port, min_ex())
|
||||||
|
time.sleep(3)
|
||||||
|
continue
|
||||||
|
|
||||||
|
# server failed to start; could be due to dualstack (ipv6 managed to bind and this is ipv4)
|
||||||
|
if ip != "0.0.0.0" or "::" not in self.ips:
|
||||||
|
# nope, it's fatal
|
||||||
|
t = "tftp server [%s]:%d failed to start:\n%s"
|
||||||
|
error(t, ip, self.port, min_ex())
|
||||||
|
|
||||||
|
# yep; ignore
|
||||||
|
# (TODO: move the "listening @ ..." infolog in partftpy to
|
||||||
|
# after the bind attempt so it doesn't print twice)
|
||||||
|
return
|
||||||
|
|
||||||
|
info("tftp server [%s]:%d terminated", ip, self.port)
|
||||||
|
break
|
||||||
|
|
||||||
|
def stop(self):
|
||||||
|
with self.mutex:
|
||||||
|
srvs = self.srv[:]
|
||||||
|
|
||||||
|
for srv in srvs:
|
||||||
|
srv.stop()
|
||||||
|
|
||||||
|
def _v2a(self, caller: str, vpath: str, perms: list, *a: Any) -> tuple[VFS, str]:
|
||||||
|
vpath = vpath.replace("\\", "/").lstrip("/")
|
||||||
|
if not perms:
|
||||||
|
perms = [True, True]
|
||||||
|
|
||||||
|
debug('%s("%s", %s) %s\033[K\033[0m', caller, vpath, str(a), perms)
|
||||||
|
vfs, rem = self.asrv.vfs.get(vpath, "*", *perms)
|
||||||
|
return vfs, vfs.canonical(rem)
|
||||||
|
|
||||||
|
def _ls(self, vpath: str, raddress: str, rport: int, force=False) -> Any:
|
||||||
|
# generate file listing if vpath is dir.txt and return as file object
|
||||||
|
if not force:
|
||||||
|
vpath, fn = os.path.split(vpath.replace("\\", "/"))
|
||||||
|
ptn = self.args.tftp_lsf
|
||||||
|
if not ptn or not ptn.match(fn.lower()):
|
||||||
|
return None
|
||||||
|
|
||||||
|
vn, rem = self.asrv.vfs.get(vpath, "*", True, False)
|
||||||
|
fsroot, vfs_ls, vfs_virt = vn.ls(
|
||||||
|
rem,
|
||||||
|
"*",
|
||||||
|
not self.args.no_scandir,
|
||||||
|
[[True, False]],
|
||||||
|
)
|
||||||
|
dnames = set([x[0] for x in vfs_ls if stat.S_ISDIR(x[1].st_mode)])
|
||||||
|
dirs1 = [(v.st_mtime, v.st_size, k + "/") for k, v in vfs_ls if k in dnames]
|
||||||
|
fils1 = [(v.st_mtime, v.st_size, k) for k, v in vfs_ls if k not in dnames]
|
||||||
|
real1 = dirs1 + fils1
|
||||||
|
realt = [(datetime.fromtimestamp(mt), sz, fn) for mt, sz, fn in real1]
|
||||||
|
reals = [
|
||||||
|
(
|
||||||
|
"%04d-%02d-%02d %02d:%02d:%02d"
|
||||||
|
% (
|
||||||
|
zd.year,
|
||||||
|
zd.month,
|
||||||
|
zd.day,
|
||||||
|
zd.hour,
|
||||||
|
zd.minute,
|
||||||
|
zd.second,
|
||||||
|
),
|
||||||
|
sz,
|
||||||
|
fn,
|
||||||
|
)
|
||||||
|
for zd, sz, fn in realt
|
||||||
|
]
|
||||||
|
virs = [("????-??-?? ??:??:??", 0, k + "/") for k in vfs_virt.keys()]
|
||||||
|
ls = virs + reals
|
||||||
|
|
||||||
|
if "*" not in vn.axs.udot:
|
||||||
|
names = set(exclude_dotfiles([x[2] for x in ls]))
|
||||||
|
ls = [x for x in ls if x[2] in names]
|
||||||
|
|
||||||
|
try:
|
||||||
|
biggest = max([x[1] for x in ls])
|
||||||
|
except:
|
||||||
|
biggest = 0
|
||||||
|
|
||||||
|
perms = []
|
||||||
|
if "*" in vn.axs.uread:
|
||||||
|
perms.append("read")
|
||||||
|
if "*" in vn.axs.udot:
|
||||||
|
perms.append("hidden")
|
||||||
|
if "*" in vn.axs.uwrite:
|
||||||
|
if "*" in vn.axs.udel:
|
||||||
|
perms.append("overwrite")
|
||||||
|
else:
|
||||||
|
perms.append("write")
|
||||||
|
|
||||||
|
fmt = "{{}} {{:{},}} {{}}"
|
||||||
|
fmt = fmt.format(len("{:,}".format(biggest)))
|
||||||
|
retl = ["# permissions: %s" % (", ".join(perms),)]
|
||||||
|
retl += [fmt.format(*x) for x in ls]
|
||||||
|
ret = "\n".join(retl).encode("utf-8", "replace")
|
||||||
|
return BytesIO(ret + b"\n")
|
||||||
|
|
||||||
|
def _open(self, vpath: str, mode: str, *a: Any, **ka: Any) -> Any:
|
||||||
|
rd = wr = False
|
||||||
|
if mode == "rb":
|
||||||
|
rd = True
|
||||||
|
elif mode == "wb":
|
||||||
|
wr = True
|
||||||
|
else:
|
||||||
|
raise Exception("bad mode %s" % (mode,))
|
||||||
|
|
||||||
|
vfs, ap = self._v2a("open", vpath, [rd, wr])
|
||||||
|
if wr:
|
||||||
|
if "*" not in vfs.axs.uwrite:
|
||||||
|
yeet("blocked write; folder not world-writable: /%s" % (vpath,))
|
||||||
|
|
||||||
|
if bos.path.exists(ap) and "*" not in vfs.axs.udel:
|
||||||
|
yeet("blocked write; folder not world-deletable: /%s" % (vpath,))
|
||||||
|
|
||||||
|
xbu = vfs.flags.get("xbu")
|
||||||
|
if xbu and not runhook(
|
||||||
|
self.nlog, xbu, ap, vpath, "", "", 0, 0, "8.3.8.7", 0, ""
|
||||||
|
):
|
||||||
|
yeet("blocked by xbu server config: " + vpath)
|
||||||
|
|
||||||
|
if not self.args.tftp_nols and bos.path.isdir(ap):
|
||||||
|
return self._ls(vpath, "", 0, True)
|
||||||
|
|
||||||
|
return open(ap, mode, *a, **ka)
|
||||||
|
|
||||||
|
def _mkdir(self, vpath: str, *a) -> None:
|
||||||
|
vfs, ap = self._v2a("mkdir", vpath, [])
|
||||||
|
if "*" not in vfs.axs.uwrite:
|
||||||
|
yeet("blocked mkdir; folder not world-writable: /%s" % (vpath,))
|
||||||
|
|
||||||
|
return bos.mkdir(ap)
|
||||||
|
|
||||||
|
def _unlink(self, vpath: str) -> None:
|
||||||
|
# return bos.unlink(self._v2a("stat", vpath, *a)[1])
|
||||||
|
vfs, ap = self._v2a("delete", vpath, [True, False, False, True])
|
||||||
|
|
||||||
|
try:
|
||||||
|
inf = bos.stat(ap)
|
||||||
|
except:
|
||||||
|
return
|
||||||
|
|
||||||
|
if not stat.S_ISREG(inf.st_mode) or inf.st_size:
|
||||||
|
yeet("attempted delete of non-empty file")
|
||||||
|
|
||||||
|
vpath = vpath.replace("\\", "/").lstrip("/")
|
||||||
|
self.hub.up2k.handle_rm("*", "8.3.8.7", [vpath], [], False)
|
||||||
|
|
||||||
|
def _access(self, *a: Any) -> bool:
|
||||||
|
return True
|
||||||
|
|
||||||
|
def _p_abspath(self, vpath: str) -> str:
|
||||||
|
return "/" + undot(vpath)
|
||||||
|
|
||||||
|
def _p_normpath(self, *a: Any) -> str:
|
||||||
|
return ""
|
||||||
|
|
||||||
|
def _p_exists(self, vpath: str) -> bool:
|
||||||
|
try:
|
||||||
|
ap = self._v2a("p.exists", vpath, [False, False])[1]
|
||||||
|
bos.stat(ap)
|
||||||
|
return True
|
||||||
|
except:
|
||||||
|
return False
|
||||||
|
|
||||||
|
def _p_isdir(self, vpath: str) -> bool:
|
||||||
|
try:
|
||||||
|
st = bos.stat(self._v2a("p.isdir", vpath, [False, False])[1])
|
||||||
|
ret = stat.S_ISDIR(st.st_mode)
|
||||||
|
return ret
|
||||||
|
except:
|
||||||
|
return False
|
||||||
|
|
||||||
|
def _hook(self, *a: Any, **ka: Any) -> None:
|
||||||
|
src = inspect.currentframe().f_back.f_code.co_name
|
||||||
|
error("\033[31m%s:hook(%s)\033[0m", src, a)
|
||||||
|
raise Exception("nope")
|
||||||
|
|
||||||
|
def _disarm(self, fos: SimpleNamespace) -> None:
|
||||||
|
fos.chmod = self._hook
|
||||||
|
fos.chown = self._hook
|
||||||
|
fos.close = self._hook
|
||||||
|
fos.ftruncate = self._hook
|
||||||
|
fos.lchown = self._hook
|
||||||
|
fos.link = self._hook
|
||||||
|
fos.listdir = self._hook
|
||||||
|
fos.lstat = self._hook
|
||||||
|
fos.open = self._hook
|
||||||
|
fos.remove = self._hook
|
||||||
|
fos.rename = self._hook
|
||||||
|
fos.replace = self._hook
|
||||||
|
fos.scandir = self._hook
|
||||||
|
fos.stat = self._hook
|
||||||
|
fos.symlink = self._hook
|
||||||
|
fos.truncate = self._hook
|
||||||
|
fos.utime = self._hook
|
||||||
|
fos.walk = self._hook
|
||||||
|
|
||||||
|
fos.path.expanduser = self._hook
|
||||||
|
fos.path.expandvars = self._hook
|
||||||
|
fos.path.getatime = self._hook
|
||||||
|
fos.path.getctime = self._hook
|
||||||
|
fos.path.getmtime = self._hook
|
||||||
|
fos.path.getsize = self._hook
|
||||||
|
fos.path.isabs = self._hook
|
||||||
|
fos.path.isfile = self._hook
|
||||||
|
fos.path.islink = self._hook
|
||||||
|
fos.path.realpath = self._hook
|
||||||
|
|
||||||
|
|
||||||
|
def yeet(msg: str) -> None:
|
||||||
|
warning(msg)
|
||||||
|
raise TftpException(msg)
|
|
@ -78,16 +78,34 @@ class ThumbCli(object):
|
||||||
if rem.startswith(".hist/th/") and rem.split(".")[-1] in ["webp", "jpg", "png"]:
|
if rem.startswith(".hist/th/") and rem.split(".")[-1] in ["webp", "jpg", "png"]:
|
||||||
return os.path.join(ptop, rem)
|
return os.path.join(ptop, rem)
|
||||||
|
|
||||||
if fmt == "j" and self.args.th_no_jpg:
|
if fmt[:1] in "jw":
|
||||||
fmt = "w"
|
sfmt = fmt[:1]
|
||||||
|
|
||||||
if fmt == "w":
|
if sfmt == "j" and self.args.th_no_jpg:
|
||||||
if (
|
sfmt = "w"
|
||||||
self.args.th_no_webp
|
|
||||||
or (is_img and not self.can_webp)
|
if sfmt == "w":
|
||||||
or (self.args.th_ff_jpg and (not is_img or preferred == "ff"))
|
if (
|
||||||
):
|
self.args.th_no_webp
|
||||||
fmt = "j"
|
or (is_img and not self.can_webp)
|
||||||
|
or (self.args.th_ff_jpg and (not is_img or preferred == "ff"))
|
||||||
|
):
|
||||||
|
sfmt = "j"
|
||||||
|
|
||||||
|
vf_crop = dbv.flags["crop"]
|
||||||
|
vf_th3x = dbv.flags["th3x"]
|
||||||
|
|
||||||
|
if "f" in vf_crop:
|
||||||
|
sfmt += "f" if "n" in vf_crop else ""
|
||||||
|
else:
|
||||||
|
sfmt += "f" if "f" in fmt else ""
|
||||||
|
|
||||||
|
if "f" in vf_th3x:
|
||||||
|
sfmt += "3" if "y" in vf_th3x else ""
|
||||||
|
else:
|
||||||
|
sfmt += "3" if "3" in fmt else ""
|
||||||
|
|
||||||
|
fmt = sfmt
|
||||||
|
|
||||||
histpath = self.asrv.vfs.histtab.get(ptop)
|
histpath = self.asrv.vfs.histtab.get(ptop)
|
||||||
if not histpath:
|
if not histpath:
|
||||||
|
|
|
@ -97,8 +97,8 @@ def thumb_path(histpath: str, rem: str, mtime: float, fmt: str, ffa: set[str]) -
|
||||||
|
|
||||||
# spectrograms are never cropped; strip fullsize flag
|
# spectrograms are never cropped; strip fullsize flag
|
||||||
ext = rem.split(".")[-1].lower()
|
ext = rem.split(".")[-1].lower()
|
||||||
if ext in ffa and fmt in ("wf", "jf"):
|
if ext in ffa and fmt[:2] in ("wf", "jf"):
|
||||||
fmt = fmt[:1]
|
fmt = fmt.replace("f", "")
|
||||||
|
|
||||||
rd += "\n" + fmt
|
rd += "\n" + fmt
|
||||||
h = hashlib.sha512(afsenc(rd)).digest()
|
h = hashlib.sha512(afsenc(rd)).digest()
|
||||||
|
@ -200,9 +200,10 @@ class ThumbSrv(object):
|
||||||
with self.mutex:
|
with self.mutex:
|
||||||
return not self.nthr
|
return not self.nthr
|
||||||
|
|
||||||
def getres(self, vn: VFS) -> tuple[int, int]:
|
def getres(self, vn: VFS, fmt: str) -> tuple[int, int]:
|
||||||
|
mul = 3 if "3" in fmt else 1
|
||||||
w, h = vn.flags["thsize"].split("x")
|
w, h = vn.flags["thsize"].split("x")
|
||||||
return int(w), int(h)
|
return int(w) * mul, int(h) * mul
|
||||||
|
|
||||||
def get(self, ptop: str, rem: str, mtime: float, fmt: str) -> Optional[str]:
|
def get(self, ptop: str, rem: str, mtime: float, fmt: str) -> Optional[str]:
|
||||||
histpath = self.asrv.vfs.histtab.get(ptop)
|
histpath = self.asrv.vfs.histtab.get(ptop)
|
||||||
|
@ -364,7 +365,7 @@ class ThumbSrv(object):
|
||||||
|
|
||||||
def fancy_pillow(self, im: "Image.Image", fmt: str, vn: VFS) -> "Image.Image":
|
def fancy_pillow(self, im: "Image.Image", fmt: str, vn: VFS) -> "Image.Image":
|
||||||
# exif_transpose is expensive (loads full image + unconditional copy)
|
# exif_transpose is expensive (loads full image + unconditional copy)
|
||||||
res = self.getres(vn)
|
res = self.getres(vn, fmt)
|
||||||
r = max(*res) * 2
|
r = max(*res) * 2
|
||||||
im.thumbnail((r, r), resample=Image.LANCZOS)
|
im.thumbnail((r, r), resample=Image.LANCZOS)
|
||||||
try:
|
try:
|
||||||
|
@ -379,7 +380,7 @@ class ThumbSrv(object):
|
||||||
if rot in rots:
|
if rot in rots:
|
||||||
im = im.transpose(rots[rot])
|
im = im.transpose(rots[rot])
|
||||||
|
|
||||||
if fmt.endswith("f"):
|
if "f" in fmt:
|
||||||
im.thumbnail(res, resample=Image.LANCZOS)
|
im.thumbnail(res, resample=Image.LANCZOS)
|
||||||
else:
|
else:
|
||||||
iw, ih = im.size
|
iw, ih = im.size
|
||||||
|
@ -396,7 +397,7 @@ class ThumbSrv(object):
|
||||||
im = self.fancy_pillow(im, fmt, vn)
|
im = self.fancy_pillow(im, fmt, vn)
|
||||||
except Exception as ex:
|
except Exception as ex:
|
||||||
self.log("fancy_pillow {}".format(ex), "90")
|
self.log("fancy_pillow {}".format(ex), "90")
|
||||||
im.thumbnail(self.getres(vn))
|
im.thumbnail(self.getres(vn, fmt))
|
||||||
|
|
||||||
fmts = ["RGB", "L"]
|
fmts = ["RGB", "L"]
|
||||||
args = {"quality": 40}
|
args = {"quality": 40}
|
||||||
|
@ -422,10 +423,10 @@ class ThumbSrv(object):
|
||||||
def conv_vips(self, abspath: str, tpath: str, fmt: str, vn: VFS) -> None:
|
def conv_vips(self, abspath: str, tpath: str, fmt: str, vn: VFS) -> None:
|
||||||
self.wait4ram(0.2, tpath)
|
self.wait4ram(0.2, tpath)
|
||||||
crops = ["centre", "none"]
|
crops = ["centre", "none"]
|
||||||
if fmt.endswith("f"):
|
if "f" in fmt:
|
||||||
crops = ["none"]
|
crops = ["none"]
|
||||||
|
|
||||||
w, h = self.getres(vn)
|
w, h = self.getres(vn, fmt)
|
||||||
kw = {"height": h, "size": "down", "intent": "relative"}
|
kw = {"height": h, "size": "down", "intent": "relative"}
|
||||||
|
|
||||||
for c in crops:
|
for c in crops:
|
||||||
|
@ -454,12 +455,12 @@ class ThumbSrv(object):
|
||||||
seek = [b"-ss", "{:.0f}".format(dur / 3).encode("utf-8")]
|
seek = [b"-ss", "{:.0f}".format(dur / 3).encode("utf-8")]
|
||||||
|
|
||||||
scale = "scale={0}:{1}:force_original_aspect_ratio="
|
scale = "scale={0}:{1}:force_original_aspect_ratio="
|
||||||
if fmt.endswith("f"):
|
if "f" in fmt:
|
||||||
scale += "decrease,setsar=1:1"
|
scale += "decrease,setsar=1:1"
|
||||||
else:
|
else:
|
||||||
scale += "increase,crop={0}:{1},setsar=1:1"
|
scale += "increase,crop={0}:{1},setsar=1:1"
|
||||||
|
|
||||||
res = self.getres(vn)
|
res = self.getres(vn, fmt)
|
||||||
bscale = scale.format(*list(res)).encode("utf-8")
|
bscale = scale.format(*list(res)).encode("utf-8")
|
||||||
# fmt: off
|
# fmt: off
|
||||||
cmd = [
|
cmd = [
|
||||||
|
@ -594,7 +595,11 @@ class ThumbSrv(object):
|
||||||
need = 0.2 + dur / coeff
|
need = 0.2 + dur / coeff
|
||||||
self.wait4ram(need, tpath)
|
self.wait4ram(need, tpath)
|
||||||
|
|
||||||
fc = "[0:a:0]aresample=48000{},showspectrumpic=s=640x512,crop=780:544:70:50[o]"
|
fc = "[0:a:0]aresample=48000{},showspectrumpic=s="
|
||||||
|
if "3" in fmt:
|
||||||
|
fc += "1280x1024,crop=1420:1056:70:48[o]"
|
||||||
|
else:
|
||||||
|
fc += "640x512,crop=780:544:70:48[o]"
|
||||||
|
|
||||||
if self.args.th_ff_swr:
|
if self.args.th_ff_swr:
|
||||||
fco = ":filter_size=128:cutoff=0.877"
|
fco = ":filter_size=128:cutoff=0.877"
|
||||||
|
|
|
@ -21,7 +21,7 @@ from copy import deepcopy
|
||||||
|
|
||||||
from queue import Queue
|
from queue import Queue
|
||||||
|
|
||||||
from .__init__ import ANYWIN, PY2, TYPE_CHECKING, WINDOWS
|
from .__init__ import ANYWIN, PY2, TYPE_CHECKING, WINDOWS, E
|
||||||
from .authsrv import LEELOO_DALLAS, SSEELOG, VFS, AuthSrv
|
from .authsrv import LEELOO_DALLAS, SSEELOG, VFS, AuthSrv
|
||||||
from .bos import bos
|
from .bos import bos
|
||||||
from .cfg import vf_bmap, vf_cmap, vf_vmap
|
from .cfg import vf_bmap, vf_cmap, vf_vmap
|
||||||
|
@ -35,6 +35,7 @@ from .util import (
|
||||||
Pebkac,
|
Pebkac,
|
||||||
ProgressPrinter,
|
ProgressPrinter,
|
||||||
absreal,
|
absreal,
|
||||||
|
alltrace,
|
||||||
atomic_move,
|
atomic_move,
|
||||||
db_ex_chk,
|
db_ex_chk,
|
||||||
dir_is_empty,
|
dir_is_empty,
|
||||||
|
@ -87,6 +88,9 @@ zsg = "avif,avifs,bmp,gif,heic,heics,heif,heifs,ico,j2p,j2k,jp2,jpeg,jpg,jpx,png
|
||||||
CV_EXTS = set(zsg.split(","))
|
CV_EXTS = set(zsg.split(","))
|
||||||
|
|
||||||
|
|
||||||
|
HINT_HISTPATH = "you could try moving the database to another location (preferably an SSD or NVME drive) using either the --hist argument (global option for all volumes), or the hist volflag (just for this volume)"
|
||||||
|
|
||||||
|
|
||||||
class Dbw(object):
|
class Dbw(object):
|
||||||
def __init__(self, c: "sqlite3.Cursor", n: int, t: float) -> None:
|
def __init__(self, c: "sqlite3.Cursor", n: int, t: float) -> None:
|
||||||
self.c = c
|
self.c = c
|
||||||
|
@ -150,7 +154,7 @@ class Up2k(object):
|
||||||
self.hashq: Queue[
|
self.hashq: Queue[
|
||||||
tuple[str, str, dict[str, Any], str, str, str, float, str, bool]
|
tuple[str, str, dict[str, Any], str, str, str, float, str, bool]
|
||||||
] = Queue()
|
] = Queue()
|
||||||
self.tagq: Queue[tuple[str, str, str, str, str, float]] = Queue()
|
self.tagq: Queue[tuple[str, str, str, str, int, str, float]] = Queue()
|
||||||
self.tag_event = threading.Condition()
|
self.tag_event = threading.Condition()
|
||||||
self.hashq_mutex = threading.Lock()
|
self.hashq_mutex = threading.Lock()
|
||||||
self.n_hashq = 0
|
self.n_hashq = 0
|
||||||
|
@ -553,7 +557,7 @@ class Up2k(object):
|
||||||
runihook(self.log, cmd, vol, ups)
|
runihook(self.log, cmd, vol, ups)
|
||||||
|
|
||||||
def _vis_job_progress(self, job: dict[str, Any]) -> str:
|
def _vis_job_progress(self, job: dict[str, Any]) -> str:
|
||||||
perc = 100 - (len(job["need"]) * 100.0 / len(job["hash"]))
|
perc = 100 - (len(job["need"]) * 100.0 / (len(job["hash"]) or 1))
|
||||||
path = djoin(job["ptop"], job["prel"], job["name"])
|
path = djoin(job["ptop"], job["prel"], job["name"])
|
||||||
return "{:5.1f}% {}".format(perc, path)
|
return "{:5.1f}% {}".format(perc, path)
|
||||||
|
|
||||||
|
@ -897,7 +901,7 @@ class Up2k(object):
|
||||||
return None
|
return None
|
||||||
|
|
||||||
try:
|
try:
|
||||||
cur = self._open_db(db_path)
|
cur = self._open_db_wd(db_path)
|
||||||
|
|
||||||
# speeds measured uploading 520 small files on a WD20SPZX (SMR 2.5" 5400rpm 4kb)
|
# speeds measured uploading 520 small files on a WD20SPZX (SMR 2.5" 5400rpm 4kb)
|
||||||
dbd = flags["dbd"]
|
dbd = flags["dbd"]
|
||||||
|
@ -940,8 +944,8 @@ class Up2k(object):
|
||||||
|
|
||||||
return cur, db_path
|
return cur, db_path
|
||||||
except:
|
except:
|
||||||
msg = "cannot use database at [{}]:\n{}"
|
msg = "ERROR: cannot use database at [%s]:\n%s\n\033[33mhint: %s\n"
|
||||||
self.log(msg.format(ptop, traceback.format_exc()))
|
self.log(msg % (db_path, traceback.format_exc(), HINT_HISTPATH), 1)
|
||||||
|
|
||||||
return None
|
return None
|
||||||
|
|
||||||
|
@ -2056,12 +2060,13 @@ class Up2k(object):
|
||||||
return
|
return
|
||||||
|
|
||||||
try:
|
try:
|
||||||
|
st = bos.stat(qe.abspath)
|
||||||
if not qe.mtp:
|
if not qe.mtp:
|
||||||
if self.args.mtag_vv:
|
if self.args.mtag_vv:
|
||||||
t = "tag-thr: {}({})"
|
t = "tag-thr: {}({})"
|
||||||
self.log(t.format(self.mtag.backend, qe.abspath), "90")
|
self.log(t.format(self.mtag.backend, qe.abspath), "90")
|
||||||
|
|
||||||
tags = self.mtag.get(qe.abspath)
|
tags = self.mtag.get(qe.abspath) if st.st_size else {}
|
||||||
else:
|
else:
|
||||||
if self.args.mtag_vv:
|
if self.args.mtag_vv:
|
||||||
t = "tag-thr: {}({})"
|
t = "tag-thr: {}({})"
|
||||||
|
@ -2102,11 +2107,16 @@ class Up2k(object):
|
||||||
"""will mutex"""
|
"""will mutex"""
|
||||||
assert self.mtag
|
assert self.mtag
|
||||||
|
|
||||||
if not bos.path.isfile(abspath):
|
try:
|
||||||
|
st = bos.stat(abspath)
|
||||||
|
except:
|
||||||
|
return 0
|
||||||
|
|
||||||
|
if not stat.S_ISREG(st.st_mode):
|
||||||
return 0
|
return 0
|
||||||
|
|
||||||
try:
|
try:
|
||||||
tags = self.mtag.get(abspath)
|
tags = self.mtag.get(abspath) if st.st_size else {}
|
||||||
except Exception as ex:
|
except Exception as ex:
|
||||||
self._log_tag_err("", abspath, ex)
|
self._log_tag_err("", abspath, ex)
|
||||||
return 0
|
return 0
|
||||||
|
@ -2160,6 +2170,46 @@ class Up2k(object):
|
||||||
def _trace(self, msg: str) -> None:
|
def _trace(self, msg: str) -> None:
|
||||||
self.log("ST: {}".format(msg))
|
self.log("ST: {}".format(msg))
|
||||||
|
|
||||||
|
def _open_db_wd(self, db_path: str) -> "sqlite3.Cursor":
|
||||||
|
ok: list[int] = []
|
||||||
|
Daemon(self._open_db_timeout, "opendb_watchdog", [db_path, ok])
|
||||||
|
try:
|
||||||
|
return self._open_db(db_path)
|
||||||
|
finally:
|
||||||
|
ok.append(1)
|
||||||
|
|
||||||
|
def _open_db_timeout(self, db_path, ok: list[int]) -> None:
|
||||||
|
# give it plenty of time due to the count statement (and wisdom from byte's box)
|
||||||
|
for _ in range(60):
|
||||||
|
time.sleep(1)
|
||||||
|
if ok:
|
||||||
|
return
|
||||||
|
|
||||||
|
t = "WARNING:\n\n initializing an up2k database is taking longer than one minute; something has probably gone wrong:\n\n"
|
||||||
|
self._log_sqlite_incompat(db_path, t)
|
||||||
|
|
||||||
|
def _log_sqlite_incompat(self, db_path, t0) -> None:
|
||||||
|
txt = t0 or ""
|
||||||
|
digest = hashlib.sha512(db_path.encode("utf-8", "replace")).digest()
|
||||||
|
stackname = base64.urlsafe_b64encode(digest[:9]).decode("utf-8")
|
||||||
|
stackpath = os.path.join(E.cfg, "stack-%s.txt" % (stackname,))
|
||||||
|
|
||||||
|
t = " the filesystem at %s may not support locking, or is otherwise incompatible with sqlite\n\n %s\n\n"
|
||||||
|
t += " PS: if you think this is a bug and wish to report it, please include your configuration + the following file: %s\n"
|
||||||
|
txt += t % (db_path, HINT_HISTPATH, stackpath)
|
||||||
|
self.log(txt, 3)
|
||||||
|
|
||||||
|
try:
|
||||||
|
stk = alltrace()
|
||||||
|
with open(stackpath, "wb") as f:
|
||||||
|
f.write(stk.encode("utf-8", "replace"))
|
||||||
|
except Exception as ex:
|
||||||
|
self.log("warning: failed to write %s: %s" % (stackpath, ex), 3)
|
||||||
|
|
||||||
|
if self.args.q:
|
||||||
|
t = "-" * 72
|
||||||
|
raise Exception("%s\n%s\n%s" % (t, txt, t))
|
||||||
|
|
||||||
def _orz(self, db_path: str) -> "sqlite3.Cursor":
|
def _orz(self, db_path: str) -> "sqlite3.Cursor":
|
||||||
c = sqlite3.connect(
|
c = sqlite3.connect(
|
||||||
db_path, timeout=self.timeout, check_same_thread=False
|
db_path, timeout=self.timeout, check_same_thread=False
|
||||||
|
@ -2172,7 +2222,7 @@ class Up2k(object):
|
||||||
cur = self._orz(db_path)
|
cur = self._orz(db_path)
|
||||||
ver = self._read_ver(cur)
|
ver = self._read_ver(cur)
|
||||||
if not existed and ver is None:
|
if not existed and ver is None:
|
||||||
return self._create_db(db_path, cur)
|
return self._try_create_db(db_path, cur)
|
||||||
|
|
||||||
if ver == 4:
|
if ver == 4:
|
||||||
try:
|
try:
|
||||||
|
@ -2210,8 +2260,16 @@ class Up2k(object):
|
||||||
db = cur.connection
|
db = cur.connection
|
||||||
cur.close()
|
cur.close()
|
||||||
db.close()
|
db.close()
|
||||||
bos.unlink(db_path)
|
self._delete_db(db_path)
|
||||||
return self._create_db(db_path, None)
|
return self._try_create_db(db_path, None)
|
||||||
|
|
||||||
|
def _delete_db(self, db_path: str):
|
||||||
|
for suf in ("", "-shm", "-wal", "-journal"):
|
||||||
|
try:
|
||||||
|
bos.unlink(db_path + suf)
|
||||||
|
except:
|
||||||
|
if not suf:
|
||||||
|
raise
|
||||||
|
|
||||||
def _backup_db(
|
def _backup_db(
|
||||||
self, db_path: str, cur: "sqlite3.Cursor", ver: Optional[int], msg: str
|
self, db_path: str, cur: "sqlite3.Cursor", ver: Optional[int], msg: str
|
||||||
|
@ -2248,6 +2306,18 @@ class Up2k(object):
|
||||||
return int(rows[0][0])
|
return int(rows[0][0])
|
||||||
return None
|
return None
|
||||||
|
|
||||||
|
def _try_create_db(
|
||||||
|
self, db_path: str, cur: Optional["sqlite3.Cursor"]
|
||||||
|
) -> "sqlite3.Cursor":
|
||||||
|
try:
|
||||||
|
return self._create_db(db_path, cur)
|
||||||
|
except:
|
||||||
|
try:
|
||||||
|
self._delete_db(db_path)
|
||||||
|
except:
|
||||||
|
pass
|
||||||
|
raise
|
||||||
|
|
||||||
def _create_db(
|
def _create_db(
|
||||||
self, db_path: str, cur: Optional["sqlite3.Cursor"]
|
self, db_path: str, cur: Optional["sqlite3.Cursor"]
|
||||||
) -> "sqlite3.Cursor":
|
) -> "sqlite3.Cursor":
|
||||||
|
@ -3039,7 +3109,7 @@ class Up2k(object):
|
||||||
raise
|
raise
|
||||||
|
|
||||||
if "e2t" in self.flags[ptop]:
|
if "e2t" in self.flags[ptop]:
|
||||||
self.tagq.put((ptop, wark, rd, fn, ip, at))
|
self.tagq.put((ptop, wark, rd, fn, sz, ip, at))
|
||||||
self.n_tagq += 1
|
self.n_tagq += 1
|
||||||
|
|
||||||
return True
|
return True
|
||||||
|
@ -3621,9 +3691,10 @@ class Up2k(object):
|
||||||
)
|
)
|
||||||
job = reg.get(wark) if wark else None
|
job = reg.get(wark) if wark else None
|
||||||
if job:
|
if job:
|
||||||
t = "forgetting partial upload {} ({})"
|
if job["need"]:
|
||||||
p = self._vis_job_progress(job)
|
t = "forgetting partial upload {} ({})"
|
||||||
self.log(t.format(wark, p))
|
p = self._vis_job_progress(job)
|
||||||
|
self.log(t.format(wark, p))
|
||||||
assert wark
|
assert wark
|
||||||
del reg[wark]
|
del reg[wark]
|
||||||
|
|
||||||
|
@ -3996,14 +4067,14 @@ class Up2k(object):
|
||||||
with self.mutex:
|
with self.mutex:
|
||||||
self.n_tagq -= 1
|
self.n_tagq -= 1
|
||||||
|
|
||||||
ptop, wark, rd, fn, ip, at = self.tagq.get()
|
ptop, wark, rd, fn, sz, ip, at = self.tagq.get()
|
||||||
if "e2t" not in self.flags[ptop]:
|
if "e2t" not in self.flags[ptop]:
|
||||||
continue
|
continue
|
||||||
|
|
||||||
# self.log("\n " + repr([ptop, rd, fn]))
|
# self.log("\n " + repr([ptop, rd, fn]))
|
||||||
abspath = djoin(ptop, rd, fn)
|
abspath = djoin(ptop, rd, fn)
|
||||||
try:
|
try:
|
||||||
tags = self.mtag.get(abspath)
|
tags = self.mtag.get(abspath) if sz else {}
|
||||||
ntags1 = len(tags)
|
ntags1 = len(tags)
|
||||||
parsers = self._get_parsers(ptop, tags, abspath)
|
parsers = self._get_parsers(ptop, tags, abspath)
|
||||||
if self.args.mtag_vv:
|
if self.args.mtag_vv:
|
||||||
|
|
|
@ -423,16 +423,32 @@ try:
|
||||||
except:
|
except:
|
||||||
PYFTPD_VER = "(None)"
|
PYFTPD_VER = "(None)"
|
||||||
|
|
||||||
|
try:
|
||||||
|
from partftpy.__init__ import __version__ as PARTFTPY_VER
|
||||||
|
except:
|
||||||
|
PARTFTPY_VER = "(None)"
|
||||||
|
|
||||||
|
|
||||||
PY_DESC = py_desc()
|
PY_DESC = py_desc()
|
||||||
|
|
||||||
VERSIONS = "copyparty v{} ({})\n{}\n sqlite v{} | jinja v{} | pyftpd v{}".format(
|
VERSIONS = (
|
||||||
S_VERSION, S_BUILD_DT, PY_DESC, SQLITE_VER, JINJA_VER, PYFTPD_VER
|
"copyparty v{} ({})\n{}\n sqlite {} | jinja {} | pyftpd {} | tftp {}".format(
|
||||||
|
S_VERSION, S_BUILD_DT, PY_DESC, SQLITE_VER, JINJA_VER, PYFTPD_VER, PARTFTPY_VER
|
||||||
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
_: Any = (mp, BytesIO, quote, unquote, SQLITE_VER, JINJA_VER, PYFTPD_VER)
|
_: Any = (mp, BytesIO, quote, unquote, SQLITE_VER, JINJA_VER, PYFTPD_VER, PARTFTPY_VER)
|
||||||
__all__ = ["mp", "BytesIO", "quote", "unquote", "SQLITE_VER", "JINJA_VER", "PYFTPD_VER"]
|
__all__ = [
|
||||||
|
"mp",
|
||||||
|
"BytesIO",
|
||||||
|
"quote",
|
||||||
|
"unquote",
|
||||||
|
"SQLITE_VER",
|
||||||
|
"JINJA_VER",
|
||||||
|
"PYFTPD_VER",
|
||||||
|
"PARTFTPY_VER",
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
class Daemon(threading.Thread):
|
class Daemon(threading.Thread):
|
||||||
|
@ -536,6 +552,8 @@ class HLog(logging.Handler):
|
||||||
elif record.name.startswith("impacket"):
|
elif record.name.startswith("impacket"):
|
||||||
if self.ptn_smb_ign.match(msg):
|
if self.ptn_smb_ign.match(msg):
|
||||||
return
|
return
|
||||||
|
elif record.name.startswith("partftpy."):
|
||||||
|
record.name = record.name[9:]
|
||||||
|
|
||||||
self.log_func(record.name[-21:], msg, c)
|
self.log_func(record.name[-21:], msg, c)
|
||||||
|
|
||||||
|
@ -1750,7 +1768,7 @@ def get_spd(nbyte: int, t0: float, t: Optional[float] = None) -> str:
|
||||||
if t is None:
|
if t is None:
|
||||||
t = time.time()
|
t = time.time()
|
||||||
|
|
||||||
bps = nbyte / ((t - t0) + 0.001)
|
bps = nbyte / ((t - t0) or 0.001)
|
||||||
s1 = humansize(nbyte).replace(" ", "\033[33m").replace("iB", "")
|
s1 = humansize(nbyte).replace(" ", "\033[33m").replace("iB", "")
|
||||||
s2 = humansize(bps).replace(" ", "\033[35m").replace("iB", "")
|
s2 = humansize(bps).replace(" ", "\033[35m").replace("iB", "")
|
||||||
return "%s \033[0m%s/s\033[0m" % (s1, s2)
|
return "%s \033[0m%s/s\033[0m" % (s1, s2)
|
||||||
|
|
|
@ -17,8 +17,10 @@ window.baguetteBox = (function () {
|
||||||
titleTag: false,
|
titleTag: false,
|
||||||
async: false,
|
async: false,
|
||||||
preload: 2,
|
preload: 2,
|
||||||
|
refocus: true,
|
||||||
afterShow: null,
|
afterShow: null,
|
||||||
afterHide: null,
|
afterHide: null,
|
||||||
|
duringHide: null,
|
||||||
onChange: null,
|
onChange: null,
|
||||||
},
|
},
|
||||||
overlay, slider, btnPrev, btnNext, btnHelp, btnAnim, btnRotL, btnRotR, btnSel, btnFull, btnVmode, btnClose,
|
overlay, slider, btnPrev, btnNext, btnHelp, btnAnim, btnRotL, btnRotR, btnSel, btnFull, btnVmode, btnClose,
|
||||||
|
@ -144,7 +146,7 @@ window.baguetteBox = (function () {
|
||||||
selectorData.galleries.push(gallery);
|
selectorData.galleries.push(gallery);
|
||||||
});
|
});
|
||||||
|
|
||||||
return selectorData.galleries;
|
return [selectorData.galleries, options];
|
||||||
}
|
}
|
||||||
|
|
||||||
function clearCachedData() {
|
function clearCachedData() {
|
||||||
|
@ -593,6 +595,9 @@ window.baguetteBox = (function () {
|
||||||
if (overlay.style.display === 'none')
|
if (overlay.style.display === 'none')
|
||||||
return;
|
return;
|
||||||
|
|
||||||
|
if (options.duringHide)
|
||||||
|
options.duringHide();
|
||||||
|
|
||||||
sethash('');
|
sethash('');
|
||||||
unbindEvents();
|
unbindEvents();
|
||||||
try {
|
try {
|
||||||
|
@ -613,7 +618,7 @@ window.baguetteBox = (function () {
|
||||||
if (options.afterHide)
|
if (options.afterHide)
|
||||||
options.afterHide();
|
options.afterHide();
|
||||||
|
|
||||||
documentLastFocus && documentLastFocus.focus();
|
options.refocus && documentLastFocus && documentLastFocus.focus();
|
||||||
isOverlayVisible = false;
|
isOverlayVisible = false;
|
||||||
unvid();
|
unvid();
|
||||||
unfig();
|
unfig();
|
||||||
|
|
|
@ -985,6 +985,10 @@ html.y #path a:hover {
|
||||||
margin: 0 auto;
|
margin: 0 auto;
|
||||||
display: block;
|
display: block;
|
||||||
}
|
}
|
||||||
|
#ggrid.nocrop>a img {
|
||||||
|
max-height: 20em;
|
||||||
|
max-height: calc(var(--grid-sz)*2);
|
||||||
|
}
|
||||||
#ggrid>a.dir:before {
|
#ggrid>a.dir:before {
|
||||||
content: '📂';
|
content: '📂';
|
||||||
}
|
}
|
||||||
|
@ -1151,9 +1155,6 @@ html.y #widget.open {
|
||||||
@keyframes spin {
|
@keyframes spin {
|
||||||
100% {transform: rotate(360deg)}
|
100% {transform: rotate(360deg)}
|
||||||
}
|
}
|
||||||
@media (prefers-reduced-motion) {
|
|
||||||
@keyframes spin { }
|
|
||||||
}
|
|
||||||
@keyframes fadein {
|
@keyframes fadein {
|
||||||
0% {opacity: 0}
|
0% {opacity: 0}
|
||||||
100% {opacity: 1}
|
100% {opacity: 1}
|
||||||
|
@ -1247,6 +1248,13 @@ html.y #widget.open {
|
||||||
0% {opacity:0}
|
0% {opacity:0}
|
||||||
100% {opacity:1}
|
100% {opacity:1}
|
||||||
}
|
}
|
||||||
|
#ggrid>a.glow {
|
||||||
|
animation: gexit .6s ease-out;
|
||||||
|
}
|
||||||
|
@keyframes gexit {
|
||||||
|
0% {box-shadow: 0 0 0 2em var(--a)}
|
||||||
|
100% {box-shadow: 0 0 0em 0em var(--a)}
|
||||||
|
}
|
||||||
#wzip a {
|
#wzip a {
|
||||||
font-size: .4em;
|
font-size: .4em;
|
||||||
margin: -.3em .1em;
|
margin: -.3em .1em;
|
||||||
|
@ -1775,6 +1783,7 @@ html.y #tree.nowrap .ntree a+a:hover {
|
||||||
padding: 0;
|
padding: 0;
|
||||||
}
|
}
|
||||||
#thumbs,
|
#thumbs,
|
||||||
|
#au_prescan,
|
||||||
#au_fullpre,
|
#au_fullpre,
|
||||||
#au_os_seek,
|
#au_os_seek,
|
||||||
#au_osd_cv,
|
#au_osd_cv,
|
||||||
|
@ -1782,7 +1791,8 @@ html.y #tree.nowrap .ntree a+a:hover {
|
||||||
opacity: .3;
|
opacity: .3;
|
||||||
}
|
}
|
||||||
#griden.on+#thumbs,
|
#griden.on+#thumbs,
|
||||||
#au_preload.on+#au_fullpre,
|
#au_preload.on+#au_prescan,
|
||||||
|
#au_preload.on+#au_prescan+#au_fullpre,
|
||||||
#au_os_ctl.on+#au_os_seek,
|
#au_os_ctl.on+#au_os_seek,
|
||||||
#au_os_ctl.on+#au_os_seek+#au_osd_cv,
|
#au_os_ctl.on+#au_os_seek+#au_osd_cv,
|
||||||
#u2turbo.on+#u2tdate {
|
#u2turbo.on+#u2tdate {
|
||||||
|
@ -3136,7 +3146,7 @@ html.d #treepar {
|
||||||
margin-top: 1.7em;
|
margin-top: 1.7em;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@supports (display: grid) {
|
@supports (display: grid) and (gap: 1em) {
|
||||||
#ggrid {
|
#ggrid {
|
||||||
display: grid;
|
display: grid;
|
||||||
margin: 0em 0.25em;
|
margin: 0em 0.25em;
|
||||||
|
@ -3161,3 +3171,24 @@ html.d #treepar {
|
||||||
padding: 0.2em;
|
padding: 0.2em;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
@media (prefers-reduced-motion) {
|
||||||
|
@keyframes spin { }
|
||||||
|
@keyframes gexit { }
|
||||||
|
@keyframes bounce { }
|
||||||
|
@keyframes bounceFromLeft { }
|
||||||
|
@keyframes bounceFromRight { }
|
||||||
|
|
||||||
|
#ggrid>a:before,
|
||||||
|
#widget.anim,
|
||||||
|
#u2tabw,
|
||||||
|
.dropdesc,
|
||||||
|
.dropdesc b,
|
||||||
|
.dropdesc>div>div {
|
||||||
|
transition: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -161,3 +161,4 @@
|
||||||
</body>
|
</body>
|
||||||
|
|
||||||
</html>
|
</html>
|
||||||
|
|
||||||
|
|
|
@ -194,6 +194,7 @@ var Ls = {
|
||||||
|
|
||||||
"ct_thumb": "in grid-view, toggle icons or thumbnails$NHotkey: T",
|
"ct_thumb": "in grid-view, toggle icons or thumbnails$NHotkey: T",
|
||||||
"ct_csel": "use CTRL and SHIFT for file selection in grid-view",
|
"ct_csel": "use CTRL and SHIFT for file selection in grid-view",
|
||||||
|
"ct_ihop": "when the image viewer is closed, scroll down to the last viewed file",
|
||||||
"ct_dots": "show hidden files (if server permits)",
|
"ct_dots": "show hidden files (if server permits)",
|
||||||
"ct_dir1st": "sort folders before files",
|
"ct_dir1st": "sort folders before files",
|
||||||
"ct_readme": "show README.md in folder listings",
|
"ct_readme": "show README.md in folder listings",
|
||||||
|
@ -240,6 +241,7 @@ var Ls = {
|
||||||
|
|
||||||
"mt_shuf": "shuffle the songs in each folder\">🔀",
|
"mt_shuf": "shuffle the songs in each folder\">🔀",
|
||||||
"mt_preload": "start loading the next song near the end for gapless playback\">preload",
|
"mt_preload": "start loading the next song near the end for gapless playback\">preload",
|
||||||
|
"mt_prescan": "go to the next folder before the last song$Nends, keeping the webbrowser happy$Nso it doesn't stop the playback\">nav",
|
||||||
"mt_fullpre": "try to preload the entire song;$N✅ enable on <b>unreliable</b> connections,$N❌ <b>disable</b> on slow connections probably\">full",
|
"mt_fullpre": "try to preload the entire song;$N✅ enable on <b>unreliable</b> connections,$N❌ <b>disable</b> on slow connections probably\">full",
|
||||||
"mt_waves": "waveform seekbar:$Nshow audio amplitude in the scrubber\">~s",
|
"mt_waves": "waveform seekbar:$Nshow audio amplitude in the scrubber\">~s",
|
||||||
"mt_npclip": "show buttons for clipboarding the currently playing song\">/np",
|
"mt_npclip": "show buttons for clipboarding the currently playing song\">/np",
|
||||||
|
@ -272,6 +274,8 @@ var Ls = {
|
||||||
"mm_e403": "Could not play audio; error 403: Access denied.\n\nTry pressing F5 to reload, maybe you got logged out",
|
"mm_e403": "Could not play audio; error 403: Access denied.\n\nTry pressing F5 to reload, maybe you got logged out",
|
||||||
"mm_e5xx": "Could not play audio; server error ",
|
"mm_e5xx": "Could not play audio; server error ",
|
||||||
"mm_nof": "not finding any more audio files nearby",
|
"mm_nof": "not finding any more audio files nearby",
|
||||||
|
"mm_prescan": "Looking for music to play next...",
|
||||||
|
"mm_scank": "Found the next song:",
|
||||||
"mm_uncache": "cache cleared; all songs will redownload on next playback",
|
"mm_uncache": "cache cleared; all songs will redownload on next playback",
|
||||||
"mm_pwrsv": "<p>it looks like playback is being interrupted by your phone's power-saving settings!</p>" + '<p>please go to <a target="_blank" href="https://user-images.githubusercontent.com/241032/235262121-2ffc51ae-7821-4310-a322-c3b7a507890c.png">the app settings of your browser</a> and then <a target="_blank" href="https://user-images.githubusercontent.com/241032/235262123-c328cca9-3930-4948-bd18-3949b9fd3fcf.png">allow unrestricted battery usage</a> to fix it.</p><p><em>however,</em> it could also be due to the browser\'s autoplay settings;</p><p>Firefox: tap the icon on the left side of the address bar, then select "autoplay" and "allow audio"</p><p>Chrome: the problem will gradually dissipate as you play more music on this site</p>',
|
"mm_pwrsv": "<p>it looks like playback is being interrupted by your phone's power-saving settings!</p>" + '<p>please go to <a target="_blank" href="https://user-images.githubusercontent.com/241032/235262121-2ffc51ae-7821-4310-a322-c3b7a507890c.png">the app settings of your browser</a> and then <a target="_blank" href="https://user-images.githubusercontent.com/241032/235262123-c328cca9-3930-4948-bd18-3949b9fd3fcf.png">allow unrestricted battery usage</a> to fix it.</p><p><em>however,</em> it could also be due to the browser\'s autoplay settings;</p><p>Firefox: tap the icon on the left side of the address bar, then select "autoplay" and "allow audio"</p><p>Chrome: the problem will gradually dissipate as you play more music on this site</p>',
|
||||||
"mm_iosblk": "<p>your web browser thinks the audio playback is unwanted, and it decided to block playback until you start another track manually... unfortunately we are both powerless in telling it otherwise</p><p>supposedly this will get better as you continue playing music on this site, but I'm unfamiliar with apple devices so idk if that's true</p><p>you could try another browser, maybe firefox or chrome?</p>",
|
"mm_iosblk": "<p>your web browser thinks the audio playback is unwanted, and it decided to block playback until you start another track manually... unfortunately we are both powerless in telling it otherwise</p><p>supposedly this will get better as you continue playing music on this site, but I'm unfamiliar with apple devices so idk if that's true</p><p>you could try another browser, maybe firefox or chrome?</p>",
|
||||||
|
@ -346,7 +350,8 @@ var Ls = {
|
||||||
"tvt_edit": "open file in text editor$NHotkey: E\">✏️ edit",
|
"tvt_edit": "open file in text editor$NHotkey: E\">✏️ edit",
|
||||||
|
|
||||||
"gt_msel": "enable file selection; ctrl-click a file to override$N$N<em>when active: doubleclick a file / folder to open it</em>$N$NHotkey: S\">multiselect",
|
"gt_msel": "enable file selection; ctrl-click a file to override$N$N<em>when active: doubleclick a file / folder to open it</em>$N$NHotkey: S\">multiselect",
|
||||||
"gt_full": "show uncropped thumbnails\">full",
|
"gt_crop": "center-crop thumbnails\">crop",
|
||||||
|
"gt_3x": "hi-res thumbnails\">3x",
|
||||||
"gt_zoom": "zoom",
|
"gt_zoom": "zoom",
|
||||||
"gt_chop": "chop",
|
"gt_chop": "chop",
|
||||||
"gt_sort": "sort by",
|
"gt_sort": "sort by",
|
||||||
|
@ -686,6 +691,7 @@ var Ls = {
|
||||||
|
|
||||||
"ct_thumb": "vis miniatyrbilder istedenfor ikoner$NSnarvei: T",
|
"ct_thumb": "vis miniatyrbilder istedenfor ikoner$NSnarvei: T",
|
||||||
"ct_csel": "bruk tastene CTRL og SHIFT for markering av filer i ikonvisning",
|
"ct_csel": "bruk tastene CTRL og SHIFT for markering av filer i ikonvisning",
|
||||||
|
"ct_ihop": "bla ned til sist viste bilde når bildeviseren lukkes",
|
||||||
"ct_dots": "vis skjulte filer (gitt at serveren tillater det)",
|
"ct_dots": "vis skjulte filer (gitt at serveren tillater det)",
|
||||||
"ct_dir1st": "sorter slik at mapper kommer foran filer",
|
"ct_dir1st": "sorter slik at mapper kommer foran filer",
|
||||||
"ct_readme": "vis README.md nedenfor filene",
|
"ct_readme": "vis README.md nedenfor filene",
|
||||||
|
@ -732,6 +738,7 @@ var Ls = {
|
||||||
|
|
||||||
"mt_shuf": "sangene i hver mappe$Nspilles i tilfeldig rekkefølge\">🔀",
|
"mt_shuf": "sangene i hver mappe$Nspilles i tilfeldig rekkefølge\">🔀",
|
||||||
"mt_preload": "hent ned litt av neste sang i forkant,$Nslik at pausen i overgangen blir mindre\">forles",
|
"mt_preload": "hent ned litt av neste sang i forkant,$Nslik at pausen i overgangen blir mindre\">forles",
|
||||||
|
"mt_prescan": "ved behov, bla til neste mappe$Nslik at nettleseren lar oss$Nfortsette å spille musikk\">bla",
|
||||||
"mt_fullpre": "hent ned hele neste sang, ikke bare litt:$N✅ skru på hvis nettet ditt er <b>ustabilt</b>,$N❌ skru av hvis nettet ditt er <b>tregt</b>\">full",
|
"mt_fullpre": "hent ned hele neste sang, ikke bare litt:$N✅ skru på hvis nettet ditt er <b>ustabilt</b>,$N❌ skru av hvis nettet ditt er <b>tregt</b>\">full",
|
||||||
"mt_waves": "waveform seekbar:$Nvis volumkurve i avspillingsfeltet\">~s",
|
"mt_waves": "waveform seekbar:$Nvis volumkurve i avspillingsfeltet\">~s",
|
||||||
"mt_npclip": "vis knapper for å kopiere info om sangen du hører på\">/np",
|
"mt_npclip": "vis knapper for å kopiere info om sangen du hører på\">/np",
|
||||||
|
@ -764,6 +771,8 @@ var Ls = {
|
||||||
"mm_e403": "Avspilling feilet: Tilgang nektet.\n\nKanskje du ble logget ut?\nPrøv å trykk F5 for å laste siden på nytt.",
|
"mm_e403": "Avspilling feilet: Tilgang nektet.\n\nKanskje du ble logget ut?\nPrøv å trykk F5 for å laste siden på nytt.",
|
||||||
"mm_e5xx": "Avspilling feilet: ",
|
"mm_e5xx": "Avspilling feilet: ",
|
||||||
"mm_nof": "finner ikke flere sanger i nærheten",
|
"mm_nof": "finner ikke flere sanger i nærheten",
|
||||||
|
"mm_prescan": "Leter etter neste sang...",
|
||||||
|
"mm_scank": "Fant neste sang:",
|
||||||
"mm_uncache": "alle sanger vil lastes på nytt ved neste avspilling",
|
"mm_uncache": "alle sanger vil lastes på nytt ved neste avspilling",
|
||||||
"mm_pwrsv": "<p>det ser ut som musikken ble avbrutt av telefonen sine strømsparings-innstillinger!</p>" + '<p>ta en tur innom <a target="_blank" href="https://user-images.githubusercontent.com/241032/235262121-2ffc51ae-7821-4310-a322-c3b7a507890c.png">app-innstillingene til nettleseren din</a> og så <a target="_blank" href="https://user-images.githubusercontent.com/241032/235262123-c328cca9-3930-4948-bd18-3949b9fd3fcf.png">tillat ubegrenset batteriforbruk</a></p><p>NB: det kan også være pga. autoplay-innstillingene, så prøv dette:</p><p>Firefox: klikk på ikonet i venstre side av addressefeltet, velg "autoplay" og "tillat lyd"</p><p>Chrome: problemet vil minske gradvis jo mer musikk du spiller på denne siden</p>',
|
"mm_pwrsv": "<p>det ser ut som musikken ble avbrutt av telefonen sine strømsparings-innstillinger!</p>" + '<p>ta en tur innom <a target="_blank" href="https://user-images.githubusercontent.com/241032/235262121-2ffc51ae-7821-4310-a322-c3b7a507890c.png">app-innstillingene til nettleseren din</a> og så <a target="_blank" href="https://user-images.githubusercontent.com/241032/235262123-c328cca9-3930-4948-bd18-3949b9fd3fcf.png">tillat ubegrenset batteriforbruk</a></p><p>NB: det kan også være pga. autoplay-innstillingene, så prøv dette:</p><p>Firefox: klikk på ikonet i venstre side av addressefeltet, velg "autoplay" og "tillat lyd"</p><p>Chrome: problemet vil minske gradvis jo mer musikk du spiller på denne siden</p>',
|
||||||
"mm_iosblk": "<p>nettleseren din tror at musikken er uønsket, og den bestemte seg for å stoppe avspillingen slik at du manuelt må velge en ny sang... dessverre er både du og jeg maktesløse når den har bestemt seg.</p><p>det ryktes at problemet vil minske jo mer musikk du spiller på denne siden, men jeg er ikke godt kjent med apple-dingser så jeg er ikke sikker.</p><p>kanskje firefox eller chrome fungerer bedre?</p>",
|
"mm_iosblk": "<p>nettleseren din tror at musikken er uønsket, og den bestemte seg for å stoppe avspillingen slik at du manuelt må velge en ny sang... dessverre er både du og jeg maktesløse når den har bestemt seg.</p><p>det ryktes at problemet vil minske jo mer musikk du spiller på denne siden, men jeg er ikke godt kjent med apple-dingser så jeg er ikke sikker.</p><p>kanskje firefox eller chrome fungerer bedre?</p>",
|
||||||
|
@ -838,7 +847,8 @@ var Ls = {
|
||||||
"tvt_edit": "redigér filen$NSnarvei: E\">✏️ endre",
|
"tvt_edit": "redigér filen$NSnarvei: E\">✏️ endre",
|
||||||
|
|
||||||
"gt_msel": "markér filer istedenfor å åpne dem; ctrl-klikk filer for å overstyre$N$N<em>når aktiv: dobbelklikk en fil / mappe for å åpne</em>$N$NSnarvei: S\">markering",
|
"gt_msel": "markér filer istedenfor å åpne dem; ctrl-klikk filer for å overstyre$N$N<em>når aktiv: dobbelklikk en fil / mappe for å åpne</em>$N$NSnarvei: S\">markering",
|
||||||
"gt_full": "ikke beskjær bildene\">full",
|
"gt_crop": "beskjær ikonene så de passer bedre\">✂",
|
||||||
|
"gt_3x": "høyere oppløsning på ikoner\">3x",
|
||||||
"gt_zoom": "zoom",
|
"gt_zoom": "zoom",
|
||||||
"gt_chop": "trim",
|
"gt_chop": "trim",
|
||||||
"gt_sort": "sorter",
|
"gt_sort": "sorter",
|
||||||
|
@ -1177,6 +1187,7 @@ ebi('op_cfg').innerHTML = (
|
||||||
' <a id="griden" class="tgl btn" href="#" tt="' + L.wt_grid + '">田 the grid</a>\n' +
|
' <a id="griden" class="tgl btn" href="#" tt="' + L.wt_grid + '">田 the grid</a>\n' +
|
||||||
' <a id="thumbs" class="tgl btn" href="#" tt="' + L.ct_thumb + '">🖼️ thumbs</a>\n' +
|
' <a id="thumbs" class="tgl btn" href="#" tt="' + L.ct_thumb + '">🖼️ thumbs</a>\n' +
|
||||||
' <a id="csel" class="tgl btn" href="#" tt="' + L.ct_csel + '">sel</a>\n' +
|
' <a id="csel" class="tgl btn" href="#" tt="' + L.ct_csel + '">sel</a>\n' +
|
||||||
|
' <a id="ihop" class="tgl btn" href="#" tt="' + L.ct_ihop + '">g⮯</a>\n' +
|
||||||
' <a id="dotfiles" class="tgl btn" href="#" tt="' + L.ct_dots + '">dotfiles</a>\n' +
|
' <a id="dotfiles" class="tgl btn" href="#" tt="' + L.ct_dots + '">dotfiles</a>\n' +
|
||||||
' <a id="dir1st" class="tgl btn" href="#" tt="' + L.ct_dir1st + '">📁 first</a>\n' +
|
' <a id="dir1st" class="tgl btn" href="#" tt="' + L.ct_dir1st + '">📁 first</a>\n' +
|
||||||
' <a id="ireadme" class="tgl btn" href="#" tt="' + L.ct_readme + '">📜 readme</a>\n' +
|
' <a id="ireadme" class="tgl btn" href="#" tt="' + L.ct_readme + '">📜 readme</a>\n' +
|
||||||
|
@ -1391,6 +1402,7 @@ var mpl = (function () {
|
||||||
'<div><h3>' + L.cl_opts + '</h3><div>' +
|
'<div><h3>' + L.cl_opts + '</h3><div>' +
|
||||||
'<a href="#" class="tgl btn" id="au_shuf" tt="' + L.mt_shuf + '</a>' +
|
'<a href="#" class="tgl btn" id="au_shuf" tt="' + L.mt_shuf + '</a>' +
|
||||||
'<a href="#" class="tgl btn" id="au_preload" tt="' + L.mt_preload + '</a>' +
|
'<a href="#" class="tgl btn" id="au_preload" tt="' + L.mt_preload + '</a>' +
|
||||||
|
'<a href="#" class="tgl btn" id="au_prescan" tt="' + L.mt_prescan + '</a>' +
|
||||||
'<a href="#" class="tgl btn" id="au_fullpre" tt="' + L.mt_fullpre + '</a>' +
|
'<a href="#" class="tgl btn" id="au_fullpre" tt="' + L.mt_fullpre + '</a>' +
|
||||||
'<a href="#" class="tgl btn" id="au_waves" tt="' + L.mt_waves + '</a>' +
|
'<a href="#" class="tgl btn" id="au_waves" tt="' + L.mt_waves + '</a>' +
|
||||||
'<a href="#" class="tgl btn" id="au_npclip" tt="' + L.mt_npclip + '</a>' +
|
'<a href="#" class="tgl btn" id="au_npclip" tt="' + L.mt_npclip + '</a>' +
|
||||||
|
@ -1435,6 +1447,7 @@ var mpl = (function () {
|
||||||
mp.read_order(); // don't bind
|
mp.read_order(); // don't bind
|
||||||
});
|
});
|
||||||
bcfg_bind(r, 'preload', 'au_preload', true);
|
bcfg_bind(r, 'preload', 'au_preload', true);
|
||||||
|
bcfg_bind(r, 'prescan', 'au_prescan', true);
|
||||||
bcfg_bind(r, 'fullpre', 'au_fullpre', false);
|
bcfg_bind(r, 'fullpre', 'au_fullpre', false);
|
||||||
bcfg_bind(r, 'waves', 'au_waves', true, function (v) {
|
bcfg_bind(r, 'waves', 'au_waves', true, function (v) {
|
||||||
if (!v) pbar.unwave();
|
if (!v) pbar.unwave();
|
||||||
|
@ -1580,8 +1593,10 @@ var mpl = (function () {
|
||||||
ebi('np_title').textContent = np.title || '';
|
ebi('np_title').textContent = np.title || '';
|
||||||
ebi('np_dur').textContent = np['.dur'] || '';
|
ebi('np_dur').textContent = np['.dur'] || '';
|
||||||
ebi('np_url').textContent = get_vpath() + np.file.split('?')[0];
|
ebi('np_url').textContent = get_vpath() + np.file.split('?')[0];
|
||||||
if (!MOBILE)
|
if (!MOBILE && cover)
|
||||||
ebi('np_img').setAttribute('src', cover || '');
|
ebi('np_img').setAttribute('src', cover);
|
||||||
|
else
|
||||||
|
ebi('np_img').removeAttribute('src');
|
||||||
|
|
||||||
navigator.mediaSession.metadata = new MediaMetadata(tags);
|
navigator.mediaSession.metadata = new MediaMetadata(tags);
|
||||||
navigator.mediaSession.setActionHandler('play', mplay);
|
navigator.mediaSession.setActionHandler('play', mplay);
|
||||||
|
@ -1768,10 +1783,12 @@ function MPlayer() {
|
||||||
}
|
}
|
||||||
|
|
||||||
r.preload = function (url, full) {
|
r.preload = function (url, full) {
|
||||||
|
var t0 = Date.now(),
|
||||||
|
fname = uricom_dec(url.split('/').pop());
|
||||||
|
|
||||||
url = mpl.acode(url);
|
url = mpl.acode(url);
|
||||||
url += (url.indexOf('?') < 0 ? '?' : '&') + 'cache=987&_=' + ACB;
|
url += (url.indexOf('?') < 0 ? '?' : '&') + 'cache=987&_=' + ACB;
|
||||||
mpl.preload_url = full ? url : null;
|
mpl.preload_url = full ? url : null;
|
||||||
var t0 = Date.now();
|
|
||||||
|
|
||||||
if (mpl.waves)
|
if (mpl.waves)
|
||||||
fetch(url.replace(/\bth=opus&/, '') + '&th=p').then(function (x) {
|
fetch(url.replace(/\bth=opus&/, '') + '&th=p').then(function (x) {
|
||||||
|
@ -1805,6 +1822,12 @@ function MPlayer() {
|
||||||
r.au2.onloadeddata = r.au2.onloadedmetadata = r.nopause;
|
r.au2.onloadeddata = r.au2.onloadedmetadata = r.nopause;
|
||||||
r.au2.preload = "auto";
|
r.au2.preload = "auto";
|
||||||
r.au2.src = r.au2.rsrc = url;
|
r.au2.src = r.au2.rsrc = url;
|
||||||
|
|
||||||
|
if (mpl.prescan_evp) {
|
||||||
|
mpl.prescan_evp = null;
|
||||||
|
toast.ok(7, L.mm_scank + "\n" + esc(fname));
|
||||||
|
}
|
||||||
|
console.log("preloading " + fname);
|
||||||
};
|
};
|
||||||
|
|
||||||
r.nopause = function () {
|
r.nopause = function () {
|
||||||
|
@ -2451,6 +2474,10 @@ var mpui = (function () {
|
||||||
timer.add(updater_impl, true);
|
timer.add(updater_impl, true);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
function repreload() {
|
||||||
|
preloaded = fpreloaded = null;
|
||||||
|
}
|
||||||
|
|
||||||
function updater_impl() {
|
function updater_impl() {
|
||||||
if (!mp.au) {
|
if (!mp.au) {
|
||||||
widget.paused(true);
|
widget.paused(true);
|
||||||
|
@ -2512,7 +2539,26 @@ var mpui = (function () {
|
||||||
|
|
||||||
if (full !== null)
|
if (full !== null)
|
||||||
try {
|
try {
|
||||||
mp.preload(mp.tracks[mp.order[mp.order.indexOf(mp.au.tid) + 1]], full);
|
var oi = mp.order.indexOf(mp.au.tid) + 1,
|
||||||
|
evp = get_evpath();
|
||||||
|
|
||||||
|
if (mpl.pb_mode == 'loop' || mp.au.evp != evp)
|
||||||
|
oi = 0;
|
||||||
|
|
||||||
|
if (oi >= mp.order.length) {
|
||||||
|
if (!mpl.prescan)
|
||||||
|
throw "prescan disabled";
|
||||||
|
|
||||||
|
if (mpl.prescan_evp == evp)
|
||||||
|
throw "evp match";
|
||||||
|
|
||||||
|
mpl.prescan_evp = evp;
|
||||||
|
toast.inf(10, L.mm_prescan);
|
||||||
|
treectl.ls_cb = repreload;
|
||||||
|
tree_neigh(1);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
mp.preload(mp.tracks[mp.order[oi]], full);
|
||||||
}
|
}
|
||||||
catch (ex) {
|
catch (ex) {
|
||||||
console.log("preload failed", ex);
|
console.log("preload failed", ex);
|
||||||
|
@ -3039,6 +3085,7 @@ function play(tid, is_ev, seek) {
|
||||||
}, 500);
|
}, 500);
|
||||||
|
|
||||||
mp.au.tid = tid;
|
mp.au.tid = tid;
|
||||||
|
mp.au.evp = get_evpath();
|
||||||
mp.au.volume = mp.expvol(mp.vol);
|
mp.au.volume = mp.expvol(mp.vol);
|
||||||
var trs = QSA('#files tr.play');
|
var trs = QSA('#files tr.play');
|
||||||
for (var a = 0, aa = trs.length; a < aa; a++)
|
for (var a = 0, aa = trs.length; a < aa; a++)
|
||||||
|
@ -4473,9 +4520,11 @@ var thegrid = (function () {
|
||||||
gfiles.innerHTML = (
|
gfiles.innerHTML = (
|
||||||
'<div id="ghead" class="ghead">' +
|
'<div id="ghead" class="ghead">' +
|
||||||
'<a href="#" class="tgl btn" id="gridsel" tt="' + L.gt_msel + '</a> ' +
|
'<a href="#" class="tgl btn" id="gridsel" tt="' + L.gt_msel + '</a> ' +
|
||||||
'<a href="#" class="tgl btn" id="gridfull" tt="' + L.gt_full + '</a> <span>' + L.gt_zoom + ': ' +
|
'<a href="#" class="tgl btn" id="gridcrop" tt="' + L.gt_crop + '</a> ' +
|
||||||
'<a href="#" class="btn" z="-1.2" tt="Hotkey: shift-A">–</a> ' +
|
'<a href="#" class="tgl btn" id="grid3x" tt="' + L.gt_3x + '</a> ' +
|
||||||
'<a href="#" class="btn" z="1.2" tt="Hotkey: shift-D">+</a></span> <span>' + L.gt_chop + ': ' +
|
'<span>' + L.gt_zoom + ': ' +
|
||||||
|
'<a href="#" class="btn" z="-1.1" tt="Hotkey: shift-A">–</a> ' +
|
||||||
|
'<a href="#" class="btn" z="1.1" tt="Hotkey: shift-D">+</a></span> <span>' + L.gt_chop + ': ' +
|
||||||
'<a href="#" class="btn" l="-1" tt="' + L.gt_c1 + '">–</a> ' +
|
'<a href="#" class="btn" l="-1" tt="' + L.gt_c1 + '">–</a> ' +
|
||||||
'<a href="#" class="btn" l="1" tt="' + L.gt_c2 + '">+</a></span> <span>' + L.gt_sort + ': ' +
|
'<a href="#" class="btn" l="1" tt="' + L.gt_c2 + '">+</a></span> <span>' + L.gt_sort + ': ' +
|
||||||
'<a href="#" s="href">' + L.gt_name + '</a> ' +
|
'<a href="#" s="href">' + L.gt_name + '</a> ' +
|
||||||
|
@ -4486,9 +4535,10 @@ var thegrid = (function () {
|
||||||
'<div id="ggrid"></div>'
|
'<div id="ggrid"></div>'
|
||||||
);
|
);
|
||||||
lfiles.parentNode.insertBefore(gfiles, lfiles);
|
lfiles.parentNode.insertBefore(gfiles, lfiles);
|
||||||
|
var ggrid = ebi('ggrid');
|
||||||
|
|
||||||
var r = {
|
var r = {
|
||||||
'sz': clamp(fcfg_get('gridsz', 10), 4, 40),
|
'sz': clamp(fcfg_get('gridsz', 10), 4, 80),
|
||||||
'ln': clamp(icfg_get('gridln', 3), 1, 7),
|
'ln': clamp(icfg_get('gridln', 3), 1, 7),
|
||||||
'isdirty': true,
|
'isdirty': true,
|
||||||
'bbox': null
|
'bbox': null
|
||||||
|
@ -4506,7 +4556,7 @@ var thegrid = (function () {
|
||||||
if (l)
|
if (l)
|
||||||
return setln(parseInt(l));
|
return setln(parseInt(l));
|
||||||
|
|
||||||
var t = ebi('files').tHead.rows[0].cells;
|
var t = lfiles.tHead.rows[0].cells;
|
||||||
for (var a = 0; a < t.length; a++)
|
for (var a = 0; a < t.length; a++)
|
||||||
if (t[a].getAttribute('name') == s) {
|
if (t[a].getAttribute('name') == s) {
|
||||||
t[a].click();
|
t[a].click();
|
||||||
|
@ -4531,10 +4581,13 @@ var thegrid = (function () {
|
||||||
|
|
||||||
lfiles = ebi('files');
|
lfiles = ebi('files');
|
||||||
gfiles = ebi('gfiles');
|
gfiles = ebi('gfiles');
|
||||||
|
ggrid = ebi('ggrid');
|
||||||
|
|
||||||
var vis = has(perms, "read");
|
var vis = has(perms, "read");
|
||||||
gfiles.style.display = vis && r.en ? '' : 'none';
|
gfiles.style.display = vis && r.en ? '' : 'none';
|
||||||
lfiles.style.display = vis && !r.en ? '' : 'none';
|
lfiles.style.display = vis && !r.en ? '' : 'none';
|
||||||
|
clmod(ggrid, 'crop', r.crop);
|
||||||
|
clmod(ggrid, 'nocrop', !r.crop);
|
||||||
ebi('pro').style.display = ebi('epi').style.display = ebi('lazy').style.display = ebi('treeul').style.display = ebi('treepar').style.display = '';
|
ebi('pro').style.display = ebi('epi').style.display = ebi('lazy').style.display = ebi('treeul').style.display = ebi('treepar').style.display = '';
|
||||||
ebi('bdoc').style.display = 'none';
|
ebi('bdoc').style.display = 'none';
|
||||||
clmod(ebi('wrap'), 'doc');
|
clmod(ebi('wrap'), 'doc');
|
||||||
|
@ -4551,10 +4604,10 @@ var thegrid = (function () {
|
||||||
|
|
||||||
r.setdirty = function () {
|
r.setdirty = function () {
|
||||||
r.dirty = true;
|
r.dirty = true;
|
||||||
if (r.en) {
|
if (r.en)
|
||||||
loadgrid();
|
loadgrid();
|
||||||
}
|
else
|
||||||
r.setvis();
|
r.setvis();
|
||||||
};
|
};
|
||||||
|
|
||||||
function setln(v) {
|
function setln(v) {
|
||||||
|
@ -4574,7 +4627,7 @@ var thegrid = (function () {
|
||||||
|
|
||||||
function setsz(v) {
|
function setsz(v) {
|
||||||
if (v !== undefined) {
|
if (v !== undefined) {
|
||||||
r.sz = clamp(v, 4, 40);
|
r.sz = clamp(v, 4, 80);
|
||||||
swrite('gridsz', r.sz);
|
swrite('gridsz', r.sz);
|
||||||
setTimeout(r.tippen, 20);
|
setTimeout(r.tippen, 20);
|
||||||
}
|
}
|
||||||
|
@ -4582,6 +4635,7 @@ var thegrid = (function () {
|
||||||
document.documentElement.style.setProperty('--grid-sz', r.sz + 'em');
|
document.documentElement.style.setProperty('--grid-sz', r.sz + 'em');
|
||||||
}
|
}
|
||||||
catch (ex) { }
|
catch (ex) { }
|
||||||
|
aligngriditems();
|
||||||
}
|
}
|
||||||
setsz();
|
setsz();
|
||||||
|
|
||||||
|
@ -4723,7 +4777,7 @@ var thegrid = (function () {
|
||||||
pels[a].removeAttribute('tt');
|
pels[a].removeAttribute('tt');
|
||||||
}
|
}
|
||||||
|
|
||||||
tt.att(ebi('ggrid'));
|
tt.att(ggrid);
|
||||||
};
|
};
|
||||||
|
|
||||||
function loadgrid() {
|
function loadgrid() {
|
||||||
|
@ -4734,8 +4788,11 @@ var thegrid = (function () {
|
||||||
if (!r.dirty)
|
if (!r.dirty)
|
||||||
return r.loadsel();
|
return r.loadsel();
|
||||||
|
|
||||||
if (dfull != r.full && !sread('gridfull'))
|
if (dcrop.startsWith('f') || !sread('gridcrop'))
|
||||||
bcfg_upd_ui('gridfull', r.full = dfull);
|
bcfg_upd_ui('gridcrop', r.crop = ('y' == dcrop.slice(-1)));
|
||||||
|
|
||||||
|
if (dth3x.startsWith('f') || !sread('grid3x'))
|
||||||
|
bcfg_upd_ui('grid3x', r.x3 = ('y' == dth3x.slice(-1)));
|
||||||
|
|
||||||
var html = [],
|
var html = [],
|
||||||
svgs = new Set(),
|
svgs = new Set(),
|
||||||
|
@ -4754,8 +4811,10 @@ var thegrid = (function () {
|
||||||
|
|
||||||
if (r.thumbs) {
|
if (r.thumbs) {
|
||||||
ihref += '?th=' + (have_webp ? 'w' : 'j');
|
ihref += '?th=' + (have_webp ? 'w' : 'j');
|
||||||
if (r.full)
|
if (!r.crop)
|
||||||
ihref += 'f'
|
ihref += 'f';
|
||||||
|
if (r.x3)
|
||||||
|
ihref += '3';
|
||||||
if (href == "#")
|
if (href == "#")
|
||||||
ihref = SR + '/.cpr/ico/' + (ref == 'moar' ? '++' : 'exit');
|
ihref = SR + '/.cpr/ico/' + (ref == 'moar' ? '++' : 'exit');
|
||||||
}
|
}
|
||||||
|
@ -4788,13 +4847,17 @@ var thegrid = (function () {
|
||||||
ihref = SR + '/.cpr/ico/' + ext;
|
ihref = SR + '/.cpr/ico/' + ext;
|
||||||
}
|
}
|
||||||
ihref += (ihref.indexOf('?') > 0 ? '&' : '?') + 'cache=i&_=' + ACB;
|
ihref += (ihref.indexOf('?') > 0 ? '&' : '?') + 'cache=i&_=' + ACB;
|
||||||
|
if (CHROME)
|
||||||
|
ihref += "&raster";
|
||||||
|
|
||||||
html.push('<a href="' + ohref + '" ref="' + ref +
|
html.push('<a href="' + ohref + '" ref="' + ref +
|
||||||
'"' + ac + ' ttt="' + esc(name) + '"><img style="height:' +
|
'"' + ac + ' ttt="' + esc(name) + '"><img style="height:' +
|
||||||
(r.sz / 1.25) + 'em" onload="th_onload(this)" src="' +
|
(r.sz / 1.25) + 'em" loading="lazy" onload="th_onload(this)" src="' +
|
||||||
ihref + '" /><span' + ac + '>' + ao.innerHTML + '</span></a>');
|
ihref + '" /><span' + ac + '>' + ao.innerHTML + '</span></a>');
|
||||||
}
|
}
|
||||||
ebi('ggrid').innerHTML = html.join('\n');
|
ggrid.innerHTML = html.join('\n');
|
||||||
|
clmod(ggrid, 'crop', r.crop);
|
||||||
|
clmod(ggrid, 'nocrop', !r.crop);
|
||||||
|
|
||||||
var srch = ebi('unsearch'),
|
var srch = ebi('unsearch'),
|
||||||
gsel = ebi('gridsel');
|
gsel = ebi('gridsel');
|
||||||
|
@ -4812,6 +4875,7 @@ var thegrid = (function () {
|
||||||
r.dirty = false;
|
r.dirty = false;
|
||||||
r.bagit('#ggrid');
|
r.bagit('#ggrid');
|
||||||
r.loadsel();
|
r.loadsel();
|
||||||
|
aligngriditems();
|
||||||
setTimeout(r.tippen, 20);
|
setTimeout(r.tippen, 20);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -4822,7 +4886,12 @@ var thegrid = (function () {
|
||||||
if (r.bbox)
|
if (r.bbox)
|
||||||
baguetteBox.destroy();
|
baguetteBox.destroy();
|
||||||
|
|
||||||
r.bbox = baguetteBox.run(isrc, {
|
var br = baguetteBox.run(isrc, {
|
||||||
|
duringHide: r.onhide,
|
||||||
|
afterShow: function () {
|
||||||
|
r.bbox_opts.refocus = true;
|
||||||
|
document.body.style.overflow = 'hidden';
|
||||||
|
},
|
||||||
captions: function (g) {
|
captions: function (g) {
|
||||||
var idx = -1,
|
var idx = -1,
|
||||||
h = '' + g;
|
h = '' + g;
|
||||||
|
@ -4838,11 +4907,71 @@ var thegrid = (function () {
|
||||||
onChange: function (i) {
|
onChange: function (i) {
|
||||||
sethash('g' + r.bbox[i].imageElement.getAttribute('ref'));
|
sethash('g' + r.bbox[i].imageElement.getAttribute('ref'));
|
||||||
}
|
}
|
||||||
})[0];
|
});
|
||||||
|
r.bbox = br[0][0];
|
||||||
|
r.bbox_opts = br[1];
|
||||||
|
};
|
||||||
|
|
||||||
|
r.onhide = function () {
|
||||||
|
document.body.style.overflow = '';
|
||||||
|
if (!thegrid.ihop)
|
||||||
|
return;
|
||||||
|
|
||||||
|
try {
|
||||||
|
var el = QS('#ggrid a[ref="' + location.hash.slice(2) + '"]'),
|
||||||
|
f = function () {
|
||||||
|
try {
|
||||||
|
el.focus();
|
||||||
|
}
|
||||||
|
catch (ex) { }
|
||||||
|
};
|
||||||
|
|
||||||
|
f();
|
||||||
|
setTimeout(f, 10);
|
||||||
|
setTimeout(f, 100);
|
||||||
|
setTimeout(f, 200);
|
||||||
|
// thx fullscreen api
|
||||||
|
|
||||||
|
if (ANIM) {
|
||||||
|
clmod(el, 'glow', 1);
|
||||||
|
setTimeout(function () {
|
||||||
|
try {
|
||||||
|
clmod(el, 'glow');
|
||||||
|
}
|
||||||
|
catch (ex) { }
|
||||||
|
}, 600);
|
||||||
|
}
|
||||||
|
r.bbox_opts.refocus = false;
|
||||||
|
}
|
||||||
|
catch (ex) {
|
||||||
|
console.log('ihop:', ex);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
r.set_crop = function (en) {
|
||||||
|
if (!dcrop.startsWith('f'))
|
||||||
|
return r.setdirty();
|
||||||
|
|
||||||
|
r.crop = dcrop.endsWith('y');
|
||||||
|
bcfg_upd_ui('gridcrop', r.crop);
|
||||||
|
if (r.crop != en)
|
||||||
|
toast.warn(10, L.ul_btnlk);
|
||||||
|
};
|
||||||
|
|
||||||
|
r.set_x3 = function (en) {
|
||||||
|
if (!dth3x.startsWith('f'))
|
||||||
|
return r.setdirty();
|
||||||
|
|
||||||
|
r.x3 = dth3x.endsWith('y');
|
||||||
|
bcfg_upd_ui('grid3x', r.x3);
|
||||||
|
if (r.x3 != en)
|
||||||
|
toast.warn(10, L.ul_btnlk);
|
||||||
};
|
};
|
||||||
|
|
||||||
bcfg_bind(r, 'thumbs', 'thumbs', true, r.setdirty);
|
bcfg_bind(r, 'thumbs', 'thumbs', true, r.setdirty);
|
||||||
bcfg_bind(r, 'full', 'gridfull', false, r.setdirty);
|
bcfg_bind(r, 'ihop', 'ihop', true);
|
||||||
|
bcfg_bind(r, 'crop', 'gridcrop', !dcrop.endsWith('n'), r.set_crop);
|
||||||
|
bcfg_bind(r, 'x3', 'grid3x', dth3x.endsWith('y'), r.set_x3);
|
||||||
bcfg_bind(r, 'sel', 'gridsel', false, r.loadsel);
|
bcfg_bind(r, 'sel', 'gridsel', false, r.loadsel);
|
||||||
bcfg_bind(r, 'en', 'griden', dgrid, function (v) {
|
bcfg_bind(r, 'en', 'griden', dgrid, function (v) {
|
||||||
v ? loadgrid() : r.setvis(true);
|
v ? loadgrid() : r.setvis(true);
|
||||||
|
@ -5046,23 +5175,24 @@ document.onkeydown = function (e) {
|
||||||
return ebi('griden').click();
|
return ebi('griden').click();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (aet == 'tr' && ae.closest('#files')) {
|
if ((aet == 'tr' || aet == 'td') && ae.closest('#files')) {
|
||||||
var d = '', rem = 0;
|
var d = '', rem = 0;
|
||||||
if (k == 'ArrowUp') d = 'previous';
|
if (aet == 'td') ae = ae.closest('tr'); //ie11
|
||||||
if (k == 'ArrowDown') d = 'next';
|
if (k == 'ArrowUp' || k == 'Up') d = 'previous';
|
||||||
|
if (k == 'ArrowDown' || k == 'Down') d = 'next';
|
||||||
if (k == 'PageUp') { d = 'previous'; rem = 0.6; }
|
if (k == 'PageUp') { d = 'previous'; rem = 0.6; }
|
||||||
if (k == 'PageDown') { d = 'next'; rem = 0.6; }
|
if (k == 'PageDown') { d = 'next'; rem = 0.6; }
|
||||||
if (d) {
|
if (d) {
|
||||||
fselfunw(e, ae, d, rem);
|
fselfunw(e, ae, d, rem);
|
||||||
return ev(e);
|
return ev(e);
|
||||||
}
|
}
|
||||||
if (k == 'Space') {
|
if (k == 'Space' || k == 'Spacebar') {
|
||||||
clmod(ae, 'sel', 't');
|
clmod(ae, 'sel', 't');
|
||||||
msel.origin_tr(ae);
|
msel.origin_tr(ae);
|
||||||
msel.selui();
|
msel.selui();
|
||||||
return ev(e);
|
return ev(e);
|
||||||
}
|
}
|
||||||
if (k == 'KeyA' && ctrl(e)) {
|
if ((k == 'KeyA' || k == 'a') && ctrl(e)) {
|
||||||
var sel = msel.getsel(),
|
var sel = msel.getsel(),
|
||||||
all = msel.getall();
|
all = msel.getall();
|
||||||
|
|
||||||
|
@ -5073,7 +5203,7 @@ document.onkeydown = function (e) {
|
||||||
}
|
}
|
||||||
|
|
||||||
if (ae && ae.closest('pre')) {
|
if (ae && ae.closest('pre')) {
|
||||||
if (k == 'KeyA' && ctrl(e)) {
|
if ((k == 'KeyA' || k == 'a') && ctrl(e)) {
|
||||||
var sel = document.getSelection(),
|
var sel = document.getSelection(),
|
||||||
ran = document.createRange();
|
ran = document.createRange();
|
||||||
|
|
||||||
|
@ -5519,23 +5649,29 @@ function aligngriditems() {
|
||||||
if (!treectl)
|
if (!treectl)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
var em2px = parseFloat(getComputedStyle(ebi('ggrid')).fontSize);
|
var ggrid = ebi('ggrid'),
|
||||||
var gridsz = 10;
|
em2px = parseFloat(getComputedStyle(ggrid).fontSize),
|
||||||
|
gridsz = 10;
|
||||||
try {
|
try {
|
||||||
gridsz = cprop('--grid-sz').slice(0, -2);
|
gridsz = cprop('--grid-sz').slice(0, -2);
|
||||||
}
|
}
|
||||||
catch (ex) { }
|
catch (ex) { }
|
||||||
var gridwidth = ebi('ggrid').clientWidth;
|
var gridwidth = ggrid.clientWidth,
|
||||||
var griditemcount = ebi('ggrid').children.length;
|
griditemcount = ggrid.children.length,
|
||||||
var totalgapwidth = em2px * griditemcount;
|
totalgapwidth = em2px * griditemcount;
|
||||||
|
|
||||||
if (/b/.test(themen + ''))
|
if (/b/.test(themen + ''))
|
||||||
totalgapwidth *= 2.8;
|
totalgapwidth *= 2.8;
|
||||||
|
|
||||||
|
var val, st = ggrid.style;
|
||||||
|
|
||||||
if (((griditemcount * em2px) * gridsz) + totalgapwidth < gridwidth) {
|
if (((griditemcount * em2px) * gridsz) + totalgapwidth < gridwidth) {
|
||||||
ebi('ggrid').style.justifyContent = 'left';
|
val = 'left';
|
||||||
} else {
|
} else {
|
||||||
ebi('ggrid').style.justifyContent = treectl.hidden ? 'center' : 'space-between';
|
val = treectl.hidden ? 'center' : 'space-between';
|
||||||
}
|
}
|
||||||
|
if (st.justifyContent != val)
|
||||||
|
st.justifyContent = val;
|
||||||
}
|
}
|
||||||
onresize100.add(aligngriditems);
|
onresize100.add(aligngriditems);
|
||||||
|
|
||||||
|
@ -6040,13 +6176,14 @@ var treectl = (function () {
|
||||||
|
|
||||||
r.nextdir = null;
|
r.nextdir = null;
|
||||||
var cdir = get_evpath(),
|
var cdir = get_evpath(),
|
||||||
cur = ebi('files').getAttribute('ts');
|
lfiles = ebi('files'),
|
||||||
|
cur = lfiles.getAttribute('ts');
|
||||||
|
|
||||||
if (cur && parseInt(cur) > this.ts) {
|
if (cur && parseInt(cur) > this.ts) {
|
||||||
console.log("reject ls");
|
console.log("reject ls");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
ebi('files').setAttribute('ts', this.ts);
|
lfiles.setAttribute('ts', this.ts);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
var res = JSON.parse(this.responseText);
|
var res = JSON.parse(this.responseText);
|
||||||
|
@ -6066,7 +6203,8 @@ var treectl = (function () {
|
||||||
res.files[a].tags = {};
|
res.files[a].tags = {};
|
||||||
|
|
||||||
read_dsort(res.dsort);
|
read_dsort(res.dsort);
|
||||||
dfull = res.dfull;
|
dcrop = res.dcrop;
|
||||||
|
dth3x = res.dth3x;
|
||||||
|
|
||||||
srvinf = res.srvinf;
|
srvinf = res.srvinf;
|
||||||
try {
|
try {
|
||||||
|
|
|
@ -61,3 +61,4 @@
|
||||||
|
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|
||||||
|
|
|
@ -25,3 +25,4 @@
|
||||||
</body>
|
</body>
|
||||||
|
|
||||||
</html>
|
</html>
|
||||||
|
|
||||||
|
|
|
@ -160,3 +160,4 @@ try { l.light = drk? 0:1; } catch (ex) { }
|
||||||
<script src="{{ r }}/.cpr/md2.js?_={{ ts }}"></script>
|
<script src="{{ r }}/.cpr/md2.js?_={{ ts }}"></script>
|
||||||
{%- endif %}
|
{%- endif %}
|
||||||
</body></html>
|
</body></html>
|
||||||
|
|
||||||
|
|
|
@ -54,3 +54,4 @@ try { l.light = drk? 0:1; } catch (ex) { }
|
||||||
<script src="{{ r }}/.cpr/deps/easymde.js?_={{ ts }}"></script>
|
<script src="{{ r }}/.cpr/deps/easymde.js?_={{ ts }}"></script>
|
||||||
<script src="{{ r }}/.cpr/mde.js?_={{ ts }}"></script>
|
<script src="{{ r }}/.cpr/mde.js?_={{ ts }}"></script>
|
||||||
</body></html>
|
</body></html>
|
||||||
|
|
||||||
|
|
|
@ -49,3 +49,4 @@
|
||||||
</body>
|
</body>
|
||||||
|
|
||||||
</html>
|
</html>
|
||||||
|
|
||||||
|
|
|
@ -118,3 +118,4 @@ document.documentElement.className = (STG && STG.cpp_thm) || "{{ this.args.theme
|
||||||
<script src="{{ r }}/.cpr/splash.js?_={{ ts }}"></script>
|
<script src="{{ r }}/.cpr/splash.js?_={{ ts }}"></script>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|
||||||
|
|
|
@ -246,3 +246,4 @@ document.documentElement.className = (STG && STG.cpp_thm) || "{{ args.theme }}";
|
||||||
<script src="{{ r }}/.cpr/svcs.js?_={{ ts }}"></script>
|
<script src="{{ r }}/.cpr/svcs.js?_={{ ts }}"></script>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|
||||||
|
|
|
@ -580,3 +580,11 @@ hr {
|
||||||
border: .07em dashed #444;
|
border: .07em dashed #444;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@media (prefers-reduced-motion) {
|
||||||
|
#toast,
|
||||||
|
#toast a#toastc,
|
||||||
|
#tt {
|
||||||
|
transition: none;
|
||||||
|
}
|
||||||
|
}
|
|
@ -157,6 +157,10 @@ catch (ex) {
|
||||||
}
|
}
|
||||||
var crashed = false, ignexd = {}, evalex_fatal = false;
|
var crashed = false, ignexd = {}, evalex_fatal = false;
|
||||||
function vis_exh(msg, url, lineNo, columnNo, error) {
|
function vis_exh(msg, url, lineNo, columnNo, error) {
|
||||||
|
var ekey = url + '\n' + lineNo + '\n' + msg;
|
||||||
|
if (ignexd[ekey] || crashed)
|
||||||
|
return;
|
||||||
|
|
||||||
msg = String(msg);
|
msg = String(msg);
|
||||||
url = String(url);
|
url = String(url);
|
||||||
|
|
||||||
|
@ -175,9 +179,8 @@ function vis_exh(msg, url, lineNo, columnNo, error) {
|
||||||
if (IE && url.indexOf('prism.js') + 1)
|
if (IE && url.indexOf('prism.js') + 1)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
var ekey = url + '\n' + lineNo + '\n' + msg;
|
if (url.indexOf('easymde.js') + 1)
|
||||||
if (ignexd[ekey] || crashed)
|
return; // clicking the preview pane
|
||||||
return;
|
|
||||||
|
|
||||||
if (url.indexOf('deps/marked.js') + 1 && !window.WebAssembly)
|
if (url.indexOf('deps/marked.js') + 1 && !window.WebAssembly)
|
||||||
return; // ff<52
|
return; // ff<52
|
||||||
|
|
|
@ -1,3 +1,90 @@
|
||||||
|
▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀
|
||||||
|
# 2024-0218-1554 `v1.10.1` big thumbs
|
||||||
|
|
||||||
|
## new features
|
||||||
|
|
||||||
|
* button to enable hi-res thumbnails 33f41f3e 58ae38c6
|
||||||
|
* enable with the `3x` button in the gridview
|
||||||
|
* can be force-enabled/disabled serverside with `--th-x3` or volflag `th3x`
|
||||||
|
* tftp: IPv6 support and UTF-8 filenames + optimizations 0504b010
|
||||||
|
* ux:
|
||||||
|
* when closing the image viewer, scroll to the last viewed pic bbc37990
|
||||||
|
* respect `prefers-reduced-motion` some more places fbfdd833
|
||||||
|
|
||||||
|
## bugfixes
|
||||||
|
|
||||||
|
* #72 impossible to delete recently uploaded zerobyte files if database was disabled 6bd087dd
|
||||||
|
* tftp now works in `copyparty.exe`, `copyparty32.exe`, `copyparty-winpe64.exe`
|
||||||
|
* the [sharex config example](https://github.com/9001/copyparty/tree/hovudstraum/contrib#sharexsxcu) was still using cookie-auth 8ff7094e
|
||||||
|
* ux:
|
||||||
|
* prevent scrolling while a pic is open 7f1c9926
|
||||||
|
* fix gridview in older firefox versions 7f1c9926
|
||||||
|
|
||||||
|
## other changes
|
||||||
|
|
||||||
|
* thumbnail center-cropping can be force-enabled/disabled serverside with `--th-crop` or volflag `crop`
|
||||||
|
* replaces `--th-no-crop` which is now deprecated (but will continue to work)
|
||||||
|
|
||||||
|
----
|
||||||
|
|
||||||
|
this release contains a build of `copyparty-winpe64.exe` which is almost **entirely useless,** except for in *extremely specific scenarios*, namely the kind where a TFTP server could also be useful -- the [previous build](https://github.com/9001/copyparty/releases/download/v1.8.7/copyparty-winpe64.exe) was from [version 1.8.7](https://github.com/9001/copyparty/releases/tag/v1.8.7) (2023-07-23)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀
|
||||||
|
# 2024-0215-0000 `v1.10.0` tftp
|
||||||
|
|
||||||
|
## new features
|
||||||
|
|
||||||
|
* TFTP server d636316a 8796c09f acbb8267 02879713
|
||||||
|
* based on [partftpy](https://github.com/9001/partftpy), has most essential features EXCEPT for [rfc7440](https://datatracker.ietf.org/doc/html/rfc7440) so WAN will be slow
|
||||||
|
* is already doing real work out in the wild! see the fantastic quote in the [readme](https://github.com/9001/copyparty?tab=readme-ov-file#tftp-server)
|
||||||
|
* detect some (un)common configuration mistakes
|
||||||
|
* buggy reverse-proxy which strips away all URL parameters 136c0fdc
|
||||||
|
* could cause the browser to get stuck in a refresh-loop
|
||||||
|
* a volume on an sqlite-incompatible filesystem (a remote cifs server or such) and an up2k volume inside d4da3861
|
||||||
|
* sqlite could deadlock or randomly throw exceptions; serverlog will now explain how to fix it
|
||||||
|
* ie11: file selection with shift-up/down 64ad5853
|
||||||
|
|
||||||
|
## bugfixes
|
||||||
|
|
||||||
|
* prevent music playback from stopping at the end of a folder f262aee8
|
||||||
|
* preloader will now proactively hunt for the next file to play as the last song is ending
|
||||||
|
* in very specific scenarios, clients could be told their upload had finished processing a tiny bit too early, while the HDD was still busy taking in the last couple bytes 6f8a588c
|
||||||
|
* so if you expected to find the complete file on the server HDD immediately as the final chunk got confirmed, that was not necessarily the case if your server HDD was severely overloaded to the point where closing a file takes half a minute
|
||||||
|
* huge thx to friend with said overloaded server for finding all the crazy edge cases
|
||||||
|
* ignore harmless javascript errors from easymde 879e83e2
|
||||||
|
|
||||||
|
## other changes
|
||||||
|
|
||||||
|
* the "copy currently playing song info to clipboard" button now excludes the uploader IP ed524d84
|
||||||
|
* mention that enabling `-j0` can improve HDD load during uploads 5d92f4df
|
||||||
|
* mention a debian-specific docker bug which prevents starting most containers (not just copyparty) 4e797a71
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀
|
||||||
|
# 2024-0203-1533 `v1.9.31` eject
|
||||||
|
|
||||||
|
## new features
|
||||||
|
|
||||||
|
* disable mkdir / new-doc buttons until a name is provided d3db6d29
|
||||||
|
* warning about browsers limiting the number of connections c354a38b
|
||||||
|
|
||||||
|
## bugfixes
|
||||||
|
|
||||||
|
* #71 stop videos from buffering in the background a17c267d
|
||||||
|
* improve up2k ETA on slow networks / many connections c1180d6f
|
||||||
|
* u2c: exclude-filter didn't apply to file deletions b2e23340
|
||||||
|
* `--touch` / `re📅` didn't apply to zerobyte files 945170e2
|
||||||
|
|
||||||
|
## other changes
|
||||||
|
|
||||||
|
* notes on [hardlink/symlink conversion](https://github.com/9001/copyparty/blob/6c2c6090/docs/notes.sh#L35-L46) 6c2c6090
|
||||||
|
* [lore](https://github.com/9001/copyparty/blob/hovudstraum/docs/notes.md#trivia--lore) b1cf5884
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀
|
▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀
|
||||||
# 2024-0125-2252 `v1.9.30` retime
|
# 2024-0125-2252 `v1.9.30` retime
|
||||||
|
|
||||||
|
|
|
@ -242,6 +242,7 @@ python3 -m venv .venv
|
||||||
pip install jinja2 strip_hints # MANDATORY
|
pip install jinja2 strip_hints # MANDATORY
|
||||||
pip install mutagen # audio metadata
|
pip install mutagen # audio metadata
|
||||||
pip install pyftpdlib # ftp server
|
pip install pyftpdlib # ftp server
|
||||||
|
pip install partftpy # tftp server
|
||||||
pip install impacket # smb server -- disable Windows Defender if you REALLY need this on windows
|
pip install impacket # smb server -- disable Windows Defender if you REALLY need this on windows
|
||||||
pip install Pillow pyheif-pillow-opener pillow-avif-plugin # thumbnails
|
pip install Pillow pyheif-pillow-opener pillow-avif-plugin # thumbnails
|
||||||
pip install pyvips # faster thumbnails
|
pip install pyvips # faster thumbnails
|
||||||
|
|
|
@ -24,6 +24,10 @@ https://github.com/giampaolo/pyftpdlib/
|
||||||
C: 2007 Giampaolo Rodola
|
C: 2007 Giampaolo Rodola
|
||||||
L: MIT
|
L: MIT
|
||||||
|
|
||||||
|
https://github.com/9001/partftpy
|
||||||
|
C: 2010-2021 Michael P. Soulier
|
||||||
|
L: MIT
|
||||||
|
|
||||||
https://github.com/nayuki/QR-Code-generator/
|
https://github.com/nayuki/QR-Code-generator/
|
||||||
C: Project Nayuki
|
C: Project Nayuki
|
||||||
L: MIT
|
L: MIT
|
||||||
|
|
|
@ -200,9 +200,10 @@ symbol legend,
|
||||||
| ----------------------- | - | - | - | - | - | - | - | - | - | - | - | - |
|
| ----------------------- | - | - | - | - | - | - | - | - | - | - | - | - |
|
||||||
| serve https | █ | | █ | █ | █ | █ | █ | █ | █ | █ | █ | █ |
|
| serve https | █ | | █ | █ | █ | █ | █ | █ | █ | █ | █ | █ |
|
||||||
| serve webdav | █ | | | █ | █ | █ | █ | | █ | | | █ |
|
| serve webdav | █ | | | █ | █ | █ | █ | | █ | | | █ |
|
||||||
| serve ftp | █ | | | | | █ | | | | | | █ |
|
| serve ftp (tcp) | █ | | | | | █ | | | | | | █ |
|
||||||
| serve ftps | █ | | | | | █ | | | | | | █ |
|
| serve ftps (tls) | █ | | | | | █ | | | | | | █ |
|
||||||
| serve sftp | | | | | | █ | | | | | | █ |
|
| serve tftp (udp) | █ | | | | | | | | | | | |
|
||||||
|
| serve sftp (ssh) | | | | | | █ | | | | | | █ |
|
||||||
| serve smb/cifs | ╱ | | | | | █ | | | | | | |
|
| serve smb/cifs | ╱ | | | | | █ | | | | | | |
|
||||||
| serve dlna | | | | | | █ | | | | | | |
|
| serve dlna | | | | | | █ | | | | | | |
|
||||||
| listen on unix-socket | | | | █ | █ | | █ | █ | █ | | █ | █ |
|
| listen on unix-socket | | | | █ | █ | | █ | █ | █ | | █ | █ |
|
||||||
|
|
|
@ -28,6 +28,7 @@ classifiers = [
|
||||||
"Programming Language :: Python :: Implementation :: CPython",
|
"Programming Language :: Python :: Implementation :: CPython",
|
||||||
"Programming Language :: Python :: Implementation :: Jython",
|
"Programming Language :: Python :: Implementation :: Jython",
|
||||||
"Programming Language :: Python :: Implementation :: PyPy",
|
"Programming Language :: Python :: Implementation :: PyPy",
|
||||||
|
"Operating System :: OS Independent",
|
||||||
"Environment :: Console",
|
"Environment :: Console",
|
||||||
"Environment :: No Input/Output (Daemon)",
|
"Environment :: No Input/Output (Daemon)",
|
||||||
"Intended Audience :: End Users/Desktop",
|
"Intended Audience :: End Users/Desktop",
|
||||||
|
@ -48,6 +49,7 @@ thumbnails2 = ["pyvips"]
|
||||||
audiotags = ["mutagen"]
|
audiotags = ["mutagen"]
|
||||||
ftpd = ["pyftpdlib"]
|
ftpd = ["pyftpdlib"]
|
||||||
ftps = ["pyftpdlib", "pyopenssl"]
|
ftps = ["pyftpdlib", "pyopenssl"]
|
||||||
|
tftpd = ["partftpy>=0.3.0"]
|
||||||
pwhash = ["argon2-cffi"]
|
pwhash = ["argon2-cffi"]
|
||||||
|
|
||||||
[project.scripts]
|
[project.scripts]
|
||||||
|
|
|
@ -3,7 +3,7 @@ WORKDIR /z
|
||||||
ENV ver_asmcrypto=c72492f4a66e17a0e5dd8ad7874de354f3ccdaa5 \
|
ENV ver_asmcrypto=c72492f4a66e17a0e5dd8ad7874de354f3ccdaa5 \
|
||||||
ver_hashwasm=4.10.0 \
|
ver_hashwasm=4.10.0 \
|
||||||
ver_marked=4.3.0 \
|
ver_marked=4.3.0 \
|
||||||
ver_dompf=3.0.8 \
|
ver_dompf=3.0.9 \
|
||||||
ver_mde=2.18.0 \
|
ver_mde=2.18.0 \
|
||||||
ver_codemirror=5.65.16 \
|
ver_codemirror=5.65.16 \
|
||||||
ver_fontawesome=5.13.0 \
|
ver_fontawesome=5.13.0 \
|
||||||
|
|
|
@ -77,13 +77,14 @@ function have() {
|
||||||
}
|
}
|
||||||
|
|
||||||
function load_env() {
|
function load_env() {
|
||||||
. buildenv/bin/activate
|
. buildenv/bin/activate || return 1
|
||||||
have setuptools
|
have setuptools &&
|
||||||
have wheel
|
have wheel &&
|
||||||
have build
|
have build &&
|
||||||
have twine
|
have twine &&
|
||||||
have jinja2
|
have jinja2 &&
|
||||||
have strip_hints
|
have strip_hints &&
|
||||||
|
return 0 || return 1
|
||||||
}
|
}
|
||||||
|
|
||||||
load_env || {
|
load_env || {
|
||||||
|
|
|
@ -26,8 +26,9 @@ help() { exec cat <<'EOF'
|
||||||
# _____________________________________________________________________
|
# _____________________________________________________________________
|
||||||
# core features:
|
# core features:
|
||||||
#
|
#
|
||||||
# `no-ftp` saves ~33k by removing the ftp server and filetype detector,
|
# `no-ftp` saves ~30k by removing the ftp server, disabling --ftp
|
||||||
# disabling --ftpd and --magic
|
#
|
||||||
|
# `no-tfp` saves ~10k by removing the tftp server, disabling --tftp
|
||||||
#
|
#
|
||||||
# `no-smb` saves ~3.5k by removing the smb / cifs server
|
# `no-smb` saves ~3.5k by removing the smb / cifs server
|
||||||
#
|
#
|
||||||
|
@ -114,6 +115,7 @@ while [ ! -z "$1" ]; do
|
||||||
gz) use_gz=1 ; ;;
|
gz) use_gz=1 ; ;;
|
||||||
gzz) shift;use_gzz=$1;use_gz=1; ;;
|
gzz) shift;use_gzz=$1;use_gz=1; ;;
|
||||||
no-ftp) no_ftp=1 ; ;;
|
no-ftp) no_ftp=1 ; ;;
|
||||||
|
no-tfp) no_tfp=1 ; ;;
|
||||||
no-smb) no_smb=1 ; ;;
|
no-smb) no_smb=1 ; ;;
|
||||||
no-zm) no_zm=1 ; ;;
|
no-zm) no_zm=1 ; ;;
|
||||||
no-fnt) no_fnt=1 ; ;;
|
no-fnt) no_fnt=1 ; ;;
|
||||||
|
@ -165,7 +167,8 @@ necho() {
|
||||||
[ $repack ] && {
|
[ $repack ] && {
|
||||||
old="$tmpdir/pe-copyparty.$(id -u)"
|
old="$tmpdir/pe-copyparty.$(id -u)"
|
||||||
echo "repack of files in $old"
|
echo "repack of files in $old"
|
||||||
cp -pR "$old/"*{py2,py37,j2,copyparty} .
|
cp -pR "$old/"*{py2,py37,magic,j2,copyparty} .
|
||||||
|
cp -pR "$old/"*partftpy . || true
|
||||||
cp -pR "$old/"*ftp . || true
|
cp -pR "$old/"*ftp . || true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -221,6 +224,16 @@ necho() {
|
||||||
mkdir ftp/
|
mkdir ftp/
|
||||||
mv pyftpdlib ftp/
|
mv pyftpdlib ftp/
|
||||||
|
|
||||||
|
necho collecting partftpy
|
||||||
|
f="../build/partftpy-0.3.0.tar.gz"
|
||||||
|
[ -e "$f" ] ||
|
||||||
|
(url=https://files.pythonhosted.org/packages/06/ce/531978c831c47f79bc72d5bbb3f12757daf1602d1fffad012305f2d270f6/partftpy-0.3.0.tar.gz;
|
||||||
|
wget -O$f "$url" || curl -L "$url" >$f)
|
||||||
|
|
||||||
|
tar -zxf $f
|
||||||
|
mv partftpy-*/partftpy .
|
||||||
|
rm -rf partftpy-* partftpy/bin
|
||||||
|
|
||||||
necho collecting python-magic
|
necho collecting python-magic
|
||||||
v=0.4.27
|
v=0.4.27
|
||||||
f="../build/python-magic-$v.tar.gz"
|
f="../build/python-magic-$v.tar.gz"
|
||||||
|
@ -234,7 +247,6 @@ necho() {
|
||||||
rm -rf python-magic-*
|
rm -rf python-magic-*
|
||||||
rm magic/compat.py
|
rm magic/compat.py
|
||||||
iawk '/^def _add_compat/{o=1} !o; /^_add_compat/{o=0}' magic/__init__.py
|
iawk '/^def _add_compat/{o=1} !o; /^_add_compat/{o=0}' magic/__init__.py
|
||||||
mv magic ftp/ # doesn't provide a version label anyways
|
|
||||||
|
|
||||||
# enable this to dynamically remove type hints at startup,
|
# enable this to dynamically remove type hints at startup,
|
||||||
# in case a future python version can use them for performance
|
# in case a future python version can use them for performance
|
||||||
|
@ -409,8 +421,10 @@ iawk '/^ {0,4}[^ ]/{s=0}/^ {4}def (serve_forever|_loop)/{s=1}!s' ftp/pyftpdlib/s
|
||||||
rm -f ftp/pyftpdlib/{__main__,prefork}.py
|
rm -f ftp/pyftpdlib/{__main__,prefork}.py
|
||||||
|
|
||||||
[ $no_ftp ] &&
|
[ $no_ftp ] &&
|
||||||
rm -rf copyparty/ftpd.py ftp &&
|
rm -rf copyparty/ftpd.py ftp
|
||||||
sed -ri '/\.ftp/d' copyparty/svchub.py
|
|
||||||
|
[ $no_tfp ] &&
|
||||||
|
rm -rf copyparty/tftpd.py partftpy
|
||||||
|
|
||||||
[ $no_smb ] &&
|
[ $no_smb ] &&
|
||||||
rm -f copyparty/smbd.py
|
rm -f copyparty/smbd.py
|
||||||
|
@ -584,7 +598,7 @@ nf=$(ls -1 "$zdir"/arc.* 2>/dev/null | wc -l)
|
||||||
|
|
||||||
|
|
||||||
echo gen tarlist
|
echo gen tarlist
|
||||||
for d in copyparty j2 py2 py37 ftp; do find $d -type f; done | # strip_hints
|
for d in copyparty partftpy magic j2 py2 py37 ftp; do find $d -type f || true; done | # strip_hints
|
||||||
sed -r 's/(.*)\.(.*)/\2 \1/' | LC_ALL=C sort |
|
sed -r 's/(.*)\.(.*)/\2 \1/' | LC_ALL=C sort |
|
||||||
sed -r 's/([^ ]*) (.*)/\2.\1/' | grep -vE '/list1?$' > list1
|
sed -r 's/([^ ]*) (.*)/\2.\1/' | grep -vE '/list1?$' > list1
|
||||||
|
|
||||||
|
|
|
@ -37,7 +37,7 @@ rm -rf $TEMP/pe-copyparty*
|
||||||
python copyparty-sfx.py --version
|
python copyparty-sfx.py --version
|
||||||
|
|
||||||
rm -rf mods; mkdir mods
|
rm -rf mods; mkdir mods
|
||||||
cp -pR $TEMP/pe-copyparty/copyparty/ $TEMP/pe-copyparty/{ftp,j2}/* mods/
|
cp -pR $TEMP/pe-copyparty/{copyparty,partftpy}/ $TEMP/pe-copyparty/{ftp,j2}/* mods/
|
||||||
[ $w10 ] && rm -rf mods/{jinja2,markupsafe}
|
[ $w10 ] && rm -rf mods/{jinja2,markupsafe}
|
||||||
|
|
||||||
af() { awk "$1" <$2 >tf; mv tf "$2"; }
|
af() { awk "$1" <$2 >tf; mv tf "$2"; }
|
||||||
|
|
|
@ -1,13 +1,10 @@
|
||||||
f117016b1e6a7d7e745db30d3e67f1acf7957c443a0dd301b6c5e10b8368f2aa4db6be9782d2d3f84beadd139bfeef4982e40f21ca5d9065cb794eeb0e473e82 altgraph-0.17.4-py2.py3-none-any.whl
|
f117016b1e6a7d7e745db30d3e67f1acf7957c443a0dd301b6c5e10b8368f2aa4db6be9782d2d3f84beadd139bfeef4982e40f21ca5d9065cb794eeb0e473e82 altgraph-0.17.4-py2.py3-none-any.whl
|
||||||
eda6c38fc4d813fee897e969ff9ecc5acc613df755ae63df0392217bbd67408b5c1f6c676f2bf5497b772a3eb4e1a360e1245e1c16ee83f0af555f1ab82c3977 Git-2.39.1-32-bit.exe
|
eda6c38fc4d813fee897e969ff9ecc5acc613df755ae63df0392217bbd67408b5c1f6c676f2bf5497b772a3eb4e1a360e1245e1c16ee83f0af555f1ab82c3977 Git-2.39.1-32-bit.exe
|
||||||
17ce52ba50692a9d964f57a23ac163fb74c77fdeb2ca988a6d439ae1fe91955ff43730c073af97a7b3223093ffea3479a996b9b50ee7fba0869247a56f74baa6 pefile-2023.2.7-py3-none-any.whl
|
17ce52ba50692a9d964f57a23ac163fb74c77fdeb2ca988a6d439ae1fe91955ff43730c073af97a7b3223093ffea3479a996b9b50ee7fba0869247a56f74baa6 pefile-2023.2.7-py3-none-any.whl
|
||||||
f298e34356b5590dde7477d7b3a88ad39c622a2bcf3fcd7c53870ce8384dd510f690af81b8f42e121a22d3968a767d2e07595036b2ed7049c8ef4d112bcf3a61 pyinstaller-5.13.2-py3-none-win32.whl
|
f042aabe6cca2ae368180eaf313dd58f9ee96384c0ac1064aefe24a9e0e7e9cd6efa74eacb125d51a8feb61eaf200bc84812ab4d90c08fe33ef315eb2d9e6c30 pyinstaller_hooks_contrib-2024.1-py2.py3-none-any.whl
|
||||||
f23615c522ed58b9a05978ba4c69c06224590f3a6adbd8e89b31838b181a57160739ceff1fc2ba6f4239b8fee46f92ce02910b2debda2710558ed42cff1ce3f1 pyinstaller-6.1.0-py3-none-win_amd64.whl
|
|
||||||
5747b3b119629c4cf956f0eaa85f29218bb3680d3a4a262fa6e976e56b35067302e153d2c0a001505f2cb642b1f78752567889b3b82e342d6cd29aac8b70e92e pyinstaller_hooks_contrib-2023.10-py2.py3-none-any.whl
|
|
||||||
749a473646c6d4c7939989649733d4c7699fd1c359c27046bf5bc9c070d1a4b8b986bbc65f60d7da725baf16dbfdd75a4c2f5bb8335f2cb5685073f5fee5c2d1 pywin32_ctypes-0.2.2-py3-none-any.whl
|
749a473646c6d4c7939989649733d4c7699fd1c359c27046bf5bc9c070d1a4b8b986bbc65f60d7da725baf16dbfdd75a4c2f5bb8335f2cb5685073f5fee5c2d1 pywin32_ctypes-0.2.2-py3-none-any.whl
|
||||||
6e0d854040baff861e1647d2bece7d090bc793b2bd9819c56105b94090df54881a6a9b43ebd82578cd7c76d47181571b671e60672afd9def389d03c9dae84fcf setuptools-68.2.2-py3-none-any.whl
|
6e0d854040baff861e1647d2bece7d090bc793b2bd9819c56105b94090df54881a6a9b43ebd82578cd7c76d47181571b671e60672afd9def389d03c9dae84fcf setuptools-68.2.2-py3-none-any.whl
|
||||||
3c5adf0a36516d284a2ede363051edc1bcc9df925c5a8a9fa2e03cab579dd8d847fdad42f7fd5ba35992e08234c97d2dbfec40a9d12eec61c8dc03758f2bd88e typing_extensions-4.4.0-py3-none-any.whl
|
3c5adf0a36516d284a2ede363051edc1bcc9df925c5a8a9fa2e03cab579dd8d847fdad42f7fd5ba35992e08234c97d2dbfec40a9d12eec61c8dc03758f2bd88e typing_extensions-4.4.0-py3-none-any.whl
|
||||||
8d16a967a0a7872a7575b1005cf66915deacda6ee8611fbb52f42fc3e3beb2f901a5140c942a5d146bd412b92bfa9cbadd82beeba83df6d70930c6dc26608a5b upx-4.1.0-win32.zip
|
|
||||||
# u2c (win7)
|
# u2c (win7)
|
||||||
f3390290b896019b2fa169932390e4930d1c03c014e1f6db2405ca2eb1f51f5f5213f725885853805b742997b0edb369787e5c0069d217bc4e8b957f847f58b6 certifi-2023.11.17-py3-none-any.whl
|
f3390290b896019b2fa169932390e4930d1c03c014e1f6db2405ca2eb1f51f5f5213f725885853805b742997b0edb369787e5c0069d217bc4e8b957f847f58b6 certifi-2023.11.17-py3-none-any.whl
|
||||||
904eb57b13bea80aea861de86987e618665d37fa9ea0856e0125a9ba767a53e5064de0b9c4735435a2ddf4f16f7f7d2c75a682e1de83d9f57922bdca8e29988c charset_normalizer-3.3.0-cp37-cp37m-win32.whl
|
904eb57b13bea80aea861de86987e618665d37fa9ea0856e0125a9ba767a53e5064de0b9c4735435a2ddf4f16f7f7d2c75a682e1de83d9f57922bdca8e29988c charset_normalizer-3.3.0-cp37-cp37m-win32.whl
|
||||||
|
@ -18,15 +15,19 @@ b795abb26ba2f04f1afcfb196f21f638014b26c8186f8f488f1c2d91e8e0220962fbd259dbc9c387
|
||||||
91c025f7d94bcdf93df838fab67053165a414fc84e8496f92ecbb910dd55f6b6af5e360bbd051444066880c5a6877e75157bd95e150ead46e5c605930dfc50f2 future-0.18.2.tar.gz
|
91c025f7d94bcdf93df838fab67053165a414fc84e8496f92ecbb910dd55f6b6af5e360bbd051444066880c5a6877e75157bd95e150ead46e5c605930dfc50f2 future-0.18.2.tar.gz
|
||||||
c06b3295d1d0b0f0a6f9a6cd0be861b9b643b4a5ea37857f0bd41c45deaf27bb927b71922dab74e633e43d75d04a9bd0d1c4ad875569740b0f2a98dd2bfa5113 importlib_metadata-5.0.0-py3-none-any.whl
|
c06b3295d1d0b0f0a6f9a6cd0be861b9b643b4a5ea37857f0bd41c45deaf27bb927b71922dab74e633e43d75d04a9bd0d1c4ad875569740b0f2a98dd2bfa5113 importlib_metadata-5.0.0-py3-none-any.whl
|
||||||
016a8cbd09384f1a9a44cb0e8274df75a8bcb2f3966bb5d708c62145289efaa5db98f75256c97e4f8046735ce2e529fbb076f284a46cdb716e89a75660200ad9 pip-23.2.1-py3-none-any.whl
|
016a8cbd09384f1a9a44cb0e8274df75a8bcb2f3966bb5d708c62145289efaa5db98f75256c97e4f8046735ce2e529fbb076f284a46cdb716e89a75660200ad9 pip-23.2.1-py3-none-any.whl
|
||||||
|
f298e34356b5590dde7477d7b3a88ad39c622a2bcf3fcd7c53870ce8384dd510f690af81b8f42e121a22d3968a767d2e07595036b2ed7049c8ef4d112bcf3a61 pyinstaller-5.13.2-py3-none-win32.whl
|
||||||
6bb73cc2db795c59c92f2115727f5c173cacc9465af7710db9ff2f2aec2d73130d0992d0f16dcb3fac222dc15c0916562d0813b2337401022020673a4461df3d python-3.7.9-amd64.exe
|
6bb73cc2db795c59c92f2115727f5c173cacc9465af7710db9ff2f2aec2d73130d0992d0f16dcb3fac222dc15c0916562d0813b2337401022020673a4461df3d python-3.7.9-amd64.exe
|
||||||
500747651c87f59f2436c5ab91207b5b657856e43d10083f3ce27efb196a2580fadd199a4209519b409920c562aaaa7dcbdfb83ed2072a43eaccae6e2d056f31 python-3.7.9.exe
|
500747651c87f59f2436c5ab91207b5b657856e43d10083f3ce27efb196a2580fadd199a4209519b409920c562aaaa7dcbdfb83ed2072a43eaccae6e2d056f31 python-3.7.9.exe
|
||||||
|
2e04acff170ca3bbceeeb18489c687126c951ec0bfd53cccfb389ba8d29a4576c1a9e8f2e5ea26c84dd21bfa2912f4e71fa72c1e2653b71e34afc0e65f1722d4 upx-4.2.2-win32.zip
|
||||||
68e1b618d988be56aaae4e2eb92bc0093627a00441c1074ebe680c41aa98a6161e52733ad0c59888c643a33fe56884e4f935178b2557fbbdd105e92e0d993df6 windows6.1-kb2533623-x64.msu
|
68e1b618d988be56aaae4e2eb92bc0093627a00441c1074ebe680c41aa98a6161e52733ad0c59888c643a33fe56884e4f935178b2557fbbdd105e92e0d993df6 windows6.1-kb2533623-x64.msu
|
||||||
479a63e14586ab2f2228208116fc149ed8ee7b1e4ff360754f5bda4bf765c61af2e04b5ef123976623d04df4976b7886e0445647269da81436bd0a7b5671d361 windows6.1-kb2533623-x86.msu
|
479a63e14586ab2f2228208116fc149ed8ee7b1e4ff360754f5bda4bf765c61af2e04b5ef123976623d04df4976b7886e0445647269da81436bd0a7b5671d361 windows6.1-kb2533623-x86.msu
|
||||||
ba91ab0518c61eff13e5612d9e6b532940813f6b56e6ed81ea6c7c4d45acee4d98136a383a25067512b8f75538c67c987cf3944bfa0229e3cb677e2fb81e763e zipp-3.10.0-py3-none-any.whl
|
ba91ab0518c61eff13e5612d9e6b532940813f6b56e6ed81ea6c7c4d45acee4d98136a383a25067512b8f75538c67c987cf3944bfa0229e3cb677e2fb81e763e zipp-3.10.0-py3-none-any.whl
|
||||||
# win10
|
# win10
|
||||||
00558cca2e0ac813d404252f6e5aeacb50546822ecb5d0570228b8ddd29d94e059fbeb6b90393dee5abcddaca1370aca784dc9b095cbb74e980b3c024767fb24 Jinja2-3.1.2-py3-none-any.whl
|
e3e2e6bd511dec484dd0292f4c46c55c88a885eabf15413d53edea2dd4a4dbae1571735b9424f78c0cd7f1082476a8259f31fd3f63990f726175470f636df2b3 Jinja2-3.1.3-py3-none-any.whl
|
||||||
7f8f4daa4f4f2dbf24cdd534b2952ee3fba6334eb42b37465ccda3aa1cccc3d6204aa6bfffb8a83bf42ec59c702b5b5247d4c8ee0d4df906334ae53072ef8c4c MarkupSafe-2.1.3-cp311-cp311-win_amd64.whl
|
e21495f1d473d855103fb4a243095b498ec90eb68776b0f9b48e994990534f7286c0292448e129c507e5d70409f8a05cca58b98d59ce2a815993d0a873dfc480 MarkupSafe-2.1.5-cp311-cp311-win_amd64.whl
|
||||||
8a6e2b13a2ec4ef914a5d62aad3db6464d45e525a82e07f6051ed10474eae959069e165dba011aefb8207cdfd55391d73d6f06362c7eb247b08763106709526e mutagen-1.47.0-py3-none-any.whl
|
8a6e2b13a2ec4ef914a5d62aad3db6464d45e525a82e07f6051ed10474eae959069e165dba011aefb8207cdfd55391d73d6f06362c7eb247b08763106709526e mutagen-1.47.0-py3-none-any.whl
|
||||||
656015f5cc2c04aa0653ee5609c39a7e5f0b6a58c84fe26b20bd070c52d20b4effb810132f7fb771168483e9fd975cc3302837dd7a1a687ee058b0460c857cc4 packaging-23.2-py3-none-any.whl
|
656015f5cc2c04aa0653ee5609c39a7e5f0b6a58c84fe26b20bd070c52d20b4effb810132f7fb771168483e9fd975cc3302837dd7a1a687ee058b0460c857cc4 packaging-23.2-py3-none-any.whl
|
||||||
424e20dc7263a31d524307bc39ed755a9dd82f538086fff68d98dd97e236c9b00777a8ac2e3853081b532b0e93cef44983e74d0ab274877440e8b7341b19358a pillow-10.2.0-cp311-cp311-win_amd64.whl
|
424e20dc7263a31d524307bc39ed755a9dd82f538086fff68d98dd97e236c9b00777a8ac2e3853081b532b0e93cef44983e74d0ab274877440e8b7341b19358a pillow-10.2.0-cp311-cp311-win_amd64.whl
|
||||||
|
533b1aec21439032cf13084d84c4d862e41835a0468f34fef36c5d7cb9cf106a030826ac2e95c9e860f623f6a55ea58548f749c31594f388207d0809dc0859b5 pyinstaller-6.4.0-py3-none-win_amd64.whl
|
||||||
e6bdbae1affd161e62fc87407c912462dfe875f535ba9f344d0c4ade13715c947cd3ae832eff60f1bad4161938311d06ac8bc9b52ef203f7b0d9de1409f052a5 python-3.11.8-amd64.exe
|
e6bdbae1affd161e62fc87407c912462dfe875f535ba9f344d0c4ade13715c947cd3ae832eff60f1bad4161938311d06ac8bc9b52ef203f7b0d9de1409f052a5 python-3.11.8-amd64.exe
|
||||||
|
729dc52f1a02bc6274d012ce33f534102975a828cba11f6029600ea40e2d23aefeb07bf4ae19f9621d0565dd03eb2635bbb97d45fb692c1f756315e8c86c5255 upx-4.2.2-win64.zip
|
||||||
|
|
|
@ -17,19 +17,19 @@ uname -s | grep NT-10 && w10=1 || {
|
||||||
fns=(
|
fns=(
|
||||||
altgraph-0.17.4-py2.py3-none-any.whl
|
altgraph-0.17.4-py2.py3-none-any.whl
|
||||||
pefile-2023.2.7-py3-none-any.whl
|
pefile-2023.2.7-py3-none-any.whl
|
||||||
pyinstaller_hooks_contrib-2023.10-py2.py3-none-any.whl
|
pyinstaller_hooks_contrib-2024.1-py2.py3-none-any.whl
|
||||||
pywin32_ctypes-0.2.2-py3-none-any.whl
|
pywin32_ctypes-0.2.2-py3-none-any.whl
|
||||||
setuptools-68.2.2-py3-none-any.whl
|
setuptools-68.2.2-py3-none-any.whl
|
||||||
upx-4.1.0-win32.zip
|
|
||||||
)
|
)
|
||||||
[ $w10 ] && fns+=(
|
[ $w10 ] && fns+=(
|
||||||
pyinstaller-6.1.0-py3-none-win_amd64.whl
|
pyinstaller-6.4.0-py3-none-win_amd64.whl
|
||||||
Jinja2-3.1.2-py3-none-any.whl
|
Jinja2-3.1.3-py3-none-any.whl
|
||||||
MarkupSafe-2.1.3-cp311-cp311-win_amd64.whl
|
MarkupSafe-2.1.5-cp311-cp311-win_amd64.whl
|
||||||
mutagen-1.47.0-py3-none-any.whl
|
mutagen-1.47.0-py3-none-any.whl
|
||||||
packaging-23.2-py3-none-any.whl
|
packaging-23.2-py3-none-any.whl
|
||||||
pillow-10.2.0-cp311-cp311-win_amd64.whl
|
pillow-10.2.0-cp311-cp311-win_amd64.whl
|
||||||
python-3.11.8-amd64.exe
|
python-3.11.8-amd64.exe
|
||||||
|
upx-4.2.2-win64.zip
|
||||||
)
|
)
|
||||||
[ $w7 ] && fns+=(
|
[ $w7 ] && fns+=(
|
||||||
pyinstaller-5.13.2-py3-none-win32.whl
|
pyinstaller-5.13.2-py3-none-win32.whl
|
||||||
|
@ -38,6 +38,7 @@ fns=(
|
||||||
idna-3.4-py3-none-any.whl
|
idna-3.4-py3-none-any.whl
|
||||||
requests-2.28.2-py3-none-any.whl
|
requests-2.28.2-py3-none-any.whl
|
||||||
urllib3-1.26.14-py2.py3-none-any.whl
|
urllib3-1.26.14-py2.py3-none-any.whl
|
||||||
|
upx-4.2.2-win32.zip
|
||||||
)
|
)
|
||||||
[ $w7 ] && fns+=(
|
[ $w7 ] && fns+=(
|
||||||
future-0.18.2.tar.gz
|
future-0.18.2.tar.gz
|
||||||
|
|
|
@ -54,6 +54,7 @@ copyparty/sutil.py,
|
||||||
copyparty/svchub.py,
|
copyparty/svchub.py,
|
||||||
copyparty/szip.py,
|
copyparty/szip.py,
|
||||||
copyparty/tcpsrv.py,
|
copyparty/tcpsrv.py,
|
||||||
|
copyparty/tftpd.py,
|
||||||
copyparty/th_cli.py,
|
copyparty/th_cli.py,
|
||||||
copyparty/th_srv.py,
|
copyparty/th_srv.py,
|
||||||
copyparty/u2idx.py,
|
copyparty/u2idx.py,
|
||||||
|
|
36
scripts/test/tftp.sh
Executable file
36
scripts/test/tftp.sh
Executable file
|
@ -0,0 +1,36 @@
|
||||||
|
#!/bin/bash
|
||||||
|
set -ex
|
||||||
|
|
||||||
|
# PYTHONPATH=.:~/dev/partftpy/ taskset -c 0 python3 -m copyparty -v srv::r -v srv/junk:junk:A --tftp 3969
|
||||||
|
|
||||||
|
get_src=~/dev/copyparty/srv/palette.flac
|
||||||
|
get_fn=${get_src##*/}
|
||||||
|
|
||||||
|
put_src=~/Downloads/102.zip
|
||||||
|
put_dst=~/dev/copyparty/srv/junk/102.zip
|
||||||
|
|
||||||
|
cd /dev/shm
|
||||||
|
|
||||||
|
echo curl get 1428 v4; curl --tftp-blksize 1428 tftp://127.0.0.1:3969/$get_fn | cmp $get_src || exit 1
|
||||||
|
echo curl get 1428 v6; curl --tftp-blksize 1428 tftp://[::1]:3969/$get_fn | cmp $get_src || exit 1
|
||||||
|
|
||||||
|
echo curl put 1428 v4; rm -f $put_dst && curl --tftp-blksize 1428 -T $put_src tftp://127.0.0.1:3969/junk/ && cmp $put_src $put_dst || exit 1
|
||||||
|
echo curl put 1428 v6; rm -f $put_dst && curl --tftp-blksize 1428 -T $put_src tftp://[::1]:3969/junk/ && cmp $put_src $put_dst || exit 1
|
||||||
|
|
||||||
|
echo atftp get 1428; rm -f $get_fn && ~/src/atftp/atftp --option "blksize 1428" -g -r $get_fn 127.0.0.1 3969 && cmp $get_fn $get_src || exit 1
|
||||||
|
|
||||||
|
echo atftp put 1428; rm -f $put_dst && ~/src/atftp/atftp --option "blksize 1428" 127.0.0.1 3969 -p -l $put_src -r junk/102.zip && cmp $put_src $put_dst || exit 1
|
||||||
|
|
||||||
|
echo tftp-hpa get; rm -f $put_dst && tftp -v -m binary 127.0.0.1 3969 -c get $get_fn && cmp $get_src $get_fn || exit 1
|
||||||
|
|
||||||
|
echo tftp-hpa put; rm -f $put_dst && tftp -v -m binary 127.0.0.1 3969 -c put $put_src junk/102.zip && cmp $put_src $put_dst || exit 1
|
||||||
|
|
||||||
|
echo curl get 512; curl tftp://127.0.0.1:3969/$get_fn | cmp $get_src || exit 1
|
||||||
|
|
||||||
|
echo curl put 512; rm -f $put_dst && curl -T $put_src tftp://127.0.0.1:3969/junk/ && cmp $put_src $put_dst || exit 1
|
||||||
|
|
||||||
|
echo atftp get 512; rm -f $get_fn && ~/src/atftp/atftp -g -r $get_fn 127.0.0.1 3969 && cmp $get_fn $get_src || exit 1
|
||||||
|
|
||||||
|
echo atftp put 512; rm -f $put_dst && ~/src/atftp/atftp 127.0.0.1 3969 -p -l $put_src -r junk/102.zip && cmp $put_src $put_dst || exit 1
|
||||||
|
|
||||||
|
echo nice
|
4
setup.py
4
setup.py
|
@ -84,7 +84,7 @@ args = {
|
||||||
"version": about["__version__"],
|
"version": about["__version__"],
|
||||||
"description": (
|
"description": (
|
||||||
"Portable file server with accelerated resumable uploads, "
|
"Portable file server with accelerated resumable uploads, "
|
||||||
+ "deduplication, WebDAV, FTP, zeroconf, media indexer, "
|
+ "deduplication, WebDAV, FTP, TFTP, zeroconf, media indexer, "
|
||||||
+ "video thumbnails, audio transcoding, and write-only folders"
|
+ "video thumbnails, audio transcoding, and write-only folders"
|
||||||
),
|
),
|
||||||
"long_description": long_description,
|
"long_description": long_description,
|
||||||
|
@ -111,6 +111,7 @@ args = {
|
||||||
"Programming Language :: Python :: Implementation :: CPython",
|
"Programming Language :: Python :: Implementation :: CPython",
|
||||||
"Programming Language :: Python :: Implementation :: Jython",
|
"Programming Language :: Python :: Implementation :: Jython",
|
||||||
"Programming Language :: Python :: Implementation :: PyPy",
|
"Programming Language :: Python :: Implementation :: PyPy",
|
||||||
|
"Operating System :: OS Independent",
|
||||||
"Environment :: Console",
|
"Environment :: Console",
|
||||||
"Environment :: No Input/Output (Daemon)",
|
"Environment :: No Input/Output (Daemon)",
|
||||||
"Intended Audience :: End Users/Desktop",
|
"Intended Audience :: End Users/Desktop",
|
||||||
|
@ -140,6 +141,7 @@ args = {
|
||||||
"audiotags": ["mutagen"],
|
"audiotags": ["mutagen"],
|
||||||
"ftpd": ["pyftpdlib"],
|
"ftpd": ["pyftpdlib"],
|
||||||
"ftps": ["pyftpdlib", "pyopenssl"],
|
"ftps": ["pyftpdlib", "pyopenssl"],
|
||||||
|
"tftpd": ["partftpy>=0.3.0"],
|
||||||
"pwhash": ["argon2-cffi"],
|
"pwhash": ["argon2-cffi"],
|
||||||
},
|
},
|
||||||
"entry_points": {"console_scripts": ["copyparty = copyparty.__main__:main"]},
|
"entry_points": {"console_scripts": ["copyparty = copyparty.__main__:main"]},
|
||||||
|
|
|
@ -11,8 +11,8 @@ import unittest
|
||||||
|
|
||||||
from copyparty.authsrv import AuthSrv
|
from copyparty.authsrv import AuthSrv
|
||||||
from copyparty.httpcli import HttpCli
|
from copyparty.httpcli import HttpCli
|
||||||
from copyparty.up2k import Up2k
|
|
||||||
from copyparty.u2idx import U2idx
|
from copyparty.u2idx import U2idx
|
||||||
|
from copyparty.up2k import Up2k
|
||||||
from tests import util as tu
|
from tests import util as tu
|
||||||
from tests.util import Cfg
|
from tests.util import Cfg
|
||||||
|
|
||||||
|
|
|
@ -43,8 +43,8 @@ if MACOS:
|
||||||
|
|
||||||
from copyparty.__init__ import E
|
from copyparty.__init__ import E
|
||||||
from copyparty.__main__ import init_E
|
from copyparty.__main__ import init_E
|
||||||
from copyparty.util import FHC, Garda, Unrecv
|
|
||||||
from copyparty.u2idx import U2idx
|
from copyparty.u2idx import U2idx
|
||||||
|
from copyparty.util import FHC, Garda, Unrecv
|
||||||
|
|
||||||
init_E(E)
|
init_E(E)
|
||||||
|
|
||||||
|
@ -110,7 +110,7 @@ class Cfg(Namespace):
|
||||||
def __init__(self, a=None, v=None, c=None, **ka0):
|
def __init__(self, a=None, v=None, c=None, **ka0):
|
||||||
ka = {}
|
ka = {}
|
||||||
|
|
||||||
ex = "daw dav_auth dav_inf dav_mac dav_rt e2d e2ds e2dsa e2t e2ts e2tsr e2v e2vu e2vp ed emp exp force_js getmod grid hardlink ih ihead magic never_symlink nid nih no_acode no_athumb no_dav no_dedup no_del no_dupe no_lifetime no_logues no_mv no_readme no_robots no_sb_md no_sb_lg no_scandir no_tarcmp no_thumb no_vthumb no_zip nrand nw q rand smb srch_dbg stats th_no_crop vague_403 vc ver xdev xlink xvol"
|
ex = "daw dav_auth dav_inf dav_mac dav_rt e2d e2ds e2dsa e2t e2ts e2tsr e2v e2vu e2vp ed emp exp force_js getmod grid hardlink ih ihead magic never_symlink nid nih no_acode no_athumb no_dav no_dedup no_del no_dupe no_lifetime no_logues no_mv no_readme no_robots no_sb_md no_sb_lg no_scandir no_tarcmp no_thumb no_vthumb no_zip nrand nw q rand smb srch_dbg stats vague_403 vc ver xdev xlink xvol"
|
||||||
ka.update(**{k: False for k in ex.split()})
|
ka.update(**{k: False for k in ex.split()})
|
||||||
|
|
||||||
ex = "dotpart dotsrch no_dhash no_fastboot no_rescan no_sendfile no_voldump re_dhash plain_ip"
|
ex = "dotpart dotsrch no_dhash no_fastboot no_rescan no_sendfile no_voldump re_dhash plain_ip"
|
||||||
|
@ -157,7 +157,9 @@ class Cfg(Namespace):
|
||||||
s_wr_sz=512 * 1024,
|
s_wr_sz=512 * 1024,
|
||||||
sort="href",
|
sort="href",
|
||||||
srch_hits=99999,
|
srch_hits=99999,
|
||||||
|
th_crop="y",
|
||||||
th_size="320x256",
|
th_size="320x256",
|
||||||
|
th_x3="n",
|
||||||
u2sort="s",
|
u2sort="s",
|
||||||
u2ts="c",
|
u2ts="c",
|
||||||
unpost=600,
|
unpost=600,
|
||||||
|
@ -244,6 +246,7 @@ class VHttpConn(object):
|
||||||
self.log_func = log
|
self.log_func = log
|
||||||
self.log_src = "a"
|
self.log_src = "a"
|
||||||
self.mutex = threading.Lock()
|
self.mutex = threading.Lock()
|
||||||
|
self.u2mutex = threading.Lock()
|
||||||
self.nbyte = 0
|
self.nbyte = 0
|
||||||
self.nid = None
|
self.nid = None
|
||||||
self.nreq = -1
|
self.nreq = -1
|
||||||
|
|
Loading…
Reference in a new issue