Compare commits
25 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
d3ccacccb1 | ||
|
|
df386c8fbc | ||
|
|
4d15dd6e17 | ||
|
|
56a0499636 | ||
|
|
10fc4768e8 | ||
|
|
2b63d7d10d | ||
|
|
1f177528c1 | ||
|
|
fc3bbb70a3 | ||
|
|
ce3cab0295 | ||
|
|
c784e5285e | ||
|
|
2bf9055cae | ||
|
|
8aba5aed4f | ||
|
|
0ce7cf5e10 | ||
|
|
96edcbccd7 | ||
|
|
4603afb6de | ||
|
|
56317b00af | ||
|
|
cacec9c1f3 | ||
|
|
44ee07f0b2 | ||
|
|
6a8d5e1731 | ||
|
|
d9962f65b3 | ||
|
|
119e88d87b | ||
|
|
71d9e010d9 | ||
|
|
5718caa957 | ||
|
|
efd8a32ed6 | ||
|
|
b22d700e16 |
10
README.md
10
README.md
@@ -428,7 +428,7 @@ configuring accounts/volumes with arguments:
|
|||||||
|
|
||||||
permissions:
|
permissions:
|
||||||
* `r` (read): browse folder contents, download files, download as zip/tar, see filekeys/dirkeys
|
* `r` (read): browse folder contents, download files, download as zip/tar, see filekeys/dirkeys
|
||||||
* `w` (write): upload files, move files *into* this folder
|
* `w` (write): upload files, move/copy files *into* this folder
|
||||||
* `m` (move): move files/folders *from* this folder
|
* `m` (move): move files/folders *from* this folder
|
||||||
* `d` (delete): delete files/folders
|
* `d` (delete): delete files/folders
|
||||||
* `.` (dots): user can ask to show dotfiles in directory listings
|
* `.` (dots): user can ask to show dotfiles in directory listings
|
||||||
@@ -508,7 +508,8 @@ the browser has the following hotkeys (always qwerty)
|
|||||||
* `ESC` close various things
|
* `ESC` close various things
|
||||||
* `ctrl-K` delete selected files/folders
|
* `ctrl-K` delete selected files/folders
|
||||||
* `ctrl-X` cut selected files/folders
|
* `ctrl-X` cut selected files/folders
|
||||||
* `ctrl-V` paste
|
* `ctrl-C` copy selected files/folders to clipboard
|
||||||
|
* `ctrl-V` paste (move/copy)
|
||||||
* `Y` download selected files
|
* `Y` download selected files
|
||||||
* `F2` [rename](#batch-rename) selected file/folder
|
* `F2` [rename](#batch-rename) selected file/folder
|
||||||
* when a file/folder is selected (in not-grid-view):
|
* when a file/folder is selected (in not-grid-view):
|
||||||
@@ -577,6 +578,7 @@ click the `🌲` or pressing the `B` hotkey to toggle between breadcrumbs path (
|
|||||||
|
|
||||||
press `g` or `田` to toggle grid-view instead of the file listing and `t` toggles icons / thumbnails
|
press `g` or `田` to toggle grid-view instead of the file listing and `t` toggles icons / thumbnails
|
||||||
* can be made default globally with `--grid` or per-volume with volflag `grid`
|
* can be made default globally with `--grid` or per-volume with volflag `grid`
|
||||||
|
* enable by adding `?imgs` to a link, or disable with `?imgs=0`
|
||||||
|
|
||||||

|

|
||||||
|
|
||||||
@@ -756,10 +758,11 @@ file selection: click somewhere on the line (not the link itself), then:
|
|||||||
* shift-click another line for range-select
|
* shift-click another line for range-select
|
||||||
|
|
||||||
* cut: select some files and `ctrl-x`
|
* cut: select some files and `ctrl-x`
|
||||||
|
* copy: select some files and `ctrl-c`
|
||||||
* paste: `ctrl-v` in another folder
|
* paste: `ctrl-v` in another folder
|
||||||
* rename: `F2`
|
* rename: `F2`
|
||||||
|
|
||||||
you can move files across browser tabs (cut in one tab, paste in another)
|
you can copy/move files across browser tabs (cut/copy in one tab, paste in another)
|
||||||
|
|
||||||
|
|
||||||
## shares
|
## shares
|
||||||
@@ -1684,6 +1687,7 @@ scrape_configs:
|
|||||||
currently the following metrics are available,
|
currently the following metrics are available,
|
||||||
* `cpp_uptime_seconds` time since last copyparty restart
|
* `cpp_uptime_seconds` time since last copyparty restart
|
||||||
* `cpp_boot_unixtime_seconds` same but as an absolute timestamp
|
* `cpp_boot_unixtime_seconds` same but as an absolute timestamp
|
||||||
|
* `cpp_active_dl` number of active downloads
|
||||||
* `cpp_http_conns` number of open http(s) connections
|
* `cpp_http_conns` number of open http(s) connections
|
||||||
* `cpp_http_reqs` number of http(s) requests handled
|
* `cpp_http_reqs` number of http(s) requests handled
|
||||||
* `cpp_sus_reqs` number of 403/422/malicious requests
|
* `cpp_sus_reqs` number of 403/422/malicious requests
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ standalone programs which are executed by copyparty when an event happens (uploa
|
|||||||
|
|
||||||
these programs either take zero arguments, or a filepath (the affected file), or a json message with filepath + additional info
|
these programs either take zero arguments, or a filepath (the affected file), or a json message with filepath + additional info
|
||||||
|
|
||||||
run copyparty with `--help-hooks` for usage details / hook type explanations (xm/xbu/xau/xiu/xbr/xar/xbd/xad/xban)
|
run copyparty with `--help-hooks` for usage details / hook type explanations (xm/xbu/xau/xiu/xbc/xac/xbr/xar/xbd/xad/xban)
|
||||||
|
|
||||||
> **note:** in addition to event hooks (the stuff described here), copyparty has another api to run your programs/scripts while providing way more information such as audio tags / video codecs / etc and optionally daisychaining data between scripts in a processing pipeline; if that's what you want then see [mtp plugins](../mtag/) instead
|
> **note:** in addition to event hooks (the stuff described here), copyparty has another api to run your programs/scripts while providing way more information such as audio tags / video codecs / etc and optionally daisychaining data between scripts in a processing pipeline; if that's what you want then see [mtp plugins](../mtag/) instead
|
||||||
|
|
||||||
|
|||||||
@@ -393,7 +393,8 @@ class Gateway(object):
|
|||||||
if r.status != 200:
|
if r.status != 200:
|
||||||
self.closeconn()
|
self.closeconn()
|
||||||
info("http error %s reading dir %r", r.status, web_path)
|
info("http error %s reading dir %r", r.status, web_path)
|
||||||
raise FuseOSError(errno.ENOENT)
|
err = errno.ENOENT if r.status == 404 else errno.EIO
|
||||||
|
raise FuseOSError(err)
|
||||||
|
|
||||||
ctype = r.getheader("Content-Type", "")
|
ctype = r.getheader("Content-Type", "")
|
||||||
if ctype == "application/json":
|
if ctype == "application/json":
|
||||||
|
|||||||
23
bin/u2c.py
23
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 = "2.5"
|
S_VERSION = "2.6"
|
||||||
S_BUILD_DT = "2024-10-18"
|
S_BUILD_DT = "2024-11-10"
|
||||||
|
|
||||||
"""
|
"""
|
||||||
u2c.py: upload to copyparty
|
u2c.py: upload to copyparty
|
||||||
@@ -189,6 +189,8 @@ class HCli(object):
|
|||||||
return C(self.addr, self.port, timeout=timeout, **args)
|
return C(self.addr, self.port, timeout=timeout, **args)
|
||||||
|
|
||||||
def req(self, meth, vpath, hdrs, body=None, ctype=None):
|
def req(self, meth, vpath, hdrs, body=None, ctype=None):
|
||||||
|
now = time.time()
|
||||||
|
|
||||||
hdrs.update(self.base_hdrs)
|
hdrs.update(self.base_hdrs)
|
||||||
if self.ar.a:
|
if self.ar.a:
|
||||||
hdrs["PW"] = self.ar.a
|
hdrs["PW"] = self.ar.a
|
||||||
@@ -201,7 +203,9 @@ class HCli(object):
|
|||||||
|
|
||||||
# large timeout for handshakes (safededup)
|
# large timeout for handshakes (safededup)
|
||||||
conns = self.hconns if ctype == MJ else self.conns
|
conns = self.hconns if ctype == MJ else self.conns
|
||||||
c = conns.pop() if conns else self._connect(999 if ctype == MJ else 128)
|
while conns and self.ar.cxp < now - conns[0][0]:
|
||||||
|
conns.pop(0)[1].close()
|
||||||
|
c = conns.pop()[1] if conns else self._connect(999 if ctype == MJ else 128)
|
||||||
try:
|
try:
|
||||||
c.request(meth, vpath, body, hdrs)
|
c.request(meth, vpath, body, hdrs)
|
||||||
if PY27:
|
if PY27:
|
||||||
@@ -210,8 +214,15 @@ class HCli(object):
|
|||||||
rsp = c.getresponse()
|
rsp = c.getresponse()
|
||||||
|
|
||||||
data = rsp.read()
|
data = rsp.read()
|
||||||
conns.append(c)
|
conns.append((time.time(), c))
|
||||||
return rsp.status, data.decode("utf-8")
|
return rsp.status, data.decode("utf-8")
|
||||||
|
except http_client.BadStatusLine:
|
||||||
|
if self.ar.cxp > 4:
|
||||||
|
t = "\nWARNING: --cxp probably too high; reducing from %d to 4"
|
||||||
|
print(t % (self.ar.cxp,))
|
||||||
|
self.ar.cxp = 4
|
||||||
|
c.close()
|
||||||
|
raise
|
||||||
except:
|
except:
|
||||||
c.close()
|
c.close()
|
||||||
raise
|
raise
|
||||||
@@ -1142,7 +1153,10 @@ class Ctl(object):
|
|||||||
|
|
||||||
if self.ar.drd:
|
if self.ar.drd:
|
||||||
dp = os.path.join(top, rd)
|
dp = os.path.join(top, rd)
|
||||||
|
try:
|
||||||
lnodes = set(os.listdir(dp))
|
lnodes = set(os.listdir(dp))
|
||||||
|
except:
|
||||||
|
lnodes = list(ls) # fs eio; don't delete
|
||||||
if ptn:
|
if ptn:
|
||||||
zs = dp.replace(sep, b"/").rstrip(b"/") + b"/"
|
zs = dp.replace(sep, b"/").rstrip(b"/") + b"/"
|
||||||
zls = [zs + x for x in lnodes]
|
zls = [zs + x for x in lnodes]
|
||||||
@@ -1500,6 +1514,7 @@ source file/folder selection uses rsync syntax, meaning that:
|
|||||||
ap.add_argument("--szm", type=int, metavar="MiB", default=96, help="max size of each POST (default is cloudflare max)")
|
ap.add_argument("--szm", type=int, metavar="MiB", default=96, help="max size of each POST (default is cloudflare max)")
|
||||||
ap.add_argument("-nh", action="store_true", help="disable hashing while uploading")
|
ap.add_argument("-nh", action="store_true", help="disable hashing while uploading")
|
||||||
ap.add_argument("-ns", action="store_true", help="no status panel (for slow consoles and macos)")
|
ap.add_argument("-ns", action="store_true", help="no status panel (for slow consoles and macos)")
|
||||||
|
ap.add_argument("--cxp", type=float, metavar="SEC", default=57, help="assume http connections expired after SEConds")
|
||||||
ap.add_argument("--cd", type=float, metavar="SEC", default=5, help="delay before reattempting a failed handshake/upload")
|
ap.add_argument("--cd", type=float, metavar="SEC", default=5, help="delay before reattempting a failed handshake/upload")
|
||||||
ap.add_argument("--safe", action="store_true", help="use simple fallback approach")
|
ap.add_argument("--safe", action="store_true", help="use simple fallback approach")
|
||||||
ap.add_argument("-z", action="store_true", help="ZOOMIN' (skip uploading files if they exist at the destination with the ~same last-modified timestamp, so same as yolo / turbo with date-chk but even faster)")
|
ap.add_argument("-z", action="store_true", help="ZOOMIN' (skip uploading files if they exist at the destination with the ~same last-modified timestamp, so same as yolo / turbo with date-chk but even faster)")
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
# Maintainer: icxes <dev.null@need.moe>
|
# Maintainer: icxes <dev.null@need.moe>
|
||||||
pkgname=copyparty
|
pkgname=copyparty
|
||||||
pkgver="1.15.9"
|
pkgver="1.16.0"
|
||||||
pkgrel=1
|
pkgrel=1
|
||||||
pkgdesc="File server with accelerated resumable uploads, dedup, WebDAV, FTP, TFTP, zeroconf, media indexer, thumbnails++"
|
pkgdesc="File server with accelerated resumable uploads, dedup, WebDAV, FTP, TFTP, zeroconf, media indexer, thumbnails++"
|
||||||
arch=("any")
|
arch=("any")
|
||||||
@@ -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=("f75668c752468cab3e4d7bb93323ab9ac7309ee74a138d3597ef0edabd9235de")
|
sha256sums=("8a802bbb4392ead6bc92bcb1c71ecd1855a05a5b4d0312499c33f65424c12a00")
|
||||||
|
|
||||||
build() {
|
build() {
|
||||||
cd "${srcdir}/${pkgname}-${pkgver}"
|
cd "${srcdir}/${pkgname}-${pkgver}"
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
{
|
{
|
||||||
"url": "https://github.com/9001/copyparty/releases/download/v1.15.9/copyparty-sfx.py",
|
"url": "https://github.com/9001/copyparty/releases/download/v1.16.0/copyparty-sfx.py",
|
||||||
"version": "1.15.9",
|
"version": "1.16.0",
|
||||||
"hash": "sha256-pxX1RkfO3h1XyPQnrwkFp8laOS4sqXAwbsi2V18VnVY="
|
"hash": "sha256-H9imF66HcE6I/gGPZdJ5zkzATC3Vkc4luTAbRy8GRh4="
|
||||||
}
|
}
|
||||||
@@ -80,6 +80,7 @@ web/deps/prismd.css
|
|||||||
web/deps/scp.woff2
|
web/deps/scp.woff2
|
||||||
web/deps/sha512.ac.js
|
web/deps/sha512.ac.js
|
||||||
web/deps/sha512.hw.js
|
web/deps/sha512.hw.js
|
||||||
|
web/iiam.gif
|
||||||
web/md.css
|
web/md.css
|
||||||
web/md.html
|
web/md.html
|
||||||
web/md.js
|
web/md.js
|
||||||
|
|||||||
@@ -50,6 +50,8 @@ from .util import (
|
|||||||
PARTFTPY_VER,
|
PARTFTPY_VER,
|
||||||
PY_DESC,
|
PY_DESC,
|
||||||
PYFTPD_VER,
|
PYFTPD_VER,
|
||||||
|
RAM_AVAIL,
|
||||||
|
RAM_TOTAL,
|
||||||
SQLITE_VER,
|
SQLITE_VER,
|
||||||
UNPLICATIONS,
|
UNPLICATIONS,
|
||||||
Daemon,
|
Daemon,
|
||||||
@@ -684,6 +686,8 @@ def get_sects():
|
|||||||
\033[36mxbu\033[35m executes CMD before a file upload starts
|
\033[36mxbu\033[35m executes CMD before a file upload starts
|
||||||
\033[36mxau\033[35m executes CMD after a file upload finishes
|
\033[36mxau\033[35m executes CMD after a file upload finishes
|
||||||
\033[36mxiu\033[35m executes CMD after all uploads finish and volume is idle
|
\033[36mxiu\033[35m executes CMD after all uploads finish and volume is idle
|
||||||
|
\033[36mxbc\033[35m executes CMD before a file copy
|
||||||
|
\033[36mxac\033[35m executes CMD after a file copy
|
||||||
\033[36mxbr\033[35m executes CMD before a file rename/move
|
\033[36mxbr\033[35m executes CMD before a file rename/move
|
||||||
\033[36mxar\033[35m executes CMD after a file rename/move
|
\033[36mxar\033[35m executes CMD after a file rename/move
|
||||||
\033[36mxbd\033[35m executes CMD before a file delete
|
\033[36mxbd\033[35m executes CMD before a file delete
|
||||||
@@ -874,8 +878,9 @@ def get_sects():
|
|||||||
use argon2id with timecost 3, 256 MiB, 4 threads, version 19 (0x13/v1.3)
|
use argon2id with timecost 3, 256 MiB, 4 threads, version 19 (0x13/v1.3)
|
||||||
|
|
||||||
\033[36m--ah-alg scrypt\033[0m # which is the same as:
|
\033[36m--ah-alg scrypt\033[0m # which is the same as:
|
||||||
\033[36m--ah-alg scrypt,13,2,8,4\033[0m
|
\033[36m--ah-alg scrypt,13,2,8,4,32\033[0m
|
||||||
use scrypt with cost 2**13, 2 iterations, blocksize 8, 4 threads
|
use scrypt with cost 2**13, 2 iterations, blocksize 8, 4 threads,
|
||||||
|
and allow using up to 32 MiB RAM (ram=cost*blksz roughly)
|
||||||
|
|
||||||
\033[36m--ah-alg sha2\033[0m # which is the same as:
|
\033[36m--ah-alg sha2\033[0m # which is the same as:
|
||||||
\033[36m--ah-alg sha2,424242\033[0m
|
\033[36m--ah-alg sha2,424242\033[0m
|
||||||
@@ -1201,6 +1206,8 @@ def add_hooks(ap):
|
|||||||
ap2.add_argument("--xbu", metavar="CMD", type=u, action="append", help="execute \033[33mCMD\033[0m before a file upload starts")
|
ap2.add_argument("--xbu", metavar="CMD", type=u, action="append", help="execute \033[33mCMD\033[0m before a file upload starts")
|
||||||
ap2.add_argument("--xau", metavar="CMD", type=u, action="append", help="execute \033[33mCMD\033[0m after a file upload finishes")
|
ap2.add_argument("--xau", metavar="CMD", type=u, action="append", help="execute \033[33mCMD\033[0m after a file upload finishes")
|
||||||
ap2.add_argument("--xiu", metavar="CMD", type=u, action="append", help="execute \033[33mCMD\033[0m after all uploads finish and volume is idle")
|
ap2.add_argument("--xiu", metavar="CMD", type=u, action="append", help="execute \033[33mCMD\033[0m after all uploads finish and volume is idle")
|
||||||
|
ap2.add_argument("--xbc", metavar="CMD", type=u, action="append", help="execute \033[33mCMD\033[0m before a file copy")
|
||||||
|
ap2.add_argument("--xac", metavar="CMD", type=u, action="append", help="execute \033[33mCMD\033[0m after a file copy")
|
||||||
ap2.add_argument("--xbr", metavar="CMD", type=u, action="append", help="execute \033[33mCMD\033[0m before a file move/rename")
|
ap2.add_argument("--xbr", metavar="CMD", type=u, action="append", help="execute \033[33mCMD\033[0m before a file move/rename")
|
||||||
ap2.add_argument("--xar", metavar="CMD", type=u, action="append", help="execute \033[33mCMD\033[0m after a file move/rename")
|
ap2.add_argument("--xar", metavar="CMD", type=u, action="append", help="execute \033[33mCMD\033[0m after a file move/rename")
|
||||||
ap2.add_argument("--xbd", metavar="CMD", type=u, action="append", help="execute \033[33mCMD\033[0m before a file delete")
|
ap2.add_argument("--xbd", metavar="CMD", type=u, action="append", help="execute \033[33mCMD\033[0m before a file delete")
|
||||||
@@ -1233,6 +1240,7 @@ def add_optouts(ap):
|
|||||||
ap2.add_argument("--no-dav", action="store_true", help="disable webdav support")
|
ap2.add_argument("--no-dav", action="store_true", help="disable webdav support")
|
||||||
ap2.add_argument("--no-del", action="store_true", help="disable delete operations")
|
ap2.add_argument("--no-del", action="store_true", help="disable delete operations")
|
||||||
ap2.add_argument("--no-mv", action="store_true", help="disable move/rename operations")
|
ap2.add_argument("--no-mv", action="store_true", help="disable move/rename operations")
|
||||||
|
ap2.add_argument("--no-cp", action="store_true", help="disable copy operations")
|
||||||
ap2.add_argument("-nth", action="store_true", help="no title hostname; don't show \033[33m--name\033[0m in <title>")
|
ap2.add_argument("-nth", action="store_true", help="no title hostname; don't show \033[33m--name\033[0m in <title>")
|
||||||
ap2.add_argument("-nih", action="store_true", help="no info hostname -- don't show in UI")
|
ap2.add_argument("-nih", action="store_true", help="no info hostname -- don't show in UI")
|
||||||
ap2.add_argument("-nid", action="store_true", help="no info disk-usage -- don't show in UI")
|
ap2.add_argument("-nid", action="store_true", help="no info disk-usage -- don't show in UI")
|
||||||
@@ -1316,9 +1324,12 @@ def add_admin(ap):
|
|||||||
ap2.add_argument("--no-reload", action="store_true", help="disable ?reload=cfg (reload users/volumes/volflags from config file)")
|
ap2.add_argument("--no-reload", action="store_true", help="disable ?reload=cfg (reload users/volumes/volflags from config file)")
|
||||||
ap2.add_argument("--no-rescan", action="store_true", help="disable ?scan (volume reindexing)")
|
ap2.add_argument("--no-rescan", action="store_true", help="disable ?scan (volume reindexing)")
|
||||||
ap2.add_argument("--no-stack", action="store_true", help="disable ?stack (list all stacks)")
|
ap2.add_argument("--no-stack", action="store_true", help="disable ?stack (list all stacks)")
|
||||||
|
ap2.add_argument("--dl-list", metavar="LVL", type=int, default=2, help="who can see active downloads in the controlpanel? [\033[32m0\033[0m]=nobody, [\033[32m1\033[0m]=admins, [\033[32m2\033[0m]=everyone")
|
||||||
|
|
||||||
|
|
||||||
def add_thumbnail(ap):
|
def add_thumbnail(ap):
|
||||||
|
th_ram = (RAM_AVAIL or RAM_TOTAL or 9) * 0.6
|
||||||
|
th_ram = int(max(min(th_ram, 6), 1) * 10) / 10
|
||||||
ap2 = ap.add_argument_group('thumbnail options')
|
ap2 = ap.add_argument_group('thumbnail options')
|
||||||
ap2.add_argument("--no-thumb", action="store_true", help="disable all thumbnails (volflag=dthumb)")
|
ap2.add_argument("--no-thumb", action="store_true", help="disable all thumbnails (volflag=dthumb)")
|
||||||
ap2.add_argument("--no-vthumb", action="store_true", help="disable video thumbnails (volflag=dvthumb)")
|
ap2.add_argument("--no-vthumb", action="store_true", help="disable video thumbnails (volflag=dvthumb)")
|
||||||
@@ -1326,7 +1337,7 @@ def add_thumbnail(ap):
|
|||||||
ap2.add_argument("--th-size", metavar="WxH", default="320x256", help="thumbnail res (volflag=thsize)")
|
ap2.add_argument("--th-size", metavar="WxH", default="320x256", help="thumbnail res (volflag=thsize)")
|
||||||
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.0, help="conversion timeout in seconds (volflag=convt)")
|
ap2.add_argument("--th-convt", metavar="SEC", type=float, default=60.0, help="conversion timeout in seconds (volflag=convt)")
|
||||||
ap2.add_argument("--th-ram-max", metavar="GB", type=float, default=6.0, help="max memory usage (GiB) permitted by thumbnailer; not very accurate")
|
ap2.add_argument("--th-ram-max", metavar="GB", type=float, default=th_ram, help="max memory usage (GiB) permitted by thumbnailer; not very accurate")
|
||||||
ap2.add_argument("--th-crop", metavar="TXT", type=u, default="y", help="crop thumbnails to 4:3 or keep dynamic height; client can override in UI unless force. [\033[32my\033[0m]=crop, [\033[32mn\033[0m]=nocrop, [\033[32mfy\033[0m]=force-y, [\033[32mfn\033[0m]=force-n (volflag=crop)")
|
ap2.add_argument("--th-crop", metavar="TXT", type=u, default="y", help="crop thumbnails to 4:3 or keep dynamic height; client can override in UI unless force. [\033[32my\033[0m]=crop, [\033[32mn\033[0m]=nocrop, [\033[32mfy\033[0m]=force-y, [\033[32mfn\033[0m]=force-n (volflag=crop)")
|
||||||
ap2.add_argument("--th-x3", metavar="TXT", type=u, default="n", help="show thumbs at 3x resolution; client can override in UI unless force. [\033[32my\033[0m]=yes, [\033[32mn\033[0m]=no, [\033[32mfy\033[0m]=force-yes, [\033[32mfn\033[0m]=force-no (volflag=th3x)")
|
ap2.add_argument("--th-x3", metavar="TXT", type=u, default="n", help="show thumbs at 3x resolution; client can override in UI unless force. [\033[32my\033[0m]=yes, [\033[32mn\033[0m]=no, [\033[32mfy\033[0m]=force-yes, [\033[32mfn\033[0m]=force-no (volflag=th3x)")
|
||||||
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")
|
||||||
@@ -1341,12 +1352,12 @@ def add_thumbnail(ap):
|
|||||||
# https://pillow.readthedocs.io/en/stable/handbook/image-file-formats.html
|
# https://pillow.readthedocs.io/en/stable/handbook/image-file-formats.html
|
||||||
# https://github.com/libvips/libvips
|
# https://github.com/libvips/libvips
|
||||||
# ffmpeg -hide_banner -demuxers | awk '/^ D /{print$2}' | while IFS= read -r x; do ffmpeg -hide_banner -h demuxer=$x; done | grep -E '^Demuxer |extensions:'
|
# ffmpeg -hide_banner -demuxers | awk '/^ D /{print$2}' | while IFS= read -r x; do ffmpeg -hide_banner -h demuxer=$x; done | grep -E '^Demuxer |extensions:'
|
||||||
ap2.add_argument("--th-r-pil", metavar="T,T", type=u, default="avif,avifs,blp,bmp,dcx,dds,dib,emf,eps,fits,flc,fli,fpx,gif,heic,heics,heif,heifs,icns,ico,im,j2p,j2k,jp2,jpeg,jpg,jpx,pbm,pcx,pgm,png,pnm,ppm,psd,qoi,sgi,spi,tga,tif,tiff,webp,wmf,xbm,xpm", help="image formats to decode using pillow")
|
ap2.add_argument("--th-r-pil", metavar="T,T", type=u, default="avif,avifs,blp,bmp,cbz,dcx,dds,dib,emf,eps,fits,flc,fli,fpx,gif,heic,heics,heif,heifs,icns,ico,im,j2p,j2k,jp2,jpeg,jpg,jpx,pbm,pcx,pgm,png,pnm,ppm,psd,qoi,sgi,spi,tga,tif,tiff,webp,wmf,xbm,xpm", help="image formats to decode using pillow")
|
||||||
ap2.add_argument("--th-r-vips", metavar="T,T", type=u, default="avif,exr,fit,fits,fts,gif,hdr,heic,jp2,jpeg,jpg,jpx,jxl,nii,pfm,pgm,png,ppm,svg,tif,tiff,webp", help="image formats to decode using pyvips")
|
ap2.add_argument("--th-r-vips", metavar="T,T", type=u, default="avif,exr,fit,fits,fts,gif,hdr,heic,jp2,jpeg,jpg,jpx,jxl,nii,pfm,pgm,png,ppm,svg,tif,tiff,webp", help="image formats to decode using pyvips")
|
||||||
ap2.add_argument("--th-r-ffi", metavar="T,T", type=u, default="apng,avif,avifs,bmp,dds,dib,fit,fits,fts,gif,hdr,heic,heics,heif,heifs,icns,ico,jp2,jpeg,jpg,jpx,jxl,pbm,pcx,pfm,pgm,png,pnm,ppm,psd,qoi,sgi,tga,tif,tiff,webp,xbm,xpm", help="image formats to decode using ffmpeg")
|
ap2.add_argument("--th-r-ffi", metavar="T,T", type=u, default="apng,avif,avifs,bmp,cbz,dds,dib,fit,fits,fts,gif,hdr,heic,heics,heif,heifs,icns,ico,jp2,jpeg,jpg,jpx,jxl,pbm,pcx,pfm,pgm,png,pnm,ppm,psd,qoi,sgi,tga,tif,tiff,webp,xbm,xpm", help="image formats to decode using ffmpeg")
|
||||||
ap2.add_argument("--th-r-ffv", metavar="T,T", type=u, default="3gp,asf,av1,avc,avi,flv,h264,h265,hevc,m4v,mjpeg,mjpg,mkv,mov,mp4,mpeg,mpeg2,mpegts,mpg,mpg2,mts,nut,ogm,ogv,rm,ts,vob,webm,wmv", help="video formats to decode using ffmpeg")
|
ap2.add_argument("--th-r-ffv", metavar="T,T", type=u, default="3gp,asf,av1,avc,avi,flv,h264,h265,hevc,m4v,mjpeg,mjpg,mkv,mov,mp4,mpeg,mpeg2,mpegts,mpg,mpg2,mts,nut,ogm,ogv,rm,ts,vob,webm,wmv", help="video formats to decode using ffmpeg")
|
||||||
ap2.add_argument("--th-r-ffa", metavar="T,T", type=u, default="aac,ac3,aif,aiff,alac,alaw,amr,apac,ape,au,bonk,dfpwm,dts,flac,gsm,ilbc,it,itgz,itxz,itz,m4a,mdgz,mdxz,mdz,mo3,mod,mp2,mp3,mpc,mptm,mt2,mulaw,ogg,okt,opus,ra,s3m,s3gz,s3xz,s3z,tak,tta,ulaw,wav,wma,wv,xm,xmgz,xmxz,xmz,xpk", help="audio formats to decode using ffmpeg")
|
ap2.add_argument("--th-r-ffa", metavar="T,T", type=u, default="aac,ac3,aif,aiff,alac,alaw,amr,apac,ape,au,bonk,dfpwm,dts,flac,gsm,ilbc,it,itgz,itxz,itz,m4a,mdgz,mdxz,mdz,mo3,mod,mp2,mp3,mpc,mptm,mt2,mulaw,ogg,okt,opus,ra,s3m,s3gz,s3xz,s3z,tak,tta,ulaw,wav,wma,wv,xm,xmgz,xmxz,xmz,xpk", help="audio formats to decode using ffmpeg")
|
||||||
ap2.add_argument("--au-unpk", metavar="E=F.C", type=u, default="mdz=mod.zip, mdgz=mod.gz, mdxz=mod.xz, s3z=s3m.zip, s3gz=s3m.gz, s3xz=s3m.xz, xmz=xm.zip, xmgz=xm.gz, xmxz=xm.xz, itz=it.zip, itgz=it.gz, itxz=it.xz", help="audio formats to decompress before passing to ffmpeg")
|
ap2.add_argument("--au-unpk", metavar="E=F.C", type=u, default="mdz=mod.zip, mdgz=mod.gz, mdxz=mod.xz, s3z=s3m.zip, s3gz=s3m.gz, s3xz=s3m.xz, xmz=xm.zip, xmgz=xm.gz, xmxz=xm.xz, itz=it.zip, itgz=it.gz, itxz=it.xz, cbz=jpg.cbz", help="audio/image formats to decompress before passing to ffmpeg")
|
||||||
|
|
||||||
|
|
||||||
def add_transcoding(ap):
|
def add_transcoding(ap):
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
# coding: utf-8
|
# coding: utf-8
|
||||||
|
|
||||||
VERSION = (1, 15, 10)
|
VERSION = (1, 16, 1)
|
||||||
CODENAME = "fill the drives"
|
CODENAME = "COPYparty"
|
||||||
BUILD_DT = (2024, 10, 26)
|
BUILD_DT = (2024, 11, 15)
|
||||||
|
|
||||||
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)
|
||||||
|
|||||||
@@ -367,18 +367,19 @@ class VFS(object):
|
|||||||
self.ahtml: dict[str, list[str]] = {}
|
self.ahtml: dict[str, list[str]] = {}
|
||||||
self.aadmin: dict[str, list[str]] = {}
|
self.aadmin: dict[str, list[str]] = {}
|
||||||
self.adot: dict[str, list[str]] = {}
|
self.adot: dict[str, list[str]] = {}
|
||||||
self.all_vols: dict[str, VFS] = {}
|
|
||||||
|
|
||||||
if realpath:
|
if realpath:
|
||||||
rp = realpath + ("" if realpath.endswith(os.sep) else os.sep)
|
rp = realpath + ("" if realpath.endswith(os.sep) else os.sep)
|
||||||
vp = vpath + ("/" if vpath else "")
|
vp = vpath + ("/" if vpath else "")
|
||||||
self.histpath = os.path.join(realpath, ".hist") # db / thumbcache
|
self.histpath = os.path.join(realpath, ".hist") # db / thumbcache
|
||||||
self.all_vols = {vpath: self} # flattened recursive
|
self.all_vols = {vpath: self} # flattened recursive
|
||||||
|
self.all_nodes = {vpath: self} # also jumpvols
|
||||||
self.all_aps = [(rp, self)]
|
self.all_aps = [(rp, self)]
|
||||||
self.all_vps = [(vp, self)]
|
self.all_vps = [(vp, self)]
|
||||||
else:
|
else:
|
||||||
self.histpath = ""
|
self.histpath = ""
|
||||||
self.all_vols = {}
|
self.all_vols = {}
|
||||||
|
self.all_nodes = {}
|
||||||
self.all_aps = []
|
self.all_aps = []
|
||||||
self.all_vps = []
|
self.all_vps = []
|
||||||
|
|
||||||
@@ -396,9 +397,11 @@ class VFS(object):
|
|||||||
def get_all_vols(
|
def get_all_vols(
|
||||||
self,
|
self,
|
||||||
vols: dict[str, "VFS"],
|
vols: dict[str, "VFS"],
|
||||||
|
nodes: dict[str, "VFS"],
|
||||||
aps: list[tuple[str, "VFS"]],
|
aps: list[tuple[str, "VFS"]],
|
||||||
vps: list[tuple[str, "VFS"]],
|
vps: list[tuple[str, "VFS"]],
|
||||||
) -> None:
|
) -> None:
|
||||||
|
nodes[self.vpath] = self
|
||||||
if self.realpath:
|
if self.realpath:
|
||||||
vols[self.vpath] = self
|
vols[self.vpath] = self
|
||||||
rp = self.realpath
|
rp = self.realpath
|
||||||
@@ -408,7 +411,7 @@ class VFS(object):
|
|||||||
vps.append((vp, self))
|
vps.append((vp, self))
|
||||||
|
|
||||||
for v in self.nodes.values():
|
for v in self.nodes.values():
|
||||||
v.get_all_vols(vols, aps, vps)
|
v.get_all_vols(vols, nodes, aps, vps)
|
||||||
|
|
||||||
def add(self, src: str, dst: str) -> "VFS":
|
def add(self, src: str, dst: str) -> "VFS":
|
||||||
"""get existing, or add new path to the vfs"""
|
"""get existing, or add new path to the vfs"""
|
||||||
@@ -591,10 +594,11 @@ class VFS(object):
|
|||||||
scandir: bool,
|
scandir: bool,
|
||||||
permsets: list[list[bool]],
|
permsets: list[list[bool]],
|
||||||
lstat: bool = False,
|
lstat: bool = False,
|
||||||
|
throw: bool = False,
|
||||||
) -> tuple[str, list[tuple[str, os.stat_result]], dict[str, "VFS"]]:
|
) -> tuple[str, list[tuple[str, os.stat_result]], dict[str, "VFS"]]:
|
||||||
"""replaces _ls for certain shares (single-file, or file selection)"""
|
"""replaces _ls for certain shares (single-file, or file selection)"""
|
||||||
vn, rem = self.shr_src # type: ignore
|
vn, rem = self.shr_src # type: ignore
|
||||||
abspath, real, _ = vn.ls(rem, "\n", scandir, permsets, lstat)
|
abspath, real, _ = vn.ls(rem, "\n", scandir, permsets, lstat, throw)
|
||||||
real = [x for x in real if os.path.basename(x[0]) in self.shr_files]
|
real = [x for x in real if os.path.basename(x[0]) in self.shr_files]
|
||||||
return abspath, real, {}
|
return abspath, real, {}
|
||||||
|
|
||||||
@@ -605,11 +609,12 @@ class VFS(object):
|
|||||||
scandir: bool,
|
scandir: bool,
|
||||||
permsets: list[list[bool]],
|
permsets: list[list[bool]],
|
||||||
lstat: bool = False,
|
lstat: bool = False,
|
||||||
|
throw: bool = False,
|
||||||
) -> tuple[str, list[tuple[str, os.stat_result]], dict[str, "VFS"]]:
|
) -> tuple[str, list[tuple[str, os.stat_result]], dict[str, "VFS"]]:
|
||||||
"""return user-readable [fsdir,real,virt] items at vpath"""
|
"""return user-readable [fsdir,real,virt] items at vpath"""
|
||||||
virt_vis = {} # nodes readable by user
|
virt_vis = {} # nodes readable by user
|
||||||
abspath = self.canonical(rem)
|
abspath = self.canonical(rem)
|
||||||
real = list(statdir(self.log, scandir, lstat, abspath))
|
real = list(statdir(self.log, scandir, lstat, abspath, throw))
|
||||||
real.sort()
|
real.sort()
|
||||||
if not rem:
|
if not rem:
|
||||||
# no vfs nodes in the list of real inodes
|
# no vfs nodes in the list of real inodes
|
||||||
@@ -671,6 +676,10 @@ class VFS(object):
|
|||||||
"""
|
"""
|
||||||
recursively yields from ./rem;
|
recursively yields from ./rem;
|
||||||
rel is a unix-style user-defined vpath (not vfs-related)
|
rel is a unix-style user-defined vpath (not vfs-related)
|
||||||
|
|
||||||
|
NOTE: don't invoke this function from a dbv; subvols are only
|
||||||
|
descended into if rem is blank due to the _ls `if not rem:`
|
||||||
|
which intention is to prevent unintended access to subvols
|
||||||
"""
|
"""
|
||||||
|
|
||||||
fsroot, vfs_ls, vfs_virt = self.ls(rem, uname, scandir, permsets, lstat=lstat)
|
fsroot, vfs_ls, vfs_virt = self.ls(rem, uname, scandir, permsets, lstat=lstat)
|
||||||
@@ -911,7 +920,7 @@ class AuthSrv(object):
|
|||||||
self._reload()
|
self._reload()
|
||||||
return True
|
return True
|
||||||
|
|
||||||
broker.ask("_reload_blocking", False).get()
|
broker.ask("reload", False, True).get()
|
||||||
return True
|
return True
|
||||||
|
|
||||||
def _map_volume_idp(
|
def _map_volume_idp(
|
||||||
@@ -1381,7 +1390,7 @@ class AuthSrv(object):
|
|||||||
flags[name] = True
|
flags[name] = True
|
||||||
return
|
return
|
||||||
|
|
||||||
zs = "mtp on403 on404 xbu xau xiu xbr xar xbd xad xm xban"
|
zs = "mtp on403 on404 xbu xau xiu xbc xac xbr xar xbd xad xm xban"
|
||||||
if name not in zs.split():
|
if name not in zs.split():
|
||||||
if value is True:
|
if value is True:
|
||||||
t = "└─add volflag [{}] = {} ({})"
|
t = "└─add volflag [{}] = {} ({})"
|
||||||
@@ -1529,10 +1538,11 @@ class AuthSrv(object):
|
|||||||
|
|
||||||
assert vfs # type: ignore
|
assert vfs # type: ignore
|
||||||
vfs.all_vols = {}
|
vfs.all_vols = {}
|
||||||
|
vfs.all_nodes = {}
|
||||||
vfs.all_aps = []
|
vfs.all_aps = []
|
||||||
vfs.all_vps = []
|
vfs.all_vps = []
|
||||||
vfs.get_all_vols(vfs.all_vols, vfs.all_aps, vfs.all_vps)
|
vfs.get_all_vols(vfs.all_vols, vfs.all_nodes, vfs.all_aps, vfs.all_vps)
|
||||||
for vol in vfs.all_vols.values():
|
for vol in vfs.all_nodes.values():
|
||||||
vol.all_aps.sort(key=lambda x: len(x[0]), reverse=True)
|
vol.all_aps.sort(key=lambda x: len(x[0]), reverse=True)
|
||||||
vol.all_vps.sort(key=lambda x: len(x[0]), reverse=True)
|
vol.all_vps.sort(key=lambda x: len(x[0]), reverse=True)
|
||||||
vol.root = vfs
|
vol.root = vfs
|
||||||
@@ -1583,7 +1593,7 @@ class AuthSrv(object):
|
|||||||
|
|
||||||
vfs.nodes[shr] = vfs.all_vols[shr] = shv
|
vfs.nodes[shr] = vfs.all_vols[shr] = shv
|
||||||
for vol in shv.nodes.values():
|
for vol in shv.nodes.values():
|
||||||
vfs.all_vols[vol.vpath] = vol
|
vfs.all_vols[vol.vpath] = vfs.all_nodes[vol.vpath] = vol
|
||||||
vol.get_dbv = vol._get_share_src
|
vol.get_dbv = vol._get_share_src
|
||||||
vol.ls = vol._ls_nope
|
vol.ls = vol._ls_nope
|
||||||
|
|
||||||
@@ -1726,7 +1736,19 @@ class AuthSrv(object):
|
|||||||
|
|
||||||
self.log("\n\n".join(ta) + "\n", c=3)
|
self.log("\n\n".join(ta) + "\n", c=3)
|
||||||
|
|
||||||
vfs.histtab = {zv.realpath: zv.histpath for zv in vfs.all_vols.values()}
|
rhisttab = {}
|
||||||
|
vfs.histtab = {}
|
||||||
|
for zv in vfs.all_vols.values():
|
||||||
|
histp = zv.histpath
|
||||||
|
is_shr = shr and zv.vpath.split("/")[0] == shr
|
||||||
|
if histp and not is_shr and histp in rhisttab:
|
||||||
|
zv2 = rhisttab[histp]
|
||||||
|
t = "invalid config; multiple volumes share the same histpath (database location):\n histpath: %s\n volume 1: /%s [%s]\n volume 2: %s [%s]"
|
||||||
|
t = t % (histp, zv2.vpath, zv2.realpath, zv.vpath, zv.realpath)
|
||||||
|
self.log(t, 1)
|
||||||
|
raise Exception(t)
|
||||||
|
rhisttab[histp] = zv
|
||||||
|
vfs.histtab[zv.realpath] = histp
|
||||||
|
|
||||||
for vol in vfs.all_vols.values():
|
for vol in vfs.all_vols.values():
|
||||||
lim = Lim(self.log_func)
|
lim = Lim(self.log_func)
|
||||||
@@ -1785,12 +1807,12 @@ class AuthSrv(object):
|
|||||||
vol.lim = lim
|
vol.lim = lim
|
||||||
|
|
||||||
if self.args.no_robots:
|
if self.args.no_robots:
|
||||||
for vol in vfs.all_vols.values():
|
for vol in vfs.all_nodes.values():
|
||||||
# volflag "robots" overrides global "norobots", allowing indexing by search engines for this vol
|
# volflag "robots" overrides global "norobots", allowing indexing by search engines for this vol
|
||||||
if not vol.flags.get("robots"):
|
if not vol.flags.get("robots"):
|
||||||
vol.flags["norobots"] = True
|
vol.flags["norobots"] = True
|
||||||
|
|
||||||
for vol in vfs.all_vols.values():
|
for vol in vfs.all_nodes.values():
|
||||||
if self.args.no_vthumb:
|
if self.args.no_vthumb:
|
||||||
vol.flags["dvthumb"] = True
|
vol.flags["dvthumb"] = True
|
||||||
if self.args.no_athumb:
|
if self.args.no_athumb:
|
||||||
@@ -1802,7 +1824,7 @@ class AuthSrv(object):
|
|||||||
vol.flags["dithumb"] = True
|
vol.flags["dithumb"] = True
|
||||||
|
|
||||||
have_fk = False
|
have_fk = False
|
||||||
for vol in vfs.all_vols.values():
|
for vol in vfs.all_nodes.values():
|
||||||
fk = vol.flags.get("fk")
|
fk = vol.flags.get("fk")
|
||||||
fka = vol.flags.get("fka")
|
fka = vol.flags.get("fka")
|
||||||
if fka and not fk:
|
if fka and not fk:
|
||||||
@@ -1834,7 +1856,7 @@ class AuthSrv(object):
|
|||||||
zs = os.path.join(E.cfg, "fk-salt.txt")
|
zs = os.path.join(E.cfg, "fk-salt.txt")
|
||||||
self.log(t % (fk_len, 16, zs), 3)
|
self.log(t % (fk_len, 16, zs), 3)
|
||||||
|
|
||||||
for vol in vfs.all_vols.values():
|
for vol in vfs.all_nodes.values():
|
||||||
if "pk" in vol.flags and "gz" not in vol.flags and "xz" not in vol.flags:
|
if "pk" in vol.flags and "gz" not in vol.flags and "xz" not in vol.flags:
|
||||||
vol.flags["gz"] = False # def.pk
|
vol.flags["gz"] = False # def.pk
|
||||||
|
|
||||||
@@ -1845,7 +1867,7 @@ class AuthSrv(object):
|
|||||||
|
|
||||||
all_mte = {}
|
all_mte = {}
|
||||||
errors = False
|
errors = False
|
||||||
for vol in vfs.all_vols.values():
|
for vol in vfs.all_nodes.values():
|
||||||
if (self.args.e2ds and vol.axs.uwrite) or self.args.e2dsa:
|
if (self.args.e2ds and vol.axs.uwrite) or self.args.e2dsa:
|
||||||
vol.flags["e2ds"] = True
|
vol.flags["e2ds"] = True
|
||||||
|
|
||||||
@@ -1936,7 +1958,7 @@ class AuthSrv(object):
|
|||||||
vol.flags[k] = odfusion(getattr(self.args, k), vol.flags[k])
|
vol.flags[k] = odfusion(getattr(self.args, k), vol.flags[k])
|
||||||
|
|
||||||
# append additive args from argv to volflags
|
# append additive args from argv to volflags
|
||||||
hooks = "xbu xau xiu xbr xar xbd xad xm xban".split()
|
hooks = "xbu xau xiu xbc xac xbr xar xbd xad xm xban".split()
|
||||||
for name in "mtp on404 on403".split() + hooks:
|
for name in "mtp on404 on403".split() + hooks:
|
||||||
self._read_volflag(vol.flags, name, getattr(self.args, name), True)
|
self._read_volflag(vol.flags, name, getattr(self.args, name), True)
|
||||||
|
|
||||||
@@ -2063,7 +2085,7 @@ class AuthSrv(object):
|
|||||||
errors = True
|
errors = True
|
||||||
|
|
||||||
have_daw = False
|
have_daw = False
|
||||||
for vol in vfs.all_vols.values():
|
for vol in vfs.all_nodes.values():
|
||||||
daw = vol.flags.get("daw") or self.args.daw
|
daw = vol.flags.get("daw") or self.args.daw
|
||||||
if daw:
|
if daw:
|
||||||
vol.flags["daw"] = True
|
vol.flags["daw"] = True
|
||||||
@@ -2078,13 +2100,12 @@ class AuthSrv(object):
|
|||||||
self.log("--smb can only be used when --ah-alg is none", 1)
|
self.log("--smb can only be used when --ah-alg is none", 1)
|
||||||
errors = True
|
errors = True
|
||||||
|
|
||||||
for vol in vfs.all_vols.values():
|
for vol in vfs.all_nodes.values():
|
||||||
for k in list(vol.flags.keys()):
|
for k in list(vol.flags.keys()):
|
||||||
if re.match("^-[^-]+$", k):
|
if re.match("^-[^-]+$", k):
|
||||||
vol.flags.pop(k[1:], None)
|
vol.flags.pop(k[1:], None)
|
||||||
vol.flags.pop(k)
|
vol.flags.pop(k)
|
||||||
|
|
||||||
for vol in vfs.all_vols.values():
|
|
||||||
if vol.flags.get("dots"):
|
if vol.flags.get("dots"):
|
||||||
for name in vol.axs.uread:
|
for name in vol.axs.uread:
|
||||||
vol.axs.udot.add(name)
|
vol.axs.udot.add(name)
|
||||||
@@ -2226,6 +2247,11 @@ class AuthSrv(object):
|
|||||||
for x, y in vfs.all_vols.items()
|
for x, y in vfs.all_vols.items()
|
||||||
if x != shr and not x.startswith(shrs)
|
if x != shr and not x.startswith(shrs)
|
||||||
}
|
}
|
||||||
|
vfs.all_nodes = {
|
||||||
|
x: y
|
||||||
|
for x, y in vfs.all_nodes.items()
|
||||||
|
if x != shr and not x.startswith(shrs)
|
||||||
|
}
|
||||||
|
|
||||||
assert db and cur and cur2 and shv # type: ignore
|
assert db and cur and cur2 and shv # type: ignore
|
||||||
for row in cur.execute("select * from sh"):
|
for row in cur.execute("select * from sh"):
|
||||||
@@ -2387,7 +2413,7 @@ class AuthSrv(object):
|
|||||||
self._reload()
|
self._reload()
|
||||||
return True, "new password OK"
|
return True, "new password OK"
|
||||||
|
|
||||||
broker.ask("_reload_blocking", False, False).get()
|
broker.ask("reload", False, False).get()
|
||||||
return True, "new password OK"
|
return True, "new password OK"
|
||||||
|
|
||||||
def setup_chpw(self, acct: dict[str, str]) -> None:
|
def setup_chpw(self, acct: dict[str, str]) -> None:
|
||||||
@@ -2639,7 +2665,7 @@ class AuthSrv(object):
|
|||||||
]
|
]
|
||||||
|
|
||||||
csv = set("i p th_covers zm_on zm_off zs_on zs_off".split())
|
csv = set("i p th_covers zm_on zm_off zs_on zs_off".split())
|
||||||
zs = "c ihead ohead mtm mtp on403 on404 xad xar xau xiu xban xbd xbr xbu xm"
|
zs = "c ihead ohead mtm mtp on403 on404 xac xad xar xau xiu xban xbc xbd xbr xbu xm"
|
||||||
lst = set(zs.split())
|
lst = set(zs.split())
|
||||||
askip = set("a v c vc cgen exp_lg exp_md theme".split())
|
askip = set("a v c vc cgen exp_lg exp_md theme".split())
|
||||||
fskip = set("exp_lg exp_md mv_re_r mv_re_t rm_re_r rm_re_t".split())
|
fskip = set("exp_lg exp_md mv_re_r mv_re_t rm_re_r rm_re_t".split())
|
||||||
|
|||||||
@@ -43,6 +43,9 @@ class BrokerMp(object):
|
|||||||
self.procs = []
|
self.procs = []
|
||||||
self.mutex = threading.Lock()
|
self.mutex = threading.Lock()
|
||||||
|
|
||||||
|
self.retpend: dict[int, Any] = {}
|
||||||
|
self.retpend_mutex = threading.Lock()
|
||||||
|
|
||||||
self.num_workers = self.args.j or CORES
|
self.num_workers = self.args.j or CORES
|
||||||
self.log("broker", "booting {} subprocesses".format(self.num_workers))
|
self.log("broker", "booting {} subprocesses".format(self.num_workers))
|
||||||
for n in range(1, self.num_workers + 1):
|
for n in range(1, self.num_workers + 1):
|
||||||
@@ -54,6 +57,8 @@ class BrokerMp(object):
|
|||||||
self.procs.append(proc)
|
self.procs.append(proc)
|
||||||
proc.start()
|
proc.start()
|
||||||
|
|
||||||
|
Daemon(self.periodic, "mp-periodic")
|
||||||
|
|
||||||
def shutdown(self) -> None:
|
def shutdown(self) -> None:
|
||||||
self.log("broker", "shutting down")
|
self.log("broker", "shutting down")
|
||||||
for n, proc in enumerate(self.procs):
|
for n, proc in enumerate(self.procs):
|
||||||
@@ -90,8 +95,10 @@ class BrokerMp(object):
|
|||||||
self.log(*args)
|
self.log(*args)
|
||||||
|
|
||||||
elif dest == "retq":
|
elif dest == "retq":
|
||||||
# response from previous ipc call
|
with self.retpend_mutex:
|
||||||
raise Exception("invalid broker_mp usage")
|
retq = self.retpend.pop(retq_id)
|
||||||
|
|
||||||
|
retq.put(args[0])
|
||||||
|
|
||||||
else:
|
else:
|
||||||
# new ipc invoking managed service in hub
|
# new ipc invoking managed service in hub
|
||||||
@@ -109,7 +116,6 @@ class BrokerMp(object):
|
|||||||
proc.q_pend.put((retq_id, "retq", rv))
|
proc.q_pend.put((retq_id, "retq", rv))
|
||||||
|
|
||||||
def ask(self, dest: str, *args: Any) -> Union[ExceptionalQueue, NotExQueue]:
|
def ask(self, dest: str, *args: Any) -> Union[ExceptionalQueue, NotExQueue]:
|
||||||
|
|
||||||
# new non-ipc invoking managed service in hub
|
# new non-ipc invoking managed service in hub
|
||||||
obj = self.hub
|
obj = self.hub
|
||||||
for node in dest.split("."):
|
for node in dest.split("."):
|
||||||
@@ -121,17 +127,30 @@ class BrokerMp(object):
|
|||||||
retq.put(rv)
|
retq.put(rv)
|
||||||
return retq
|
return retq
|
||||||
|
|
||||||
|
def wask(self, dest: str, *args: Any) -> list[Union[ExceptionalQueue, NotExQueue]]:
|
||||||
|
# call from hub to workers
|
||||||
|
ret = []
|
||||||
|
for p in self.procs:
|
||||||
|
retq = ExceptionalQueue(1)
|
||||||
|
retq_id = id(retq)
|
||||||
|
with self.retpend_mutex:
|
||||||
|
self.retpend[retq_id] = retq
|
||||||
|
|
||||||
|
p.q_pend.put((retq_id, dest, list(args)))
|
||||||
|
ret.append(retq)
|
||||||
|
return ret
|
||||||
|
|
||||||
def say(self, dest: str, *args: Any) -> None:
|
def say(self, dest: str, *args: Any) -> None:
|
||||||
"""
|
"""
|
||||||
send message to non-hub component in other process,
|
send message to non-hub component in other process,
|
||||||
returns a Queue object which eventually contains the response if want_retval
|
returns a Queue object which eventually contains the response if want_retval
|
||||||
(not-impl here since nothing uses it yet)
|
(not-impl here since nothing uses it yet)
|
||||||
"""
|
"""
|
||||||
if dest == "listen":
|
if dest == "httpsrv.listen":
|
||||||
for p in self.procs:
|
for p in self.procs:
|
||||||
p.q_pend.put((0, dest, [args[0], len(self.procs)]))
|
p.q_pend.put((0, dest, [args[0], len(self.procs)]))
|
||||||
|
|
||||||
elif dest == "set_netdevs":
|
elif dest == "httpsrv.set_netdevs":
|
||||||
for p in self.procs:
|
for p in self.procs:
|
||||||
p.q_pend.put((0, dest, list(args)))
|
p.q_pend.put((0, dest, list(args)))
|
||||||
|
|
||||||
@@ -140,3 +159,19 @@ class BrokerMp(object):
|
|||||||
|
|
||||||
else:
|
else:
|
||||||
raise Exception("what is " + str(dest))
|
raise Exception("what is " + str(dest))
|
||||||
|
|
||||||
|
def periodic(self) -> None:
|
||||||
|
while True:
|
||||||
|
time.sleep(1)
|
||||||
|
|
||||||
|
tdli = {}
|
||||||
|
tdls = {}
|
||||||
|
qs = self.wask("httpsrv.read_dls")
|
||||||
|
for q in qs:
|
||||||
|
qr = q.get()
|
||||||
|
dli, dls = qr
|
||||||
|
tdli.update(dli)
|
||||||
|
tdls.update(dls)
|
||||||
|
tdl = (tdli, tdls)
|
||||||
|
for p in self.procs:
|
||||||
|
p.q_pend.put((0, "httpsrv.write_dls", tdl))
|
||||||
|
|||||||
@@ -82,37 +82,38 @@ class MpWorker(BrokerCli):
|
|||||||
while True:
|
while True:
|
||||||
retq_id, dest, args = self.q_pend.get()
|
retq_id, dest, args = self.q_pend.get()
|
||||||
|
|
||||||
# self.logw("work: [{}]".format(d[0]))
|
if dest == "retq":
|
||||||
|
# response from previous ipc call
|
||||||
|
with self.retpend_mutex:
|
||||||
|
retq = self.retpend.pop(retq_id)
|
||||||
|
|
||||||
|
retq.put(args)
|
||||||
|
continue
|
||||||
|
|
||||||
if dest == "shutdown":
|
if dest == "shutdown":
|
||||||
self.httpsrv.shutdown()
|
self.httpsrv.shutdown()
|
||||||
self.logw("ok bye")
|
self.logw("ok bye")
|
||||||
sys.exit(0)
|
sys.exit(0)
|
||||||
return
|
return
|
||||||
|
|
||||||
elif dest == "reload":
|
if dest == "reload":
|
||||||
self.logw("mpw.asrv reloading")
|
self.logw("mpw.asrv reloading")
|
||||||
self.asrv.reload()
|
self.asrv.reload()
|
||||||
self.logw("mpw.asrv reloaded")
|
self.logw("mpw.asrv reloaded")
|
||||||
|
continue
|
||||||
|
|
||||||
elif dest == "reload_sessions":
|
if dest == "reload_sessions":
|
||||||
with self.asrv.mutex:
|
with self.asrv.mutex:
|
||||||
self.asrv.load_sessions()
|
self.asrv.load_sessions()
|
||||||
|
continue
|
||||||
|
|
||||||
elif dest == "listen":
|
obj = self
|
||||||
self.httpsrv.listen(args[0], args[1])
|
for node in dest.split("."):
|
||||||
|
obj = getattr(obj, node)
|
||||||
|
|
||||||
elif dest == "set_netdevs":
|
rv = obj(*args) # type: ignore
|
||||||
self.httpsrv.set_netdevs(args[0])
|
if retq_id:
|
||||||
|
self.say("retq", rv, retq_id=retq_id)
|
||||||
elif dest == "retq":
|
|
||||||
# response from previous ipc call
|
|
||||||
with self.retpend_mutex:
|
|
||||||
retq = self.retpend.pop(retq_id)
|
|
||||||
|
|
||||||
retq.put(args)
|
|
||||||
|
|
||||||
else:
|
|
||||||
raise Exception("what is " + str(dest))
|
|
||||||
|
|
||||||
def ask(self, dest: str, *args: Any) -> Union[ExceptionalQueue, NotExQueue]:
|
def ask(self, dest: str, *args: Any) -> Union[ExceptionalQueue, NotExQueue]:
|
||||||
retq = ExceptionalQueue(1)
|
retq = ExceptionalQueue(1)
|
||||||
@@ -123,5 +124,5 @@ class MpWorker(BrokerCli):
|
|||||||
self.q_yield.put((retq_id, dest, list(args)))
|
self.q_yield.put((retq_id, dest, list(args)))
|
||||||
return retq
|
return retq
|
||||||
|
|
||||||
def say(self, dest: str, *args: Any) -> None:
|
def say(self, dest: str, *args: Any, retq_id=0) -> None:
|
||||||
self.q_yield.put((0, dest, list(args)))
|
self.q_yield.put((retq_id, dest, list(args)))
|
||||||
|
|||||||
@@ -53,11 +53,11 @@ class BrokerThr(BrokerCli):
|
|||||||
return NotExQueue(obj(*args)) # type: ignore
|
return NotExQueue(obj(*args)) # type: ignore
|
||||||
|
|
||||||
def say(self, dest: str, *args: Any) -> None:
|
def say(self, dest: str, *args: Any) -> None:
|
||||||
if dest == "listen":
|
if dest == "httpsrv.listen":
|
||||||
self.httpsrv.listen(args[0], 1)
|
self.httpsrv.listen(args[0], 1)
|
||||||
return
|
return
|
||||||
|
|
||||||
if dest == "set_netdevs":
|
if dest == "httpsrv.set_netdevs":
|
||||||
self.httpsrv.set_netdevs(args[0])
|
self.httpsrv.set_netdevs(args[0])
|
||||||
return
|
return
|
||||||
|
|
||||||
|
|||||||
@@ -103,10 +103,12 @@ def vf_cmap() -> dict[str, str]:
|
|||||||
"mte",
|
"mte",
|
||||||
"mth",
|
"mth",
|
||||||
"mtp",
|
"mtp",
|
||||||
|
"xac",
|
||||||
"xad",
|
"xad",
|
||||||
"xar",
|
"xar",
|
||||||
"xau",
|
"xau",
|
||||||
"xban",
|
"xban",
|
||||||
|
"xbc",
|
||||||
"xbd",
|
"xbd",
|
||||||
"xbr",
|
"xbr",
|
||||||
"xbu",
|
"xbu",
|
||||||
@@ -212,6 +214,8 @@ flagcats = {
|
|||||||
"xbu=CMD": "execute CMD before a file upload starts",
|
"xbu=CMD": "execute CMD before a file upload starts",
|
||||||
"xau=CMD": "execute CMD after a file upload finishes",
|
"xau=CMD": "execute CMD after a file upload finishes",
|
||||||
"xiu=CMD": "execute CMD after all uploads finish and volume is idle",
|
"xiu=CMD": "execute CMD after all uploads finish and volume is idle",
|
||||||
|
"xbc=CMD": "execute CMD before a file copy",
|
||||||
|
"xac=CMD": "execute CMD after a file copy",
|
||||||
"xbr=CMD": "execute CMD before a file rename/move",
|
"xbr=CMD": "execute CMD before a file rename/move",
|
||||||
"xar=CMD": "execute CMD after a file rename/move",
|
"xar=CMD": "execute CMD after a file rename/move",
|
||||||
"xbd=CMD": "execute CMD before a file delete",
|
"xbd=CMD": "execute CMD before a file delete",
|
||||||
|
|||||||
@@ -296,6 +296,7 @@ class FtpFs(AbstractedFS):
|
|||||||
self.uname,
|
self.uname,
|
||||||
not self.args.no_scandir,
|
not self.args.no_scandir,
|
||||||
[[True, False], [False, True]],
|
[[True, False], [False, True]],
|
||||||
|
throw=True,
|
||||||
)
|
)
|
||||||
vfs_ls = [x[0] for x in vfs_ls1]
|
vfs_ls = [x[0] for x in vfs_ls1]
|
||||||
vfs_ls.extend(vfs_virt.keys())
|
vfs_ls.extend(vfs_virt.keys())
|
||||||
|
|||||||
@@ -186,6 +186,7 @@ class HttpCli(object):
|
|||||||
self.rem = " "
|
self.rem = " "
|
||||||
self.vpath = " "
|
self.vpath = " "
|
||||||
self.vpaths = " "
|
self.vpaths = " "
|
||||||
|
self.dl_id = ""
|
||||||
self.gctx = " " # additional context for garda
|
self.gctx = " " # additional context for garda
|
||||||
self.trailing_slash = True
|
self.trailing_slash = True
|
||||||
self.uname = " "
|
self.uname = " "
|
||||||
@@ -637,7 +638,7 @@ class HttpCli(object):
|
|||||||
avn.can_access("", self.uname) if avn else [False] * 8
|
avn.can_access("", self.uname) if avn else [False] * 8
|
||||||
)
|
)
|
||||||
self.avn = avn
|
self.avn = avn
|
||||||
self.vn = vn
|
self.vn = vn # note: do not dbv due to walk/zipgen
|
||||||
self.rem = rem
|
self.rem = rem
|
||||||
|
|
||||||
self.s.settimeout(self.args.s_tbody or None)
|
self.s.settimeout(self.args.s_tbody or None)
|
||||||
@@ -726,6 +727,11 @@ class HttpCli(object):
|
|||||||
except Pebkac:
|
except Pebkac:
|
||||||
return False
|
return False
|
||||||
|
|
||||||
|
finally:
|
||||||
|
if self.dl_id:
|
||||||
|
self.conn.hsrv.dli.pop(self.dl_id, None)
|
||||||
|
self.conn.hsrv.dls.pop(self.dl_id, None)
|
||||||
|
|
||||||
def dip(self) -> str:
|
def dip(self) -> str:
|
||||||
if self.args.plain_ip:
|
if self.args.plain_ip:
|
||||||
return self.ip.replace(":", ".")
|
return self.ip.replace(":", ".")
|
||||||
@@ -1196,6 +1202,9 @@ class HttpCli(object):
|
|||||||
if "move" in self.uparam:
|
if "move" in self.uparam:
|
||||||
return self.handle_mv()
|
return self.handle_mv()
|
||||||
|
|
||||||
|
if "copy" in self.uparam:
|
||||||
|
return self.handle_cp()
|
||||||
|
|
||||||
if not self.vpath and self.ouparam:
|
if not self.vpath and self.ouparam:
|
||||||
if "reload" in self.uparam:
|
if "reload" in self.uparam:
|
||||||
return self.handle_reload()
|
return self.handle_reload()
|
||||||
@@ -1215,6 +1224,9 @@ class HttpCli(object):
|
|||||||
if "shares" in self.uparam:
|
if "shares" in self.uparam:
|
||||||
return self.tx_shares()
|
return self.tx_shares()
|
||||||
|
|
||||||
|
if "dls" in self.uparam:
|
||||||
|
return self.tx_dls()
|
||||||
|
|
||||||
if "h" in self.uparam:
|
if "h" in self.uparam:
|
||||||
return self.tx_mounts()
|
return self.tx_mounts()
|
||||||
|
|
||||||
@@ -1450,6 +1462,7 @@ class HttpCli(object):
|
|||||||
not self.args.no_scandir,
|
not self.args.no_scandir,
|
||||||
[[True, False]],
|
[[True, False]],
|
||||||
lstat="davrt" not in vn.flags,
|
lstat="davrt" not in vn.flags,
|
||||||
|
throw=True,
|
||||||
)
|
)
|
||||||
if not self.can_read:
|
if not self.can_read:
|
||||||
vfs_ls = []
|
vfs_ls = []
|
||||||
@@ -1790,6 +1803,9 @@ class HttpCli(object):
|
|||||||
if "move" in self.uparam:
|
if "move" in self.uparam:
|
||||||
return self.handle_mv()
|
return self.handle_mv()
|
||||||
|
|
||||||
|
if "copy" in self.uparam:
|
||||||
|
return self.handle_cp()
|
||||||
|
|
||||||
if "delete" in self.uparam:
|
if "delete" in self.uparam:
|
||||||
return self.handle_rm([])
|
return self.handle_rm([])
|
||||||
|
|
||||||
@@ -3683,6 +3699,8 @@ class HttpCli(object):
|
|||||||
self.args.s_wr_sz,
|
self.args.s_wr_sz,
|
||||||
self.args.s_wr_slp,
|
self.args.s_wr_slp,
|
||||||
not self.args.no_poll,
|
not self.args.no_poll,
|
||||||
|
{},
|
||||||
|
"",
|
||||||
)
|
)
|
||||||
res.close()
|
res.close()
|
||||||
|
|
||||||
@@ -3729,6 +3747,7 @@ class HttpCli(object):
|
|||||||
editions: dict[str, tuple[str, int]] = {}
|
editions: dict[str, tuple[str, int]] = {}
|
||||||
for ext in ("", ".gz"):
|
for ext in ("", ".gz"):
|
||||||
if ptop is not None:
|
if ptop is not None:
|
||||||
|
assert job and ap_data # type: ignore # !rm
|
||||||
sz = job["size"]
|
sz = job["size"]
|
||||||
file_ts = job["lmod"]
|
file_ts = job["lmod"]
|
||||||
editions["plain"] = (ap_data, sz)
|
editions["plain"] = (ap_data, sz)
|
||||||
@@ -3897,7 +3916,21 @@ class HttpCli(object):
|
|||||||
self.send_headers(length=upper - lower, status=status, mime=mime)
|
self.send_headers(length=upper - lower, status=status, mime=mime)
|
||||||
return True
|
return True
|
||||||
|
|
||||||
|
dls = self.conn.hsrv.dls
|
||||||
|
if upper - lower > 0x400000: # 4m
|
||||||
|
now = time.time()
|
||||||
|
self.dl_id = "%s:%s" % (self.ip, self.addr[1])
|
||||||
|
dls[self.dl_id] = (now, 0)
|
||||||
|
self.conn.hsrv.dli[self.dl_id] = (
|
||||||
|
now,
|
||||||
|
upper - lower,
|
||||||
|
self.vn,
|
||||||
|
self.vpath,
|
||||||
|
self.uname,
|
||||||
|
)
|
||||||
|
|
||||||
if ptop is not None:
|
if ptop is not None:
|
||||||
|
assert job and ap_data # type: ignore # !rm
|
||||||
return self.tx_pipe(
|
return self.tx_pipe(
|
||||||
ptop, req_path, ap_data, job, lower, upper, status, mime, logmsg
|
ptop, req_path, ap_data, job, lower, upper, status, mime, logmsg
|
||||||
)
|
)
|
||||||
@@ -3916,6 +3949,8 @@ class HttpCli(object):
|
|||||||
self.args.s_wr_sz,
|
self.args.s_wr_sz,
|
||||||
self.args.s_wr_slp,
|
self.args.s_wr_slp,
|
||||||
not self.args.no_poll,
|
not self.args.no_poll,
|
||||||
|
dls,
|
||||||
|
self.dl_id,
|
||||||
)
|
)
|
||||||
|
|
||||||
if remains > 0:
|
if remains > 0:
|
||||||
@@ -4066,6 +4101,8 @@ class HttpCli(object):
|
|||||||
wr_sz,
|
wr_sz,
|
||||||
wr_slp,
|
wr_slp,
|
||||||
not self.args.no_poll,
|
not self.args.no_poll,
|
||||||
|
self.conn.hsrv.dls,
|
||||||
|
self.dl_id,
|
||||||
)
|
)
|
||||||
|
|
||||||
spd = self._spd((upper - lower) - remains)
|
spd = self._spd((upper - lower) - remains)
|
||||||
@@ -4151,6 +4188,18 @@ class HttpCli(object):
|
|||||||
self.log("transcoding to [{}]".format(cfmt))
|
self.log("transcoding to [{}]".format(cfmt))
|
||||||
fgen = gfilter(fgen, self.thumbcli, self.uname, vpath, cfmt)
|
fgen = gfilter(fgen, self.thumbcli, self.uname, vpath, cfmt)
|
||||||
|
|
||||||
|
now = time.time()
|
||||||
|
self.dl_id = "%s:%s" % (self.ip, self.addr[1])
|
||||||
|
self.conn.hsrv.dli[self.dl_id] = (
|
||||||
|
now,
|
||||||
|
0,
|
||||||
|
self.vn,
|
||||||
|
"%s :%s" % (self.vpath, ext),
|
||||||
|
self.uname,
|
||||||
|
)
|
||||||
|
dls = self.conn.hsrv.dls
|
||||||
|
dls[self.dl_id] = (time.time(), 0)
|
||||||
|
|
||||||
bgen = packer(
|
bgen = packer(
|
||||||
self.log,
|
self.log,
|
||||||
self.asrv,
|
self.asrv,
|
||||||
@@ -4159,6 +4208,7 @@ class HttpCli(object):
|
|||||||
pre_crc="crc" in uarg,
|
pre_crc="crc" in uarg,
|
||||||
cmp=uarg if cancmp or uarg == "pax" else "",
|
cmp=uarg if cancmp or uarg == "pax" else "",
|
||||||
)
|
)
|
||||||
|
n = 0
|
||||||
bsent = 0
|
bsent = 0
|
||||||
for buf in bgen.gen():
|
for buf in bgen.gen():
|
||||||
if not buf:
|
if not buf:
|
||||||
@@ -4172,6 +4222,11 @@ class HttpCli(object):
|
|||||||
bgen.stop()
|
bgen.stop()
|
||||||
break
|
break
|
||||||
|
|
||||||
|
n += 1
|
||||||
|
if n >= 4:
|
||||||
|
n = 0
|
||||||
|
dls[self.dl_id] = (time.time(), bsent)
|
||||||
|
|
||||||
spd = self._spd(bsent)
|
spd = self._spd(bsent)
|
||||||
self.log("{}, {}".format(logmsg, spd))
|
self.log("{}, {}".format(logmsg, spd))
|
||||||
return True
|
return True
|
||||||
@@ -4429,6 +4484,32 @@ class HttpCli(object):
|
|||||||
|
|
||||||
assert vstate.items and vs # type: ignore # !rm
|
assert vstate.items and vs # type: ignore # !rm
|
||||||
|
|
||||||
|
dls = dl_list = []
|
||||||
|
if self.conn.hsrv.tdls:
|
||||||
|
zi = self.args.dl_list
|
||||||
|
if zi == 2 or (zi == 1 and self.avol):
|
||||||
|
dl_list = self.get_dls()
|
||||||
|
for t0, t1, sent, sz, vp, dl_id, uname in dl_list:
|
||||||
|
rem = sz - sent
|
||||||
|
td = max(0.1, now - t0)
|
||||||
|
rd, fn = vsplit(vp)
|
||||||
|
if not rd:
|
||||||
|
rd = "/"
|
||||||
|
erd = quotep(rd)
|
||||||
|
rds = rd.replace("/", " / ")
|
||||||
|
spd = humansize(sent / td, True) + "/s"
|
||||||
|
hsent = humansize(sent, True)
|
||||||
|
idle = s2hms(now - t1, True)
|
||||||
|
usr = "%s @%s" % (dl_id, uname) if dl_id else uname
|
||||||
|
if sz and sent and td:
|
||||||
|
eta = s2hms((sz - sent) / (sent / td), True)
|
||||||
|
perc = int(100 * sent / sz)
|
||||||
|
else:
|
||||||
|
eta = perc = "--"
|
||||||
|
|
||||||
|
fn = html_escape(fn) if fn else self.conn.hsrv.iiam
|
||||||
|
dls.append((perc, hsent, spd, eta, idle, usr, erd, rds, fn))
|
||||||
|
|
||||||
fmt = self.uparam.get("ls", "")
|
fmt = self.uparam.get("ls", "")
|
||||||
if not fmt and (self.ua.startswith("curl/") or self.ua.startswith("fetch")):
|
if not fmt and (self.ua.startswith("curl/") or self.ua.startswith("fetch")):
|
||||||
fmt = "v"
|
fmt = "v"
|
||||||
@@ -4450,6 +4531,12 @@ class HttpCli(object):
|
|||||||
txt += "\n%s" % (", ".join((str(x) for x in zt)),)
|
txt += "\n%s" % (", ".join((str(x) for x in zt)),)
|
||||||
txt += "\n"
|
txt += "\n"
|
||||||
|
|
||||||
|
if dls:
|
||||||
|
txt += "\n\nactive downloads:"
|
||||||
|
for zt in dls:
|
||||||
|
txt += "\n%s" % (", ".join((str(x) for x in zt)),)
|
||||||
|
txt += "\n"
|
||||||
|
|
||||||
if rvol:
|
if rvol:
|
||||||
txt += "\nyou can browse:"
|
txt += "\nyou can browse:"
|
||||||
for v in rvol:
|
for v in rvol:
|
||||||
@@ -4473,6 +4560,7 @@ class HttpCli(object):
|
|||||||
avol=avol,
|
avol=avol,
|
||||||
in_shr=self.args.shr and self.vpath.startswith(self.args.shr1),
|
in_shr=self.args.shr and self.vpath.startswith(self.args.shr1),
|
||||||
vstate=vstate,
|
vstate=vstate,
|
||||||
|
dls=dls,
|
||||||
ups=ups,
|
ups=ups,
|
||||||
scanning=vs["scanning"],
|
scanning=vs["scanning"],
|
||||||
hashq=vs["hashq"],
|
hashq=vs["hashq"],
|
||||||
@@ -4535,8 +4623,14 @@ class HttpCli(object):
|
|||||||
|
|
||||||
t = t.format(self.args.SR)
|
t = t.format(self.args.SR)
|
||||||
qv = quotep(self.vpaths) + self.ourlq()
|
qv = quotep(self.vpaths) + self.ourlq()
|
||||||
in_shr = self.args.shr and self.vpath.startswith(self.args.shr1)
|
html = self.j2s(
|
||||||
html = self.j2s("splash", this=self, qvpath=qv, in_shr=in_shr, msg=t)
|
"splash",
|
||||||
|
this=self,
|
||||||
|
qvpath=qv,
|
||||||
|
msg=t,
|
||||||
|
in_shr=self.args.shr and self.vpath.startswith(self.args.shr1),
|
||||||
|
ahttps="" if self.is_https else "https://" + self.host + self.req,
|
||||||
|
)
|
||||||
self.reply(html.encode("utf-8"), status=rc)
|
self.reply(html.encode("utf-8"), status=rc)
|
||||||
return True
|
return True
|
||||||
|
|
||||||
@@ -4584,7 +4678,7 @@ class HttpCli(object):
|
|||||||
if self.args.no_reload:
|
if self.args.no_reload:
|
||||||
raise Pebkac(403, "the reload feature is disabled in server config")
|
raise Pebkac(403, "the reload feature is disabled in server config")
|
||||||
|
|
||||||
x = self.conn.hsrv.broker.ask("reload")
|
x = self.conn.hsrv.broker.ask("reload", True, True)
|
||||||
return self.redirect("", "?h", x.get(), "return to", False)
|
return self.redirect("", "?h", x.get(), "return to", False)
|
||||||
|
|
||||||
def tx_stack(self) -> bool:
|
def tx_stack(self) -> bool:
|
||||||
@@ -4687,6 +4781,40 @@ class HttpCli(object):
|
|||||||
ret["a"] = dirs
|
ret["a"] = dirs
|
||||||
return ret
|
return ret
|
||||||
|
|
||||||
|
def get_dls(self) -> list[list[Any]]:
|
||||||
|
ret = []
|
||||||
|
dls = self.conn.hsrv.tdls
|
||||||
|
for dl_id, (t0, sz, vn, vp, uname) in self.conn.hsrv.tdli.items():
|
||||||
|
t1, sent = dls[dl_id]
|
||||||
|
if sent > 0x100000: # 1m; buffers 2~4
|
||||||
|
sent -= 0x100000
|
||||||
|
if self.uname not in vn.axs.uread:
|
||||||
|
vp = ""
|
||||||
|
elif self.uname not in vn.axs.udot and (vp.startswith(".") or "/." in vp):
|
||||||
|
vp = ""
|
||||||
|
if self.uname not in vn.axs.uadmin:
|
||||||
|
dl_id = uname = ""
|
||||||
|
|
||||||
|
ret.append([t0, t1, sent, sz, vp, dl_id, uname])
|
||||||
|
return ret
|
||||||
|
|
||||||
|
def tx_dls(self) -> bool:
|
||||||
|
ret = [
|
||||||
|
{
|
||||||
|
"t0": x[0],
|
||||||
|
"t1": x[1],
|
||||||
|
"sent": x[2],
|
||||||
|
"size": x[3],
|
||||||
|
"path": x[4],
|
||||||
|
"conn": x[5],
|
||||||
|
"uname": x[6],
|
||||||
|
}
|
||||||
|
for x in self.get_dls()
|
||||||
|
]
|
||||||
|
zs = json.dumps(ret, separators=(",\n", ": "))
|
||||||
|
self.reply(zs.encode("utf-8", "replace"), mime="application/json")
|
||||||
|
return True
|
||||||
|
|
||||||
def tx_ups(self) -> bool:
|
def tx_ups(self) -> bool:
|
||||||
idx = self.conn.get_u2idx()
|
idx = self.conn.get_u2idx()
|
||||||
if not idx or not hasattr(idx, "p_end"):
|
if not idx or not hasattr(idx, "p_end"):
|
||||||
@@ -4872,7 +5000,7 @@ class HttpCli(object):
|
|||||||
|
|
||||||
cur.connection.commit()
|
cur.connection.commit()
|
||||||
if reload:
|
if reload:
|
||||||
self.conn.hsrv.broker.ask("_reload_blocking", False, False).get()
|
self.conn.hsrv.broker.ask("reload", False, False).get()
|
||||||
self.conn.hsrv.broker.ask("up2k.wake_rescanner").get()
|
self.conn.hsrv.broker.ask("up2k.wake_rescanner").get()
|
||||||
|
|
||||||
self.redirect(self.args.SRS + "?shares")
|
self.redirect(self.args.SRS + "?shares")
|
||||||
@@ -4963,7 +5091,7 @@ class HttpCli(object):
|
|||||||
cur.execute(q, (skey, fn))
|
cur.execute(q, (skey, fn))
|
||||||
|
|
||||||
cur.connection.commit()
|
cur.connection.commit()
|
||||||
self.conn.hsrv.broker.ask("_reload_blocking", False, False).get()
|
self.conn.hsrv.broker.ask("reload", False, False).get()
|
||||||
self.conn.hsrv.broker.ask("up2k.wake_rescanner").get()
|
self.conn.hsrv.broker.ask("up2k.wake_rescanner").get()
|
||||||
|
|
||||||
fn = quotep(fns[0]) if len(fns) == 1 else ""
|
fn = quotep(fns[0]) if len(fns) == 1 else ""
|
||||||
@@ -5014,16 +5142,39 @@ class HttpCli(object):
|
|||||||
return self._mv(self.vpath, dst.lstrip("/"))
|
return self._mv(self.vpath, dst.lstrip("/"))
|
||||||
|
|
||||||
def _mv(self, vsrc: str, vdst: str) -> bool:
|
def _mv(self, vsrc: str, vdst: str) -> bool:
|
||||||
if not self.can_move:
|
|
||||||
raise Pebkac(403, "not allowed for user " + self.uname)
|
|
||||||
|
|
||||||
if self.args.no_mv:
|
if self.args.no_mv:
|
||||||
raise Pebkac(403, "the rename/move feature is disabled in server config")
|
raise Pebkac(403, "the rename/move feature is disabled in server config")
|
||||||
|
|
||||||
|
self.asrv.vfs.get(vsrc, self.uname, True, False, True)
|
||||||
|
self.asrv.vfs.get(vdst, self.uname, False, True)
|
||||||
|
|
||||||
x = self.conn.hsrv.broker.ask("up2k.handle_mv", self.uname, self.ip, vsrc, vdst)
|
x = self.conn.hsrv.broker.ask("up2k.handle_mv", self.uname, self.ip, vsrc, vdst)
|
||||||
self.loud_reply(x.get(), status=201)
|
self.loud_reply(x.get(), status=201)
|
||||||
return True
|
return True
|
||||||
|
|
||||||
|
def handle_cp(self) -> bool:
|
||||||
|
# full path of new loc (incl filename)
|
||||||
|
dst = self.uparam.get("copy")
|
||||||
|
|
||||||
|
if self.is_vproxied and dst and dst.startswith(self.args.SR):
|
||||||
|
dst = dst[len(self.args.RS) :]
|
||||||
|
|
||||||
|
if not dst:
|
||||||
|
raise Pebkac(400, "need dst vpath")
|
||||||
|
|
||||||
|
return self._cp(self.vpath, dst.lstrip("/"))
|
||||||
|
|
||||||
|
def _cp(self, vsrc: str, vdst: str) -> bool:
|
||||||
|
if self.args.no_cp:
|
||||||
|
raise Pebkac(403, "the copy feature is disabled in server config")
|
||||||
|
|
||||||
|
self.asrv.vfs.get(vsrc, self.uname, True, False)
|
||||||
|
self.asrv.vfs.get(vdst, self.uname, False, True)
|
||||||
|
|
||||||
|
x = self.conn.hsrv.broker.ask("up2k.handle_cp", self.uname, self.ip, vsrc, vdst)
|
||||||
|
self.loud_reply(x.get(), status=201)
|
||||||
|
return True
|
||||||
|
|
||||||
def tx_ls(self, ls: dict[str, Any]) -> bool:
|
def tx_ls(self, ls: dict[str, Any]) -> bool:
|
||||||
dirs = ls["dirs"]
|
dirs = ls["dirs"]
|
||||||
files = ls["files"]
|
files = ls["files"]
|
||||||
@@ -5453,6 +5604,7 @@ class HttpCli(object):
|
|||||||
not self.args.no_scandir,
|
not self.args.no_scandir,
|
||||||
[[True, False], [False, True]],
|
[[True, False], [False, True]],
|
||||||
lstat="lt" in self.uparam,
|
lstat="lt" in self.uparam,
|
||||||
|
throw=True,
|
||||||
)
|
)
|
||||||
stats = {k: v for k, v in vfs_ls}
|
stats = {k: v for k, v in vfs_ls}
|
||||||
ls_names = [x[0] for x in vfs_ls]
|
ls_names = [x[0] for x in vfs_ls]
|
||||||
|
|||||||
@@ -81,6 +81,7 @@ from .util import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
if TYPE_CHECKING:
|
if TYPE_CHECKING:
|
||||||
|
from .authsrv import VFS
|
||||||
from .broker_util import BrokerCli
|
from .broker_util import BrokerCli
|
||||||
from .ssdp import SSDPr
|
from .ssdp import SSDPr
|
||||||
|
|
||||||
@@ -130,6 +131,12 @@ class HttpSrv(object):
|
|||||||
self.bans: dict[str, int] = {}
|
self.bans: dict[str, int] = {}
|
||||||
self.aclose: dict[str, int] = {}
|
self.aclose: dict[str, int] = {}
|
||||||
|
|
||||||
|
dli: dict[str, tuple[float, int, "VFS", str, str]] = {} # info
|
||||||
|
dls: dict[str, tuple[float, int]] = {} # state
|
||||||
|
self.dli = self.tdli = dli
|
||||||
|
self.dls = self.tdls = dls
|
||||||
|
self.iiam = '<img src="%s.cpr/iiam.gif" />' % (self.args.SRS,)
|
||||||
|
|
||||||
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()
|
||||||
@@ -205,6 +212,9 @@ class HttpSrv(object):
|
|||||||
self.start_threads(4)
|
self.start_threads(4)
|
||||||
|
|
||||||
if nid:
|
if nid:
|
||||||
|
self.tdli = {}
|
||||||
|
self.tdls = {}
|
||||||
|
|
||||||
if self.args.stackmon:
|
if self.args.stackmon:
|
||||||
start_stackmon(self.args.stackmon, nid)
|
start_stackmon(self.args.stackmon, nid)
|
||||||
|
|
||||||
@@ -579,3 +589,32 @@ class HttpSrv(object):
|
|||||||
ident += "a"
|
ident += "a"
|
||||||
|
|
||||||
self.u2idx_free[ident] = u2idx
|
self.u2idx_free[ident] = u2idx
|
||||||
|
|
||||||
|
def read_dls(
|
||||||
|
self,
|
||||||
|
) -> tuple[
|
||||||
|
dict[str, tuple[float, int, str, str, str]], dict[str, tuple[float, int]]
|
||||||
|
]:
|
||||||
|
"""
|
||||||
|
mp-broker asking for local dl-info + dl-state;
|
||||||
|
reduce overhead by sending just the vfs vpath
|
||||||
|
"""
|
||||||
|
dli = {k: (a, b, c.vpath, d, e) for k, (a, b, c, d, e) in self.dli.items()}
|
||||||
|
return (dli, self.dls)
|
||||||
|
|
||||||
|
def write_dls(
|
||||||
|
self,
|
||||||
|
sdli: dict[str, tuple[float, int, str, str, str]],
|
||||||
|
dls: dict[str, tuple[float, int]],
|
||||||
|
) -> None:
|
||||||
|
"""
|
||||||
|
mp-broker pushing total dl-info + dl-state;
|
||||||
|
swap out the vfs vpath with the vfs node
|
||||||
|
"""
|
||||||
|
dli: dict[str, tuple[float, int, "VFS", str, str]] = {}
|
||||||
|
for k, (a, b, c, d, e) in sdli.items():
|
||||||
|
vn = self.asrv.vfs.all_nodes[c]
|
||||||
|
dli[k] = (a, b, vn, d, e)
|
||||||
|
|
||||||
|
self.tdli = dli
|
||||||
|
self.tdls = dls
|
||||||
|
|||||||
@@ -72,6 +72,9 @@ class Metrics(object):
|
|||||||
v = "{:.3f}".format(self.hsrv.t0)
|
v = "{:.3f}".format(self.hsrv.t0)
|
||||||
addug("cpp_boot_unixtime", "seconds", v, t)
|
addug("cpp_boot_unixtime", "seconds", v, t)
|
||||||
|
|
||||||
|
t = "number of active downloads"
|
||||||
|
addg("cpp_active_dl", str(len(self.hsrv.tdls)), t)
|
||||||
|
|
||||||
t = "number of open http(s) client connections"
|
t = "number of open http(s) client connections"
|
||||||
addg("cpp_http_conns", str(self.hsrv.ncli), t)
|
addg("cpp_http_conns", str(self.hsrv.ncli), t)
|
||||||
|
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ from __future__ import print_function, unicode_literals
|
|||||||
import argparse
|
import argparse
|
||||||
import json
|
import json
|
||||||
import os
|
import os
|
||||||
|
import re
|
||||||
import shutil
|
import shutil
|
||||||
import subprocess as sp
|
import subprocess as sp
|
||||||
import sys
|
import sys
|
||||||
@@ -62,6 +63,9 @@ def have_ff(scmd: str) -> bool:
|
|||||||
HAVE_FFMPEG = not os.environ.get("PRTY_NO_FFMPEG") and have_ff("ffmpeg")
|
HAVE_FFMPEG = not os.environ.get("PRTY_NO_FFMPEG") and have_ff("ffmpeg")
|
||||||
HAVE_FFPROBE = not os.environ.get("PRTY_NO_FFPROBE") and have_ff("ffprobe")
|
HAVE_FFPROBE = not os.environ.get("PRTY_NO_FFPROBE") and have_ff("ffprobe")
|
||||||
|
|
||||||
|
CBZ_PICS = set("png jpg jpeg gif bmp tga tif tiff webp avif".split())
|
||||||
|
CBZ_01 = re.compile(r"(^|[^0-9v])0+[01]\b")
|
||||||
|
|
||||||
|
|
||||||
class MParser(object):
|
class MParser(object):
|
||||||
def __init__(self, cmdline: str) -> None:
|
def __init__(self, cmdline: str) -> None:
|
||||||
@@ -126,6 +130,7 @@ def au_unpk(
|
|||||||
log: "NamedLogger", fmt_map: dict[str, str], abspath: str, vn: Optional[VFS] = None
|
log: "NamedLogger", fmt_map: dict[str, str], abspath: str, vn: Optional[VFS] = None
|
||||||
) -> str:
|
) -> str:
|
||||||
ret = ""
|
ret = ""
|
||||||
|
maxsz = 1024 * 1024 * 64
|
||||||
try:
|
try:
|
||||||
ext = abspath.split(".")[-1].lower()
|
ext = abspath.split(".")[-1].lower()
|
||||||
au, pk = fmt_map[ext].split(".")
|
au, pk = fmt_map[ext].split(".")
|
||||||
@@ -148,17 +153,41 @@ def au_unpk(
|
|||||||
zf = zipfile.ZipFile(abspath, "r")
|
zf = zipfile.ZipFile(abspath, "r")
|
||||||
zil = zf.infolist()
|
zil = zf.infolist()
|
||||||
zil = [x for x in zil if x.filename.lower().split(".")[-1] == au]
|
zil = [x for x in zil if x.filename.lower().split(".")[-1] == au]
|
||||||
|
if not zil:
|
||||||
|
raise Exception("no audio inside zip")
|
||||||
fi = zf.open(zil[0])
|
fi = zf.open(zil[0])
|
||||||
|
|
||||||
|
elif pk == "cbz":
|
||||||
|
import zipfile
|
||||||
|
|
||||||
|
zf = zipfile.ZipFile(abspath, "r")
|
||||||
|
znil = [(x.filename.lower(), x) for x in zf.infolist()]
|
||||||
|
nf = len(znil)
|
||||||
|
znil = [x for x in znil if x[0].split(".")[-1] in CBZ_PICS]
|
||||||
|
znil = [x for x in znil if "cover" in x[0]] or znil
|
||||||
|
znil = [x for x in znil if CBZ_01.search(x[0])] or znil
|
||||||
|
t = "cbz: %d files, %d hits" % (nf, len(znil))
|
||||||
|
if znil:
|
||||||
|
t += ", using " + znil[0][1].filename
|
||||||
|
log(t)
|
||||||
|
if not znil:
|
||||||
|
raise Exception("no images inside cbz")
|
||||||
|
fi = zf.open(znil[0][1])
|
||||||
|
|
||||||
else:
|
else:
|
||||||
raise Exception("unknown compression %s" % (pk,))
|
raise Exception("unknown compression %s" % (pk,))
|
||||||
|
|
||||||
|
fsz = 0
|
||||||
with os.fdopen(fd, "wb") as fo:
|
with os.fdopen(fd, "wb") as fo:
|
||||||
while True:
|
while True:
|
||||||
buf = fi.read(32768)
|
buf = fi.read(32768)
|
||||||
if not buf:
|
if not buf:
|
||||||
break
|
break
|
||||||
|
|
||||||
|
fsz += len(buf)
|
||||||
|
if fsz > maxsz:
|
||||||
|
raise Exception("zipbomb defused")
|
||||||
|
|
||||||
fo.write(buf)
|
fo.write(buf)
|
||||||
|
|
||||||
return ret
|
return ret
|
||||||
|
|||||||
@@ -24,17 +24,13 @@ class PWHash(object):
|
|||||||
def __init__(self, args: argparse.Namespace):
|
def __init__(self, args: argparse.Namespace):
|
||||||
self.args = args
|
self.args = args
|
||||||
|
|
||||||
try:
|
zsl = args.ah_alg.split(",")
|
||||||
alg, ac = args.ah_alg.split(",")
|
alg = zsl[0]
|
||||||
except:
|
|
||||||
alg = args.ah_alg
|
|
||||||
ac = {}
|
|
||||||
|
|
||||||
if alg == "none":
|
if alg == "none":
|
||||||
alg = ""
|
alg = ""
|
||||||
|
|
||||||
self.alg = alg
|
self.alg = alg
|
||||||
self.ac = ac
|
self.ac = zsl[1:]
|
||||||
if not alg:
|
if not alg:
|
||||||
self.on = False
|
self.on = False
|
||||||
self.hash = unicode
|
self.hash = unicode
|
||||||
@@ -90,17 +86,23 @@ class PWHash(object):
|
|||||||
its = 2
|
its = 2
|
||||||
blksz = 8
|
blksz = 8
|
||||||
para = 4
|
para = 4
|
||||||
|
ramcap = 0 # openssl 1.1 = 32 MiB
|
||||||
try:
|
try:
|
||||||
cost = 2 << int(self.ac[0])
|
cost = 2 << int(self.ac[0])
|
||||||
its = int(self.ac[1])
|
its = int(self.ac[1])
|
||||||
blksz = int(self.ac[2])
|
blksz = int(self.ac[2])
|
||||||
para = int(self.ac[3])
|
para = int(self.ac[3])
|
||||||
|
ramcap = int(self.ac[4]) * 1024 * 1024
|
||||||
except:
|
except:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
cfg = {"salt": self.salt, "n": cost, "r": blksz, "p": para, "dklen": 24}
|
||||||
|
if ramcap:
|
||||||
|
cfg["maxmem"] = ramcap
|
||||||
|
|
||||||
ret = plain.encode("utf-8")
|
ret = plain.encode("utf-8")
|
||||||
for _ in range(its):
|
for _ in range(its):
|
||||||
ret = hashlib.scrypt(ret, salt=self.salt, n=cost, r=blksz, p=para, dklen=24)
|
ret = hashlib.scrypt(ret, **cfg)
|
||||||
|
|
||||||
return "+" + base64.urlsafe_b64encode(ret).decode("utf-8")
|
return "+" + base64.urlsafe_b64encode(ret).decode("utf-8")
|
||||||
|
|
||||||
|
|||||||
@@ -112,7 +112,7 @@ class SvcHub(object):
|
|||||||
self.stopping = False
|
self.stopping = False
|
||||||
self.stopped = False
|
self.stopped = False
|
||||||
self.reload_req = False
|
self.reload_req = False
|
||||||
self.reloading = 0
|
self.reload_mutex = threading.Lock()
|
||||||
self.stop_cond = threading.Condition()
|
self.stop_cond = threading.Condition()
|
||||||
self.nsigs = 3
|
self.nsigs = 3
|
||||||
self.retcode = 0
|
self.retcode = 0
|
||||||
@@ -211,6 +211,15 @@ class SvcHub(object):
|
|||||||
t = "WARNING: --s-rd-sz (%d) is larger than --iobuf (%d); this may lead to reduced performance"
|
t = "WARNING: --s-rd-sz (%d) is larger than --iobuf (%d); this may lead to reduced performance"
|
||||||
self.log("root", t % (args.s_rd_sz, args.iobuf), 3)
|
self.log("root", t % (args.s_rd_sz, args.iobuf), 3)
|
||||||
|
|
||||||
|
zs = ""
|
||||||
|
if args.th_ram_max < 0.22:
|
||||||
|
zs = "generate thumbnails"
|
||||||
|
elif args.th_ram_max < 1:
|
||||||
|
zs = "generate audio waveforms or spectrograms"
|
||||||
|
if zs:
|
||||||
|
t = "WARNING: --th-ram-max is very small (%.2f GiB); will not be able to %s"
|
||||||
|
self.log("root", t % (args.th_ram_max, zs), 3)
|
||||||
|
|
||||||
if args.chpw and args.idp_h_usr:
|
if args.chpw and args.idp_h_usr:
|
||||||
t = "ERROR: user-changeable passwords is incompatible with IdP/identity-providers; you must disable either --chpw or --idp-h-usr"
|
t = "ERROR: user-changeable passwords is incompatible with IdP/identity-providers; you must disable either --chpw or --idp-h-usr"
|
||||||
self.log("root", t, 1)
|
self.log("root", t, 1)
|
||||||
@@ -1004,41 +1013,18 @@ class SvcHub(object):
|
|||||||
except:
|
except:
|
||||||
self.log("root", "ssdp startup failed;\n" + min_ex(), 3)
|
self.log("root", "ssdp startup failed;\n" + min_ex(), 3)
|
||||||
|
|
||||||
def reload(self) -> str:
|
def reload(self, rescan_all_vols: bool, up2k: bool) -> str:
|
||||||
with self.up2k.mutex:
|
t = "config has been reloaded"
|
||||||
if self.reloading:
|
with self.reload_mutex:
|
||||||
return "cannot reload; already in progress"
|
|
||||||
self.reloading = 1
|
|
||||||
|
|
||||||
Daemon(self._reload, "reloading")
|
|
||||||
return "reload initiated"
|
|
||||||
|
|
||||||
def _reload(self, rescan_all_vols: bool = True, up2k: bool = True) -> None:
|
|
||||||
with self.up2k.mutex:
|
|
||||||
if self.reloading != 1:
|
|
||||||
return
|
|
||||||
self.reloading = 2
|
|
||||||
self.log("root", "reloading config")
|
self.log("root", "reloading config")
|
||||||
self.asrv.reload(9 if up2k else 4)
|
self.asrv.reload(9 if up2k else 4)
|
||||||
if up2k:
|
if up2k:
|
||||||
self.up2k.reload(rescan_all_vols)
|
self.up2k.reload(rescan_all_vols)
|
||||||
|
t += "; volumes are now reinitializing"
|
||||||
else:
|
else:
|
||||||
self.log("root", "reload done")
|
self.log("root", "reload done")
|
||||||
self.broker.reload()
|
self.broker.reload()
|
||||||
self.reloading = 0
|
return t
|
||||||
|
|
||||||
def _reload_blocking(self, rescan_all_vols: bool = True, up2k: bool = True) -> None:
|
|
||||||
while True:
|
|
||||||
with self.up2k.mutex:
|
|
||||||
if self.reloading < 2:
|
|
||||||
self.reloading = 1
|
|
||||||
break
|
|
||||||
time.sleep(0.05)
|
|
||||||
|
|
||||||
# try to handle multiple pending IdP reloads at once:
|
|
||||||
time.sleep(0.2)
|
|
||||||
|
|
||||||
self._reload(rescan_all_vols=rescan_all_vols, up2k=up2k)
|
|
||||||
|
|
||||||
def _reload_sessions(self) -> None:
|
def _reload_sessions(self) -> None:
|
||||||
with self.asrv.mutex:
|
with self.asrv.mutex:
|
||||||
@@ -1052,7 +1038,7 @@ class SvcHub(object):
|
|||||||
|
|
||||||
if self.reload_req:
|
if self.reload_req:
|
||||||
self.reload_req = False
|
self.reload_req = False
|
||||||
self.reload()
|
self.reload(True, True)
|
||||||
|
|
||||||
self.shutdown()
|
self.shutdown()
|
||||||
|
|
||||||
|
|||||||
@@ -100,7 +100,7 @@ def gen_hdr(
|
|||||||
|
|
||||||
# spec says to put zeros when !crc if bit3 (streaming)
|
# spec says to put zeros when !crc if bit3 (streaming)
|
||||||
# however infozip does actual sz and it even works on winxp
|
# however infozip does actual sz and it even works on winxp
|
||||||
# (same reasning for z64 extradata later)
|
# (same reasoning for z64 extradata later)
|
||||||
vsz = 0xFFFFFFFF if z64 else sz
|
vsz = 0xFFFFFFFF if z64 else sz
|
||||||
ret += spack(b"<LL", vsz, vsz)
|
ret += spack(b"<LL", vsz, vsz)
|
||||||
|
|
||||||
|
|||||||
@@ -371,7 +371,7 @@ class TcpSrv(object):
|
|||||||
if self.args.q:
|
if self.args.q:
|
||||||
print(msg)
|
print(msg)
|
||||||
|
|
||||||
self.hub.broker.say("listen", srv)
|
self.hub.broker.say("httpsrv.listen", srv)
|
||||||
|
|
||||||
self.srv = srvs
|
self.srv = srvs
|
||||||
self.bound = bound
|
self.bound = bound
|
||||||
@@ -379,7 +379,7 @@ class TcpSrv(object):
|
|||||||
self._distribute_netdevs()
|
self._distribute_netdevs()
|
||||||
|
|
||||||
def _distribute_netdevs(self):
|
def _distribute_netdevs(self):
|
||||||
self.hub.broker.say("set_netdevs", self.netdevs)
|
self.hub.broker.say("httpsrv.set_netdevs", self.netdevs)
|
||||||
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()
|
||||||
|
|||||||
@@ -269,6 +269,7 @@ class Tftpd(object):
|
|||||||
"*",
|
"*",
|
||||||
not self.args.no_scandir,
|
not self.args.no_scandir,
|
||||||
[[True, False]],
|
[[True, False]],
|
||||||
|
throw=True,
|
||||||
)
|
)
|
||||||
dnames = set([x[0] for x in vfs_ls if stat.S_ISDIR(x[1].st_mode)])
|
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]
|
dirs1 = [(v.st_mtime, v.st_size, k + "/") for k, v in vfs_ls if k in dnames]
|
||||||
|
|||||||
@@ -20,7 +20,6 @@ from .util import (
|
|||||||
FFMPEG_URL,
|
FFMPEG_URL,
|
||||||
Cooldown,
|
Cooldown,
|
||||||
Daemon,
|
Daemon,
|
||||||
Pebkac,
|
|
||||||
afsenc,
|
afsenc,
|
||||||
fsenc,
|
fsenc,
|
||||||
min_ex,
|
min_ex,
|
||||||
@@ -164,6 +163,7 @@ class ThumbSrv(object):
|
|||||||
self.ram: dict[str, float] = {}
|
self.ram: dict[str, float] = {}
|
||||||
self.memcond = threading.Condition(self.mutex)
|
self.memcond = threading.Condition(self.mutex)
|
||||||
self.stopping = False
|
self.stopping = False
|
||||||
|
self.rm_nullthumbs = True # forget failed conversions on startup
|
||||||
self.nthr = max(1, self.args.th_mt)
|
self.nthr = max(1, self.args.th_mt)
|
||||||
|
|
||||||
self.q: Queue[Optional[tuple[str, str, str, VFS]]] = Queue(self.nthr * 4)
|
self.q: Queue[Optional[tuple[str, str, str, VFS]]] = Queue(self.nthr * 4)
|
||||||
@@ -862,7 +862,6 @@ class ThumbSrv(object):
|
|||||||
def cleaner(self) -> None:
|
def cleaner(self) -> None:
|
||||||
interval = self.args.th_clean
|
interval = self.args.th_clean
|
||||||
while True:
|
while True:
|
||||||
time.sleep(interval)
|
|
||||||
ndirs = 0
|
ndirs = 0
|
||||||
for vol, histpath in self.asrv.vfs.histtab.items():
|
for vol, histpath in self.asrv.vfs.histtab.items():
|
||||||
if histpath.startswith(vol):
|
if histpath.startswith(vol):
|
||||||
@@ -876,6 +875,8 @@ class ThumbSrv(object):
|
|||||||
self.log("\033[Jcln err in %s: %r" % (histpath, ex), 3)
|
self.log("\033[Jcln err in %s: %r" % (histpath, ex), 3)
|
||||||
|
|
||||||
self.log("\033[Jcln ok; rm {} dirs".format(ndirs))
|
self.log("\033[Jcln ok; rm {} dirs".format(ndirs))
|
||||||
|
self.rm_nullthumbs = False
|
||||||
|
time.sleep(interval)
|
||||||
|
|
||||||
def clean(self, histpath: str) -> int:
|
def clean(self, histpath: str) -> int:
|
||||||
ret = 0
|
ret = 0
|
||||||
@@ -896,7 +897,9 @@ class ThumbSrv(object):
|
|||||||
prev_b64 = None
|
prev_b64 = None
|
||||||
prev_fp = ""
|
prev_fp = ""
|
||||||
try:
|
try:
|
||||||
t1 = statdir(self.log_func, not self.args.no_scandir, False, thumbpath)
|
t1 = statdir(
|
||||||
|
self.log_func, not self.args.no_scandir, False, thumbpath, False
|
||||||
|
)
|
||||||
ents = sorted(list(t1))
|
ents = sorted(list(t1))
|
||||||
except:
|
except:
|
||||||
return 0
|
return 0
|
||||||
@@ -937,6 +940,10 @@ class ThumbSrv(object):
|
|||||||
|
|
||||||
continue
|
continue
|
||||||
|
|
||||||
|
if self.rm_nullthumbs and not inf.st_size:
|
||||||
|
bos.unlink(fp)
|
||||||
|
continue
|
||||||
|
|
||||||
if b64 == prev_b64:
|
if b64 == prev_b64:
|
||||||
self.log("rm replaced [{}]".format(fp))
|
self.log("rm replaced [{}]".format(fp))
|
||||||
bos.unlink(prev_fp)
|
bos.unlink(prev_fp)
|
||||||
|
|||||||
@@ -89,6 +89,8 @@ 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(","))
|
||||||
|
|
||||||
|
|
||||||
|
SBUSY = "cannot receive uploads right now;\nserver busy with %s.\nPlease wait; the client will retry..."
|
||||||
|
|
||||||
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)"
|
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)"
|
||||||
|
|
||||||
|
|
||||||
@@ -125,12 +127,22 @@ class Up2k(object):
|
|||||||
self.args = hub.args
|
self.args = hub.args
|
||||||
self.log_func = hub.log
|
self.log_func = hub.log
|
||||||
|
|
||||||
|
self.vfs = self.asrv.vfs
|
||||||
|
self.acct = self.asrv.acct
|
||||||
|
self.iacct = self.asrv.iacct
|
||||||
|
self.grps = self.asrv.grps
|
||||||
|
|
||||||
self.salt = self.args.warksalt
|
self.salt = self.args.warksalt
|
||||||
self.r_hash = re.compile("^[0-9a-zA-Z_-]{44}$")
|
self.r_hash = re.compile("^[0-9a-zA-Z_-]{44}$")
|
||||||
|
|
||||||
self.gid = 0
|
self.gid = 0
|
||||||
|
self.gt0 = 0
|
||||||
|
self.gt1 = 0
|
||||||
self.stop = False
|
self.stop = False
|
||||||
self.mutex = threading.Lock()
|
self.mutex = threading.Lock()
|
||||||
|
self.reload_mutex = threading.Lock()
|
||||||
|
self.reload_flag = 0
|
||||||
|
self.reloading = False
|
||||||
self.blocked: Optional[str] = None
|
self.blocked: Optional[str] = None
|
||||||
self.pp: Optional[ProgressPrinter] = None
|
self.pp: Optional[ProgressPrinter] = None
|
||||||
self.rescan_cond = threading.Condition()
|
self.rescan_cond = threading.Condition()
|
||||||
@@ -203,7 +215,38 @@ class Up2k(object):
|
|||||||
|
|
||||||
Daemon(self.deferred_init, "up2k-deferred-init")
|
Daemon(self.deferred_init, "up2k-deferred-init")
|
||||||
|
|
||||||
|
def unpp(self) -> None:
|
||||||
|
self.gt1 = time.time()
|
||||||
|
if self.pp:
|
||||||
|
self.pp.end = True
|
||||||
|
self.pp = None
|
||||||
|
|
||||||
def reload(self, rescan_all_vols: bool) -> None:
|
def reload(self, rescan_all_vols: bool) -> None:
|
||||||
|
n = 2 if rescan_all_vols else 1
|
||||||
|
with self.reload_mutex:
|
||||||
|
if self.reload_flag < n:
|
||||||
|
self.reload_flag = n
|
||||||
|
with self.rescan_cond:
|
||||||
|
self.rescan_cond.notify_all()
|
||||||
|
|
||||||
|
def _reload_thr(self) -> None:
|
||||||
|
while self.pp:
|
||||||
|
time.sleep(0.1)
|
||||||
|
while True:
|
||||||
|
with self.reload_mutex:
|
||||||
|
if not self.reload_flag:
|
||||||
|
break
|
||||||
|
rav = self.reload_flag == 2
|
||||||
|
self.reload_flag = 0
|
||||||
|
gt1 = self.gt1
|
||||||
|
with self.mutex:
|
||||||
|
self._reload(rav)
|
||||||
|
while gt1 == self.gt1 or self.pp:
|
||||||
|
time.sleep(0.1)
|
||||||
|
|
||||||
|
self.reloading = False
|
||||||
|
|
||||||
|
def _reload(self, rescan_all_vols: bool) -> None:
|
||||||
"""mutex(main) me"""
|
"""mutex(main) me"""
|
||||||
self.log("reload #{} scheduled".format(self.gid + 1))
|
self.log("reload #{} scheduled".format(self.gid + 1))
|
||||||
all_vols = self.asrv.vfs.all_vols
|
all_vols = self.asrv.vfs.all_vols
|
||||||
@@ -228,10 +271,7 @@ class Up2k(object):
|
|||||||
with self.mutex, self.reg_mutex:
|
with self.mutex, self.reg_mutex:
|
||||||
self._drop_caches()
|
self._drop_caches()
|
||||||
|
|
||||||
if self.pp:
|
self.unpp()
|
||||||
self.pp.end = True
|
|
||||||
self.pp = None
|
|
||||||
|
|
||||||
return
|
return
|
||||||
|
|
||||||
if not self.pp and self.args.exit == "idx":
|
if not self.pp and self.args.exit == "idx":
|
||||||
@@ -311,8 +351,8 @@ class Up2k(object):
|
|||||||
|
|
||||||
def _active_uploads(self, uname: str) -> list[tuple[float, int, int, str]]:
|
def _active_uploads(self, uname: str) -> list[tuple[float, int, int, str]]:
|
||||||
ret = []
|
ret = []
|
||||||
for vtop in self.asrv.vfs.aread[uname]:
|
for vtop in self.vfs.aread.get(uname) or []:
|
||||||
vfs = self.asrv.vfs.all_vols.get(vtop)
|
vfs = self.vfs.all_vols.get(vtop)
|
||||||
if not vfs: # dbv only
|
if not vfs: # dbv only
|
||||||
continue
|
continue
|
||||||
ptop = vfs.realpath
|
ptop = vfs.realpath
|
||||||
@@ -485,6 +525,12 @@ class Up2k(object):
|
|||||||
if self.stop:
|
if self.stop:
|
||||||
return
|
return
|
||||||
|
|
||||||
|
with self.reload_mutex:
|
||||||
|
if self.reload_flag and not self.reloading:
|
||||||
|
self.reloading = True
|
||||||
|
zs = "up2k-reload-%d" % (self.gid,)
|
||||||
|
Daemon(self._reload_thr, zs)
|
||||||
|
|
||||||
now = time.time()
|
now = time.time()
|
||||||
if now < cooldown:
|
if now < cooldown:
|
||||||
# self.log("SR: cd - now = {:.2f}".format(cooldown - now), 5)
|
# self.log("SR: cd - now = {:.2f}".format(cooldown - now), 5)
|
||||||
@@ -521,7 +567,7 @@ class Up2k(object):
|
|||||||
raise
|
raise
|
||||||
|
|
||||||
with self.mutex:
|
with self.mutex:
|
||||||
for vp, vol in sorted(self.asrv.vfs.all_vols.items()):
|
for vp, vol in sorted(self.vfs.all_vols.items()):
|
||||||
maxage = vol.flags.get("scan")
|
maxage = vol.flags.get("scan")
|
||||||
if not maxage:
|
if not maxage:
|
||||||
continue
|
continue
|
||||||
@@ -554,7 +600,7 @@ class Up2k(object):
|
|||||||
|
|
||||||
if vols:
|
if vols:
|
||||||
cooldown = now + 10
|
cooldown = now + 10
|
||||||
err = self.rescan(self.asrv.vfs.all_vols, vols, False, False)
|
err = self.rescan(self.vfs.all_vols, vols, False, False)
|
||||||
if err:
|
if err:
|
||||||
for v in vols:
|
for v in vols:
|
||||||
self.need_rescan.add(v)
|
self.need_rescan.add(v)
|
||||||
@@ -567,7 +613,7 @@ class Up2k(object):
|
|||||||
def _check_lifetimes(self) -> float:
|
def _check_lifetimes(self) -> float:
|
||||||
now = time.time()
|
now = time.time()
|
||||||
timeout = now + 9001
|
timeout = now + 9001
|
||||||
for vp, vol in sorted(self.asrv.vfs.all_vols.items()):
|
for vp, vol in sorted(self.vfs.all_vols.items()):
|
||||||
lifetime = vol.flags.get("lifetime")
|
lifetime = vol.flags.get("lifetime")
|
||||||
if not lifetime:
|
if not lifetime:
|
||||||
continue
|
continue
|
||||||
@@ -621,7 +667,7 @@ class Up2k(object):
|
|||||||
maxage = self.args.shr_rt * 60
|
maxage = self.args.shr_rt * 60
|
||||||
low = now - maxage
|
low = now - maxage
|
||||||
|
|
||||||
vn = self.asrv.vfs.nodes.get(self.args.shr.strip("/"))
|
vn = self.vfs.nodes.get(self.args.shr.strip("/"))
|
||||||
active = vn and vn.nodes
|
active = vn and vn.nodes
|
||||||
|
|
||||||
db = sqlite3.connect(self.args.shr_db, timeout=2)
|
db = sqlite3.connect(self.args.shr_db, timeout=2)
|
||||||
@@ -646,7 +692,7 @@ class Up2k(object):
|
|||||||
db.commit()
|
db.commit()
|
||||||
|
|
||||||
if reload:
|
if reload:
|
||||||
Daemon(self.hub._reload_blocking, "sharedrop", (False, False))
|
Daemon(self.hub.reload, "sharedrop", (False, False))
|
||||||
|
|
||||||
q = "select min(t1) from sh where t1 > ?"
|
q = "select min(t1) from sh where t1 > ?"
|
||||||
(earliest,) = cur.execute(q, (1,)).fetchone()
|
(earliest,) = cur.execute(q, (1,)).fetchone()
|
||||||
@@ -672,7 +718,7 @@ class Up2k(object):
|
|||||||
return 2
|
return 2
|
||||||
|
|
||||||
ret = 9001
|
ret = 9001
|
||||||
for _, vol in sorted(self.asrv.vfs.all_vols.items()):
|
for _, vol in sorted(self.vfs.all_vols.items()):
|
||||||
rp = vol.realpath
|
rp = vol.realpath
|
||||||
cur = self.cur.get(rp)
|
cur = self.cur.get(rp)
|
||||||
if not cur:
|
if not cur:
|
||||||
@@ -774,6 +820,8 @@ class Up2k(object):
|
|||||||
with self.mutex:
|
with self.mutex:
|
||||||
gid = self.gid
|
gid = self.gid
|
||||||
|
|
||||||
|
self.gt0 = time.time()
|
||||||
|
|
||||||
nspin = 0
|
nspin = 0
|
||||||
while True:
|
while True:
|
||||||
nspin += 1
|
nspin += 1
|
||||||
@@ -796,6 +844,11 @@ class Up2k(object):
|
|||||||
if gid:
|
if gid:
|
||||||
self.log("reload #%d running" % (gid,))
|
self.log("reload #%d running" % (gid,))
|
||||||
|
|
||||||
|
self.vfs = self.asrv.vfs
|
||||||
|
self.acct = self.asrv.acct
|
||||||
|
self.iacct = self.asrv.iacct
|
||||||
|
self.grps = self.asrv.grps
|
||||||
|
|
||||||
vols = list(all_vols.values())
|
vols = list(all_vols.values())
|
||||||
t0 = time.time()
|
t0 = time.time()
|
||||||
have_e2d = False
|
have_e2d = False
|
||||||
@@ -859,7 +912,7 @@ class Up2k(object):
|
|||||||
self._drop_caches()
|
self._drop_caches()
|
||||||
|
|
||||||
for vol in vols:
|
for vol in vols:
|
||||||
if self.stop:
|
if self.stop or gid != self.gid:
|
||||||
break
|
break
|
||||||
|
|
||||||
en = set(vol.flags.get("mte", {}))
|
en = set(vol.flags.get("mte", {}))
|
||||||
@@ -990,7 +1043,7 @@ class Up2k(object):
|
|||||||
if self.mtag:
|
if self.mtag:
|
||||||
Daemon(self._run_all_mtp, "up2k-mtp-scan", (gid,))
|
Daemon(self._run_all_mtp, "up2k-mtp-scan", (gid,))
|
||||||
else:
|
else:
|
||||||
self.pp = None
|
self.unpp()
|
||||||
|
|
||||||
return have_e2d
|
return have_e2d
|
||||||
|
|
||||||
@@ -998,7 +1051,7 @@ class Up2k(object):
|
|||||||
self, ptop: str, flags: dict[str, Any]
|
self, ptop: str, flags: dict[str, Any]
|
||||||
) -> Optional[tuple["sqlite3.Cursor", str]]:
|
) -> Optional[tuple["sqlite3.Cursor", str]]:
|
||||||
"""mutex(main,reg) me"""
|
"""mutex(main,reg) me"""
|
||||||
histpath = self.asrv.vfs.histtab.get(ptop)
|
histpath = self.vfs.histtab.get(ptop)
|
||||||
if not histpath:
|
if not histpath:
|
||||||
self.log("no histpath for [{}]".format(ptop))
|
self.log("no histpath for [{}]".format(ptop))
|
||||||
return None
|
return None
|
||||||
@@ -1011,7 +1064,7 @@ class Up2k(object):
|
|||||||
return None
|
return None
|
||||||
|
|
||||||
vpath = "?"
|
vpath = "?"
|
||||||
for k, v in self.asrv.vfs.all_vols.items():
|
for k, v in self.vfs.all_vols.items():
|
||||||
if v.realpath == ptop:
|
if v.realpath == ptop:
|
||||||
vpath = k
|
vpath = k
|
||||||
|
|
||||||
@@ -1178,7 +1231,7 @@ class Up2k(object):
|
|||||||
def _verify_db_cache(self, cur: "sqlite3.Cursor", vpath: str) -> None:
|
def _verify_db_cache(self, cur: "sqlite3.Cursor", vpath: str) -> None:
|
||||||
# check if list of intersecting volumes changed since last use; drop caches if so
|
# check if list of intersecting volumes changed since last use; drop caches if so
|
||||||
prefix = (vpath + "/").lstrip("/")
|
prefix = (vpath + "/").lstrip("/")
|
||||||
zsl = [x for x in self.asrv.vfs.all_vols if x.startswith(prefix)]
|
zsl = [x for x in self.vfs.all_vols if x.startswith(prefix)]
|
||||||
zsl = [x[len(prefix) :] for x in zsl]
|
zsl = [x[len(prefix) :] for x in zsl]
|
||||||
zsl.sort()
|
zsl.sort()
|
||||||
zb = hashlib.sha1("\n".join(zsl).encode("utf-8", "replace")).digest()
|
zb = hashlib.sha1("\n".join(zsl).encode("utf-8", "replace")).digest()
|
||||||
@@ -1223,7 +1276,7 @@ class Up2k(object):
|
|||||||
if d != vol and (d.vpath.startswith(vol.vpath + "/") or not vol.vpath)
|
if d != vol and (d.vpath.startswith(vol.vpath + "/") or not vol.vpath)
|
||||||
]
|
]
|
||||||
excl += [absreal(x) for x in excl]
|
excl += [absreal(x) for x in excl]
|
||||||
excl += list(self.asrv.vfs.histtab.values())
|
excl += list(self.vfs.histtab.values())
|
||||||
if WINDOWS:
|
if WINDOWS:
|
||||||
excl = [x.replace("/", "\\") for x in excl]
|
excl = [x.replace("/", "\\") for x in excl]
|
||||||
else:
|
else:
|
||||||
@@ -1347,7 +1400,7 @@ class Up2k(object):
|
|||||||
rds = rd + "/" if rd else ""
|
rds = rd + "/" if rd else ""
|
||||||
cdirs = cdir + os.sep
|
cdirs = cdir + os.sep
|
||||||
|
|
||||||
g = statdir(self.log_func, not self.args.no_scandir, True, cdir)
|
g = statdir(self.log_func, not self.args.no_scandir, True, cdir, False)
|
||||||
gl = sorted(g)
|
gl = sorted(g)
|
||||||
partials = set([x[0] for x in gl if "PARTIAL" in x[0]])
|
partials = set([x[0] for x in gl if "PARTIAL" in x[0]])
|
||||||
for iname, inf in gl:
|
for iname, inf in gl:
|
||||||
@@ -1411,7 +1464,7 @@ class Up2k(object):
|
|||||||
t = "failed to index subdir [{}]:\n{}"
|
t = "failed to index subdir [{}]:\n{}"
|
||||||
self.log(t.format(abspath, min_ex()), c=1)
|
self.log(t.format(abspath, min_ex()), c=1)
|
||||||
elif not stat.S_ISREG(inf.st_mode):
|
elif not stat.S_ISREG(inf.st_mode):
|
||||||
self.log("skip type-{:x} file [{}]".format(inf.st_mode, abspath))
|
self.log("skip type-0%o file [%s]" % (inf.st_mode, abspath))
|
||||||
else:
|
else:
|
||||||
# self.log("file: {}".format(abspath))
|
# self.log("file: {}".format(abspath))
|
||||||
if rp.endswith(".PARTIAL") and time.time() - lmod < 60:
|
if rp.endswith(".PARTIAL") and time.time() - lmod < 60:
|
||||||
@@ -1733,7 +1786,7 @@ class Up2k(object):
|
|||||||
|
|
||||||
excl = [
|
excl = [
|
||||||
d[len(vol.vpath) :].lstrip("/")
|
d[len(vol.vpath) :].lstrip("/")
|
||||||
for d in self.asrv.vfs.all_vols
|
for d in self.vfs.all_vols
|
||||||
if d != vol.vpath and (d.startswith(vol.vpath + "/") or not vol.vpath)
|
if d != vol.vpath and (d.startswith(vol.vpath + "/") or not vol.vpath)
|
||||||
]
|
]
|
||||||
qexa: list[str] = []
|
qexa: list[str] = []
|
||||||
@@ -1885,7 +1938,7 @@ class Up2k(object):
|
|||||||
def _drop_caches(self) -> None:
|
def _drop_caches(self) -> None:
|
||||||
"""mutex(main,reg) me"""
|
"""mutex(main,reg) me"""
|
||||||
self.log("dropping caches for a full filesystem scan")
|
self.log("dropping caches for a full filesystem scan")
|
||||||
for vol in self.asrv.vfs.all_vols.values():
|
for vol in self.vfs.all_vols.values():
|
||||||
reg = self.register_vpath(vol.realpath, vol.flags)
|
reg = self.register_vpath(vol.realpath, vol.flags)
|
||||||
if not reg:
|
if not reg:
|
||||||
continue
|
continue
|
||||||
@@ -2113,7 +2166,7 @@ class Up2k(object):
|
|||||||
self._run_one_mtp(ptop, gid)
|
self._run_one_mtp(ptop, gid)
|
||||||
|
|
||||||
vtop = "\n"
|
vtop = "\n"
|
||||||
for vol in self.asrv.vfs.all_vols.values():
|
for vol in self.vfs.all_vols.values():
|
||||||
if vol.realpath == ptop:
|
if vol.realpath == ptop:
|
||||||
vtop = vol.vpath
|
vtop = vol.vpath
|
||||||
if "running mtp" in self.volstate.get(vtop, ""):
|
if "running mtp" in self.volstate.get(vtop, ""):
|
||||||
@@ -2123,7 +2176,7 @@ class Up2k(object):
|
|||||||
msg = "mtp finished in {:.2f} sec ({})"
|
msg = "mtp finished in {:.2f} sec ({})"
|
||||||
self.log(msg.format(td, s2hms(td, True)))
|
self.log(msg.format(td, s2hms(td, True)))
|
||||||
|
|
||||||
self.pp = None
|
self.unpp()
|
||||||
if self.args.exit == "idx":
|
if self.args.exit == "idx":
|
||||||
self.hub.sigterm()
|
self.hub.sigterm()
|
||||||
|
|
||||||
@@ -2765,6 +2818,9 @@ class Up2k(object):
|
|||||||
) -> dict[str, Any]:
|
) -> dict[str, Any]:
|
||||||
# busy_aps is u2fh (always undefined if -j0) so this is safe
|
# busy_aps is u2fh (always undefined if -j0) so this is safe
|
||||||
self.busy_aps = busy_aps
|
self.busy_aps = busy_aps
|
||||||
|
if self.reload_flag or self.reloading:
|
||||||
|
raise Pebkac(503, SBUSY % ("fs-reload",))
|
||||||
|
|
||||||
got_lock = False
|
got_lock = False
|
||||||
try:
|
try:
|
||||||
# bit expensive; 3.9=10x 3.11=2x
|
# bit expensive; 3.9=10x 3.11=2x
|
||||||
@@ -2773,8 +2829,7 @@ class Up2k(object):
|
|||||||
with self.reg_mutex:
|
with self.reg_mutex:
|
||||||
ret = self._handle_json(cj)
|
ret = self._handle_json(cj)
|
||||||
else:
|
else:
|
||||||
t = "cannot receive uploads right now;\nserver busy with {}.\nPlease wait; the client will retry..."
|
raise Pebkac(503, SBUSY % (self.blocked or "[unknown]",))
|
||||||
raise Pebkac(503, t.format(self.blocked or "[unknown]"))
|
|
||||||
except TypeError:
|
except TypeError:
|
||||||
if not PY2:
|
if not PY2:
|
||||||
raise
|
raise
|
||||||
@@ -2816,7 +2871,7 @@ class Up2k(object):
|
|||||||
if True:
|
if True:
|
||||||
jcur = self.cur.get(ptop)
|
jcur = self.cur.get(ptop)
|
||||||
reg = self.registry[ptop]
|
reg = self.registry[ptop]
|
||||||
vfs = self.asrv.vfs.all_vols[cj["vtop"]]
|
vfs = self.vfs.all_vols[cj["vtop"]]
|
||||||
n4g = bool(vfs.flags.get("noforget"))
|
n4g = bool(vfs.flags.get("noforget"))
|
||||||
noclone = bool(vfs.flags.get("noclone"))
|
noclone = bool(vfs.flags.get("noclone"))
|
||||||
rand = vfs.flags.get("rand") or cj.get("rand")
|
rand = vfs.flags.get("rand") or cj.get("rand")
|
||||||
@@ -2840,7 +2895,7 @@ class Up2k(object):
|
|||||||
|
|
||||||
alts: list[tuple[int, int, dict[str, Any], "sqlite3.Cursor", str, str]] = []
|
alts: list[tuple[int, int, dict[str, Any], "sqlite3.Cursor", str, str]] = []
|
||||||
for ptop, cur in vols:
|
for ptop, cur in vols:
|
||||||
allv = self.asrv.vfs.all_vols
|
allv = self.vfs.all_vols
|
||||||
cvfs = next((v for v in allv.values() if v.realpath == ptop), vfs)
|
cvfs = next((v for v in allv.values() if v.realpath == ptop), vfs)
|
||||||
vtop = cj["vtop"] if cur == jcur else cvfs.vpath
|
vtop = cj["vtop"] if cur == jcur else cvfs.vpath
|
||||||
|
|
||||||
@@ -3083,7 +3138,7 @@ class Up2k(object):
|
|||||||
vp,
|
vp,
|
||||||
job["host"],
|
job["host"],
|
||||||
job["user"],
|
job["user"],
|
||||||
self.asrv.vfs.get_perms(job["vtop"], job["user"]),
|
self.vfs.get_perms(job["vtop"], job["user"]),
|
||||||
job["lmod"],
|
job["lmod"],
|
||||||
job["size"],
|
job["size"],
|
||||||
job["addr"],
|
job["addr"],
|
||||||
@@ -3095,7 +3150,7 @@ class Up2k(object):
|
|||||||
self.log(t, 1)
|
self.log(t, 1)
|
||||||
raise Pebkac(403, t)
|
raise Pebkac(403, t)
|
||||||
if hr.get("reloc"):
|
if hr.get("reloc"):
|
||||||
x = pathmod(self.asrv.vfs, dst, vp, hr["reloc"])
|
x = pathmod(self.vfs, dst, vp, hr["reloc"])
|
||||||
if x:
|
if x:
|
||||||
zvfs = vfs
|
zvfs = vfs
|
||||||
pdir, _, job["name"], (vfs, rem) = x
|
pdir, _, job["name"], (vfs, rem) = x
|
||||||
@@ -3555,7 +3610,7 @@ class Up2k(object):
|
|||||||
wake_sr = False
|
wake_sr = False
|
||||||
try:
|
try:
|
||||||
flt = job["life"]
|
flt = job["life"]
|
||||||
vfs = self.asrv.vfs.all_vols[job["vtop"]]
|
vfs = self.vfs.all_vols[job["vtop"]]
|
||||||
vlt = vfs.flags["lifetime"]
|
vlt = vfs.flags["lifetime"]
|
||||||
if vlt and flt > 1 and flt < vlt:
|
if vlt and flt > 1 and flt < vlt:
|
||||||
upt -= vlt - flt
|
upt -= vlt - flt
|
||||||
@@ -3731,7 +3786,7 @@ class Up2k(object):
|
|||||||
djoin(vtop, rd, fn),
|
djoin(vtop, rd, fn),
|
||||||
host,
|
host,
|
||||||
usr,
|
usr,
|
||||||
self.asrv.vfs.get_perms(djoin(vtop, rd, fn), usr),
|
self.vfs.get_perms(djoin(vtop, rd, fn), usr),
|
||||||
ts,
|
ts,
|
||||||
sz,
|
sz,
|
||||||
ip,
|
ip,
|
||||||
@@ -3841,13 +3896,13 @@ class Up2k(object):
|
|||||||
partial = ""
|
partial = ""
|
||||||
if not unpost:
|
if not unpost:
|
||||||
permsets = [[True, False, False, True]]
|
permsets = [[True, False, False, True]]
|
||||||
vn, rem = self.asrv.vfs.get(vpath, uname, *permsets[0])
|
vn0, rem0 = self.vfs.get(vpath, uname, *permsets[0])
|
||||||
vn, rem = vn.get_dbv(rem)
|
vn, rem = vn0.get_dbv(rem0)
|
||||||
else:
|
else:
|
||||||
# unpost with missing permissions? verify with db
|
# unpost with missing permissions? verify with db
|
||||||
permsets = [[False, True]]
|
permsets = [[False, True]]
|
||||||
vn, rem = self.asrv.vfs.get(vpath, uname, *permsets[0])
|
vn0, rem0 = self.vfs.get(vpath, uname, *permsets[0])
|
||||||
vn, rem = vn.get_dbv(rem)
|
vn, rem = vn0.get_dbv(rem0)
|
||||||
ptop = vn.realpath
|
ptop = vn.realpath
|
||||||
with self.mutex, self.reg_mutex:
|
with self.mutex, self.reg_mutex:
|
||||||
abrt_cfg = self.flags.get(ptop, {}).get("u2abort", 1)
|
abrt_cfg = self.flags.get(ptop, {}).get("u2abort", 1)
|
||||||
@@ -3903,7 +3958,9 @@ class Up2k(object):
|
|||||||
|
|
||||||
scandir = not self.args.no_scandir
|
scandir = not self.args.no_scandir
|
||||||
if is_dir:
|
if is_dir:
|
||||||
g = vn.walk("", rem, [], uname, permsets, True, scandir, True)
|
# note: deletion inside shares would require a rewrite here;
|
||||||
|
# shares necessitate get_dbv which is incompatible with walk
|
||||||
|
g = vn0.walk("", rem0, [], uname, permsets, True, scandir, True)
|
||||||
if unpost:
|
if unpost:
|
||||||
raise Pebkac(400, "cannot unpost folders")
|
raise Pebkac(400, "cannot unpost folders")
|
||||||
elif stat.S_ISLNK(st.st_mode) or stat.S_ISREG(st.st_mode):
|
elif stat.S_ISLNK(st.st_mode) or stat.S_ISREG(st.st_mode):
|
||||||
@@ -3911,7 +3968,7 @@ class Up2k(object):
|
|||||||
vpath_dir = vsplit(vpath)[0]
|
vpath_dir = vsplit(vpath)[0]
|
||||||
g = [(vn, voldir, vpath_dir, adir, [(fn, 0)], [], {})] # type: ignore
|
g = [(vn, voldir, vpath_dir, adir, [(fn, 0)], [], {})] # type: ignore
|
||||||
else:
|
else:
|
||||||
self.log("rm: skip type-{:x} file [{}]".format(st.st_mode, atop))
|
self.log("rm: skip type-0%o file [%s]" % (st.st_mode, atop))
|
||||||
return 0, [], []
|
return 0, [], []
|
||||||
|
|
||||||
xbd = vn.flags.get("xbd")
|
xbd = vn.flags.get("xbd")
|
||||||
@@ -3951,7 +4008,7 @@ class Up2k(object):
|
|||||||
vpath,
|
vpath,
|
||||||
"",
|
"",
|
||||||
uname,
|
uname,
|
||||||
self.asrv.vfs.get_perms(vpath, uname),
|
self.vfs.get_perms(vpath, uname),
|
||||||
stl.st_mtime,
|
stl.st_mtime,
|
||||||
st.st_size,
|
st.st_size,
|
||||||
ip,
|
ip,
|
||||||
@@ -3991,7 +4048,7 @@ class Up2k(object):
|
|||||||
vpath,
|
vpath,
|
||||||
"",
|
"",
|
||||||
uname,
|
uname,
|
||||||
self.asrv.vfs.get_perms(vpath, uname),
|
self.vfs.get_perms(vpath, uname),
|
||||||
stl.st_mtime,
|
stl.st_mtime,
|
||||||
st.st_size,
|
st.st_size,
|
||||||
ip,
|
ip,
|
||||||
@@ -4011,17 +4068,226 @@ class Up2k(object):
|
|||||||
|
|
||||||
return n_files, ok + ok2, ng + ng2
|
return n_files, ok + ok2, ng + ng2
|
||||||
|
|
||||||
|
def handle_cp(self, uname: str, ip: str, svp: str, dvp: str) -> str:
|
||||||
|
if svp == dvp or dvp.startswith(svp + "/"):
|
||||||
|
raise Pebkac(400, "cp: cannot copy parent into subfolder")
|
||||||
|
|
||||||
|
svn, srem = self.vfs.get(svp, uname, True, False)
|
||||||
|
svn_dbv, _ = svn.get_dbv(srem)
|
||||||
|
sabs = svn.canonical(srem, False)
|
||||||
|
curs: set["sqlite3.Cursor"] = set()
|
||||||
|
self.db_act = self.vol_act[svn_dbv.realpath] = time.time()
|
||||||
|
|
||||||
|
st = bos.stat(sabs)
|
||||||
|
if stat.S_ISREG(st.st_mode) or stat.S_ISLNK(st.st_mode):
|
||||||
|
with self.mutex:
|
||||||
|
try:
|
||||||
|
ret = self._cp_file(uname, ip, svp, dvp, curs)
|
||||||
|
finally:
|
||||||
|
for v in curs:
|
||||||
|
v.connection.commit()
|
||||||
|
|
||||||
|
return ret
|
||||||
|
|
||||||
|
if not stat.S_ISDIR(st.st_mode):
|
||||||
|
raise Pebkac(400, "cannot copy type-0%o file" % (st.st_mode,))
|
||||||
|
|
||||||
|
permsets = [[True, False]]
|
||||||
|
scandir = not self.args.no_scandir
|
||||||
|
|
||||||
|
# don't use svn_dbv; would skip subvols due to _ls `if not rem:`
|
||||||
|
g = svn.walk("", srem, [], uname, permsets, True, scandir, True)
|
||||||
|
with self.mutex:
|
||||||
|
try:
|
||||||
|
for dbv, vrem, _, atop, files, rd, vd in g:
|
||||||
|
for fn in files:
|
||||||
|
self.db_act = self.vol_act[dbv.realpath] = time.time()
|
||||||
|
svpf = "/".join(x for x in [dbv.vpath, vrem, fn[0]] if x)
|
||||||
|
if not svpf.startswith(svp + "/"): # assert
|
||||||
|
self.log(min_ex(), 1)
|
||||||
|
t = "cp: bug at %s, top %s%s"
|
||||||
|
raise Pebkac(500, t % (svpf, svp, SEESLOG))
|
||||||
|
|
||||||
|
dvpf = dvp + svpf[len(svp) :]
|
||||||
|
self._cp_file(uname, ip, svpf, dvpf, curs)
|
||||||
|
|
||||||
|
for v in curs:
|
||||||
|
v.connection.commit()
|
||||||
|
curs.clear()
|
||||||
|
finally:
|
||||||
|
for v in curs:
|
||||||
|
v.connection.commit()
|
||||||
|
|
||||||
|
return "k"
|
||||||
|
|
||||||
|
def _cp_file(
|
||||||
|
self, uname: str, ip: str, svp: str, dvp: str, curs: set["sqlite3.Cursor"]
|
||||||
|
) -> str:
|
||||||
|
"""mutex(main) me; will mutex(reg)"""
|
||||||
|
svn, srem = self.vfs.get(svp, uname, True, False)
|
||||||
|
svn_dbv, srem_dbv = svn.get_dbv(srem)
|
||||||
|
|
||||||
|
dvn, drem = self.vfs.get(dvp, uname, False, True)
|
||||||
|
dvn, drem = dvn.get_dbv(drem)
|
||||||
|
|
||||||
|
sabs = svn.canonical(srem, False)
|
||||||
|
dabs = dvn.canonical(drem)
|
||||||
|
drd, dfn = vsplit(drem)
|
||||||
|
|
||||||
|
if bos.path.exists(dabs):
|
||||||
|
raise Pebkac(400, "cp2: target file exists")
|
||||||
|
|
||||||
|
st = stl = bos.lstat(sabs)
|
||||||
|
if stat.S_ISLNK(stl.st_mode):
|
||||||
|
is_link = True
|
||||||
|
try:
|
||||||
|
st = bos.stat(sabs)
|
||||||
|
except:
|
||||||
|
pass # broken symlink; keep as-is
|
||||||
|
elif not stat.S_ISREG(st.st_mode):
|
||||||
|
self.log("skipping type-0%o file [%s]" % (st.st_mode, sabs))
|
||||||
|
return ""
|
||||||
|
else:
|
||||||
|
is_link = False
|
||||||
|
|
||||||
|
ftime = stl.st_mtime
|
||||||
|
fsize = st.st_size
|
||||||
|
|
||||||
|
xbc = svn.flags.get("xbc")
|
||||||
|
xac = dvn.flags.get("xac")
|
||||||
|
if xbc:
|
||||||
|
if not runhook(
|
||||||
|
self.log,
|
||||||
|
None,
|
||||||
|
self,
|
||||||
|
"xbc",
|
||||||
|
xbc,
|
||||||
|
sabs,
|
||||||
|
svp,
|
||||||
|
"",
|
||||||
|
uname,
|
||||||
|
self.vfs.get_perms(svp, uname),
|
||||||
|
ftime,
|
||||||
|
fsize,
|
||||||
|
ip,
|
||||||
|
time.time(),
|
||||||
|
"",
|
||||||
|
):
|
||||||
|
t = "copy blocked by xbr server config: {}".format(svp)
|
||||||
|
self.log(t, 1)
|
||||||
|
raise Pebkac(405, t)
|
||||||
|
|
||||||
|
bos.makedirs(os.path.dirname(dabs))
|
||||||
|
|
||||||
|
c1, w, ftime_, fsize_, ip, at = self._find_from_vpath(
|
||||||
|
svn_dbv.realpath, srem_dbv
|
||||||
|
)
|
||||||
|
c2 = self.cur.get(dvn.realpath)
|
||||||
|
|
||||||
|
if w:
|
||||||
|
assert c1 # !rm
|
||||||
|
if c2 and c2 != c1:
|
||||||
|
self._copy_tags(c1, c2, w)
|
||||||
|
|
||||||
|
curs.add(c1)
|
||||||
|
|
||||||
|
if c2:
|
||||||
|
self.db_add(
|
||||||
|
c2,
|
||||||
|
{}, # skip upload hooks
|
||||||
|
drd,
|
||||||
|
dfn,
|
||||||
|
ftime,
|
||||||
|
fsize,
|
||||||
|
dvn.realpath,
|
||||||
|
dvn.vpath,
|
||||||
|
w,
|
||||||
|
w,
|
||||||
|
"",
|
||||||
|
"",
|
||||||
|
ip or "",
|
||||||
|
at or 0,
|
||||||
|
)
|
||||||
|
curs.add(c2)
|
||||||
|
else:
|
||||||
|
self.log("not found in src db: [{}]".format(svp))
|
||||||
|
|
||||||
|
try:
|
||||||
|
if is_link and st != stl:
|
||||||
|
# relink non-broken symlinks to still work after the move,
|
||||||
|
# but only resolve 1st level to maintain relativity
|
||||||
|
dlink = bos.readlink(sabs)
|
||||||
|
dlink = os.path.join(os.path.dirname(sabs), dlink)
|
||||||
|
dlink = bos.path.abspath(dlink)
|
||||||
|
self._symlink(dlink, dabs, dvn.flags, lmod=ftime)
|
||||||
|
else:
|
||||||
|
self._symlink(sabs, dabs, dvn.flags, lmod=ftime)
|
||||||
|
|
||||||
|
except OSError as ex:
|
||||||
|
if ex.errno != errno.EXDEV:
|
||||||
|
raise
|
||||||
|
|
||||||
|
self.log("using plain copy (%s):\n %s\n %s" % (ex.strerror, sabs, dabs))
|
||||||
|
b1, b2 = fsenc(sabs), fsenc(dabs)
|
||||||
|
is_link = os.path.islink(b1) # due to _relink
|
||||||
|
try:
|
||||||
|
shutil.copy2(b1, b2)
|
||||||
|
except:
|
||||||
|
try:
|
||||||
|
wunlink(self.log, dabs, dvn.flags)
|
||||||
|
except:
|
||||||
|
pass
|
||||||
|
|
||||||
|
if not is_link:
|
||||||
|
raise
|
||||||
|
|
||||||
|
# broken symlink? keep it as-is
|
||||||
|
try:
|
||||||
|
zb = os.readlink(b1)
|
||||||
|
os.symlink(zb, b2)
|
||||||
|
except:
|
||||||
|
wunlink(self.log, dabs, dvn.flags)
|
||||||
|
raise
|
||||||
|
|
||||||
|
if is_link:
|
||||||
|
try:
|
||||||
|
times = (int(time.time()), int(ftime))
|
||||||
|
bos.utime(dabs, times, False)
|
||||||
|
except:
|
||||||
|
pass
|
||||||
|
|
||||||
|
if xac:
|
||||||
|
runhook(
|
||||||
|
self.log,
|
||||||
|
None,
|
||||||
|
self,
|
||||||
|
"xac",
|
||||||
|
xac,
|
||||||
|
dabs,
|
||||||
|
dvp,
|
||||||
|
"",
|
||||||
|
uname,
|
||||||
|
self.vfs.get_perms(dvp, uname),
|
||||||
|
ftime,
|
||||||
|
fsize,
|
||||||
|
ip,
|
||||||
|
time.time(),
|
||||||
|
"",
|
||||||
|
)
|
||||||
|
|
||||||
|
return "k"
|
||||||
|
|
||||||
def handle_mv(self, uname: str, ip: str, svp: str, dvp: str) -> str:
|
def handle_mv(self, uname: str, ip: str, svp: str, dvp: str) -> str:
|
||||||
if svp == dvp or dvp.startswith(svp + "/"):
|
if svp == dvp or dvp.startswith(svp + "/"):
|
||||||
raise Pebkac(400, "mv: cannot move parent into subfolder")
|
raise Pebkac(400, "mv: cannot move parent into subfolder")
|
||||||
|
|
||||||
svn, srem = self.asrv.vfs.get(svp, uname, True, False, True)
|
svn, srem = self.vfs.get(svp, uname, True, False, True)
|
||||||
svn, srem = svn.get_dbv(srem)
|
jail, jail_rem = svn.get_dbv(srem)
|
||||||
sabs = svn.canonical(srem, False)
|
sabs = svn.canonical(srem, False)
|
||||||
curs: set["sqlite3.Cursor"] = set()
|
curs: set["sqlite3.Cursor"] = set()
|
||||||
self.db_act = self.vol_act[svn.realpath] = time.time()
|
self.db_act = self.vol_act[jail.realpath] = time.time()
|
||||||
|
|
||||||
if not srem:
|
if not jail_rem:
|
||||||
raise Pebkac(400, "mv: cannot move a mountpoint")
|
raise Pebkac(400, "mv: cannot move a mountpoint")
|
||||||
|
|
||||||
st = bos.lstat(sabs)
|
st = bos.lstat(sabs)
|
||||||
@@ -4035,7 +4301,9 @@ class Up2k(object):
|
|||||||
|
|
||||||
return ret
|
return ret
|
||||||
|
|
||||||
jail = svn.get_dbv(srem)[0]
|
if not stat.S_ISDIR(st.st_mode):
|
||||||
|
raise Pebkac(400, "cannot move type-0%o file" % (st.st_mode,))
|
||||||
|
|
||||||
permsets = [[True, False, True]]
|
permsets = [[True, False, True]]
|
||||||
scandir = not self.args.no_scandir
|
scandir = not self.args.no_scandir
|
||||||
|
|
||||||
@@ -4047,13 +4315,13 @@ class Up2k(object):
|
|||||||
raise Pebkac(400, "mv: source folder contains other volumes")
|
raise Pebkac(400, "mv: source folder contains other volumes")
|
||||||
|
|
||||||
g = svn.walk("", srem, [], uname, permsets, True, scandir, True)
|
g = svn.walk("", srem, [], uname, permsets, True, scandir, True)
|
||||||
|
with self.mutex:
|
||||||
|
try:
|
||||||
for dbv, vrem, _, atop, files, rd, vd in g:
|
for dbv, vrem, _, atop, files, rd, vd in g:
|
||||||
if dbv != jail:
|
if dbv != jail:
|
||||||
# the actual check (avoid toctou)
|
# the actual check (avoid toctou)
|
||||||
raise Pebkac(400, "mv: source folder contains other volumes")
|
raise Pebkac(400, "mv: source folder contains other volumes")
|
||||||
|
|
||||||
with self.mutex:
|
|
||||||
try:
|
|
||||||
for fn in files:
|
for fn in files:
|
||||||
self.db_act = self.vol_act[dbv.realpath] = time.time()
|
self.db_act = self.vol_act[dbv.realpath] = time.time()
|
||||||
svpf = "/".join(x for x in [dbv.vpath, vrem, fn[0]] if x)
|
svpf = "/".join(x for x in [dbv.vpath, vrem, fn[0]] if x)
|
||||||
@@ -4064,12 +4332,14 @@ class Up2k(object):
|
|||||||
|
|
||||||
dvpf = dvp + svpf[len(svp) :]
|
dvpf = dvp + svpf[len(svp) :]
|
||||||
self._mv_file(uname, ip, svpf, dvpf, curs)
|
self._mv_file(uname, ip, svpf, dvpf, curs)
|
||||||
|
|
||||||
|
for v in curs:
|
||||||
|
v.connection.commit()
|
||||||
|
curs.clear()
|
||||||
finally:
|
finally:
|
||||||
for v in curs:
|
for v in curs:
|
||||||
v.connection.commit()
|
v.connection.commit()
|
||||||
|
|
||||||
curs.clear()
|
|
||||||
|
|
||||||
rm_ok, rm_ng = rmdirs(self.log_func, scandir, True, sabs, 1)
|
rm_ok, rm_ng = rmdirs(self.log_func, scandir, True, sabs, 1)
|
||||||
|
|
||||||
for zsl in (rm_ok, rm_ng):
|
for zsl in (rm_ok, rm_ng):
|
||||||
@@ -4082,7 +4352,7 @@ class Up2k(object):
|
|||||||
rem = ap[len(sabs) :].replace(os.sep, "/").lstrip("/")
|
rem = ap[len(sabs) :].replace(os.sep, "/").lstrip("/")
|
||||||
vp = vjoin(dvp, rem)
|
vp = vjoin(dvp, rem)
|
||||||
try:
|
try:
|
||||||
dvn, drem = self.asrv.vfs.get(vp, uname, False, True)
|
dvn, drem = self.vfs.get(vp, uname, False, True)
|
||||||
bos.mkdir(dvn.canonical(drem))
|
bos.mkdir(dvn.canonical(drem))
|
||||||
except:
|
except:
|
||||||
pass
|
pass
|
||||||
@@ -4093,10 +4363,10 @@ class Up2k(object):
|
|||||||
self, uname: str, ip: str, svp: str, dvp: str, curs: set["sqlite3.Cursor"]
|
self, uname: str, ip: str, svp: str, dvp: str, curs: set["sqlite3.Cursor"]
|
||||||
) -> str:
|
) -> str:
|
||||||
"""mutex(main) me; will mutex(reg)"""
|
"""mutex(main) me; will mutex(reg)"""
|
||||||
svn, srem = self.asrv.vfs.get(svp, uname, True, False, True)
|
svn, srem = self.vfs.get(svp, uname, True, False, True)
|
||||||
svn, srem = svn.get_dbv(srem)
|
svn, srem = svn.get_dbv(srem)
|
||||||
|
|
||||||
dvn, drem = self.asrv.vfs.get(dvp, uname, False, True)
|
dvn, drem = self.vfs.get(dvp, uname, False, True)
|
||||||
dvn, drem = dvn.get_dbv(drem)
|
dvn, drem = dvn.get_dbv(drem)
|
||||||
|
|
||||||
sabs = svn.canonical(srem, False)
|
sabs = svn.canonical(srem, False)
|
||||||
@@ -4140,7 +4410,7 @@ class Up2k(object):
|
|||||||
svp,
|
svp,
|
||||||
"",
|
"",
|
||||||
uname,
|
uname,
|
||||||
self.asrv.vfs.get_perms(svp, uname),
|
self.vfs.get_perms(svp, uname),
|
||||||
ftime,
|
ftime,
|
||||||
fsize,
|
fsize,
|
||||||
ip,
|
ip,
|
||||||
@@ -4180,7 +4450,7 @@ class Up2k(object):
|
|||||||
dvp,
|
dvp,
|
||||||
"",
|
"",
|
||||||
uname,
|
uname,
|
||||||
self.asrv.vfs.get_perms(dvp, uname),
|
self.vfs.get_perms(dvp, uname),
|
||||||
ftime,
|
ftime,
|
||||||
fsize,
|
fsize,
|
||||||
ip,
|
ip,
|
||||||
@@ -4293,7 +4563,7 @@ class Up2k(object):
|
|||||||
dvp,
|
dvp,
|
||||||
"",
|
"",
|
||||||
uname,
|
uname,
|
||||||
self.asrv.vfs.get_perms(dvp, uname),
|
self.vfs.get_perms(dvp, uname),
|
||||||
ftime,
|
ftime,
|
||||||
fsize,
|
fsize,
|
||||||
ip,
|
ip,
|
||||||
@@ -4606,7 +4876,7 @@ class Up2k(object):
|
|||||||
vp_chk,
|
vp_chk,
|
||||||
job["host"],
|
job["host"],
|
||||||
job["user"],
|
job["user"],
|
||||||
self.asrv.vfs.get_perms(vp_chk, job["user"]),
|
self.vfs.get_perms(vp_chk, job["user"]),
|
||||||
job["lmod"],
|
job["lmod"],
|
||||||
job["size"],
|
job["size"],
|
||||||
job["addr"],
|
job["addr"],
|
||||||
@@ -4618,7 +4888,7 @@ class Up2k(object):
|
|||||||
self.log(t, 1)
|
self.log(t, 1)
|
||||||
raise Pebkac(403, t)
|
raise Pebkac(403, t)
|
||||||
if hr.get("reloc"):
|
if hr.get("reloc"):
|
||||||
x = pathmod(self.asrv.vfs, ap_chk, vp_chk, hr["reloc"])
|
x = pathmod(self.vfs, ap_chk, vp_chk, hr["reloc"])
|
||||||
if x:
|
if x:
|
||||||
zvfs = vfs
|
zvfs = vfs
|
||||||
pdir, _, job["name"], (vfs, rem) = x
|
pdir, _, job["name"], (vfs, rem) = x
|
||||||
@@ -4725,7 +4995,7 @@ class Up2k(object):
|
|||||||
|
|
||||||
def _snap_reg(self, ptop: str, reg: dict[str, dict[str, Any]]) -> None:
|
def _snap_reg(self, ptop: str, reg: dict[str, dict[str, Any]]) -> None:
|
||||||
now = time.time()
|
now = time.time()
|
||||||
histpath = self.asrv.vfs.histtab.get(ptop)
|
histpath = self.vfs.histtab.get(ptop)
|
||||||
if not histpath:
|
if not histpath:
|
||||||
return
|
return
|
||||||
|
|
||||||
@@ -4973,7 +5243,7 @@ class Up2k(object):
|
|||||||
else:
|
else:
|
||||||
fvp, fn = vsplit(fvp)
|
fvp, fn = vsplit(fvp)
|
||||||
|
|
||||||
x = pathmod(self.asrv.vfs, "", req_vp, {"vp": fvp, "fn": fn})
|
x = pathmod(self.vfs, "", req_vp, {"vp": fvp, "fn": fn})
|
||||||
if not x:
|
if not x:
|
||||||
t = "hook_fx(%s): failed to resolve %s based on %s"
|
t = "hook_fx(%s): failed to resolve %s based on %s"
|
||||||
self.log(t % (act, fvp, req_vp))
|
self.log(t % (act, fvp, req_vp))
|
||||||
|
|||||||
@@ -436,6 +436,27 @@ UNHUMANIZE_UNITS = {
|
|||||||
VF_CAREFUL = {"mv_re_t": 5, "rm_re_t": 5, "mv_re_r": 0.1, "rm_re_r": 0.1}
|
VF_CAREFUL = {"mv_re_t": 5, "rm_re_t": 5, "mv_re_r": 0.1, "rm_re_r": 0.1}
|
||||||
|
|
||||||
|
|
||||||
|
def read_ram() -> tuple[float, float]:
|
||||||
|
a = b = 0
|
||||||
|
try:
|
||||||
|
with open("/proc/meminfo", "rb", 0x10000) as f:
|
||||||
|
zsl = f.read(0x10000).decode("ascii", "replace").split("\n")
|
||||||
|
|
||||||
|
p = re.compile("^MemTotal:.* kB")
|
||||||
|
zs = next((x for x in zsl if p.match(x)))
|
||||||
|
a = int((int(zs.split()[1]) / 0x100000) * 100) / 100
|
||||||
|
|
||||||
|
p = re.compile("^MemAvailable:.* kB")
|
||||||
|
zs = next((x for x in zsl if p.match(x)))
|
||||||
|
b = int((int(zs.split()[1]) / 0x100000) * 100) / 100
|
||||||
|
except:
|
||||||
|
pass
|
||||||
|
return a, b
|
||||||
|
|
||||||
|
|
||||||
|
RAM_TOTAL, RAM_AVAIL = read_ram()
|
||||||
|
|
||||||
|
|
||||||
pybin = sys.executable or ""
|
pybin = sys.executable or ""
|
||||||
if EXE:
|
if EXE:
|
||||||
pybin = ""
|
pybin = ""
|
||||||
@@ -1030,6 +1051,7 @@ class MTHash(object):
|
|||||||
self.sz = 0
|
self.sz = 0
|
||||||
self.csz = 0
|
self.csz = 0
|
||||||
self.stop = False
|
self.stop = False
|
||||||
|
self.readsz = 1024 * 1024 * (2 if (RAM_AVAIL or 2) < 1 else 12)
|
||||||
self.omutex = threading.Lock()
|
self.omutex = threading.Lock()
|
||||||
self.imutex = threading.Lock()
|
self.imutex = threading.Lock()
|
||||||
self.work_q: Queue[int] = Queue()
|
self.work_q: Queue[int] = Queue()
|
||||||
@@ -1105,7 +1127,7 @@ class MTHash(object):
|
|||||||
while chunk_rem > 0:
|
while chunk_rem > 0:
|
||||||
with self.imutex:
|
with self.imutex:
|
||||||
f.seek(ofs)
|
f.seek(ofs)
|
||||||
buf = f.read(min(chunk_rem, 1024 * 1024 * 12))
|
buf = f.read(min(chunk_rem, self.readsz))
|
||||||
|
|
||||||
if not buf:
|
if not buf:
|
||||||
raise Exception("EOF at " + str(ofs))
|
raise Exception("EOF at " + str(ofs))
|
||||||
@@ -2791,7 +2813,10 @@ def sendfile_py(
|
|||||||
bufsz: int,
|
bufsz: int,
|
||||||
slp: float,
|
slp: float,
|
||||||
use_poll: bool,
|
use_poll: bool,
|
||||||
|
dls: dict[str, tuple[float, int]],
|
||||||
|
dl_id: str,
|
||||||
) -> int:
|
) -> int:
|
||||||
|
sent = 0
|
||||||
remains = upper - lower
|
remains = upper - lower
|
||||||
f.seek(lower)
|
f.seek(lower)
|
||||||
while remains > 0:
|
while remains > 0:
|
||||||
@@ -2808,6 +2833,10 @@ def sendfile_py(
|
|||||||
except:
|
except:
|
||||||
return remains
|
return remains
|
||||||
|
|
||||||
|
if dl_id:
|
||||||
|
sent += len(buf)
|
||||||
|
dls[dl_id] = (time.time(), sent)
|
||||||
|
|
||||||
return 0
|
return 0
|
||||||
|
|
||||||
|
|
||||||
@@ -2820,6 +2849,8 @@ def sendfile_kern(
|
|||||||
bufsz: int,
|
bufsz: int,
|
||||||
slp: float,
|
slp: float,
|
||||||
use_poll: bool,
|
use_poll: bool,
|
||||||
|
dls: dict[str, tuple[float, int]],
|
||||||
|
dl_id: str,
|
||||||
) -> int:
|
) -> int:
|
||||||
out_fd = s.fileno()
|
out_fd = s.fileno()
|
||||||
in_fd = f.fileno()
|
in_fd = f.fileno()
|
||||||
@@ -2832,7 +2863,7 @@ def sendfile_kern(
|
|||||||
while ofs < upper:
|
while ofs < upper:
|
||||||
stuck = stuck or time.time()
|
stuck = stuck or time.time()
|
||||||
try:
|
try:
|
||||||
req = min(2 ** 30, upper - ofs)
|
req = min(0x2000000, upper - ofs) # 32 MiB
|
||||||
if use_poll:
|
if use_poll:
|
||||||
poll.poll(10000)
|
poll.poll(10000)
|
||||||
else:
|
else:
|
||||||
@@ -2856,13 +2887,16 @@ def sendfile_kern(
|
|||||||
return upper - ofs
|
return upper - ofs
|
||||||
|
|
||||||
ofs += n
|
ofs += n
|
||||||
|
if dl_id:
|
||||||
|
dls[dl_id] = (time.time(), ofs - lower)
|
||||||
|
|
||||||
# print("sendfile: ok, sent {} now, {} total, {} remains".format(n, ofs - lower, upper - ofs))
|
# print("sendfile: ok, sent {} now, {} total, {} remains".format(n, ofs - lower, upper - ofs))
|
||||||
|
|
||||||
return 0
|
return 0
|
||||||
|
|
||||||
|
|
||||||
def statdir(
|
def statdir(
|
||||||
logger: Optional["RootLogger"], scandir: bool, lstat: bool, top: str
|
logger: Optional["RootLogger"], scandir: bool, lstat: bool, top: str, throw: bool
|
||||||
) -> Generator[tuple[str, os.stat_result], None, None]:
|
) -> Generator[tuple[str, os.stat_result], None, None]:
|
||||||
if lstat and ANYWIN:
|
if lstat and ANYWIN:
|
||||||
lstat = False
|
lstat = False
|
||||||
@@ -2898,6 +2932,12 @@ def statdir(
|
|||||||
logger(src, "[s] {} @ {}".format(repr(ex), fsdec(abspath)), 6)
|
logger(src, "[s] {} @ {}".format(repr(ex), fsdec(abspath)), 6)
|
||||||
|
|
||||||
except Exception as ex:
|
except Exception as ex:
|
||||||
|
if throw:
|
||||||
|
zi = getattr(ex, "errno", 0)
|
||||||
|
if zi == errno.ENOENT:
|
||||||
|
raise Pebkac(404, str(ex))
|
||||||
|
raise
|
||||||
|
|
||||||
t = "{} @ {}".format(repr(ex), top)
|
t = "{} @ {}".format(repr(ex), top)
|
||||||
if logger:
|
if logger:
|
||||||
logger(src, t, 1)
|
logger(src, t, 1)
|
||||||
@@ -2906,7 +2946,7 @@ def statdir(
|
|||||||
|
|
||||||
|
|
||||||
def dir_is_empty(logger: "RootLogger", scandir: bool, top: str):
|
def dir_is_empty(logger: "RootLogger", scandir: bool, top: str):
|
||||||
for _ in statdir(logger, scandir, False, top):
|
for _ in statdir(logger, scandir, False, top, False):
|
||||||
return False
|
return False
|
||||||
return True
|
return True
|
||||||
|
|
||||||
@@ -2919,7 +2959,7 @@ def rmdirs(
|
|||||||
top = os.path.dirname(top)
|
top = os.path.dirname(top)
|
||||||
depth -= 1
|
depth -= 1
|
||||||
|
|
||||||
stats = statdir(logger, scandir, lstat, top)
|
stats = statdir(logger, scandir, lstat, top, False)
|
||||||
dirs = [x[0] for x in stats if stat.S_ISDIR(x[1].st_mode)]
|
dirs = [x[0] for x in stats if stat.S_ISDIR(x[1].st_mode)]
|
||||||
dirs = [os.path.join(top, x) for x in dirs]
|
dirs = [os.path.join(top, x) for x in dirs]
|
||||||
ok = []
|
ok = []
|
||||||
|
|||||||
@@ -188,7 +188,6 @@ html.y {
|
|||||||
--srv-1: #555;
|
--srv-1: #555;
|
||||||
--srv-2: #c83;
|
--srv-2: #c83;
|
||||||
--srv-3: #c0a;
|
--srv-3: #c0a;
|
||||||
--srv-3b: rgba(255,68,204,0.6);
|
|
||||||
|
|
||||||
--tree-bg: #fff;
|
--tree-bg: #fff;
|
||||||
|
|
||||||
@@ -286,6 +285,7 @@ html.bz {
|
|||||||
--f-h-b1: #34384e;
|
--f-h-b1: #34384e;
|
||||||
--mp-sh: #11121d;
|
--mp-sh: #11121d;
|
||||||
/*--mp-b-bg: #2c3044;*/
|
/*--mp-b-bg: #2c3044;*/
|
||||||
|
--f-play-bg: var(--btn-1-bg);
|
||||||
}
|
}
|
||||||
html.by {
|
html.by {
|
||||||
--bg: #f2f2f2;
|
--bg: #f2f2f2;
|
||||||
@@ -389,8 +389,6 @@ html.cy {
|
|||||||
}
|
}
|
||||||
html.dz {
|
html.dz {
|
||||||
--fg: #4d4;
|
--fg: #4d4;
|
||||||
--fg-max: #fff;
|
|
||||||
--fg2-max: #fff;
|
|
||||||
--fg-weak: #2a2;
|
--fg-weak: #2a2;
|
||||||
|
|
||||||
--bg-u6: #020;
|
--bg-u6: #020;
|
||||||
@@ -400,11 +398,9 @@ html.dz {
|
|||||||
--bg-u2: #020;
|
--bg-u2: #020;
|
||||||
--bg-u1: #020;
|
--bg-u1: #020;
|
||||||
--bg: #010;
|
--bg: #010;
|
||||||
--bgg: var(--bg);
|
|
||||||
--bg-d1: #000;
|
--bg-d1: #000;
|
||||||
--bg-d2: #020;
|
--bg-d2: #020;
|
||||||
--bg-d3: #000;
|
--bg-d3: #000;
|
||||||
--bg-max: #000;
|
|
||||||
|
|
||||||
--tab-alt: #6f6;
|
--tab-alt: #6f6;
|
||||||
--row-alt: #030;
|
--row-alt: #030;
|
||||||
@@ -417,45 +413,21 @@ html.dz {
|
|||||||
--a-dark: #afa;
|
--a-dark: #afa;
|
||||||
--a-gray: #2a2;
|
--a-gray: #2a2;
|
||||||
|
|
||||||
--btn-fg: var(--a);
|
|
||||||
--btn-bg: rgba(64,128,64,0.15);
|
--btn-bg: rgba(64,128,64,0.15);
|
||||||
--btn-h-fg: var(--a-hil);
|
|
||||||
--btn-h-bg: #050;
|
--btn-h-bg: #050;
|
||||||
--btn-1-fg: #000;
|
--btn-1-fg: #000;
|
||||||
--btn-1-bg: #4f4;
|
--btn-1-bg: #4f4;
|
||||||
--btn-1h-fg: var(--btn-1-fg);
|
|
||||||
--btn-1h-bg: #3f3;
|
--btn-1h-bg: #3f3;
|
||||||
--btn-bs: 0 0 0 .1em #080 inset;
|
--btn-bs: 0 0 0 .1em #080 inset;
|
||||||
--btn-1-bs: a;
|
--btn-1-bs: a;
|
||||||
|
|
||||||
--chk-fg: var(--tab-alt);
|
|
||||||
--txt-sh: var(--bg-d2);
|
|
||||||
--txt-bg: var(--btn-bg);
|
|
||||||
|
|
||||||
--op-aa-fg: var(--a);
|
|
||||||
--op-aa-bg: var(--bg-d2);
|
|
||||||
--op-a-sh: rgba(0,0,0,0.5);
|
|
||||||
|
|
||||||
--u2-btn-b1: var(--fg-weak);
|
--u2-btn-b1: var(--fg-weak);
|
||||||
--u2-sbtn-b1: var(--fg-weak);
|
--u2-sbtn-b1: var(--fg-weak);
|
||||||
--u2-txt-bg: var(--bg-u5);
|
|
||||||
--u2-tab-bg: linear-gradient(to bottom, var(--bg), var(--bg-u1));
|
|
||||||
--u2-tab-b1: var(--fg-weak);
|
--u2-tab-b1: var(--fg-weak);
|
||||||
--u2-tab-1-fg: #fff;
|
--u2-tab-1-fg: #fff;
|
||||||
--u2-tab-1-bg: linear-gradient(to bottom, #151, var(--bg) 80%);
|
--u2-tab-1-bg: linear-gradient(to bottom, #151, var(--bg) 80%);
|
||||||
--u2-tab-1-b1: #7c5;
|
|
||||||
--u2-tab-1-b2: #583;
|
|
||||||
--u2-tab-1-sh: #280;
|
|
||||||
--u2-b-fg: #fff;
|
|
||||||
--u2-b1-bg: #3a3;
|
--u2-b1-bg: #3a3;
|
||||||
--u2-b2-bg: #3a3;
|
--u2-b2-bg: #3a3;
|
||||||
--u2-inf-bg: #07a;
|
|
||||||
--u2-inf-b1: #0be;
|
|
||||||
--u2-ok-bg: #380;
|
|
||||||
--u2-ok-b1: #8e4;
|
|
||||||
--u2-err-bg: #900;
|
|
||||||
--u2-err-b1: #d06;
|
|
||||||
--ud-b1: #888;
|
|
||||||
|
|
||||||
--sort-1: #fff;
|
--sort-1: #fff;
|
||||||
--sort-2: #3f3;
|
--sort-2: #3f3;
|
||||||
@@ -467,47 +439,12 @@ html.dz {
|
|||||||
|
|
||||||
--tree-bg: #010;
|
--tree-bg: #010;
|
||||||
|
|
||||||
--g-play-bg: #750;
|
|
||||||
--g-play-b1: #c90;
|
|
||||||
--g-play-b2: #da4;
|
|
||||||
--g-play-sh: #b83;
|
|
||||||
|
|
||||||
--g-sel-fg: #fff;
|
|
||||||
--g-sel-bg: #925;
|
|
||||||
--g-sel-b1: #c37;
|
--g-sel-b1: #c37;
|
||||||
--g-sel-sh: #b36;
|
--g-sel-sh: #b36;
|
||||||
--g-fsel-bg: #d39;
|
|
||||||
--g-fsel-b1: #d48;
|
--g-fsel-b1: #d48;
|
||||||
--g-fsel-ts: #804;
|
|
||||||
--g-fg: var(--a-hil);
|
|
||||||
--g-bg: var(--bg-u2);
|
|
||||||
--g-b1: var(--bg-u4);
|
|
||||||
--g-b2: var(--bg-u5);
|
|
||||||
--g-g1: var(--bg-u2);
|
|
||||||
--g-g2: var(--bg-u5);
|
|
||||||
--g-f-bg: var(--bg-u4);
|
|
||||||
--g-f-b1: var(--bg-u5);
|
|
||||||
--g-f-fg: var(--a-hil);
|
|
||||||
--g-sh: rgba(0,0,0,0.3);
|
|
||||||
|
|
||||||
--f-sh1: 0.33;
|
|
||||||
--f-sh2: 0.02;
|
|
||||||
--f-sh3: 0.2;
|
|
||||||
--f-h-b1: #3b3;
|
--f-h-b1: #3b3;
|
||||||
|
|
||||||
--f-play-bg: #fc5;
|
|
||||||
--f-play-fg: #000;
|
|
||||||
--f-sel-sh: #fc0;
|
|
||||||
--f-gray: #999;
|
|
||||||
|
|
||||||
--fm-off: #f6c;
|
|
||||||
--mp-sh: var(--bg-d3);
|
|
||||||
|
|
||||||
--err-fg: #fff;
|
|
||||||
--err-bg: #a20;
|
|
||||||
--err-b1: #f00;
|
|
||||||
--err-ts: #500;
|
|
||||||
|
|
||||||
text-shadow: none;
|
text-shadow: none;
|
||||||
font-family: 'scp', monospace, monospace;
|
font-family: 'scp', monospace, monospace;
|
||||||
font-family: var(--font-mono), 'scp', monospace, monospace;
|
font-family: var(--font-mono), 'scp', monospace, monospace;
|
||||||
@@ -1710,6 +1647,18 @@ html.dz .btn {
|
|||||||
background: var(--btn-1-bg);
|
background: var(--btn-1-bg);
|
||||||
text-shadow: none;
|
text-shadow: none;
|
||||||
}
|
}
|
||||||
|
#tree ul a.ld::before {
|
||||||
|
font-weight: bold;
|
||||||
|
font-family: sans-serif;
|
||||||
|
display: inline-block;
|
||||||
|
text-align: center;
|
||||||
|
width: 1em;
|
||||||
|
margin: 0 .3em 0 -1.3em;
|
||||||
|
color: var(--fg-max);
|
||||||
|
opacity: 0;
|
||||||
|
content: '◠';
|
||||||
|
animation: .5s linear infinite forwards spin, ease .25s 1 forwards fadein;
|
||||||
|
}
|
||||||
#tree ul a.par {
|
#tree ul a.par {
|
||||||
color: var(--fg-max);
|
color: var(--fg-max);
|
||||||
}
|
}
|
||||||
@@ -1931,11 +1880,10 @@ html.y #tree.nowrap .ntree a+a:hover {
|
|||||||
#rn_f.m td+td {
|
#rn_f.m td+td {
|
||||||
width: 50%;
|
width: 50%;
|
||||||
}
|
}
|
||||||
#rn_f .err td {
|
#rn_f .err td,
|
||||||
background: var(--err-bg);
|
#rn_f .err input[readonly],
|
||||||
color: var(--fg-max);
|
#rui .ng input[readonly] {
|
||||||
}
|
color: var(--err-fg);
|
||||||
#rn_f .err input[readonly] {
|
|
||||||
background: var(--err-bg);
|
background: var(--err-bg);
|
||||||
}
|
}
|
||||||
#rui input[readonly] {
|
#rui input[readonly] {
|
||||||
|
|||||||
@@ -37,8 +37,9 @@ var Ls = {
|
|||||||
["T", "toggle thumbnails / icons"],
|
["T", "toggle thumbnails / icons"],
|
||||||
["🡅 A/D", "thumbnail size"],
|
["🡅 A/D", "thumbnail size"],
|
||||||
["ctrl-K", "delete selected"],
|
["ctrl-K", "delete selected"],
|
||||||
["ctrl-X", "cut selected"],
|
["ctrl-X", "cut selection to clipboard"],
|
||||||
["ctrl-V", "paste into folder"],
|
["ctrl-C", "copy selection to clipboard"],
|
||||||
|
["ctrl-V", "paste (move/copy) here"],
|
||||||
["Y", "download selected"],
|
["Y", "download selected"],
|
||||||
["F2", "rename selected"],
|
["F2", "rename selected"],
|
||||||
|
|
||||||
@@ -83,7 +84,7 @@ var Ls = {
|
|||||||
["I/K", "prev/next file"],
|
["I/K", "prev/next file"],
|
||||||
["M", "close textfile"],
|
["M", "close textfile"],
|
||||||
["E", "edit textfile"],
|
["E", "edit textfile"],
|
||||||
["S", "select file (for cut/rename)"],
|
["S", "select file (for cut/copy/rename)"],
|
||||||
]
|
]
|
||||||
],
|
],
|
||||||
|
|
||||||
@@ -133,6 +134,7 @@ var Ls = {
|
|||||||
"wt_ren": "rename selected items$NHotkey: F2",
|
"wt_ren": "rename selected items$NHotkey: F2",
|
||||||
"wt_del": "delete selected items$NHotkey: ctrl-K",
|
"wt_del": "delete selected items$NHotkey: ctrl-K",
|
||||||
"wt_cut": "cut selected items <small>(then paste somewhere else)</small>$NHotkey: ctrl-X",
|
"wt_cut": "cut selected items <small>(then paste somewhere else)</small>$NHotkey: ctrl-X",
|
||||||
|
"wt_cpy": "copy selected items to clipboard$N(to paste them somewhere else)$NHotkey: ctrl-C",
|
||||||
"wt_pst": "paste a previously cut / copied selection$NHotkey: ctrl-V",
|
"wt_pst": "paste a previously cut / copied selection$NHotkey: ctrl-V",
|
||||||
"wt_selall": "select all files$NHotkey: ctrl-A (when file focused)",
|
"wt_selall": "select all files$NHotkey: ctrl-A (when file focused)",
|
||||||
"wt_selinv": "invert selection",
|
"wt_selinv": "invert selection",
|
||||||
@@ -327,6 +329,7 @@ var Ls = {
|
|||||||
"fr_emore": "select at least one item to rename",
|
"fr_emore": "select at least one item to rename",
|
||||||
"fd_emore": "select at least one item to delete",
|
"fd_emore": "select at least one item to delete",
|
||||||
"fc_emore": "select at least one item to cut",
|
"fc_emore": "select at least one item to cut",
|
||||||
|
"fcp_emore": "select at least one item to copy to clipboard",
|
||||||
|
|
||||||
"fs_sc": "share the folder you're in",
|
"fs_sc": "share the folder you're in",
|
||||||
"fs_ss": "share the selected files",
|
"fs_ss": "share the selected files",
|
||||||
@@ -379,16 +382,28 @@ var Ls = {
|
|||||||
"fc_ok": "cut {0} items",
|
"fc_ok": "cut {0} items",
|
||||||
"fc_warn": 'cut {0} items\n\nbut: only <b>this</b> browser-tab can paste them\n(since the selection is so absolutely massive)',
|
"fc_warn": 'cut {0} items\n\nbut: only <b>this</b> browser-tab can paste them\n(since the selection is so absolutely massive)',
|
||||||
|
|
||||||
"fp_ecut": "first cut some files / folders to paste / move\n\nnote: you can cut / paste across different browser tabs",
|
"fcc_ok": "copied {0} items to clipboard",
|
||||||
"fp_ename": "these {0} items cannot be moved here (names already exist):",
|
"fcc_warn": 'copied {0} items to clipboard\n\nbut: only <b>this</b> browser-tab can paste them\n(since the selection is so absolutely massive)',
|
||||||
|
|
||||||
|
"fp_apply": "use these names",
|
||||||
|
"fp_ecut": "first cut or copy some files / folders to paste / move\n\nnote: you can cut / paste across different browser tabs",
|
||||||
|
"fp_ename": "{0} items cannot be moved here because the names are already taken. Give them new names below to continue, or blank the name to skip them:",
|
||||||
|
"fcp_ename": "{0} items cannot be copied here because the names are already taken. Give them new names below to continue, or blank the name to skip them:",
|
||||||
|
"fp_emore": "there are still some filename collisions left to fix",
|
||||||
"fp_ok": "move OK",
|
"fp_ok": "move OK",
|
||||||
|
"fcp_ok": "copy OK",
|
||||||
"fp_busy": "moving {0} items...\n\n{1}",
|
"fp_busy": "moving {0} items...\n\n{1}",
|
||||||
|
"fcp_busy": "copying {0} items...\n\n{1}",
|
||||||
"fp_err": "move failed:\n",
|
"fp_err": "move failed:\n",
|
||||||
|
"fcp_err": "copy failed:\n",
|
||||||
"fp_confirm": "move these {0} items here?",
|
"fp_confirm": "move these {0} items here?",
|
||||||
|
"fcp_confirm": "copy these {0} items here?",
|
||||||
"fp_etab": 'failed to read clipboard from other browser tab',
|
"fp_etab": 'failed to read clipboard from other browser tab',
|
||||||
"fp_name": "uploading a file from your device. Give it a name:",
|
"fp_name": "uploading a file from your device. Give it a name:",
|
||||||
"fp_both_m": '<h6>choose what to paste</h6><code>Enter</code> = Move {0} files from «{1}»\n<code>ESC</code> = Upload {2} files from your device',
|
"fp_both_m": '<h6>choose what to paste</h6><code>Enter</code> = Move {0} files from «{1}»\n<code>ESC</code> = Upload {2} files from your device',
|
||||||
|
"fcp_both_m": '<h6>choose what to paste</h6><code>Enter</code> = Copy {0} files from «{1}»\n<code>ESC</code> = Upload {2} files from your device',
|
||||||
"fp_both_b": '<a href="#" id="modal-ok">Move</a><a href="#" id="modal-ng">Upload</a>',
|
"fp_both_b": '<a href="#" id="modal-ok">Move</a><a href="#" id="modal-ng">Upload</a>',
|
||||||
|
"fcp_both_b": '<a href="#" id="modal-ok">Copy</a><a href="#" id="modal-ng">Upload</a>',
|
||||||
|
|
||||||
"mk_noname": "type a name into the text field on the left before you do that :p",
|
"mk_noname": "type a name into the text field on the left before you do that :p",
|
||||||
|
|
||||||
@@ -400,7 +415,7 @@ var Ls = {
|
|||||||
"tvt_dl": "download this file$NHotkey: Y\">💾 download",
|
"tvt_dl": "download this file$NHotkey: Y\">💾 download",
|
||||||
"tvt_prev": "show previous document$NHotkey: i\">⬆ prev",
|
"tvt_prev": "show previous document$NHotkey: i\">⬆ prev",
|
||||||
"tvt_next": "show next document$NHotkey: K\">⬇ next",
|
"tvt_next": "show next document$NHotkey: K\">⬇ next",
|
||||||
"tvt_sel": "select file ( for cut / delete / ... )$NHotkey: S\">sel",
|
"tvt_sel": "select file ( for cut / copy / delete / ... )$NHotkey: S\">sel",
|
||||||
"tvt_edit": "open file in text editor$NHotkey: E\">✏️ edit",
|
"tvt_edit": "open file in text editor$NHotkey: E\">✏️ edit",
|
||||||
|
|
||||||
"gt_vau": "don't show videos, just play the audio\">🎧",
|
"gt_vau": "don't show videos, just play the audio\">🎧",
|
||||||
@@ -605,8 +620,9 @@ var Ls = {
|
|||||||
["T", "miniatyrbilder på/av"],
|
["T", "miniatyrbilder på/av"],
|
||||||
["🡅 A/D", "ikonstørrelse"],
|
["🡅 A/D", "ikonstørrelse"],
|
||||||
["ctrl-K", "slett valgte"],
|
["ctrl-K", "slett valgte"],
|
||||||
["ctrl-X", "klipp ut"],
|
["ctrl-X", "klipp ut valgte"],
|
||||||
["ctrl-V", "lim inn"],
|
["ctrl-C", "kopiér til utklippstavle"],
|
||||||
|
["ctrl-V", "lim inn (flytt/kopiér)"],
|
||||||
["Y", "last ned valgte"],
|
["Y", "last ned valgte"],
|
||||||
["F2", "endre navn på valgte"],
|
["F2", "endre navn på valgte"],
|
||||||
|
|
||||||
@@ -702,7 +718,8 @@ var Ls = {
|
|||||||
"wt_ren": "gi nye navn til de valgte filene$NSnarvei: F2",
|
"wt_ren": "gi nye navn til de valgte filene$NSnarvei: F2",
|
||||||
"wt_del": "slett de valgte filene$NSnarvei: ctrl-K",
|
"wt_del": "slett de valgte filene$NSnarvei: ctrl-K",
|
||||||
"wt_cut": "klipp ut de valgte filene <small>(for å lime inn et annet sted)</small>$NSnarvei: ctrl-X",
|
"wt_cut": "klipp ut de valgte filene <small>(for å lime inn et annet sted)</small>$NSnarvei: ctrl-X",
|
||||||
"wt_pst": "lim inn filer (som tidligere ble klippet ut et annet sted)$NSnarvei: ctrl-V",
|
"wt_cpy": "kopiér de valgte filene til utklippstavlen$N(for å lime inn et annet sted)$NSnarvei: ctrl-C",
|
||||||
|
"wt_pst": "lim inn filer (som tidligere ble klippet ut / kopiert et annet sted)$NSnarvei: ctrl-V",
|
||||||
"wt_selall": "velg alle filer$NSnarvei: ctrl-A (mens fokus er på en fil)",
|
"wt_selall": "velg alle filer$NSnarvei: ctrl-A (mens fokus er på en fil)",
|
||||||
"wt_selinv": "inverter utvalg",
|
"wt_selinv": "inverter utvalg",
|
||||||
"wt_selzip": "last ned de valgte filene som et arkiv",
|
"wt_selzip": "last ned de valgte filene som et arkiv",
|
||||||
@@ -845,7 +862,7 @@ var Ls = {
|
|||||||
"mt_oscv": "vis album-cover på infoskjermen\">bilde",
|
"mt_oscv": "vis album-cover på infoskjermen\">bilde",
|
||||||
"mt_follow": "bla slik at sangen som spilles alltid er synlig\">🎯",
|
"mt_follow": "bla slik at sangen som spilles alltid er synlig\">🎯",
|
||||||
"mt_compact": "tettpakket avspillerpanel\">⟎",
|
"mt_compact": "tettpakket avspillerpanel\">⟎",
|
||||||
"mt_uncache": "prøv denne hvis en sang ikke spiller riktig\">uncache",
|
"mt_uncache": "prøv denne hvis en sang ikke spiller riktig\">oppfrisk",
|
||||||
"mt_mloop": "repeter hele mappen\">🔁 gjenta",
|
"mt_mloop": "repeter hele mappen\">🔁 gjenta",
|
||||||
"mt_mnext": "hopp til neste mappe og fortsett\">📂 neste",
|
"mt_mnext": "hopp til neste mappe og fortsett\">📂 neste",
|
||||||
"mt_cflac": "konverter flac / wav-filer til opus\">flac",
|
"mt_cflac": "konverter flac / wav-filer til opus\">flac",
|
||||||
@@ -896,6 +913,7 @@ var Ls = {
|
|||||||
"fr_emore": "velg minst én fil som skal få nytt navn",
|
"fr_emore": "velg minst én fil som skal få nytt navn",
|
||||||
"fd_emore": "velg minst én fil som skal slettes",
|
"fd_emore": "velg minst én fil som skal slettes",
|
||||||
"fc_emore": "velg minst én fil som skal klippes ut",
|
"fc_emore": "velg minst én fil som skal klippes ut",
|
||||||
|
"fcp_emore": "velg minst én fil som skal kopieres til utklippstavlen",
|
||||||
|
|
||||||
"fs_sc": "del mappen du er i nå",
|
"fs_sc": "del mappen du er i nå",
|
||||||
"fs_ss": "del de valgte filene",
|
"fs_ss": "del de valgte filene",
|
||||||
@@ -948,16 +966,28 @@ var Ls = {
|
|||||||
"fc_ok": "klippet ut {0} filer",
|
"fc_ok": "klippet ut {0} filer",
|
||||||
"fc_warn": 'klippet ut {0} filer\n\nmen: kun <b>denne</b> nettleserfanen har mulighet til å lime dem inn et annet sted, siden antallet filer er helt hinsides',
|
"fc_warn": 'klippet ut {0} filer\n\nmen: kun <b>denne</b> nettleserfanen har mulighet til å lime dem inn et annet sted, siden antallet filer er helt hinsides',
|
||||||
|
|
||||||
"fp_ecut": "du må klippe ut noen filer / mapper først\n\nmerk: du kan gjerne jobbe på kryss av nettleserfaner; klippe ut i én fane, lime inn i en annen",
|
"fcc_ok": "kopierte {0} filer til utklippstavlen",
|
||||||
"fp_ename": "disse {0} filene kan ikke flyttes til målmappen fordi det allerede finnes filer med samme navn:",
|
"fcc_warn": 'kopierte {0} filer til utklippstavlen\n\nmen: kun <b>denne</b> nettleserfanen har mulighet til å lime dem inn et annet sted, siden antallet filer er helt hinsides',
|
||||||
|
|
||||||
|
"fp_apply": "bekreft og lim inn nå",
|
||||||
|
"fp_ecut": "du må klippe ut eller kopiere noen filer / mapper først\n\nmerk: du kan gjerne jobbe på kryss av nettleserfaner; klippe ut i én fane, lime inn i en annen",
|
||||||
|
"fp_ename": "{0} filer kan ikke flyttes til målmappen fordi det allerede finnes filer med samme navn. Gi dem nye navn nedenfor, eller gi dem et blankt navn for å hoppe over dem:",
|
||||||
|
"fcp_ename": "{0} filer kan ikke kopieres til målmappen fordi det allerede finnes filer med samme navn. Gi dem nye navn nedenfor, eller gi dem et blankt navn for å hoppe over dem:",
|
||||||
|
"fp_emore": "det er fortsatt flere navn som må endres",
|
||||||
"fp_ok": "flytting OK",
|
"fp_ok": "flytting OK",
|
||||||
|
"fcp_ok": "kopiering OK",
|
||||||
"fp_busy": "flytter {0} filer...\n\n{1}",
|
"fp_busy": "flytter {0} filer...\n\n{1}",
|
||||||
|
"fcp_busy": "kopierer {0} filer...\n\n{1}",
|
||||||
"fp_err": "flytting feilet:\n",
|
"fp_err": "flytting feilet:\n",
|
||||||
|
"fcp_err": "kopiering feilet:\n",
|
||||||
"fp_confirm": "flytt disse {0} filene hit?",
|
"fp_confirm": "flytt disse {0} filene hit?",
|
||||||
|
"fcp_confirm": "kopiér disse {0} filene hit?",
|
||||||
"fp_etab": 'kunne ikke lese listen med filer ifra den andre nettleserfanen',
|
"fp_etab": 'kunne ikke lese listen med filer ifra den andre nettleserfanen',
|
||||||
"fp_name": "Laster opp én fil fra enheten din. Velg filnavn:",
|
"fp_name": "Laster opp én fil fra enheten din. Velg filnavn:",
|
||||||
"fp_both_m": '<h6>hva skal limes inn her?</h6><code>Enter</code> = Flytt {0} filer fra «{1}»\n<code>ESC</code> = Last opp {2} filer fra enheten din',
|
"fp_both_m": '<h6>hva skal limes inn her?</h6><code>Enter</code> = Flytt {0} filer fra «{1}»\n<code>ESC</code> = Last opp {2} filer fra enheten din',
|
||||||
|
"fcp_both_m": '<h6>hva skal limes inn her?</h6><code>Enter</code> = Kopiér {0} filer fra «{1}»\n<code>ESC</code> = Last opp {2} filer fra enheten din',
|
||||||
"fp_both_b": '<a href="#" id="modal-ok">Flytt</a><a href="#" id="modal-ng">Last opp</a>',
|
"fp_both_b": '<a href="#" id="modal-ok">Flytt</a><a href="#" id="modal-ng">Last opp</a>',
|
||||||
|
"fcp_both_b": '<a href="#" id="modal-ok">Kopiér</a><a href="#" id="modal-ng">Last opp</a>',
|
||||||
|
|
||||||
"mk_noname": "skriv inn et navn i tekstboksen til venstre først :p",
|
"mk_noname": "skriv inn et navn i tekstboksen til venstre først :p",
|
||||||
|
|
||||||
@@ -1176,6 +1206,7 @@ var Ls = {
|
|||||||
["🡅 A/D", "缩略图大小"],
|
["🡅 A/D", "缩略图大小"],
|
||||||
["ctrl-K", "删除选中项"],
|
["ctrl-K", "删除选中项"],
|
||||||
["ctrl-X", "剪切选中项"],
|
["ctrl-X", "剪切选中项"],
|
||||||
|
["ctrl-C", "复制选中项"], //m
|
||||||
["ctrl-V", "粘贴到文件夹"],
|
["ctrl-V", "粘贴到文件夹"],
|
||||||
["Y", "下载选中项"],
|
["Y", "下载选中项"],
|
||||||
["F2", "重命名选中项"],
|
["F2", "重命名选中项"],
|
||||||
@@ -1271,6 +1302,7 @@ var Ls = {
|
|||||||
"wt_ren": "重命名选中的项目$N快捷键: F2",
|
"wt_ren": "重命名选中的项目$N快捷键: F2",
|
||||||
"wt_del": "删除选中的项目$N快捷键: ctrl-K",
|
"wt_del": "删除选中的项目$N快捷键: ctrl-K",
|
||||||
"wt_cut": "剪切选中的项目<small>(然后粘贴到其他地方)</small>$N快捷键: ctrl-X",
|
"wt_cut": "剪切选中的项目<small>(然后粘贴到其他地方)</small>$N快捷键: ctrl-X",
|
||||||
|
"wt_cpy": "将选中的项目复制到剪贴板<small>(然后粘贴到其他地方)</small>$N快捷键: ctrl-C", //m
|
||||||
"wt_pst": "粘贴之前剪切/复制的选择$N快捷键: ctrl-V",
|
"wt_pst": "粘贴之前剪切/复制的选择$N快捷键: ctrl-V",
|
||||||
"wt_selall": "选择所有文件$N快捷键: ctrl-A(当文件被聚焦时)",
|
"wt_selall": "选择所有文件$N快捷键: ctrl-A(当文件被聚焦时)",
|
||||||
"wt_selinv": "反转选择",
|
"wt_selinv": "反转选择",
|
||||||
@@ -1465,6 +1497,7 @@ var Ls = {
|
|||||||
"fr_emore": "选择至少一个项目以重命名",
|
"fr_emore": "选择至少一个项目以重命名",
|
||||||
"fd_emore": "选择至少一个项目以删除",
|
"fd_emore": "选择至少一个项目以删除",
|
||||||
"fc_emore": "选择至少一个项目以剪切",
|
"fc_emore": "选择至少一个项目以剪切",
|
||||||
|
"fcp_emore": "选择至少一个要复制到剪贴板的项目", //m
|
||||||
|
|
||||||
"fs_sc": "分享你所在的文件夹",
|
"fs_sc": "分享你所在的文件夹",
|
||||||
"fs_ss": "分享选定的文件",
|
"fs_ss": "分享选定的文件",
|
||||||
@@ -1517,16 +1550,28 @@ var Ls = {
|
|||||||
"fc_ok": "剪切 {0} 项",
|
"fc_ok": "剪切 {0} 项",
|
||||||
"fc_warn": '剪切 {0} 项\n\n但:只有 <b>这个</b> 浏览器标签页可以粘贴它们\n(因为选择非常庞大)',
|
"fc_warn": '剪切 {0} 项\n\n但:只有 <b>这个</b> 浏览器标签页可以粘贴它们\n(因为选择非常庞大)',
|
||||||
|
|
||||||
"fp_ecut": "首先剪切一些文件/文件夹以粘贴/移动\n\n注意:你可以在不同的浏览器标签页之间剪切/粘贴",
|
"fcc_ok": "已将 {0} 项复制到剪贴板", //m
|
||||||
"fp_ename": "这些 {0} 项不能移动到这里(名称已存在):",
|
"fcc_warn": '已将 {0} 项复制到剪贴板\n\n但:只有 <b>这个</b> 浏览器标签页可以粘贴它们\n(因为选择非常庞大)', //m
|
||||||
|
|
||||||
|
"fp_apply": "确认并立即粘贴", //m
|
||||||
|
"fp_ecut": "首先剪切或复制一些文件/文件夹以粘贴/移动\n\n注意:你可以在不同的浏览器标签页之间剪切/粘贴", //m
|
||||||
|
"fp_ename": "{0} 项不能移动到这里,因为名称已被占用。请在下方输入新名称以继续,或将名称留空以跳过这些项:", //m
|
||||||
|
"fcp_ename": "{0} 项不能复制到这里,因为名称已被占用。请在下方输入新名称以继续,或将名称留空以跳过这些项:", //m
|
||||||
|
"fp_emore": "还有一些文件名冲突需要解决", //m
|
||||||
"fp_ok": "移动成功",
|
"fp_ok": "移动成功",
|
||||||
|
"fcp_ok": "复制成功", //m
|
||||||
"fp_busy": "正在移动 {0} 项...\n\n{1}",
|
"fp_busy": "正在移动 {0} 项...\n\n{1}",
|
||||||
|
"fcp_busy": "正在复制 {0} 项...\n\n{1}", //m
|
||||||
"fp_err": "移动失败:\n",
|
"fp_err": "移动失败:\n",
|
||||||
|
"fcp_err": "复制失败:\n", //m
|
||||||
"fp_confirm": "将这些 {0} 项移动到这里?",
|
"fp_confirm": "将这些 {0} 项移动到这里?",
|
||||||
|
"fcp_confirm": "将这些 {0} 项复制到这里?", //m
|
||||||
"fp_etab": '无法从其他浏览器标签页读取剪贴板',
|
"fp_etab": '无法从其他浏览器标签页读取剪贴板',
|
||||||
"fp_name": "从你的设备上传一个文件。给它一个名字:",
|
"fp_name": "从你的设备上传一个文件。给它一个名字:",
|
||||||
"fp_both_m": '<h6>选择粘贴内容</h6><code>Enter</code> = 从 «{1}» 移动 {0} 个文件\n<code>ESC</code> = 从你的设备上传 {2} 个文件',
|
"fp_both_m": '<h6>选择粘贴内容</h6><code>Enter</code> = 从 «{1}» 移动 {0} 个文件\n<code>ESC</code> = 从你的设备上传 {2} 个文件',
|
||||||
|
"fcp_both_m": '<h6>选择粘贴内容</h6><code>Enter</code> = 从 «{1}» 复制 {0} 个文件\n<code>ESC</code> = 从你的设备上传 {2} 个文件', //m
|
||||||
"fp_both_b": '<a href="#" id="modal-ok">移动</a><a href="#" id="modal-ng">上传</a>',
|
"fp_both_b": '<a href="#" id="modal-ok">移动</a><a href="#" id="modal-ng">上传</a>',
|
||||||
|
"fcp_both_b": '<a href="#" id="modal-ok">复制</a><a href="#" id="modal-ng">上传</a>', //m
|
||||||
|
|
||||||
"mk_noname": "在左侧文本框中输入名称,然后再执行此操作 :p",
|
"mk_noname": "在左侧文本框中输入名称,然后再执行此操作 :p",
|
||||||
|
|
||||||
@@ -1771,6 +1816,7 @@ ebi('widget').innerHTML = (
|
|||||||
' href="#" id="fren" tt="' + L.wt_ren + '">✎<span>name</span></a><a' +
|
' href="#" id="fren" tt="' + L.wt_ren + '">✎<span>name</span></a><a' +
|
||||||
' href="#" id="fdel" tt="' + L.wt_del + '">⌫<span>del.</span></a><a' +
|
' href="#" id="fdel" tt="' + L.wt_del + '">⌫<span>del.</span></a><a' +
|
||||||
' href="#" id="fcut" tt="' + L.wt_cut + '">✂<span>cut</span></a><a' +
|
' href="#" id="fcut" tt="' + L.wt_cut + '">✂<span>cut</span></a><a' +
|
||||||
|
' href="#" id="fcpy" tt="' + L.wt_cpy + '">⧉<span>copy</span></a><a' +
|
||||||
' href="#" id="fpst" tt="' + L.wt_pst + '">📋<span>paste</span></a>' +
|
' href="#" id="fpst" tt="' + L.wt_pst + '">📋<span>paste</span></a>' +
|
||||||
'</span><span id="wzip"><a' +
|
'</span><span id="wzip"><a' +
|
||||||
' href="#" id="selall" tt="' + L.wt_selall + '">sel.<br />all</a><a' +
|
' href="#" id="selall" tt="' + L.wt_selall + '">sel.<br />all</a><a' +
|
||||||
@@ -2118,8 +2164,9 @@ function set_files_html(html) {
|
|||||||
// actx breaks background album playback on ios
|
// actx breaks background album playback on ios
|
||||||
var ACtx = !IPHONE && (window.AudioContext || window.webkitAudioContext),
|
var ACtx = !IPHONE && (window.AudioContext || window.webkitAudioContext),
|
||||||
ACB = sread('au_cbv') || 1,
|
ACB = sread('au_cbv') || 1,
|
||||||
noih = /[?&]v\b/.exec('' + location),
|
|
||||||
hash0 = location.hash,
|
hash0 = location.hash,
|
||||||
|
sloc0 = '' + location,
|
||||||
|
noih = /[?&]v\b/.exec(sloc0),
|
||||||
ldks = [],
|
ldks = [],
|
||||||
dks = {},
|
dks = {},
|
||||||
dk, mp;
|
dk, mp;
|
||||||
@@ -4091,6 +4138,12 @@ function eval_hash() {
|
|||||||
if (!im)
|
if (!im)
|
||||||
return toast.warn(10, L.im_hnf);
|
return toast.warn(10, L.im_hnf);
|
||||||
|
|
||||||
|
if (thegrid.sel)
|
||||||
|
setTimeout(function () {
|
||||||
|
thegrid.sel = true;
|
||||||
|
}, 1);
|
||||||
|
|
||||||
|
thegrid.sel = false;
|
||||||
im.click();
|
im.click();
|
||||||
im.scrollIntoView();
|
im.scrollIntoView();
|
||||||
}, 50);
|
}, 50);
|
||||||
@@ -4376,6 +4429,7 @@ var fileman = (function () {
|
|||||||
var bren = ebi('fren'),
|
var bren = ebi('fren'),
|
||||||
bdel = ebi('fdel'),
|
bdel = ebi('fdel'),
|
||||||
bcut = ebi('fcut'),
|
bcut = ebi('fcut'),
|
||||||
|
bcpy = ebi('fcpy'),
|
||||||
bpst = ebi('fpst'),
|
bpst = ebi('fpst'),
|
||||||
bshr = ebi('fshr'),
|
bshr = ebi('fshr'),
|
||||||
t_paste,
|
t_paste,
|
||||||
@@ -4388,14 +4442,19 @@ var fileman = (function () {
|
|||||||
catch (ex) { }
|
catch (ex) { }
|
||||||
|
|
||||||
r.render = function () {
|
r.render = function () {
|
||||||
if (r.clip === null)
|
if (r.clip === null) {
|
||||||
r.clip = jread('fman_clip', []).slice(1);
|
r.clip = jread('fman_clip', []).slice(1);
|
||||||
|
r.ccp = r.clip.length && r.clip[0] == '//c';
|
||||||
|
if (r.ccp)
|
||||||
|
r.clip.shift();
|
||||||
|
}
|
||||||
|
|
||||||
var sel = msel.getsel(),
|
var sel = msel.getsel(),
|
||||||
nsel = sel.length,
|
nsel = sel.length,
|
||||||
enren = nsel,
|
enren = nsel,
|
||||||
endel = nsel,
|
endel = nsel,
|
||||||
encut = nsel,
|
encut = nsel,
|
||||||
|
encpy = nsel,
|
||||||
enpst = r.clip && r.clip.length,
|
enpst = r.clip && r.clip.length,
|
||||||
hren = !(have_mv && has(perms, 'write') && has(perms, 'move')),
|
hren = !(have_mv && has(perms, 'write') && has(perms, 'move')),
|
||||||
hdel = !(have_del && has(perms, 'delete')),
|
hdel = !(have_del && has(perms, 'delete')),
|
||||||
@@ -4409,6 +4468,7 @@ var fileman = (function () {
|
|||||||
clmod(bren, 'en', enren);
|
clmod(bren, 'en', enren);
|
||||||
clmod(bdel, 'en', endel);
|
clmod(bdel, 'en', endel);
|
||||||
clmod(bcut, 'en', encut);
|
clmod(bcut, 'en', encut);
|
||||||
|
clmod(bcpy, 'en', encpy);
|
||||||
clmod(bpst, 'en', enpst);
|
clmod(bpst, 'en', enpst);
|
||||||
clmod(bshr, 'en', 1);
|
clmod(bshr, 'en', 1);
|
||||||
|
|
||||||
@@ -4703,9 +4763,9 @@ var fileman = (function () {
|
|||||||
|
|
||||||
var html = sel.length > 1 ? ['<div>'] : [
|
var html = sel.length > 1 ? ['<div>'] : [
|
||||||
'<div>',
|
'<div>',
|
||||||
'<button class="rn_dec" n="0" tt="' + L.frt_dec + '</button>',
|
'<button class="rn_dec" id="rn_dec_0" tt="' + L.frt_dec + '</button>',
|
||||||
'//',
|
'//',
|
||||||
'<button class="rn_reset" n="0" tt="' + L.frt_rst + '</button>'
|
'<button class="rn_reset" id="rn_reset_0" tt="' + L.frt_rst + '</button>'
|
||||||
];
|
];
|
||||||
|
|
||||||
html = html.concat([
|
html = html.concat([
|
||||||
@@ -4732,8 +4792,8 @@ var fileman = (function () {
|
|||||||
if (sel.length == 1)
|
if (sel.length == 1)
|
||||||
html.push(
|
html.push(
|
||||||
'<div><table id="rn_f">\n' +
|
'<div><table id="rn_f">\n' +
|
||||||
'<tr><td>old:</td><td><input type="text" id="rn_old" n="0" readonly /></td></tr>\n' +
|
'<tr><td>old:</td><td><input type="text" id="rn_old_0" readonly /></td></tr>\n' +
|
||||||
'<tr><td>new:</td><td><input type="text" id="rn_new" n="0" /></td></tr>');
|
'<tr><td>new:</td><td><input type="text" id="rn_new_0" /></td></tr>');
|
||||||
else {
|
else {
|
||||||
html.push(
|
html.push(
|
||||||
'<div><table id="rn_f" class="m">' +
|
'<div><table id="rn_f" class="m">' +
|
||||||
@@ -4742,10 +4802,10 @@ var fileman = (function () {
|
|||||||
html.push(
|
html.push(
|
||||||
'<tr><td>' +
|
'<tr><td>' +
|
||||||
(cheap ? '</td>' :
|
(cheap ? '</td>' :
|
||||||
'<button class="rn_dec" n="' + a + '">decode</button>' +
|
'<button class="rn_dec" id="rn_dec_' + a + '">decode</button>' +
|
||||||
'<button class="rn_reset" n="' + a + '">' + t_rst + '</button></td>') +
|
'<button class="rn_reset" id="rn_reset_' + a + '">' + t_rst + '</button></td>') +
|
||||||
'<td><input type="text" id="rn_new" n="' + a + '" /></td>' +
|
'<td><input type="text" id="rn_new_' + a + '" /></td>' +
|
||||||
'<td><input type="text" id="rn_old" n="' + a + '" readonly /></td></tr>');
|
'<td><input type="text" id="rn_old_' + a + '" readonly /></td></tr>');
|
||||||
}
|
}
|
||||||
html.push('</table></div>');
|
html.push('</table></div>');
|
||||||
|
|
||||||
@@ -4759,9 +4819,8 @@ var fileman = (function () {
|
|||||||
|
|
||||||
rui.innerHTML = html.join('\n');
|
rui.innerHTML = html.join('\n');
|
||||||
for (var a = 0; a < f.length; a++) {
|
for (var a = 0; a < f.length; a++) {
|
||||||
var k = '[n="' + a + '"]';
|
f[a].iold = ebi('rn_old_' + a);
|
||||||
f[a].iold = QS('#rn_old' + k);
|
f[a].inew = ebi('rn_new_' + a);
|
||||||
f[a].inew = QS('#rn_new' + k);
|
|
||||||
f[a].inew.value = f[a].iold.value = f[a].ofn;
|
f[a].inew.value = f[a].iold.value = f[a].ofn;
|
||||||
|
|
||||||
if (!cheap)
|
if (!cheap)
|
||||||
@@ -4772,11 +4831,11 @@ var fileman = (function () {
|
|||||||
if (kc.endsWith('Enter'))
|
if (kc.endsWith('Enter'))
|
||||||
return rn_apply();
|
return rn_apply();
|
||||||
};
|
};
|
||||||
QS('.rn_dec' + k).onclick = function (e) {
|
ebi('rn_dec_' + a).onclick = function (e) {
|
||||||
ev(e);
|
ev(e);
|
||||||
f[a].inew.value = uricom_dec(f[a].inew.value);
|
f[a].inew.value = uricom_dec(f[a].inew.value);
|
||||||
};
|
};
|
||||||
QS('.rn_reset' + k).onclick = function (e) {
|
ebi('rn_reset_' + a).onclick = function (e) {
|
||||||
ev(e);
|
ev(e);
|
||||||
rn_reset(a);
|
rn_reset(a);
|
||||||
};
|
};
|
||||||
@@ -4819,6 +4878,9 @@ var fileman = (function () {
|
|||||||
inew = ebi('rn_pnew'),
|
inew = ebi('rn_pnew'),
|
||||||
defp = '$lpad((tn),2,0). [(artist) - ](title).(ext)';
|
defp = '$lpad((tn),2,0). [(artist) - ](title).(ext)';
|
||||||
|
|
||||||
|
ire.value = sread('cpp_rn_re') || '';
|
||||||
|
ifmt.value = sread('cpp_rn_fmt') || '';
|
||||||
|
|
||||||
var presets = {};
|
var presets = {};
|
||||||
presets[defp] = ['', defp];
|
presets[defp] = ['', defp];
|
||||||
presets = jread("rn_pre", presets);
|
presets = jread("rn_pre", presets);
|
||||||
@@ -4909,6 +4971,8 @@ var fileman = (function () {
|
|||||||
|
|
||||||
function rn_apply(e) {
|
function rn_apply(e) {
|
||||||
ev(e);
|
ev(e);
|
||||||
|
swrite('cpp_rn_re', ire.value);
|
||||||
|
swrite('cpp_rn_fmt', ifmt.value);
|
||||||
if (r.win || r.slash) {
|
if (r.win || r.slash) {
|
||||||
var changed = 0;
|
var changed = 0;
|
||||||
for (var a = 0; a < f.length; a++) {
|
for (var a = 0; a < f.length; a++) {
|
||||||
@@ -4958,7 +5022,6 @@ var fileman = (function () {
|
|||||||
};
|
};
|
||||||
|
|
||||||
r.delete = function (e) {
|
r.delete = function (e) {
|
||||||
ev(e);
|
|
||||||
var sel = msel.getsel(),
|
var sel = msel.getsel(),
|
||||||
vps = [];
|
vps = [];
|
||||||
|
|
||||||
@@ -4968,6 +5031,8 @@ var fileman = (function () {
|
|||||||
if (!sel.length)
|
if (!sel.length)
|
||||||
return toast.err(3, L.fd_emore);
|
return toast.err(3, L.fd_emore);
|
||||||
|
|
||||||
|
ev(e);
|
||||||
|
|
||||||
if (clgot(bdel, 'hide'))
|
if (clgot(bdel, 'hide'))
|
||||||
return toast.err(3, L.fd_eperm);
|
return toast.err(3, L.fd_eperm);
|
||||||
|
|
||||||
@@ -5007,13 +5072,15 @@ var fileman = (function () {
|
|||||||
};
|
};
|
||||||
|
|
||||||
r.cut = function (e) {
|
r.cut = function (e) {
|
||||||
ev(e);
|
|
||||||
var sel = msel.getsel(),
|
var sel = msel.getsel(),
|
||||||
vps = [];
|
stamp = Date.now(),
|
||||||
|
vps = [stamp];
|
||||||
|
|
||||||
if (!sel.length)
|
if (!sel.length)
|
||||||
return toast.err(3, L.fc_emore);
|
return toast.err(3, L.fc_emore);
|
||||||
|
|
||||||
|
ev(e);
|
||||||
|
|
||||||
if (clgot(bcut, 'hide'))
|
if (clgot(bcut, 'hide'))
|
||||||
return toast.err(3, L.fc_eperm);
|
return toast.err(3, L.fc_eperm);
|
||||||
|
|
||||||
@@ -5040,9 +5107,11 @@ var fileman = (function () {
|
|||||||
catch (ex) { }
|
catch (ex) { }
|
||||||
}, 1);
|
}, 1);
|
||||||
|
|
||||||
|
r.ccp = false;
|
||||||
|
r.clip = vps.slice(1);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
var stamp = Date.now();
|
vps = JSON.stringify(vps);
|
||||||
vps = JSON.stringify([stamp].concat(vps));
|
|
||||||
if (vps.length > 1024 * 1024)
|
if (vps.length > 1024 * 1024)
|
||||||
throw 'a';
|
throw 'a';
|
||||||
|
|
||||||
@@ -5056,6 +5125,60 @@ var fileman = (function () {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
r.cpy = function (e) {
|
||||||
|
var sel = msel.getsel(),
|
||||||
|
stamp = Date.now(),
|
||||||
|
vps = [stamp, '//c'];
|
||||||
|
|
||||||
|
if (!sel.length)
|
||||||
|
return toast.err(3, L.fcp_emore);
|
||||||
|
|
||||||
|
ev(e);
|
||||||
|
|
||||||
|
var els = [], griden = thegrid.en;
|
||||||
|
for (var a = 0; a < sel.length; a++) {
|
||||||
|
vps.push(sel[a].vp);
|
||||||
|
if (sel.length < 100)
|
||||||
|
try {
|
||||||
|
if (griden)
|
||||||
|
els.push(QS('#ggrid>a[ref="' + sel[a].id + '"]'));
|
||||||
|
else
|
||||||
|
els.push(ebi(sel[a].id).closest('tr'));
|
||||||
|
|
||||||
|
clmod(els[a], 'fcut');
|
||||||
|
}
|
||||||
|
catch (ex) { }
|
||||||
|
}
|
||||||
|
|
||||||
|
setTimeout(function () {
|
||||||
|
try {
|
||||||
|
for (var a = 0; a < els.length; a++)
|
||||||
|
clmod(els[a], 'fcut', 1);
|
||||||
|
}
|
||||||
|
catch (ex) { }
|
||||||
|
}, 1);
|
||||||
|
|
||||||
|
if (vps.length < 3)
|
||||||
|
vps.pop();
|
||||||
|
|
||||||
|
r.ccp = true;
|
||||||
|
r.clip = vps.slice(2);
|
||||||
|
|
||||||
|
try {
|
||||||
|
vps = JSON.stringify(vps);
|
||||||
|
if (vps.length > 1024 * 1024)
|
||||||
|
throw 'a';
|
||||||
|
|
||||||
|
swrite('fman_clip', vps);
|
||||||
|
r.tx(stamp);
|
||||||
|
if (sel.length)
|
||||||
|
toast.inf(1.5, L.fcc_ok.format(sel.length));
|
||||||
|
}
|
||||||
|
catch (ex) {
|
||||||
|
toast.warn(30, L.fcc_warn.format(sel.length));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
document.onpaste = function (e) {
|
document.onpaste = function (e) {
|
||||||
var xfer = e.clipboardData || window.clipboardData;
|
var xfer = e.clipboardData || window.clipboardData;
|
||||||
if (!xfer || !xfer.files || !xfer.files.length)
|
if (!xfer || !xfer.files || !xfer.files.length)
|
||||||
@@ -5071,9 +5194,9 @@ var fileman = (function () {
|
|||||||
return r.clip_up(files);
|
return r.clip_up(files);
|
||||||
|
|
||||||
var src = r.clip.length == 1 ? r.clip[0] : vsplit(r.clip[0])[0],
|
var src = r.clip.length == 1 ? r.clip[0] : vsplit(r.clip[0])[0],
|
||||||
msg = L.fp_both_m.format(r.clip.length, src, files.length);
|
msg = (r.ccp ? L.fcp_both_m : L.fp_both_m).format(r.clip.length, src, files.length);
|
||||||
|
|
||||||
modal.confirm(msg, r.paste, function () { r.clip_up(files); }, null, L.fp_both_b);
|
modal.confirm(msg, r.paste, function () { r.clip_up(files); }, null, (r.ccp ? L.fcp_both_b : L.fp_both_b));
|
||||||
};
|
};
|
||||||
|
|
||||||
r.clip_up = function (files) {
|
r.clip_up = function (files) {
|
||||||
@@ -5129,65 +5252,147 @@ var fileman = (function () {
|
|||||||
if (clgot(bpst, 'hide'))
|
if (clgot(bpst, 'hide'))
|
||||||
return toast.err(3, L.fp_eperm);
|
return toast.err(3, L.fp_eperm);
|
||||||
|
|
||||||
var req = [],
|
var html = [
|
||||||
exists = [],
|
'<div>',
|
||||||
|
'<button id="rn_cancel" tt="' + L.frt_abrt + '</button>',
|
||||||
|
'<button id="rn_apply">✅ ' + L.fp_apply + '</button>',
|
||||||
|
' src: ' + esc(r.clip[0].replace(/[^/]+$/, '')),
|
||||||
|
'</div>',
|
||||||
|
'<p id="cnmt"></p>',
|
||||||
|
'<div><table id="rn_f" class="m">',
|
||||||
|
'<tr><td>' + L.fr_lnew + '</td><td>' + L.fr_lold + '</td></tr>',
|
||||||
|
],
|
||||||
|
ui = false,
|
||||||
|
f = [],
|
||||||
indir = [],
|
indir = [],
|
||||||
srcdir = vsplit(r.clip[0])[0],
|
srcdir = vsplit(r.clip[0])[0],
|
||||||
links = QSA('#files tbody td:nth-child(2) a');
|
links = QSA('#files tbody td:nth-child(2) a');
|
||||||
|
|
||||||
for (var a = 0, aa = links.length; a < aa; a++)
|
for (var a = 0, aa = links.length; a < aa; a++)
|
||||||
indir.push(vsplit(noq_href(links[a]))[1]);
|
indir.push(uricom_dec(vsplit(noq_href(links[a]))[1]));
|
||||||
|
|
||||||
for (var a = 0; a < r.clip.length; a++) {
|
for (var a = 0; a < r.clip.length; a++) {
|
||||||
var found = false;
|
var t = {
|
||||||
for (var b = 0; b < indir.length; b++) {
|
'ok': true,
|
||||||
if (r.clip[a].endsWith('/' + indir[b])) {
|
'src': r.clip[a],
|
||||||
exists.push(r.clip[a]);
|
'dst': uricom_dec(r.clip[a].split('/').pop()),
|
||||||
found = true;
|
};
|
||||||
}
|
f.push(t);
|
||||||
}
|
|
||||||
if (!found)
|
for (var b = 0; b < indir.length; b++)
|
||||||
req.push(r.clip[a]);
|
if (t.dst == indir[b]) {
|
||||||
|
t.ok = false;
|
||||||
|
ui = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (exists.length)
|
html.push('<tr' + (!t.ok ? ' class="ng"' : '') + '><td><input type="text" id="rn_new_' + a + '" value="' + esc(t.dst) + '" /></td><td><input type="text" id="rn_old_' + a + '" value="' + esc(t.dst) + '" readonly /></td></tr>');
|
||||||
toast.warn(30, L.fp_ename.format(exists.length) + '<ul>' + uricom_adec(exists, true).join('') + '</ul>');
|
}
|
||||||
|
|
||||||
if (!req.length)
|
|
||||||
return;
|
|
||||||
|
|
||||||
function paster() {
|
function paster() {
|
||||||
var xhr = new XHR(),
|
var t = f.shift();
|
||||||
vp = req.shift();
|
if (!t) {
|
||||||
|
toast.ok(2, r.ccp ? L.fcp_ok : L.fp_ok);
|
||||||
if (!vp) {
|
|
||||||
toast.ok(2, L.fp_ok);
|
|
||||||
treectl.goto();
|
treectl.goto();
|
||||||
r.tx(srcdir);
|
r.tx(srcdir);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
toast.show('inf r', 0, esc(L.fp_busy.format(req.length + 1, uricom_dec(vp))));
|
if (!t.dst)
|
||||||
|
return paster();
|
||||||
|
|
||||||
var dst = get_evpath() + vp.split('/').pop();
|
toast.show('inf r', 0, esc((r.ccp ? L.fcp_busy : L.fp_busy).format(f.length + 1, uricom_dec(t.src))));
|
||||||
|
|
||||||
xhr.open('POST', vp + '?move=' + dst, true);
|
var xhr = new XHR(),
|
||||||
|
act = r.ccp ? '?copy=' : '?move=',
|
||||||
|
dst = get_evpath() + uricom_enc(t.dst);
|
||||||
|
|
||||||
|
xhr.open('POST', t.src + act + dst, true);
|
||||||
xhr.onload = xhr.onerror = paste_cb;
|
xhr.onload = xhr.onerror = paste_cb;
|
||||||
xhr.send();
|
xhr.send();
|
||||||
}
|
}
|
||||||
function paste_cb() {
|
function paste_cb() {
|
||||||
if (this.status !== 201) {
|
if (this.status !== 201) {
|
||||||
var msg = unpre(this.responseText);
|
var msg = unpre(this.responseText);
|
||||||
toast.err(9, L.fp_err + msg);
|
toast.err(9, (r.ccp ? L.fcp_err : L.fp_err) + msg);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
paster();
|
paster();
|
||||||
}
|
}
|
||||||
|
function okgo() {
|
||||||
modal.confirm(L.fp_confirm.format(req.length) + '<ul>' + uricom_adec(req, true).join('') + '</ul>', function () {
|
|
||||||
paster();
|
paster();
|
||||||
jwrite('fman_clip', [Date.now()]);
|
jwrite('fman_clip', [Date.now()]);
|
||||||
}, null);
|
}
|
||||||
|
|
||||||
|
if (!ui) {
|
||||||
|
var src = [];
|
||||||
|
for (var a = 0; a < f.length; a++)
|
||||||
|
src.push(f[a].src);
|
||||||
|
|
||||||
|
return modal.confirm((r.ccp ? L.fcp_confirm : L.fp_confirm).format(f.length) + '<ul>' + uricom_adec(src, true).join('') + '</ul>', okgo, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
var rui = ebi('rui');
|
||||||
|
if (!rui) {
|
||||||
|
rui = mknod('div', 'rui');
|
||||||
|
document.body.appendChild(rui);
|
||||||
|
}
|
||||||
|
html.push('</table>');
|
||||||
|
rui.innerHTML = html.join('\n');
|
||||||
|
tt.att(rui);
|
||||||
|
|
||||||
|
function rn_apply(e) {
|
||||||
|
for (var a = 0; a < f.length; a++)
|
||||||
|
if (!f[a].ok) {
|
||||||
|
toast.err(30, L.fp_emore);
|
||||||
|
return setcnmt(true);
|
||||||
|
}
|
||||||
|
rn_cancel(e);
|
||||||
|
okgo();
|
||||||
|
}
|
||||||
|
function rn_cancel(e) {
|
||||||
|
ev(e);
|
||||||
|
rui.parentNode.removeChild(rui);
|
||||||
|
}
|
||||||
|
ebi('rn_cancel').onclick = rn_cancel;
|
||||||
|
ebi('rn_apply').onclick = rn_apply;
|
||||||
|
|
||||||
|
var first_bad = 0;
|
||||||
|
function setcnmt(sel) {
|
||||||
|
var nbad = 0;
|
||||||
|
for (var a = 0; a < f.length; a++) {
|
||||||
|
if (f[a].ok)
|
||||||
|
continue;
|
||||||
|
if (!nbad)
|
||||||
|
first_bad = a;
|
||||||
|
nbad += 1;
|
||||||
|
}
|
||||||
|
ebi('cnmt').innerHTML = (r.ccp ? L.fcp_ename : L.fp_ename).format(nbad);
|
||||||
|
if (sel && nbad) {
|
||||||
|
var el = ebi('rn_new_' + first_bad);
|
||||||
|
el.focus();
|
||||||
|
el.setSelectionRange(0, el.value.lastIndexOf('.'), "forward");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
setcnmt(true);
|
||||||
|
|
||||||
|
for (var a = 0; a < f.length; a++)
|
||||||
|
(function (a) {
|
||||||
|
var inew = ebi('rn_new_' + a);
|
||||||
|
inew.onkeydown = function (e) {
|
||||||
|
if (((e.code || e.key) + '').endsWith('Enter'))
|
||||||
|
return rn_apply();
|
||||||
};
|
};
|
||||||
|
inew.oninput = function (e) {
|
||||||
|
f[a].dst = this.value;
|
||||||
|
f[a].ok = true;
|
||||||
|
if (f[a].dst)
|
||||||
|
for (var b = 0; b < indir.length; b++)
|
||||||
|
if (indir[b] == this.value)
|
||||||
|
f[a].ok = false;
|
||||||
|
clmod(this.closest('tr'), 'ng', !f[a].ok);
|
||||||
|
setcnmt();
|
||||||
|
};
|
||||||
|
})(a);
|
||||||
|
}
|
||||||
|
|
||||||
function onmsg(msg) {
|
function onmsg(msg) {
|
||||||
r.clip = null;
|
r.clip = null;
|
||||||
@@ -5225,6 +5430,7 @@ var fileman = (function () {
|
|||||||
bren.onclick = r.rename;
|
bren.onclick = r.rename;
|
||||||
bdel.onclick = r.delete;
|
bdel.onclick = r.delete;
|
||||||
bcut.onclick = r.cut;
|
bcut.onclick = r.cut;
|
||||||
|
bcpy.onclick = r.cpy;
|
||||||
bpst.onclick = r.paste;
|
bpst.onclick = r.paste;
|
||||||
bshr.onclick = r.share;
|
bshr.onclick = r.share;
|
||||||
|
|
||||||
@@ -6047,6 +6253,19 @@ var thegrid = (function () {
|
|||||||
toast.warn(10, L.ul_btnlk);
|
toast.warn(10, L.ul_btnlk);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
if (/[?&]grid\b/.exec(sloc0))
|
||||||
|
swrite('griden', /[?&]grid=0\b/.exec(sloc0) ? 0 : 1)
|
||||||
|
|
||||||
|
if (/[?&]thumb\b/.exec(sloc0))
|
||||||
|
swrite('thumbs', /[?&]thumb=0\b/.exec(sloc0) ? 0 : 1)
|
||||||
|
|
||||||
|
if (/[?&]imgs\b/.exec(sloc0)) {
|
||||||
|
var n = /[?&]imgs=0\b/.exec(sloc0) ? 0 : 1;
|
||||||
|
swrite('griden', n);
|
||||||
|
if (n)
|
||||||
|
swrite('thumbs', 1);
|
||||||
|
}
|
||||||
|
|
||||||
bcfg_bind(r, 'thumbs', 'thumbs', true, r.setdirty);
|
bcfg_bind(r, 'thumbs', 'thumbs', true, r.setdirty);
|
||||||
bcfg_bind(r, 'ihop', 'ihop', true);
|
bcfg_bind(r, 'ihop', 'ihop', true);
|
||||||
bcfg_bind(r, 'vau', 'gridvau', false);
|
bcfg_bind(r, 'vau', 'gridvau', false);
|
||||||
@@ -6126,8 +6345,6 @@ function tree_neigh(n) {
|
|||||||
links[act].click();
|
links[act].click();
|
||||||
else
|
else
|
||||||
treectl.treego.call(links[act]);
|
treectl.treego.call(links[act]);
|
||||||
|
|
||||||
links[act].focus();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -6225,9 +6442,6 @@ var ahotkeys = function (e) {
|
|||||||
ae = document.activeElement,
|
ae = document.activeElement,
|
||||||
aet = ae && ae != document.body ? ae.nodeName.toLowerCase() : '';
|
aet = ae && ae != document.body ? ae.nodeName.toLowerCase() : '';
|
||||||
|
|
||||||
if (e.key == '?')
|
|
||||||
return hkhelp();
|
|
||||||
|
|
||||||
if (k == 'Escape' || k == 'Esc') {
|
if (k == 'Escape' || k == 'Esc') {
|
||||||
ae && ae.blur();
|
ae && ae.blur();
|
||||||
tt.hide();
|
tt.hide();
|
||||||
@@ -6308,15 +6522,24 @@ var ahotkeys = function (e) {
|
|||||||
if (aet && aet != 'a' && aet != 'tr' && aet != 'td' && aet != 'div' && aet != 'pre')
|
if (aet && aet != 'a' && aet != 'tr' && aet != 'td' && aet != 'div' && aet != 'pre')
|
||||||
return;
|
return;
|
||||||
|
|
||||||
if (ctrl(e)) {
|
if (e.key == '?')
|
||||||
|
return hkhelp();
|
||||||
|
|
||||||
|
if (!e.shiftKey && ctrl(e)) {
|
||||||
|
var sel = window.getSelection && window.getSelection() || {};
|
||||||
|
sel = sel && !sel.isCollapsed && sel.direction != 'none';
|
||||||
|
|
||||||
if (k == 'KeyX' || k == 'x')
|
if (k == 'KeyX' || k == 'x')
|
||||||
return fileman.cut();
|
return fileman.cut(e);
|
||||||
|
|
||||||
|
if ((k == 'KeyC' || k == 'c') && !sel)
|
||||||
|
return fileman.cpy(e);
|
||||||
|
|
||||||
if (k == 'KeyV' || k == 'v')
|
if (k == 'KeyV' || k == 'v')
|
||||||
return fileman.d_paste();
|
return fileman.d_paste(e);
|
||||||
|
|
||||||
if (k == 'KeyK' || k == 'k')
|
if (k == 'KeyK' || k == 'k')
|
||||||
return fileman.delete();
|
return fileman.delete(e);
|
||||||
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -7232,6 +7455,7 @@ var treectl = (function () {
|
|||||||
r.reqls(href, true);
|
r.reqls(href, true);
|
||||||
r.dir_cb = tree_scrollto;
|
r.dir_cb = tree_scrollto;
|
||||||
thegrid.setvis(true);
|
thegrid.setvis(true);
|
||||||
|
clmod(this, 'ld', 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
r.reqls = function (url, hpush, back, hydrate) {
|
r.reqls = function (url, hpush, back, hydrate) {
|
||||||
|
|||||||
BIN
copyparty/web/iiam.gif
Normal file
BIN
copyparty/web/iiam.gif
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 230 B |
@@ -90,6 +90,10 @@ table {
|
|||||||
text-align: left;
|
text-align: left;
|
||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
}
|
}
|
||||||
|
.vols td:empty,
|
||||||
|
.vols th:empty {
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
.num {
|
.num {
|
||||||
border-right: 1px solid #bbb;
|
border-right: 1px solid #bbb;
|
||||||
}
|
}
|
||||||
@@ -222,3 +226,6 @@ html.bz {
|
|||||||
color: #bbd;
|
color: #bbd;
|
||||||
background: #11121d;
|
background: #11121d;
|
||||||
}
|
}
|
||||||
|
html.bz .vols img {
|
||||||
|
filter: sepia(0.8) hue-rotate(180deg);
|
||||||
|
}
|
||||||
|
|||||||
@@ -44,6 +44,18 @@
|
|||||||
</table>
|
</table>
|
||||||
{%- endif %}
|
{%- endif %}
|
||||||
|
|
||||||
|
{%- if dls %}
|
||||||
|
<h1 id="ae">active downloads:</h1>
|
||||||
|
<table class="vols">
|
||||||
|
<thead><tr><th>%</th><th>sent</th><th>speed</th><th>eta</th><th>idle</th><th></th><th>dir</th><th>file</th></tr></thead>
|
||||||
|
<tbody>
|
||||||
|
{% for u in dls %}
|
||||||
|
<tr><td>{{ u[0] }}</td><td>{{ u[1] }}</td><td>{{ u[2] }}</td><td>{{ u[3] }}</td><td>{{ u[4] }}</td><td>{{ u[5] }}</td><td><a href="{{ u[6] }}">{{ u[7]|e }}</a></td><td>{{ u[8] }}</td></tr>
|
||||||
|
{% endfor %}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
{%- endif %}
|
||||||
|
|
||||||
{%- if avol %}
|
{%- if avol %}
|
||||||
<h1>admin panel:</h1>
|
<h1>admin panel:</h1>
|
||||||
<table><tr><td> <!-- hehehe -->
|
<table><tr><td> <!-- hehehe -->
|
||||||
|
|||||||
@@ -37,6 +37,7 @@ var Ls = {
|
|||||||
"ab1": "skru av no304",
|
"ab1": "skru av no304",
|
||||||
"ac1": "skru på no304",
|
"ac1": "skru på no304",
|
||||||
"ad1": "no304 stopper all bruk av cache. Hvis ikke k304 var nok, prøv denne. Vil mangedoble dataforbruk!",
|
"ad1": "no304 stopper all bruk av cache. Hvis ikke k304 var nok, prøv denne. Vil mangedoble dataforbruk!",
|
||||||
|
"ae1": "utgående:",
|
||||||
},
|
},
|
||||||
"eng": {
|
"eng": {
|
||||||
"d2": "shows the state of all active threads",
|
"d2": "shows the state of all active threads",
|
||||||
@@ -86,6 +87,7 @@ var Ls = {
|
|||||||
"ab1": "关闭 k304",
|
"ab1": "关闭 k304",
|
||||||
"ac1": "开启 k304",
|
"ac1": "开启 k304",
|
||||||
"ad1": "启用 no304 将禁用所有缓存;如果 k304 不够,可以尝试此选项。这将消耗大量的网络流量!", //m
|
"ad1": "启用 no304 将禁用所有缓存;如果 k304 不够,可以尝试此选项。这将消耗大量的网络流量!", //m
|
||||||
|
"ae1": "正在下载:", //m
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -75,7 +75,7 @@ html {
|
|||||||
top: 1px;
|
top: 1px;
|
||||||
right: 1px;
|
right: 1px;
|
||||||
left: 1px;
|
left: 1px;
|
||||||
animation: toastt var(--tmtime) steps(var(--tmstep)) forwards;
|
animation: toastt var(--tmtime) 0.07s steps(var(--tmstep)) forwards;
|
||||||
transform-origin: right;
|
transform-origin: right;
|
||||||
}
|
}
|
||||||
@keyframes toastt {
|
@keyframes toastt {
|
||||||
|
|||||||
@@ -1542,8 +1542,8 @@ var toast = (function () {
|
|||||||
|
|
||||||
var html = '';
|
var html = '';
|
||||||
if (sec) {
|
if (sec) {
|
||||||
setcvar('--tmtime', sec + 's');
|
setcvar('--tmtime', (sec - 0.15) + 's');
|
||||||
setcvar('--tmstep', sec * 15);
|
setcvar('--tmstep', Math.floor(sec * 20));
|
||||||
html += '<div id="toastt"></div>';
|
html += '<div id="toastt"></div>';
|
||||||
}
|
}
|
||||||
obj.innerHTML = html + '<a href="#" id="toastc">x</a><div id="toastb">' + lf2br(txt) + '</div>';
|
obj.innerHTML = html + '<a href="#" id="toastc">x</a><div id="toastb">' + lf2br(txt) + '</div>';
|
||||||
|
|||||||
@@ -1,3 +1,75 @@
|
|||||||
|
▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀
|
||||||
|
# 2024-1110-1932 `v1.16.0` COPYparty
|
||||||
|
|
||||||
|
## 🧪 new features
|
||||||
|
|
||||||
|
* #46 #115 copy/paste files and folders cacec9c1
|
||||||
|
* cut/paste still exists, but now you can copy too
|
||||||
|
* with a UI to rename files in case of filename collisions 56317b00
|
||||||
|
* files are created according to the dedup settings in the target volume (either full copies or symlinks/hardlinks)
|
||||||
|
* show currently active downloads in the controlpanel 8aba5aed
|
||||||
|
* can be made admin-only with `--dl-list=1` or disabled with `--dl-list=0`
|
||||||
|
* hides filenames of hidden files, and files from volumes where the viewer doesn't have access
|
||||||
|
* #114 async reinit on new [IdP users](https://github.com/9001/copyparty#identity-providers) 44ee07f0
|
||||||
|
* new IdP users can now always auth, even while a filesystem reindex is running
|
||||||
|
* ux:
|
||||||
|
* remember batch-rename settings from last time 6a8d5e17
|
||||||
|
* URL parameters to force grid/thumbs on/off 5718caa9
|
||||||
|
|
||||||
|
## 🩹 bugfixes
|
||||||
|
|
||||||
|
* folders that fail to list due to a corrupt HDD/filesystem will now return a 404 instead of an empty listing 119e88d8
|
||||||
|
* also fixes similar issues in u2c and partyfuse
|
||||||
|
* u2c (commandline uploader): detect and adapt to proxies with short connection keepalives c784e528
|
||||||
|
* ui/ux:
|
||||||
|
* show the "switch-to-https" button in 404-messages too efd8a32e
|
||||||
|
* the folder-loading indicator could steal keyboard focus d9962f65
|
||||||
|
* hotkey-help was very trigger-happy 71d9e010
|
||||||
|
|
||||||
|
## 🔧 other changes
|
||||||
|
|
||||||
|
* choose more conservative defaults when server has less than 1 GiB RAM 2bf9055c
|
||||||
|
* runs okay down to 128 MiB, but thumbnails die below 256 MiB
|
||||||
|
* update the [comparison to similar software](https://github.com/9001/copyparty/blob/hovudstraum/docs/versus.md) after years of optimizations on both sides 0ce7cf5e
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀
|
||||||
|
# 2024-1027-0751 `v1.15.10` temporary upload links
|
||||||
|
|
||||||
|
## 🧪 new features
|
||||||
|
|
||||||
|
* [shares](https://github.com/9001/copyparty#shares) can now be uploaded into, and unpost works too 4bdcbc1c
|
||||||
|
* useful to create temporary URLs for other people to upload to
|
||||||
|
* shares can be write-only, so visitors can't browse or see any files
|
||||||
|
* #110 HTTP 304 (caching):
|
||||||
|
* support `If-Range` for HTTP 206 159f51b1
|
||||||
|
* add server-side and client-side options to force-disable cache dd6dbdd9
|
||||||
|
* `--no304=1` shows a button in the controlpanel to disable caching
|
||||||
|
* `--no304=2` makes that button auto-enabled
|
||||||
|
* even when `--no304` is not specified, accessing the URL `/?setck=no304=y` force-disables cache
|
||||||
|
* when cache is force-disabled, browsers will waste a lot of network traffic / data usage
|
||||||
|
* might help to avoid bugs in browsers or proxies, for example if media files suddenly stop loading
|
||||||
|
* but such bugs should be exceedingly rare, so do not enable this unless actually necessary
|
||||||
|
|
||||||
|
## 🩹 bugfixes
|
||||||
|
|
||||||
|
* #110 HTTP 304 (caching):
|
||||||
|
* remove `Content-Length` and `Content-Type` response headers from 304 replies 91240236
|
||||||
|
* browsers don't need these, and some middlewares might get confused if they're present
|
||||||
|
* #113 fix crash on startup if `-j0` was combined with `--ipa` or `--ipu` 3a0d882c
|
||||||
|
* #111 fix javascript crash if `--u2sz` was set to an invalid value b13899c6
|
||||||
|
|
||||||
|
## 🔧 other changes
|
||||||
|
|
||||||
|
* #110 HTTP 304 (caching):
|
||||||
|
* never automatically enable k304 because the `Vary` header killed support for caching in msie anyways 63013cc5
|
||||||
|
* change time comparison for `If-Modified-Since` to require an exact timestamp match, instead of the intended "modified since". This technically violates the http-spec, but should be safer for backdating file mtimes 159f51b1
|
||||||
|
* new option `--ohead` to log response headers 7678a91b
|
||||||
|
* added [nintendo 3ds](https://github.com/user-attachments/assets/88deab3d-6cad-4017-8841-2f041472b853) to the [list of supported browsers](https://github.com/9001/copyparty#browser-support) cb81f0ad
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀
|
▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀
|
||||||
# 2024-1018-2342 `v1.15.9` rss server
|
# 2024-1018-2342 `v1.15.9` rss server
|
||||||
|
|
||||||
|
|||||||
@@ -140,6 +140,7 @@ authenticate using header `Cookie: cppwd=foo` or url param `&pw=foo`
|
|||||||
| GET | `?tar&j` | pregenerate jpg thumbnails |
|
| GET | `?tar&j` | pregenerate jpg thumbnails |
|
||||||
| GET | `?tar&p` | pregenerate audio waveforms |
|
| GET | `?tar&p` | pregenerate audio waveforms |
|
||||||
| GET | `?shares` | list your shared files/folders |
|
| GET | `?shares` | list your shared files/folders |
|
||||||
|
| GET | `?dls` | show active downloads (do this as admin) |
|
||||||
| GET | `?ups` | show recent uploads from your IP |
|
| GET | `?ups` | show recent uploads from your IP |
|
||||||
| GET | `?ups&filter=f` | ...where URL contains `f` |
|
| GET | `?ups&filter=f` | ...where URL contains `f` |
|
||||||
| GET | `?mime=foo` | specify return mimetype `foo` |
|
| GET | `?mime=foo` | specify return mimetype `foo` |
|
||||||
@@ -163,6 +164,7 @@ authenticate using header `Cookie: cppwd=foo` or url param `&pw=foo`
|
|||||||
|
|
||||||
| method | params | result |
|
| method | params | result |
|
||||||
|--|--|--|
|
|--|--|--|
|
||||||
|
| POST | `?copy=/foo/bar` | copy the file/folder at URL to /foo/bar |
|
||||||
| POST | `?move=/foo/bar` | move/rename the file/folder at URL to /foo/bar |
|
| POST | `?move=/foo/bar` | move/rename the file/folder at URL to /foo/bar |
|
||||||
|
|
||||||
| method | params | body | result |
|
| method | params | body | result |
|
||||||
@@ -208,6 +210,12 @@ upload modifiers:
|
|||||||
| method | params | result |
|
| method | params | result |
|
||||||
|--|--|--|
|
|--|--|--|
|
||||||
| GET | `?pw=x` | logout |
|
| GET | `?pw=x` | logout |
|
||||||
|
| GET | `?grid` | ui: show grid-view |
|
||||||
|
| GET | `?imgs` | ui: show grid-view with thumbnails |
|
||||||
|
| GET | `?grid=0` | ui: show list-view |
|
||||||
|
| GET | `?imgs=0` | ui: show list-view |
|
||||||
|
| GET | `?thumb` | ui, grid-mode: show thumbnails |
|
||||||
|
| GET | `?thumb=0` | ui, grid-mode: show icons |
|
||||||
|
|
||||||
|
|
||||||
# event hooks
|
# event hooks
|
||||||
|
|||||||
@@ -465,11 +465,13 @@ symbol legend,
|
|||||||
## [hfs3](https://rejetto.com/hfs/)
|
## [hfs3](https://rejetto.com/hfs/)
|
||||||
* nodejs; cross-platform
|
* nodejs; cross-platform
|
||||||
* vfs with gui config, per-volume permissions
|
* vfs with gui config, per-volume permissions
|
||||||
|
* tested locally, v0.53.2 on archlinux
|
||||||
* 🔵 uploads are resumable
|
* 🔵 uploads are resumable
|
||||||
* ⚠️ uploads are not segmented; max upload size 100 MiB on cloudflare
|
* ⚠️ uploads are not segmented; max upload size 100 MiB on cloudflare
|
||||||
* ⚠️ uploads are not accelerated (copyparty is 3x faster across the atlantic)
|
* ⚠️ uploads are not accelerated (copyparty is 3x faster across the atlantic)
|
||||||
* ⚠️ uploads are not integrity-checked
|
* ⚠️ uploads are not integrity-checked
|
||||||
* ⚠️ copies the file after upload; need twice filesize free disk space
|
* ⚠️ copies the file after upload; need twice filesize free disk space
|
||||||
|
* ⚠️ uploading small files is decent; `107` files per sec (copyparty does `670`/sec, 6x faster)
|
||||||
* ⚠️ doesn't support crazy filenames
|
* ⚠️ doesn't support crazy filenames
|
||||||
* ✅ config GUI
|
* ✅ config GUI
|
||||||
* ✅ download counter
|
* ✅ download counter
|
||||||
@@ -478,11 +480,12 @@ symbol legend,
|
|||||||
|
|
||||||
## [nextcloud](https://github.com/nextcloud/server)
|
## [nextcloud](https://github.com/nextcloud/server)
|
||||||
* php, mariadb
|
* php, mariadb
|
||||||
|
* tested locally, [linuxserver/nextcloud](https://hub.docker.com/r/linuxserver/nextcloud) v30.0.2 (sqlite)
|
||||||
* ⚠️ [isolated on-disk file hierarchy] in per-user folders
|
* ⚠️ [isolated on-disk file hierarchy] in per-user folders
|
||||||
* not that bad, can probably be remedied with bindmounts or maybe symlinks
|
* not that bad, can probably be remedied with bindmounts or maybe symlinks
|
||||||
* ⚠️ uploads not resumable / accelerated / integrity-checked
|
* ⚠️ uploads not resumable / accelerated / integrity-checked
|
||||||
* ⚠️ on cloudflare: max upload size 100 MiB
|
* ⚠️ on cloudflare: max upload size 100 MiB
|
||||||
* ⚠️ uploading small files is slow; `2.2` files per sec (copyparty does `87`/sec), tested locally with [linuxserver/nextcloud](https://hub.docker.com/r/linuxserver/nextcloud) (sqlite)
|
* ⚠️ uploading small files is slow; `4` files per sec (copyparty does `670`/sec, 160x faster)
|
||||||
* ⚠️ no write-only / upload-only folders
|
* ⚠️ no write-only / upload-only folders
|
||||||
* ⚠️ http/webdav only; no ftp, zeroconf
|
* ⚠️ http/webdav only; no ftp, zeroconf
|
||||||
* ⚠️ less awesome music player
|
* ⚠️ less awesome music player
|
||||||
@@ -498,11 +501,12 @@ symbol legend,
|
|||||||
|
|
||||||
## [seafile](https://github.com/haiwen/seafile)
|
## [seafile](https://github.com/haiwen/seafile)
|
||||||
* c, mariadb
|
* c, mariadb
|
||||||
|
* tested locally, [official container](https://manual.seafile.com/latest/docker/deploy_seafile_with_docker/) v11.0.13
|
||||||
* ⚠️ [isolated on-disk file hierarchy](https://manual.seafile.com/maintain/seafile_fsck/), incompatible with other software
|
* ⚠️ [isolated on-disk file hierarchy](https://manual.seafile.com/maintain/seafile_fsck/), incompatible with other software
|
||||||
* *much worse than nextcloud* in that regard
|
* *much worse than nextcloud* in that regard
|
||||||
* ⚠️ uploads not resumable / accelerated / integrity-checked
|
* ⚠️ uploads not resumable / accelerated / integrity-checked
|
||||||
* ⚠️ on cloudflare: max upload size 100 MiB
|
* ⚠️ on cloudflare: max upload size 100 MiB
|
||||||
* ⚠️ uploading small files is slow; `2.7` files per sec (copyparty does `87`/sec), tested locally with [official container](https://manual.seafile.com/docker/deploy_seafile_with_docker/)
|
* ⚠️ uploading small files is slow; `4.7` files per sec (copyparty does `670`/sec, 140x faster)
|
||||||
* ⚠️ no write-only / upload-only folders
|
* ⚠️ no write-only / upload-only folders
|
||||||
* ⚠️ big folders cannot be zip-downloaded
|
* ⚠️ big folders cannot be zip-downloaded
|
||||||
* ⚠️ http/webdav only; no ftp, zeroconf
|
* ⚠️ http/webdav only; no ftp, zeroconf
|
||||||
@@ -526,9 +530,11 @@ symbol legend,
|
|||||||
|
|
||||||
## [dufs](https://github.com/sigoden/dufs)
|
## [dufs](https://github.com/sigoden/dufs)
|
||||||
* rust; cross-platform (windows, linux, macos)
|
* rust; cross-platform (windows, linux, macos)
|
||||||
|
* tested locally, v0.43.0 on archlinux (plain binary)
|
||||||
* ⚠️ uploads not resumable / accelerated / integrity-checked
|
* ⚠️ uploads not resumable / accelerated / integrity-checked
|
||||||
* ⚠️ on cloudflare: max upload size 100 MiB
|
* ⚠️ on cloudflare: max upload size 100 MiB
|
||||||
* ⚠️ across the atlantic, copyparty is 3x faster
|
* ⚠️ across the atlantic, copyparty is 3x faster
|
||||||
|
* ⚠️ uploading small files is decent; `97` files per sec (copyparty does `670`/sec, 7x faster)
|
||||||
* ⚠️ doesn't support crazy filenames
|
* ⚠️ doesn't support crazy filenames
|
||||||
* ✅ per-url access control (copyparty is per-volume)
|
* ✅ per-url access control (copyparty is per-volume)
|
||||||
* 🔵 basic but really snappy ui
|
* 🔵 basic but really snappy ui
|
||||||
@@ -571,10 +577,12 @@ symbol legend,
|
|||||||
|
|
||||||
## [filebrowser](https://github.com/filebrowser/filebrowser)
|
## [filebrowser](https://github.com/filebrowser/filebrowser)
|
||||||
* go; cross-platform (windows, linux, mac)
|
* go; cross-platform (windows, linux, mac)
|
||||||
|
* tested locally, v2.31.2 on archlinux (plain binary)
|
||||||
* 🔵 uploads are resumable and segmented
|
* 🔵 uploads are resumable and segmented
|
||||||
* 🔵 multiple files are uploaded in parallel, but...
|
* 🔵 multiple files are uploaded in parallel, but...
|
||||||
* ⚠️ big files are not accelerated (copyparty is 5x faster across the atlantic)
|
* ⚠️ big files are not accelerated (copyparty is 5x faster across the atlantic)
|
||||||
* ⚠️ uploads are not integrity-checked
|
* ⚠️ uploads are not integrity-checked
|
||||||
|
* ⚠️ uploading small files is decent; `69` files per sec (copyparty does `670`/sec, 9x faster)
|
||||||
* ⚠️ http only; no webdav / ftp / zeroconf
|
* ⚠️ http only; no webdav / ftp / zeroconf
|
||||||
* ⚠️ doesn't support crazy filenames
|
* ⚠️ doesn't support crazy filenames
|
||||||
* ⚠️ no directory tree nav
|
* ⚠️ no directory tree nav
|
||||||
@@ -612,6 +620,7 @@ symbol legend,
|
|||||||
* ⚠️ no zeroconf (mdns/ssdp)
|
* ⚠️ no zeroconf (mdns/ssdp)
|
||||||
* ⚠️ impractical directory URLs
|
* ⚠️ impractical directory URLs
|
||||||
* ⚠️ AGPL licensed
|
* ⚠️ AGPL licensed
|
||||||
|
* 🔵 uploading small files is fast; `340` files per sec (copyparty does `670`/sec)
|
||||||
* 🔵 ftp, ftps, webdav
|
* 🔵 ftp, ftps, webdav
|
||||||
* ✅ sftp server
|
* ✅ sftp server
|
||||||
* ✅ settings gui
|
* ✅ settings gui
|
||||||
|
|||||||
@@ -94,6 +94,7 @@ copyparty/web/deps/prismd.css,
|
|||||||
copyparty/web/deps/scp.woff2,
|
copyparty/web/deps/scp.woff2,
|
||||||
copyparty/web/deps/sha512.ac.js,
|
copyparty/web/deps/sha512.ac.js,
|
||||||
copyparty/web/deps/sha512.hw.js,
|
copyparty/web/deps/sha512.hw.js,
|
||||||
|
copyparty/web/iiam.gif,
|
||||||
copyparty/web/md.css,
|
copyparty/web/md.css,
|
||||||
copyparty/web/md.html,
|
copyparty/web/md.html,
|
||||||
copyparty/web/md.js,
|
copyparty/web/md.js,
|
||||||
|
|||||||
@@ -79,6 +79,7 @@ var tl_cpanel = {
|
|||||||
"ab1": "disable no304",
|
"ab1": "disable no304",
|
||||||
"ac1": "enable no304",
|
"ac1": "enable no304",
|
||||||
"ad1": "enabling no304 will disable all caching; try this if k304 wasn't enough. This will waste a huge amount of network traffic!",
|
"ad1": "enabling no304 will disable all caching; try this if k304 wasn't enough. This will waste a huge amount of network traffic!",
|
||||||
|
"ae1": "active downloads:",
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -121,8 +122,9 @@ var tl_browser = {
|
|||||||
["T", "toggle thumbnails / icons"],
|
["T", "toggle thumbnails / icons"],
|
||||||
["🡅 A/D", "thumbnail size"],
|
["🡅 A/D", "thumbnail size"],
|
||||||
["ctrl-K", "delete selected"],
|
["ctrl-K", "delete selected"],
|
||||||
["ctrl-X", "cut selected"],
|
["ctrl-X", "cut selection to clipboard"],
|
||||||
["ctrl-V", "paste into folder"],
|
["ctrl-C", "copy selection to clipboard"],
|
||||||
|
["ctrl-V", "paste (move/copy) here"],
|
||||||
["Y", "download selected"],
|
["Y", "download selected"],
|
||||||
["F2", "rename selected"],
|
["F2", "rename selected"],
|
||||||
|
|
||||||
@@ -167,7 +169,7 @@ var tl_browser = {
|
|||||||
["I/K", "prev/next file"],
|
["I/K", "prev/next file"],
|
||||||
["M", "close textfile"],
|
["M", "close textfile"],
|
||||||
["E", "edit textfile"],
|
["E", "edit textfile"],
|
||||||
["S", "select file (for cut/rename)"],
|
["S", "select file (for cut/copy/rename)"],
|
||||||
]
|
]
|
||||||
],
|
],
|
||||||
|
|
||||||
@@ -217,6 +219,7 @@ var tl_browser = {
|
|||||||
"wt_ren": "rename selected items$NHotkey: F2",
|
"wt_ren": "rename selected items$NHotkey: F2",
|
||||||
"wt_del": "delete selected items$NHotkey: ctrl-K",
|
"wt_del": "delete selected items$NHotkey: ctrl-K",
|
||||||
"wt_cut": "cut selected items <small>(then paste somewhere else)</small>$NHotkey: ctrl-X",
|
"wt_cut": "cut selected items <small>(then paste somewhere else)</small>$NHotkey: ctrl-X",
|
||||||
|
"wt_cpy": "copy selected items to clipboard$N(to paste them somewhere else)$NHotkey: ctrl-C",
|
||||||
"wt_pst": "paste a previously cut / copied selection$NHotkey: ctrl-V",
|
"wt_pst": "paste a previously cut / copied selection$NHotkey: ctrl-V",
|
||||||
"wt_selall": "select all files$NHotkey: ctrl-A (when file focused)",
|
"wt_selall": "select all files$NHotkey: ctrl-A (when file focused)",
|
||||||
"wt_selinv": "invert selection",
|
"wt_selinv": "invert selection",
|
||||||
@@ -411,6 +414,7 @@ var tl_browser = {
|
|||||||
"fr_emore": "select at least one item to rename",
|
"fr_emore": "select at least one item to rename",
|
||||||
"fd_emore": "select at least one item to delete",
|
"fd_emore": "select at least one item to delete",
|
||||||
"fc_emore": "select at least one item to cut",
|
"fc_emore": "select at least one item to cut",
|
||||||
|
"fcp_emore": "select at least one item to copy",
|
||||||
|
|
||||||
"fs_sc": "share the folder you're in",
|
"fs_sc": "share the folder you're in",
|
||||||
"fs_ss": "share the selected files",
|
"fs_ss": "share the selected files",
|
||||||
@@ -463,16 +467,26 @@ var tl_browser = {
|
|||||||
"fc_ok": "cut {0} items",
|
"fc_ok": "cut {0} items",
|
||||||
"fc_warn": 'cut {0} items\n\nbut: only <b>this</b> browser-tab can paste them\n(since the selection is so absolutely massive)',
|
"fc_warn": 'cut {0} items\n\nbut: only <b>this</b> browser-tab can paste them\n(since the selection is so absolutely massive)',
|
||||||
|
|
||||||
"fp_ecut": "first cut some files / folders to paste / move\n\nnote: you can cut / paste across different browser tabs",
|
"fcc_ok": "copied {0} items to clipboard",
|
||||||
|
"fcc_warn": 'copied {0} items to clipboard\n\nbut: only <b>this</b> browser-tab can paste them\n(since the selection is so absolutely massive)',
|
||||||
|
|
||||||
|
"fp_ecut": "first cut or copy some files / folders to paste / move\n\nnote: you can cut / paste across different browser tabs",
|
||||||
"fp_ename": "these {0} items cannot be moved here (names already exist):",
|
"fp_ename": "these {0} items cannot be moved here (names already exist):",
|
||||||
|
"fcp_ename": "these {0} items cannot be copied here (names already exist):",
|
||||||
"fp_ok": "move OK",
|
"fp_ok": "move OK",
|
||||||
|
"fcp_ok": "copy OK",
|
||||||
"fp_busy": "moving {0} items...\n\n{1}",
|
"fp_busy": "moving {0} items...\n\n{1}",
|
||||||
|
"fcp_busy": "copying {0} items...\n\n{1}",
|
||||||
"fp_err": "move failed:\n",
|
"fp_err": "move failed:\n",
|
||||||
|
"fcp_err": "copy failed:\n",
|
||||||
"fp_confirm": "move these {0} items here?",
|
"fp_confirm": "move these {0} items here?",
|
||||||
|
"fcp_confirm": "copy these {0} items here?",
|
||||||
"fp_etab": 'failed to read clipboard from other browser tab',
|
"fp_etab": 'failed to read clipboard from other browser tab',
|
||||||
"fp_name": "uploading a file from your device. Give it a name:",
|
"fp_name": "uploading a file from your device. Give it a name:",
|
||||||
"fp_both_m": '<h6>choose what to paste</h6><code>Enter</code> = Move {0} files from «{1}»\n<code>ESC</code> = Upload {2} files from your device',
|
"fp_both_m": '<h6>choose what to paste</h6><code>Enter</code> = Move {0} files from «{1}»\n<code>ESC</code> = Upload {2} files from your device',
|
||||||
|
"fcp_both_m": '<h6>choose what to paste</h6><code>Enter</code> = Copy {0} files from «{1}»\n<code>ESC</code> = Upload {2} files from your device',
|
||||||
"fp_both_b": '<a href="#" id="modal-ok">Move</a><a href="#" id="modal-ng">Upload</a>',
|
"fp_both_b": '<a href="#" id="modal-ok">Move</a><a href="#" id="modal-ng">Upload</a>',
|
||||||
|
"fcp_both_b": '<a href="#" id="modal-ok">Copy</a><a href="#" id="modal-ng">Upload</a>',
|
||||||
|
|
||||||
"mk_noname": "type a name into the text field on the left before you do that :p",
|
"mk_noname": "type a name into the text field on the left before you do that :p",
|
||||||
|
|
||||||
@@ -484,7 +498,7 @@ var tl_browser = {
|
|||||||
"tvt_dl": "download this file$NHotkey: Y\">💾 download",
|
"tvt_dl": "download this file$NHotkey: Y\">💾 download",
|
||||||
"tvt_prev": "show previous document$NHotkey: i\">⬆ prev",
|
"tvt_prev": "show previous document$NHotkey: i\">⬆ prev",
|
||||||
"tvt_next": "show next document$NHotkey: K\">⬇ next",
|
"tvt_next": "show next document$NHotkey: K\">⬇ next",
|
||||||
"tvt_sel": "select file ( for cut / delete / ... )$NHotkey: S\">sel",
|
"tvt_sel": "select file ( for cut / copy / delete / ... )$NHotkey: S\">sel",
|
||||||
"tvt_edit": "open file in text editor$NHotkey: E\">✏️ edit",
|
"tvt_edit": "open file in text editor$NHotkey: E\">✏️ edit",
|
||||||
|
|
||||||
"gt_vau": "don't show videos, just play the audio\">🎧",
|
"gt_vau": "don't show videos, just play the audio\">🎧",
|
||||||
|
|||||||
109
tests/test_cp.py
Normal file
109
tests/test_cp.py
Normal file
@@ -0,0 +1,109 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
# coding: utf-8
|
||||||
|
from __future__ import print_function, unicode_literals
|
||||||
|
|
||||||
|
import os
|
||||||
|
import shutil
|
||||||
|
import tempfile
|
||||||
|
import unittest
|
||||||
|
from itertools import product
|
||||||
|
|
||||||
|
from copyparty.authsrv import AuthSrv
|
||||||
|
from copyparty.httpcli import HttpCli
|
||||||
|
from tests import util as tu
|
||||||
|
from tests.util import Cfg
|
||||||
|
|
||||||
|
|
||||||
|
class TestDedup(unittest.TestCase):
|
||||||
|
def setUp(self):
|
||||||
|
self.td = tu.get_ramdisk()
|
||||||
|
|
||||||
|
def tearDown(self):
|
||||||
|
os.chdir(tempfile.gettempdir())
|
||||||
|
shutil.rmtree(self.td)
|
||||||
|
|
||||||
|
def reset(self):
|
||||||
|
td = os.path.join(self.td, "vfs")
|
||||||
|
if os.path.exists(td):
|
||||||
|
shutil.rmtree(td)
|
||||||
|
os.mkdir(td)
|
||||||
|
os.chdir(td)
|
||||||
|
for a in "abc":
|
||||||
|
os.mkdir(a)
|
||||||
|
for b in "fg":
|
||||||
|
d = "%s/%s%s" % (a, a, b)
|
||||||
|
os.mkdir(d)
|
||||||
|
for fn in "x":
|
||||||
|
fp = "%s/%s%s%s" % (d, a, b, fn)
|
||||||
|
with open(fp, "wb") as f:
|
||||||
|
f.write(fp.encode("utf-8"))
|
||||||
|
return td
|
||||||
|
|
||||||
|
def cinit(self):
|
||||||
|
if self.conn:
|
||||||
|
self.fstab = self.conn.hsrv.hub.up2k.fstab
|
||||||
|
self.conn.hsrv.hub.up2k.shutdown()
|
||||||
|
self.asrv = AuthSrv(self.args, self.log)
|
||||||
|
self.conn = tu.VHttpConn(self.args, self.asrv, self.log, b"", True)
|
||||||
|
if self.fstab:
|
||||||
|
self.conn.hsrv.hub.up2k.fstab = self.fstab
|
||||||
|
|
||||||
|
def test(self):
|
||||||
|
tc_dedup = ["sym", "no"]
|
||||||
|
vols = [".::A", "a/af:a/af:r", "b:a/b:r"]
|
||||||
|
tcs = [
|
||||||
|
"/a?copy=/c/a /a/af/afx /a/ag/agx /a/b/bf/bfx /a/b/bg/bgx /b/bf/bfx /b/bg/bgx /c/a/af/afx /c/a/ag/agx /c/a/b/bf/bfx /c/a/b/bg/bgx /c/cf/cfx /c/cg/cgx",
|
||||||
|
"/b?copy=/d /a/af/afx /a/ag/agx /a/b/bf/bfx /a/b/bg/bgx /b/bf/bfx /b/bg/bgx /c/cf/cfx /c/cg/cgx /d/bf/bfx /d/bg/bgx",
|
||||||
|
"/b/bf?copy=/d /a/af/afx /a/ag/agx /a/b/bf/bfx /a/b/bg/bgx /b/bf/bfx /b/bg/bgx /c/cf/cfx /c/cg/cgx /d/bfx",
|
||||||
|
"/a/af?copy=/d /a/af/afx /a/ag/agx /a/b/bf/bfx /a/b/bg/bgx /b/bf/bfx /b/bg/bgx /c/cf/cfx /c/cg/cgx /d/afx",
|
||||||
|
"/a/af?copy=/ /a/af/afx /a/ag/agx /a/b/bf/bfx /a/b/bg/bgx /afx /b/bf/bfx /b/bg/bgx /c/cf/cfx /c/cg/cgx",
|
||||||
|
"/a/af/afx?copy=/afx /a/af/afx /a/ag/agx /a/b/bf/bfx /a/b/bg/bgx /afx /b/bf/bfx /b/bg/bgx /c/cf/cfx /c/cg/cgx",
|
||||||
|
]
|
||||||
|
|
||||||
|
self.conn = None
|
||||||
|
self.fstab = None
|
||||||
|
self.ctr = 0 # 2304
|
||||||
|
for dedup, act_exp in product(tc_dedup, tcs):
|
||||||
|
action, expect = act_exp.split(" ", 1)
|
||||||
|
t = "dedup:%s action:%s" % (dedup, action)
|
||||||
|
print("\n\n\033[0;7m# ", t, "\033[0m")
|
||||||
|
|
||||||
|
ka = {"dav_inf": True}
|
||||||
|
if dedup == "hard":
|
||||||
|
ka["hardlink"] = True
|
||||||
|
elif dedup == "no":
|
||||||
|
ka["no_dedup"] = True
|
||||||
|
|
||||||
|
self.args = Cfg(v=vols, a=[], **ka)
|
||||||
|
self.reset()
|
||||||
|
self.cinit()
|
||||||
|
|
||||||
|
self.do_cp(action)
|
||||||
|
zs = self.propfind()
|
||||||
|
|
||||||
|
fns = " ".join(zs[1])
|
||||||
|
self.assertEqual(expect, fns)
|
||||||
|
|
||||||
|
def do_cp(self, action):
|
||||||
|
hdr = "POST %s HTTP/1.1\r\nConnection: close\r\nContent-Length: 0\r\n\r\n"
|
||||||
|
buf = (hdr % (action,)).encode("utf-8")
|
||||||
|
print("CP [%s]" % (action,))
|
||||||
|
HttpCli(self.conn.setbuf(buf)).run()
|
||||||
|
ret = self.conn.s._reply.decode("utf-8").split("\r\n\r\n", 1)
|
||||||
|
print("CP <-- ", ret)
|
||||||
|
self.assertIn(" 201 Created", ret[0])
|
||||||
|
self.assertEqual("k\r\n", ret[1])
|
||||||
|
return ret
|
||||||
|
|
||||||
|
def propfind(self):
|
||||||
|
h = "PROPFIND / HTTP/1.1\r\nConnection: close\r\n\r\n"
|
||||||
|
HttpCli(self.conn.setbuf(h.encode("utf-8"))).run()
|
||||||
|
h, t = self.conn.s._reply.decode("utf-8").split("\r\n\r\n", 1)
|
||||||
|
fns = t.split("<D:response><D:href>")[1:]
|
||||||
|
fns = [x.split("</D", 1)[0] for x in fns]
|
||||||
|
fns = [x for x in fns if not x.endswith("/")]
|
||||||
|
fns.sort()
|
||||||
|
return h, fns
|
||||||
|
|
||||||
|
def log(self, src, msg, c=0):
|
||||||
|
print(msg)
|
||||||
@@ -57,6 +57,7 @@ class TestMetrics(unittest.TestCase):
|
|||||||
ptns = r"""
|
ptns = r"""
|
||||||
cpp_uptime_seconds [0-9]\.[0-9]{3}$
|
cpp_uptime_seconds [0-9]\.[0-9]{3}$
|
||||||
cpp_boot_unixtime_seconds [0-9]{7,10}\.[0-9]{3}$
|
cpp_boot_unixtime_seconds [0-9]{7,10}\.[0-9]{3}$
|
||||||
|
cpp_active_dl 0$
|
||||||
cpp_http_reqs_created [0-9]{7,10}$
|
cpp_http_reqs_created [0-9]{7,10}$
|
||||||
cpp_http_reqs_total -1$
|
cpp_http_reqs_total -1$
|
||||||
cpp_http_conns 9$
|
cpp_http_conns 9$
|
||||||
|
|||||||
@@ -122,7 +122,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 = "chpw daw dav_auth dav_inf dav_mac dav_rt e2d e2ds e2dsa e2t e2ts e2tsr e2v e2vu e2vp early_ban ed emp exp force_js getmod grid gsel hardlink ih ihead magic hardlink_only nid nih no_acode no_athumb no_clone no_dav no_db_ip no_del no_dirsz no_dupe no_lifetime no_logues no_mv no_pipe no_poll no_readme no_robots no_sb_md no_sb_lg no_scandir no_tarcmp no_thumb no_vthumb no_zip nrand nw og og_no_head og_s_title ohead q rand re_dirsz rss smb srch_dbg stats uqe vague_403 vc ver write_uplog xdev xlink xvol zs"
|
ex = "chpw daw dav_auth dav_inf dav_mac dav_rt e2d e2ds e2dsa e2t e2ts e2tsr e2v e2vu e2vp early_ban ed emp exp force_js getmod grid gsel hardlink ih ihead magic hardlink_only nid nih no_acode no_athumb no_clone no_cp no_dav no_db_ip no_del no_dirsz no_dupe no_lifetime no_logues no_mv no_pipe no_poll no_readme no_robots no_sb_md no_sb_lg no_scandir no_tarcmp no_thumb no_vthumb no_zip nrand nw og og_no_head og_s_title ohead q rand re_dirsz rss smb srch_dbg stats uqe vague_403 vc ver write_uplog xdev xlink xvol zs"
|
||||||
ka.update(**{k: False for k in ex.split()})
|
ka.update(**{k: False for k in ex.split()})
|
||||||
|
|
||||||
ex = "dedup dotpart dotsrch hook_v no_dhash no_fastboot no_fpool no_htp no_rescan no_sendfile no_ses no_snap no_up_list no_voldump re_dhash plain_ip"
|
ex = "dedup dotpart dotsrch hook_v no_dhash no_fastboot no_fpool no_htp no_rescan no_sendfile no_ses no_snap no_up_list no_voldump re_dhash plain_ip"
|
||||||
@@ -134,7 +134,7 @@ class Cfg(Namespace):
|
|||||||
ex = "hash_mt safe_dedup srch_time u2abort u2j u2sz"
|
ex = "hash_mt safe_dedup srch_time u2abort u2j u2sz"
|
||||||
ka.update(**{k: 1 for k in ex.split()})
|
ka.update(**{k: 1 for k in ex.split()})
|
||||||
|
|
||||||
ex = "au_vol mtab_age reg_cap s_thead s_tbody th_convt"
|
ex = "au_vol dl_list mtab_age reg_cap s_thead s_tbody th_convt"
|
||||||
ka.update(**{k: 9 for k in ex.split()})
|
ka.update(**{k: 9 for k in ex.split()})
|
||||||
|
|
||||||
ex = "db_act k304 loris no304 re_maxage rproxy rsp_jtr rsp_slp s_wr_slp snap_wri theme themes turbo"
|
ex = "db_act k304 loris no304 re_maxage rproxy rsp_jtr rsp_slp s_wr_slp snap_wri theme themes turbo"
|
||||||
@@ -146,7 +146,7 @@ class Cfg(Namespace):
|
|||||||
ex = "ban_403 ban_404 ban_422 ban_pw ban_url"
|
ex = "ban_403 ban_404 ban_422 ban_pw ban_url"
|
||||||
ka.update(**{k: "no" for k in ex.split()})
|
ka.update(**{k: "no" for k in ex.split()})
|
||||||
|
|
||||||
ex = "grp on403 on404 xad xar xau xban xbd xbr xbu xiu xm"
|
ex = "grp on403 on404 xac xad xar xau xban xbc xbd xbr xbu xiu xm"
|
||||||
ka.update(**{k: [] for k in ex.split()})
|
ka.update(**{k: [] for k in ex.split()})
|
||||||
|
|
||||||
ex = "exp_lg exp_md"
|
ex = "exp_lg exp_md"
|
||||||
@@ -254,6 +254,8 @@ class VHttpSrv(object):
|
|||||||
self.broker = NullBroker(args, asrv)
|
self.broker = NullBroker(args, asrv)
|
||||||
self.prism = None
|
self.prism = None
|
||||||
self.bans = {}
|
self.bans = {}
|
||||||
|
self.tdls = self.dls = {}
|
||||||
|
self.tdli = self.dli = {}
|
||||||
self.nreq = 0
|
self.nreq = 0
|
||||||
self.nsus = 0
|
self.nsus = 0
|
||||||
|
|
||||||
@@ -292,6 +294,8 @@ class VHttpConn(object):
|
|||||||
self.args = args
|
self.args = args
|
||||||
self.asrv = asrv
|
self.asrv = asrv
|
||||||
self.bans = {}
|
self.bans = {}
|
||||||
|
self.tdls = self.dls = {}
|
||||||
|
self.tdli = self.dli = {}
|
||||||
self.freshen_pwd = 0.0
|
self.freshen_pwd = 0.0
|
||||||
|
|
||||||
Ctor = VHttpSrvUp2k if use_up2k else VHttpSrv
|
Ctor = VHttpSrvUp2k if use_up2k else VHttpSrv
|
||||||
|
|||||||
Reference in New Issue
Block a user