Compare commits
85 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
1111baacb2 | ||
|
|
1b9c913efb | ||
|
|
3524c36e1b | ||
|
|
cf87cea9f8 | ||
|
|
bfa34404b8 | ||
|
|
0aba5f35bf | ||
|
|
663bc0842a | ||
|
|
7d10c96e73 | ||
|
|
6b2720fab0 | ||
|
|
e74ad5132a | ||
|
|
1f6f89c1fd | ||
|
|
4d55e60980 | ||
|
|
ddaaccd5af | ||
|
|
c20b7dac3d | ||
|
|
1f779d5094 | ||
|
|
715401ca8e | ||
|
|
e7cd922d8b | ||
|
|
187feee0c1 | ||
|
|
49e962a7dc | ||
|
|
633ff601e5 | ||
|
|
331cf37054 | ||
|
|
23e4b9002f | ||
|
|
c0de3c8053 | ||
|
|
a82a3b084a | ||
|
|
67c298e66b | ||
|
|
c110ccb9ae | ||
|
|
0143380306 | ||
|
|
af9000d3c8 | ||
|
|
097d798e5e | ||
|
|
1d9f9f221a | ||
|
|
214a367f48 | ||
|
|
2fb46551a2 | ||
|
|
6bcf330ae0 | ||
|
|
2075a8b18c | ||
|
|
1275ac6c42 | ||
|
|
708f20b7af | ||
|
|
a2c0c708e8 | ||
|
|
2f2c65d91e | ||
|
|
cd5fcc7ca7 | ||
|
|
aa29e7be48 | ||
|
|
93febe34b0 | ||
|
|
f086e6d3c1 | ||
|
|
22e51e1c96 | ||
|
|
63a5336f31 | ||
|
|
bfc6c53cc5 | ||
|
|
236017f310 | ||
|
|
0a1d9b4dfd | ||
|
|
b50d090946 | ||
|
|
00b5db52cf | ||
|
|
24cb30e2c5 | ||
|
|
4549145ab5 | ||
|
|
67b0217754 | ||
|
|
ccae9efdf0 | ||
|
|
59d596b222 | ||
|
|
4878eb2c45 | ||
|
|
7755392f57 | ||
|
|
dc2ea20959 | ||
|
|
8eaea2bd17 | ||
|
|
58e559918f | ||
|
|
f38a3fca5b | ||
|
|
1ea145b384 | ||
|
|
0d9567575a | ||
|
|
e82f176289 | ||
|
|
d4b51c040e | ||
|
|
125d0efbd8 | ||
|
|
3215afc504 | ||
|
|
c73ff3ce1b | ||
|
|
f9c159a051 | ||
|
|
2ab1325c90 | ||
|
|
5b0f7ff506 | ||
|
|
9269bc84f2 | ||
|
|
4e8b651e18 | ||
|
|
65b4f79534 | ||
|
|
5dd43dbc45 | ||
|
|
5f73074c7e | ||
|
|
f5d6ba27b2 | ||
|
|
73fa70b41f | ||
|
|
2a1cda42e7 | ||
|
|
1bd7e31466 | ||
|
|
eb49e1fb4a | ||
|
|
9838c2f0ce | ||
|
|
6041df8370 | ||
|
|
2933dce3ef | ||
|
|
dab377d37b | ||
|
|
f35e41baf1 |
36
README.md
36
README.md
@@ -63,6 +63,7 @@ turn your phone or raspi into a portable file server with resumable uploads/down
|
|||||||
* [file parser plugins](#file-parser-plugins) - provide custom parsers to index additional tags, also see [./bin/mtag/README.md](./bin/mtag/README.md)
|
* [file parser plugins](#file-parser-plugins) - provide custom parsers to index additional tags, also see [./bin/mtag/README.md](./bin/mtag/README.md)
|
||||||
* [upload events](#upload-events) - trigger a script/program on each upload
|
* [upload events](#upload-events) - trigger a script/program on each upload
|
||||||
* [hiding from google](#hiding-from-google) - tell search engines you dont wanna be indexed
|
* [hiding from google](#hiding-from-google) - tell search engines you dont wanna be indexed
|
||||||
|
* [themes](#themes)
|
||||||
* [complete examples](#complete-examples)
|
* [complete examples](#complete-examples)
|
||||||
* [browser support](#browser-support) - TLDR: yes
|
* [browser support](#browser-support) - TLDR: yes
|
||||||
* [client examples](#client-examples) - interact with copyparty using non-browser clients
|
* [client examples](#client-examples) - interact with copyparty using non-browser clients
|
||||||
@@ -247,6 +248,8 @@ some improvement ideas
|
|||||||
|
|
||||||
## not my bugs
|
## not my bugs
|
||||||
|
|
||||||
|
* [Chrome issue 1317069](https://bugs.chromium.org/p/chromium/issues/detail?id=1317069) -- if you try to upload a folder which contains symlinks by dragging it into the browser, the symlinked files will not get uploaded
|
||||||
|
|
||||||
* iPhones: the volume control doesn't work because [apple doesn't want it to](https://developer.apple.com/library/archive/documentation/AudioVideo/Conceptual/Using_HTML5_Audio_Video/Device-SpecificConsiderations/Device-SpecificConsiderations.html#//apple_ref/doc/uid/TP40009523-CH5-SW11)
|
* iPhones: the volume control doesn't work because [apple doesn't want it to](https://developer.apple.com/library/archive/documentation/AudioVideo/Conceptual/Using_HTML5_Audio_Video/Device-SpecificConsiderations/Device-SpecificConsiderations.html#//apple_ref/doc/uid/TP40009523-CH5-SW11)
|
||||||
* *future workaround:* enable the equalizer, make it all-zero, and set a negative boost to reduce the volume
|
* *future workaround:* enable the equalizer, make it all-zero, and set a negative boost to reduce the volume
|
||||||
* "future" because `AudioContext` is broken in the current iOS version (15.1), maybe one day...
|
* "future" because `AudioContext` is broken in the current iOS version (15.1), maybe one day...
|
||||||
@@ -806,6 +809,29 @@ tell search engines you dont wanna be indexed, either using the good old [robot
|
|||||||
also, `--force-js` disables the plain HTML folder listing, making things harder to parse for search engines
|
also, `--force-js` disables the plain HTML folder listing, making things harder to parse for search engines
|
||||||
|
|
||||||
|
|
||||||
|
## themes
|
||||||
|
|
||||||
|
you can change the default theme with `--theme 2`, and add your own themes by modifying `browser.css` or providing your own css to `--css-browser`, then telling copyparty they exist by increasing `--themes`
|
||||||
|
|
||||||
|
<table><tr><td width="33%" align="center"><a href="https://user-images.githubusercontent.com/241032/165864907-17e2ac7d-319d-4f25-8718-2f376f614b51.png"><img src="https://user-images.githubusercontent.com/241032/165867551-fceb35dd-38f0-42bb-bef3-25ba651ca69b.png"></a>
|
||||||
|
0. classic dark</td><td width="33%" align="center"><a href="https://user-images.githubusercontent.com/241032/168644399-68938de5-da9b-445f-8d92-b51c74b5f345.png"><img src="https://user-images.githubusercontent.com/241032/168644404-8e1a2fdc-6e59-4c41-905e-ba5399ed686f.png"></a>
|
||||||
|
2. flat pm-monokai</td><td width="33%" align="center"><a href="https://user-images.githubusercontent.com/241032/165864901-db13a429-a5da-496d-8bc6-ce838547f69d.png"><img src="https://user-images.githubusercontent.com/241032/165867560-aa834aef-58dc-4abe-baef-7e562b647945.png"></a>
|
||||||
|
4. vice</td></tr><tr><td align="center"><a href="https://user-images.githubusercontent.com/241032/165864905-692682eb-6fb4-4d40-b6fe-27d2c7d3e2a7.png"><img src="https://user-images.githubusercontent.com/241032/165867555-080b73b6-6d85-41bb-a7c6-ad277c608365.png"></a>
|
||||||
|
1. classic light</td><td align="center"><a href="https://user-images.githubusercontent.com/241032/168645276-fb02fd19-190a-407a-b8d3-d58fee277e02.png"><img src="https://user-images.githubusercontent.com/241032/168645280-f0662b3c-9764-4875-a2e2-d91cc8199b23.png"></a>
|
||||||
|
3. flat light
|
||||||
|
</td><td align="center"><a href="https://user-images.githubusercontent.com/241032/165864898-10ce7052-a117-4fcf-845b-b56c91687908.png"><img src="https://user-images.githubusercontent.com/241032/165867562-f3003d45-dd2a-4564-8aae-fed44c1ae064.png"></a>
|
||||||
|
5. <a href="https://blog.codinghorror.com/a-tribute-to-the-windows-31-hot-dog-stand-color-scheme/">hotdog stand</a></td></tr></table>
|
||||||
|
|
||||||
|
the classname of the HTML tag is set according to the selected theme, which is used to set colors as css variables ++
|
||||||
|
|
||||||
|
* each theme *generally* has a dark theme (even numbers) and a light theme (odd numbers), showing in pairs
|
||||||
|
* the first theme (theme 0 and 1) is `html.a`, second theme (2 and 3) is `html.b`
|
||||||
|
* if a light theme is selected, `html.y` is set, otherwise `html.z` is
|
||||||
|
* so if the dark edition of the 2nd theme is selected, you use any of `html.b`, `html.z`, `html.bz` to specify rules
|
||||||
|
|
||||||
|
see the top of [./copyparty/web/browser.css](./copyparty/web/browser.css) where the color variables are set, and there's layout-specific stuff near the bottom
|
||||||
|
|
||||||
|
|
||||||
## complete examples
|
## complete examples
|
||||||
|
|
||||||
* read-only music server with bpm and key scanning
|
* read-only music server with bpm and key scanning
|
||||||
@@ -855,7 +881,7 @@ quick summary of more eccentric web-browsers trying to view a directory index:
|
|||||||
| **w3m** (0.5.3/macports) | can browse, login, upload at 100kB/s, mkdir/msg |
|
| **w3m** (0.5.3/macports) | can browse, login, upload at 100kB/s, mkdir/msg |
|
||||||
| **netsurf** (3.10/arch) | is basically ie6 with much better css (javascript has almost no effect) |
|
| **netsurf** (3.10/arch) | is basically ie6 with much better css (javascript has almost no effect) |
|
||||||
| **opera** (11.60/winxp) | OK: thumbnails, image-viewer, zip-selection, rename/cut/paste. NG: up2k, navpane, markdown, audio |
|
| **opera** (11.60/winxp) | OK: thumbnails, image-viewer, zip-selection, rename/cut/paste. NG: up2k, navpane, markdown, audio |
|
||||||
| **ie4** and **netscape** 4.0 | can browse, upload with `?b=u` |
|
| **ie4** and **netscape** 4.0 | can browse, upload with `?b=u`, auth with `&pw=wark` |
|
||||||
| **SerenityOS** (7e98457) | hits a page fault, works with `?b=u`, file upload not-impl |
|
| **SerenityOS** (7e98457) | hits a page fault, works with `?b=u`, file upload not-impl |
|
||||||
|
|
||||||
|
|
||||||
@@ -1149,12 +1175,16 @@ for the `re`pack to work, first run one of the sfx'es once to unpack it
|
|||||||
|
|
||||||
install [Termux](https://termux.com/) (see [ocv.me/termux](https://ocv.me/termux/)) and then copy-paste this into Termux (long-tap) all at once:
|
install [Termux](https://termux.com/) (see [ocv.me/termux](https://ocv.me/termux/)) and then copy-paste this into Termux (long-tap) all at once:
|
||||||
```sh
|
```sh
|
||||||
apt update && apt -y full-upgrade && termux-setup-storage && apt -y install python && python -m ensurepip && python -m pip install -U copyparty
|
apt update && apt -y full-upgrade && apt update && termux-setup-storage && apt -y install python && python -m ensurepip && python -m pip install --user -U copyparty
|
||||||
echo $?
|
echo $?
|
||||||
```
|
```
|
||||||
|
|
||||||
after the initial setup, you can launch copyparty at any time by running `copyparty` anywhere in Termux
|
after the initial setup, you can launch copyparty at any time by running `copyparty` anywhere in Termux
|
||||||
|
|
||||||
|
if you want thumbnails, `apt -y install ffmpeg`
|
||||||
|
|
||||||
|
* or if you want to use vips instead, `apt -y install libvips && python -m pip install --user -U wheel && python -m pip install --user -U pyvips && (cd /data/data/com.termux/files/usr/lib/; ln -s libgobject-2.0.so{,.0}; ln -s libvips.so{,.42})`
|
||||||
|
|
||||||
|
|
||||||
# reporting bugs
|
# reporting bugs
|
||||||
|
|
||||||
@@ -1178,7 +1208,7 @@ python3 -m venv .venv
|
|||||||
pip install jinja2 # mandatory
|
pip install jinja2 # mandatory
|
||||||
pip install mutagen # audio metadata
|
pip install mutagen # audio metadata
|
||||||
pip install Pillow pyheif-pillow-opener pillow-avif-plugin # thumbnails
|
pip install Pillow pyheif-pillow-opener pillow-avif-plugin # thumbnails
|
||||||
pip install black bandit pylint flake8 # vscode tooling
|
pip install black==21.12b0 bandit pylint flake8 # vscode tooling
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -42,6 +42,7 @@ import threading
|
|||||||
import traceback
|
import traceback
|
||||||
import http.client # py2: httplib
|
import http.client # py2: httplib
|
||||||
import urllib.parse
|
import urllib.parse
|
||||||
|
import calendar
|
||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
from urllib.parse import quote_from_bytes as quote
|
from urllib.parse import quote_from_bytes as quote
|
||||||
from urllib.parse import unquote_to_bytes as unquote
|
from urllib.parse import unquote_to_bytes as unquote
|
||||||
@@ -495,7 +496,7 @@ class Gateway(object):
|
|||||||
ts = 60 * 60 * 24 * 2
|
ts = 60 * 60 * 24 * 2
|
||||||
try:
|
try:
|
||||||
sz = int(fsize)
|
sz = int(fsize)
|
||||||
ts = datetime.strptime(fdate, "%Y-%m-%d %H:%M:%S").timestamp()
|
ts = calendar.timegm(time.strptime(fdate, "%Y-%m-%d %H:%M:%S"))
|
||||||
except:
|
except:
|
||||||
info("bad HTML or OS [{}] [{}]".format(fdate, fsize))
|
info("bad HTML or OS [{}] [{}]".format(fdate, fsize))
|
||||||
# python cannot strptime(1959-01-01) on windows
|
# python cannot strptime(1959-01-01) on windows
|
||||||
|
|||||||
@@ -45,6 +45,7 @@ import threading
|
|||||||
import traceback
|
import traceback
|
||||||
import http.client # py2: httplib
|
import http.client # py2: httplib
|
||||||
import urllib.parse
|
import urllib.parse
|
||||||
|
import calendar
|
||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
from urllib.parse import quote_from_bytes as quote
|
from urllib.parse import quote_from_bytes as quote
|
||||||
from urllib.parse import unquote_to_bytes as unquote
|
from urllib.parse import unquote_to_bytes as unquote
|
||||||
@@ -443,7 +444,7 @@ class Gateway(object):
|
|||||||
ts = 60 * 60 * 24 * 2
|
ts = 60 * 60 * 24 * 2
|
||||||
try:
|
try:
|
||||||
sz = int(fsize)
|
sz = int(fsize)
|
||||||
ts = datetime.strptime(fdate, "%Y-%m-%d %H:%M:%S").timestamp()
|
ts = calendar.timegm(time.strptime(fdate, "%Y-%m-%d %H:%M:%S"))
|
||||||
except:
|
except:
|
||||||
info("bad HTML or OS [{}] [{}]".format(fdate, fsize))
|
info("bad HTML or OS [{}] [{}]".format(fdate, fsize))
|
||||||
# python cannot strptime(1959-01-01) on windows
|
# python cannot strptime(1959-01-01) on windows
|
||||||
|
|||||||
111
bin/dbtool.py
111
bin/dbtool.py
@@ -8,7 +8,10 @@ import sqlite3
|
|||||||
import argparse
|
import argparse
|
||||||
|
|
||||||
DB_VER1 = 3
|
DB_VER1 = 3
|
||||||
DB_VER2 = 4
|
DB_VER2 = 5
|
||||||
|
|
||||||
|
BY_PATH = None
|
||||||
|
NC = None
|
||||||
|
|
||||||
|
|
||||||
def die(msg):
|
def die(msg):
|
||||||
@@ -57,8 +60,13 @@ def compare(n1, d1, n2, d2, verbose):
|
|||||||
if rd.split("/", 1)[0] == ".hist":
|
if rd.split("/", 1)[0] == ".hist":
|
||||||
continue
|
continue
|
||||||
|
|
||||||
q = "select w from up where rd = ? and fn = ?"
|
if BY_PATH:
|
||||||
hit = d2.execute(q, (rd, fn)).fetchone()
|
q = "select w from up where rd = ? and fn = ?"
|
||||||
|
hit = d2.execute(q, (rd, fn)).fetchone()
|
||||||
|
else:
|
||||||
|
q = "select w from up where substr(w,1,16) = ? and +w = ?"
|
||||||
|
hit = d2.execute(q, (w1[:16], w1)).fetchone()
|
||||||
|
|
||||||
if not hit:
|
if not hit:
|
||||||
miss += 1
|
miss += 1
|
||||||
if verbose:
|
if verbose:
|
||||||
@@ -70,27 +78,32 @@ def compare(n1, d1, n2, d2, verbose):
|
|||||||
n = 0
|
n = 0
|
||||||
miss = {}
|
miss = {}
|
||||||
nmiss = 0
|
nmiss = 0
|
||||||
for w1, k, v in d1.execute("select * from mt"):
|
for w1s, k, v in d1.execute("select * from mt"):
|
||||||
|
|
||||||
n += 1
|
n += 1
|
||||||
if n % 100_000 == 0:
|
if n % 100_000 == 0:
|
||||||
m = f"\033[36mchecked {n:,} of {nt:,} tags in {n1} against {n2}, so far {nmiss} missing tags\033[0m"
|
m = f"\033[36mchecked {n:,} of {nt:,} tags in {n1} against {n2}, so far {nmiss} missing tags\033[0m"
|
||||||
print(m)
|
print(m)
|
||||||
|
|
||||||
q = "select rd, fn from up where substr(w,1,16) = ?"
|
q = "select w, rd, fn from up where substr(w,1,16) = ?"
|
||||||
rd, fn = d1.execute(q, (w1,)).fetchone()
|
w1, rd, fn = d1.execute(q, (w1s,)).fetchone()
|
||||||
if rd.split("/", 1)[0] == ".hist":
|
if rd.split("/", 1)[0] == ".hist":
|
||||||
continue
|
continue
|
||||||
|
|
||||||
q = "select substr(w,1,16) from up where rd = ? and fn = ?"
|
if BY_PATH:
|
||||||
w2 = d2.execute(q, (rd, fn)).fetchone()
|
q = "select w from up where rd = ? and fn = ?"
|
||||||
|
w2 = d2.execute(q, (rd, fn)).fetchone()
|
||||||
|
else:
|
||||||
|
q = "select w from up where substr(w,1,16) = ? and +w = ?"
|
||||||
|
w2 = d2.execute(q, (w1s, w1)).fetchone()
|
||||||
|
|
||||||
if w2:
|
if w2:
|
||||||
w2 = w2[0]
|
w2 = w2[0]
|
||||||
|
|
||||||
v2 = None
|
v2 = None
|
||||||
if w2:
|
if w2:
|
||||||
v2 = d2.execute(
|
v2 = d2.execute(
|
||||||
"select v from mt where w = ? and +k = ?", (w2, k)
|
"select v from mt where w = ? and +k = ?", (w2[:16], k)
|
||||||
).fetchone()
|
).fetchone()
|
||||||
if v2:
|
if v2:
|
||||||
v2 = v2[0]
|
v2 = v2[0]
|
||||||
@@ -124,7 +137,7 @@ def compare(n1, d1, n2, d2, verbose):
|
|||||||
|
|
||||||
for k, v in sorted(miss.items()):
|
for k, v in sorted(miss.items()):
|
||||||
if v:
|
if v:
|
||||||
print(f"{n1} has {v:6} more {k:<6} tags than {n2}")
|
print(f"{n1} has {v:7} more {k:<7} tags than {n2}")
|
||||||
|
|
||||||
print(f"in total, {nmiss} missing tags in {n2}\n")
|
print(f"in total, {nmiss} missing tags in {n2}\n")
|
||||||
|
|
||||||
@@ -132,47 +145,75 @@ def compare(n1, d1, n2, d2, verbose):
|
|||||||
def copy_mtp(d1, d2, tag, rm):
|
def copy_mtp(d1, d2, tag, rm):
|
||||||
nt = next(d1.execute("select count(w) from mt where k = ?", (tag,)))[0]
|
nt = next(d1.execute("select count(w) from mt where k = ?", (tag,)))[0]
|
||||||
n = 0
|
n = 0
|
||||||
ndone = 0
|
ncopy = 0
|
||||||
for w1, k, v in d1.execute("select * from mt where k = ?", (tag,)):
|
nskip = 0
|
||||||
|
for w1s, k, v in d1.execute("select * from mt where k = ?", (tag,)):
|
||||||
n += 1
|
n += 1
|
||||||
if n % 25_000 == 0:
|
if n % 25_000 == 0:
|
||||||
m = f"\033[36m{n:,} of {nt:,} tags checked, so far {ndone} copied\033[0m"
|
m = f"\033[36m{n:,} of {nt:,} tags checked, so far {ncopy} copied, {nskip} skipped\033[0m"
|
||||||
print(m)
|
print(m)
|
||||||
|
|
||||||
q = "select rd, fn from up where substr(w,1,16) = ?"
|
q = "select w, rd, fn from up where substr(w,1,16) = ?"
|
||||||
rd, fn = d1.execute(q, (w1,)).fetchone()
|
w1, rd, fn = d1.execute(q, (w1s,)).fetchone()
|
||||||
if rd.split("/", 1)[0] == ".hist":
|
if rd.split("/", 1)[0] == ".hist":
|
||||||
continue
|
continue
|
||||||
|
|
||||||
q = "select substr(w,1,16) from up where rd = ? and fn = ?"
|
if BY_PATH:
|
||||||
w2 = d2.execute(q, (rd, fn)).fetchone()
|
q = "select w from up where rd = ? and fn = ?"
|
||||||
|
w2 = d2.execute(q, (rd, fn)).fetchone()
|
||||||
|
else:
|
||||||
|
q = "select w from up where substr(w,1,16) = ? and +w = ?"
|
||||||
|
w2 = d2.execute(q, (w1s, w1)).fetchone()
|
||||||
|
|
||||||
if not w2:
|
if not w2:
|
||||||
continue
|
continue
|
||||||
|
|
||||||
w2 = w2[0]
|
w2s = w2[0][:16]
|
||||||
hit = d2.execute("select v from mt where w = ? and +k = ?", (w2, k)).fetchone()
|
hit = d2.execute("select v from mt where w = ? and +k = ?", (w2s, k)).fetchone()
|
||||||
if hit:
|
if hit:
|
||||||
hit = hit[0]
|
hit = hit[0]
|
||||||
|
|
||||||
if hit != v:
|
if hit != v:
|
||||||
ndone += 1
|
if NC and hit is not None:
|
||||||
if hit is not None:
|
nskip += 1
|
||||||
d2.execute("delete from mt where w = ? and +k = ?", (w2, k))
|
continue
|
||||||
|
|
||||||
d2.execute("insert into mt values (?,?,?)", (w2, k, v))
|
ncopy += 1
|
||||||
|
if hit is not None:
|
||||||
|
d2.execute("delete from mt where w = ? and +k = ?", (w2s, k))
|
||||||
|
|
||||||
|
d2.execute("insert into mt values (?,?,?)", (w2s, k, v))
|
||||||
if rm:
|
if rm:
|
||||||
d2.execute("delete from mt where w = ? and +k = 't:mtp'", (w2,))
|
d2.execute("delete from mt where w = ? and +k = 't:mtp'", (w2s,))
|
||||||
|
|
||||||
d2.commit()
|
d2.commit()
|
||||||
print(f"copied {ndone} {tag} tags over")
|
print(f"copied {ncopy} {tag} tags over, skipped {nskip}")
|
||||||
|
|
||||||
|
|
||||||
|
def examples():
|
||||||
|
print(
|
||||||
|
"""
|
||||||
|
# clearing the journal
|
||||||
|
./dbtool.py up2k.db
|
||||||
|
|
||||||
|
# copy tags ".bpm" and "key" from old.db to up2k.db, and remove the mtp flag from matching files (so copyparty won't run any mtps on it)
|
||||||
|
./dbtool.py -ls up2k.db
|
||||||
|
./dbtool.py -src old.db up2k.db -cmp
|
||||||
|
./dbtool.py -src old.v3 up2k.db -rm-mtp-flag -copy key
|
||||||
|
./dbtool.py -src old.v3 up2k.db -rm-mtp-flag -copy .bpm -vac
|
||||||
|
|
||||||
|
"""
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
def main():
|
def main():
|
||||||
|
global NC, BY_PATH
|
||||||
os.system("")
|
os.system("")
|
||||||
print()
|
print()
|
||||||
|
|
||||||
ap = argparse.ArgumentParser()
|
ap = argparse.ArgumentParser()
|
||||||
ap.add_argument("db", help="database to work on")
|
ap.add_argument("db", help="database to work on")
|
||||||
|
ap.add_argument("-h2", action="store_true", help="show examples")
|
||||||
ap.add_argument("-src", metavar="DB", type=str, help="database to copy from")
|
ap.add_argument("-src", metavar="DB", type=str, help="database to copy from")
|
||||||
|
|
||||||
ap2 = ap.add_argument_group("informational / read-only stuff")
|
ap2 = ap.add_argument_group("informational / read-only stuff")
|
||||||
@@ -185,11 +226,29 @@ def main():
|
|||||||
ap2.add_argument(
|
ap2.add_argument(
|
||||||
"-rm-mtp-flag",
|
"-rm-mtp-flag",
|
||||||
action="store_true",
|
action="store_true",
|
||||||
help="when an mtp tag is copied over, also mark that as done, so copyparty won't run mtp on it",
|
help="when an mtp tag is copied over, also mark that file as done, so copyparty won't run any mtps on those files",
|
||||||
)
|
)
|
||||||
ap2.add_argument("-vac", action="store_true", help="optimize DB")
|
ap2.add_argument("-vac", action="store_true", help="optimize DB")
|
||||||
|
|
||||||
|
ap2 = ap.add_argument_group("behavior modifiers")
|
||||||
|
ap2.add_argument(
|
||||||
|
"-nc",
|
||||||
|
action="store_true",
|
||||||
|
help="no-clobber; don't replace/overwrite existing tags",
|
||||||
|
)
|
||||||
|
ap2.add_argument(
|
||||||
|
"-by-path",
|
||||||
|
action="store_true",
|
||||||
|
help="match files based on location rather than warks (content-hash), use this if the databases have different wark salts",
|
||||||
|
)
|
||||||
|
|
||||||
ar = ap.parse_args()
|
ar = ap.parse_args()
|
||||||
|
if ar.h2:
|
||||||
|
examples()
|
||||||
|
return
|
||||||
|
|
||||||
|
NC = ar.nc
|
||||||
|
BY_PATH = ar.by_path
|
||||||
|
|
||||||
for v in [ar.db, ar.src]:
|
for v in [ar.db, ar.src]:
|
||||||
if v and not os.path.exists(v):
|
if v and not os.path.exists(v):
|
||||||
|
|||||||
@@ -29,6 +29,7 @@ however if your copyparty is behind a reverse-proxy, you may want to use [`share
|
|||||||
### [`cfssl.sh`](cfssl.sh)
|
### [`cfssl.sh`](cfssl.sh)
|
||||||
* creates CA and server certificates using cfssl
|
* creates CA and server certificates using cfssl
|
||||||
* give a 3rd argument to install it to your copyparty config
|
* give a 3rd argument to install it to your copyparty config
|
||||||
|
* systemd service at [`systemd/cfssl.service`](systemd/cfssl.service)
|
||||||
|
|
||||||
# OS integration
|
# OS integration
|
||||||
init-scripts to start copyparty as a service
|
init-scripts to start copyparty as a service
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ srv_fqdn="$2"
|
|||||||
|
|
||||||
[ -z "$srv_fqdn" ] && {
|
[ -z "$srv_fqdn" ] && {
|
||||||
echo "need arg 1: ca name"
|
echo "need arg 1: ca name"
|
||||||
echo "need arg 2: server fqdn"
|
echo "need arg 2: server fqdn and/or IPs, comma-separated"
|
||||||
echo "optional arg 3: if set, write cert into copyparty cfg"
|
echo "optional arg 3: if set, write cert into copyparty cfg"
|
||||||
exit 1
|
exit 1
|
||||||
}
|
}
|
||||||
|
|||||||
23
contrib/systemd/cfssl.service
Normal file
23
contrib/systemd/cfssl.service
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
# systemd service which generates a new TLS certificate on each boot,
|
||||||
|
# that way the one-year expiry time won't cause any issues --
|
||||||
|
# just have everyone trust the ca.pem once every 10 years
|
||||||
|
#
|
||||||
|
# assumptions/placeholder values:
|
||||||
|
# * this script and copyparty runs as user "cpp"
|
||||||
|
# * copyparty repo is at ~cpp/dev/copyparty
|
||||||
|
# * CA is named partylan
|
||||||
|
# * server IPs = 10.1.2.3 and 192.168.123.1
|
||||||
|
# * server hostname = party.lan
|
||||||
|
|
||||||
|
[Unit]
|
||||||
|
Description=copyparty certificate generator
|
||||||
|
Before=copyparty.service
|
||||||
|
|
||||||
|
[Service]
|
||||||
|
User=cpp
|
||||||
|
Type=oneshot
|
||||||
|
SyslogIdentifier=cpp-cert
|
||||||
|
ExecStart=/bin/bash -c 'cd ~/dev/copyparty/contrib && ./cfssl.sh partylan 10.1.2.3,192.168.123.1,party.lan y'
|
||||||
|
|
||||||
|
[Install]
|
||||||
|
WantedBy=multi-user.target
|
||||||
@@ -2,16 +2,22 @@
|
|||||||
# and share '/mnt' with anonymous read+write
|
# and share '/mnt' with anonymous read+write
|
||||||
#
|
#
|
||||||
# installation:
|
# installation:
|
||||||
# cp -pv copyparty.service /etc/systemd/system && systemctl enable --now copyparty
|
# cp -pv copyparty.service /etc/systemd/system
|
||||||
# restorecon -vr /etc/systemd/system/copyparty.service
|
# restorecon -vr /etc/systemd/system/copyparty.service
|
||||||
# firewall-cmd --permanent --add-port={80,443,3923}/tcp
|
# firewall-cmd --permanent --add-port={80,443,3923}/tcp
|
||||||
# firewall-cmd --reload
|
# firewall-cmd --reload
|
||||||
|
# systemctl daemon-reload && systemctl enable --now copyparty
|
||||||
#
|
#
|
||||||
# you may want to:
|
# you may want to:
|
||||||
|
# change "User=cpp" and "/home/cpp/" to another user
|
||||||
|
# remove the nft lines to only listen on port 3923
|
||||||
|
# and in the ExecStart= line:
|
||||||
# change '/usr/bin/python3' to another interpreter
|
# change '/usr/bin/python3' to another interpreter
|
||||||
# change '/mnt::rw' to another location or permission-set
|
# change '/mnt::rw' to another location or permission-set
|
||||||
# remove '-p 80,443,3923' to only listen on port 3923
|
# add '-q' to disable logging on busy servers
|
||||||
# add '-i 127.0.0.1' to only allow local connections
|
# add '-i 127.0.0.1' to only allow local connections
|
||||||
|
# add '-e2dsa' to enable filesystem scanning + indexing
|
||||||
|
# add '-e2ts' to enable metadata indexing
|
||||||
#
|
#
|
||||||
# with `Type=notify`, copyparty will signal systemd when it is ready to
|
# with `Type=notify`, copyparty will signal systemd when it is ready to
|
||||||
# accept connections; correctly delaying units depending on copyparty.
|
# accept connections; correctly delaying units depending on copyparty.
|
||||||
@@ -19,8 +25,8 @@
|
|||||||
# python disabling line-buffering, so messages are out-of-order:
|
# python disabling line-buffering, so messages are out-of-order:
|
||||||
# https://user-images.githubusercontent.com/241032/126040249-cb535cc7-c599-4931-a796-a5d9af691bad.png
|
# https://user-images.githubusercontent.com/241032/126040249-cb535cc7-c599-4931-a796-a5d9af691bad.png
|
||||||
#
|
#
|
||||||
# if you remove -q to enable logging, you may also want to remove the
|
# unless you add -q to disable logging, you may want to remove the
|
||||||
# following line to enable buffering (slightly better performance):
|
# following line to allow buffering (slightly better performance):
|
||||||
# Environment=PYTHONUNBUFFERED=x
|
# Environment=PYTHONUNBUFFERED=x
|
||||||
#
|
#
|
||||||
# keep ExecStartPre before ExecStart, at least on rhel8
|
# keep ExecStartPre before ExecStart, at least on rhel8
|
||||||
@@ -33,8 +39,23 @@ Type=notify
|
|||||||
SyslogIdentifier=copyparty
|
SyslogIdentifier=copyparty
|
||||||
Environment=PYTHONUNBUFFERED=x
|
Environment=PYTHONUNBUFFERED=x
|
||||||
ExecReload=/bin/kill -s USR1 $MAINPID
|
ExecReload=/bin/kill -s USR1 $MAINPID
|
||||||
ExecStartPre=/bin/bash -c 'mkdir -p /run/tmpfiles.d/ && echo "x /tmp/pe-copyparty*" > /run/tmpfiles.d/copyparty.conf'
|
|
||||||
ExecStart=/usr/bin/python3 /usr/local/bin/copyparty-sfx.py -q -p 80,443,3923 -v /mnt::rw
|
# user to run as + where the TLS certificate is (if any)
|
||||||
|
User=cpp
|
||||||
|
Environment=XDG_CONFIG_HOME=/home/cpp/.config
|
||||||
|
|
||||||
|
# setup forwarding from ports 80 and 443 to port 3923
|
||||||
|
ExecStartPre=+/bin/bash -c 'nft -n -a list table nat | awk "/ to :3923 /{print\$NF}" | xargs -rL1 nft delete rule nat prerouting handle; true'
|
||||||
|
ExecStartPre=+nft add table ip nat
|
||||||
|
ExecStartPre=+nft -- add chain ip nat prerouting { type nat hook prerouting priority -100 \; }
|
||||||
|
ExecStartPre=+nft add rule ip nat prerouting tcp dport 80 redirect to :3923
|
||||||
|
ExecStartPre=+nft add rule ip nat prerouting tcp dport 443 redirect to :3923
|
||||||
|
|
||||||
|
# stop systemd-tmpfiles-clean.timer from deleting copyparty while it's running
|
||||||
|
ExecStartPre=+/bin/bash -c 'mkdir -p /run/tmpfiles.d/ && echo "x /tmp/pe-copyparty*" > /run/tmpfiles.d/copyparty.conf'
|
||||||
|
|
||||||
|
# copyparty settings
|
||||||
|
ExecStart=/usr/bin/python3 /usr/local/bin/copyparty-sfx.py -e2d -v /mnt::rw
|
||||||
|
|
||||||
[Install]
|
[Install]
|
||||||
WantedBy=multi-user.target
|
WantedBy=multi-user.target
|
||||||
|
|||||||
@@ -291,9 +291,9 @@ def run_argparse(argv, formatter):
|
|||||||
dedent(
|
dedent(
|
||||||
"""
|
"""
|
||||||
-a takes username:password,
|
-a takes username:password,
|
||||||
-v takes src:dst:perm1:perm2:permN:volflag1:volflag2:volflagN:...
|
-v takes src:dst:\033[33mperm\033[0m1:\033[33mperm\033[0m2:\033[33mperm\033[0mN:\033[32mvolflag\033[0m1:\033[32mvolflag\033[0m2:\033[32mvolflag\033[0mN:...
|
||||||
where "perm" is "permissions,username1,username2,..."
|
* "\033[33mperm\033[0m" is "permissions,username1,username2,..."
|
||||||
and "volflag" is config flags to set on this volume
|
* "\033[32mvolflag\033[0m" is config flags to set on this volume
|
||||||
|
|
||||||
list of permissions:
|
list of permissions:
|
||||||
"r" (read): list folder contents, download files
|
"r" (read): list folder contents, download files
|
||||||
@@ -365,6 +365,17 @@ def run_argparse(argv, formatter):
|
|||||||
generate ".bpm" tags from uploads (f = overwrite tags)
|
generate ".bpm" tags from uploads (f = overwrite tags)
|
||||||
\033[36mmtp=ahash,vhash=media-hash.py\033[35m collects two tags at once
|
\033[36mmtp=ahash,vhash=media-hash.py\033[35m collects two tags at once
|
||||||
|
|
||||||
|
\033[0mthumbnails:
|
||||||
|
\033[36mdthumb\033[35m disables all thumbnails
|
||||||
|
\033[36mdvthumb\033[35m disables video thumbnails
|
||||||
|
\033[36mdathumb\033[35m disables audio thumbnails (spectrograms)
|
||||||
|
\033[36mdithumb\033[35m disables image thumbnails
|
||||||
|
|
||||||
|
\033[0mclient and ux:
|
||||||
|
\033[36mhtml_head=TXT\033[35m includes TXT in the <head>
|
||||||
|
\033[36mrobots\033[35m allows indexing by search engines (default)
|
||||||
|
\033[36mnorobots\033[35m kindly asks search engines to leave
|
||||||
|
|
||||||
\033[0mothers:
|
\033[0mothers:
|
||||||
\033[36mfk=8\033[35m generates per-file accesskeys,
|
\033[36mfk=8\033[35m generates per-file accesskeys,
|
||||||
which will then be required at the "g" permission
|
which will then be required at the "g" permission
|
||||||
@@ -373,7 +384,7 @@ def run_argparse(argv, formatter):
|
|||||||
],
|
],
|
||||||
[
|
[
|
||||||
"urlform",
|
"urlform",
|
||||||
"",
|
"how to handle url-form POSTs",
|
||||||
dedent(
|
dedent(
|
||||||
"""
|
"""
|
||||||
values for --urlform:
|
values for --urlform:
|
||||||
@@ -412,40 +423,41 @@ def run_argparse(argv, formatter):
|
|||||||
ap2.add_argument("-c", metavar="PATH", type=u, action="append", help="add config file")
|
ap2.add_argument("-c", metavar="PATH", type=u, action="append", help="add config file")
|
||||||
ap2.add_argument("-nc", metavar="NUM", type=int, default=64, help="max num clients")
|
ap2.add_argument("-nc", metavar="NUM", type=int, default=64, help="max num clients")
|
||||||
ap2.add_argument("-j", metavar="CORES", type=int, default=1, help="max num cpu cores, 0=all")
|
ap2.add_argument("-j", metavar="CORES", type=int, default=1, help="max num cpu cores, 0=all")
|
||||||
ap2.add_argument("-a", metavar="ACCT", type=u, action="append", help="add account, USER:PASS; example [ed:wark")
|
ap2.add_argument("-a", metavar="ACCT", type=u, action="append", help="add account, USER:PASS; example [ed:wark]")
|
||||||
ap2.add_argument("-v", metavar="VOL", type=u, action="append", help="add volume, SRC:DST:FLAG; example [.::r], [/mnt/nas/music:/music:r:aed")
|
ap2.add_argument("-v", metavar="VOL", type=u, action="append", help="add volume, SRC:DST:FLAG; examples [.::r], [/mnt/nas/music:/music:r:aed]")
|
||||||
ap2.add_argument("-ed", action="store_true", help="enable ?dots")
|
ap2.add_argument("-ed", action="store_true", help="enable the ?dots url parameter / client option which allows clients to see dotfiles / hidden files")
|
||||||
ap2.add_argument("-emp", action="store_true", help="enable markdown plugins")
|
ap2.add_argument("-emp", action="store_true", help="enable markdown plugins -- neat but dangerous, big XSS risk")
|
||||||
ap2.add_argument("-mcr", metavar="SEC", type=int, default=60, help="md-editor mod-chk rate")
|
ap2.add_argument("-mcr", metavar="SEC", type=int, default=60, help="md-editor mod-chk rate")
|
||||||
ap2.add_argument("--urlform", metavar="MODE", type=u, default="print,get", help="how to handle url-forms; examples: [stash], [save,get]")
|
ap2.add_argument("--urlform", metavar="MODE", type=u, default="print,get", help="how to handle url-form POSTs; see --help-urlform")
|
||||||
ap2.add_argument("--wintitle", metavar="TXT", type=u, default="cpp @ $pub", help="window title, for example '$ip-10.1.2.' or '$ip-'")
|
ap2.add_argument("--wintitle", metavar="TXT", type=u, default="cpp @ $pub", help="window title, for example '$ip-10.1.2.' or '$ip-'")
|
||||||
|
|
||||||
ap2 = ap.add_argument_group('upload options')
|
ap2 = ap.add_argument_group('upload options')
|
||||||
ap2.add_argument("--dotpart", action="store_true", help="dotfile incomplete uploads")
|
ap2.add_argument("--dotpart", action="store_true", help="dotfile incomplete uploads, hiding them from clients unless -ed")
|
||||||
ap2.add_argument("--sparse", metavar="MiB", type=int, default=4, help="up2k min.size threshold (mswin-only)")
|
ap2.add_argument("--sparse", metavar="MiB", type=int, default=4, help="windows-only: minimum size of incoming uploads through up2k before they are made into sparse files")
|
||||||
ap2.add_argument("--unpost", metavar="SEC", type=int, default=3600*12, help="grace period where uploads can be deleted by the uploader, even without delete permissions; 0=disabled")
|
ap2.add_argument("--unpost", metavar="SEC", type=int, default=3600*12, help="grace period where uploads can be deleted by the uploader, even without delete permissions; 0=disabled")
|
||||||
ap2.add_argument("--no-fpool", action="store_true", help="disable file-handle pooling -- instead, repeatedly close and reopen files during upload")
|
ap2.add_argument("--no-fpool", action="store_true", help="disable file-handle pooling -- instead, repeatedly close and reopen files during upload")
|
||||||
ap2.add_argument("--use-fpool", action="store_true", help="force file-handle pooling, even if copyparty thinks you're better off without")
|
ap2.add_argument("--use-fpool", action="store_true", help="force file-handle pooling, even if copyparty thinks you're better off without -- probably useful on nfs and cow filesystems (zfs, btrfs)")
|
||||||
ap2.add_argument("--hardlink", action="store_true", help="prefer hardlinks instead of symlinks when possible (same filesystem)")
|
ap2.add_argument("--hardlink", action="store_true", help="prefer hardlinks instead of symlinks when possible (within same filesystem)")
|
||||||
ap2.add_argument("--never-symlink", action="store_true", help="do not fallback to symlinks when a hardlink cannot be made")
|
ap2.add_argument("--never-symlink", action="store_true", help="do not fallback to symlinks when a hardlink cannot be made")
|
||||||
ap2.add_argument("--no-dedup", action="store_true", help="disable symlink/hardlink creation; copy file contents instead")
|
ap2.add_argument("--no-dedup", action="store_true", help="disable symlink/hardlink creation; copy file contents instead")
|
||||||
ap2.add_argument("--reg-cap", metavar="N", type=int, default=9000, help="max number of uploads to keep in memory when running without -e2d")
|
ap2.add_argument("--reg-cap", metavar="N", type=int, default=9000, help="max number of uploads to keep in memory when running without -e2d")
|
||||||
|
ap2.add_argument("--turbo", metavar="LVL", type=int, default=0, help="configure turbo-mode in up2k client; 0 = off and warn if enabled, 1 = off, 2 = on, 3 = on and disable datecheck")
|
||||||
|
|
||||||
ap2 = ap.add_argument_group('network options')
|
ap2 = ap.add_argument_group('network options')
|
||||||
ap2.add_argument("-i", metavar="IP", type=u, default="0.0.0.0", help="ip to bind (comma-sep.)")
|
ap2.add_argument("-i", metavar="IP", type=u, default="0.0.0.0", help="ip to bind (comma-sep.)")
|
||||||
ap2.add_argument("-p", metavar="PORT", type=u, default="3923", help="ports to bind (comma/range)")
|
ap2.add_argument("-p", metavar="PORT", type=u, default="3923", help="ports to bind (comma/range)")
|
||||||
ap2.add_argument("--rproxy", metavar="DEPTH", type=int, default=1, help="which ip to keep; 0 = tcp, 1 = origin (first x-fwd), 2 = cloudflare, 3 = nginx, -1 = closest proxy")
|
ap2.add_argument("--rproxy", metavar="DEPTH", type=int, default=1, help="which ip to keep; 0 = tcp, 1 = origin (first x-fwd), 2 = cloudflare, 3 = nginx, -1 = closest proxy")
|
||||||
ap2.add_argument("--s-wr-sz", metavar="B", type=int, default=256*1024, help="socket write size in bytes")
|
ap2.add_argument("--s-wr-sz", metavar="B", type=int, default=256*1024, help="socket write size in bytes")
|
||||||
ap2.add_argument("--s-wr-slp", metavar="SEC", type=float, default=0, help="socket write delay in seconds")
|
ap2.add_argument("--s-wr-slp", metavar="SEC", type=float, default=0, help="debug: socket write delay in seconds")
|
||||||
ap2.add_argument("--rsp-slp", metavar="SEC", type=float, default=0, help="response delay in seconds")
|
ap2.add_argument("--rsp-slp", metavar="SEC", type=float, default=0, help="debug: response delay in seconds")
|
||||||
|
|
||||||
ap2 = ap.add_argument_group('SSL/TLS options')
|
ap2 = ap.add_argument_group('SSL/TLS options')
|
||||||
ap2.add_argument("--http-only", action="store_true", help="disable ssl/tls")
|
ap2.add_argument("--http-only", action="store_true", help="disable ssl/tls -- force plaintext")
|
||||||
ap2.add_argument("--https-only", action="store_true", help="disable plaintext")
|
ap2.add_argument("--https-only", action="store_true", help="disable plaintext -- force tls")
|
||||||
ap2.add_argument("--ssl-ver", metavar="LIST", type=u, help="set allowed ssl/tls versions; [help] shows available versions; default is what your python version considers safe")
|
ap2.add_argument("--ssl-ver", metavar="LIST", type=u, help="set allowed ssl/tls versions; [help] shows available versions; default is what your python version considers safe")
|
||||||
ap2.add_argument("--ciphers", metavar="LIST", type=u, help="set allowed ssl/tls ciphers; [help] shows available ciphers")
|
ap2.add_argument("--ciphers", metavar="LIST", type=u, help="set allowed ssl/tls ciphers; [help] shows available ciphers")
|
||||||
ap2.add_argument("--ssl-dbg", action="store_true", help="dump some tls info")
|
ap2.add_argument("--ssl-dbg", action="store_true", help="dump some tls info")
|
||||||
ap2.add_argument("--ssl-log", metavar="PATH", type=u, help="log master secrets")
|
ap2.add_argument("--ssl-log", metavar="PATH", type=u, help="log master secrets for later decryption in wireshark")
|
||||||
|
|
||||||
ap2 = ap.add_argument_group('FTP options')
|
ap2 = ap.add_argument_group('FTP options')
|
||||||
ap2.add_argument("--ftp", metavar="PORT", type=int, help="enable FTP server on PORT, for example 3921")
|
ap2.add_argument("--ftp", metavar="PORT", type=int, help="enable FTP server on PORT, for example 3921")
|
||||||
@@ -455,26 +467,27 @@ def run_argparse(argv, formatter):
|
|||||||
ap2.add_argument("--ftp-pr", metavar="P-P", type=u, help="the range of TCP ports to use for passive connections, for example 12000-13000")
|
ap2.add_argument("--ftp-pr", metavar="P-P", type=u, help="the range of TCP ports to use for passive connections, for example 12000-13000")
|
||||||
|
|
||||||
ap2 = ap.add_argument_group('opt-outs')
|
ap2 = ap.add_argument_group('opt-outs')
|
||||||
ap2.add_argument("-nw", action="store_true", help="disable writes (benchmark)")
|
ap2.add_argument("-nw", action="store_true", help="never write anything to disk (debug/benchmark)")
|
||||||
ap2.add_argument("--keep-qem", action="store_true", help="do not disable quick-edit-mode on windows")
|
ap2.add_argument("--keep-qem", action="store_true", help="do not disable quick-edit-mode on windows (it is disabled to avoid accidental text selection which will deadlock copyparty)")
|
||||||
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("-nih", action="store_true", help="no info hostname")
|
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")
|
ap2.add_argument("-nid", action="store_true", help="no info disk-usage -- don't show in UI")
|
||||||
ap2.add_argument("--no-zip", action="store_true", help="disable download as zip/tar")
|
ap2.add_argument("--no-zip", action="store_true", help="disable download as zip/tar")
|
||||||
ap2.add_argument("--no-lifetime", action="store_true", help="disable automatic deletion of uploads after a certain time (lifetime volflag)")
|
ap2.add_argument("--no-lifetime", action="store_true", help="disable automatic deletion of uploads after a certain time (lifetime volflag)")
|
||||||
|
|
||||||
ap2 = ap.add_argument_group('safety options')
|
ap2 = ap.add_argument_group('safety options')
|
||||||
ap2.add_argument("--ls", metavar="U[,V[,F]]", type=u, help="scan all volumes; arguments USER,VOL,FLAGS; example [**,*,ln,p,r]")
|
ap2.add_argument("--ls", metavar="U[,V[,F]]", type=u, help="do a sanity/safety check of all volumes on startup; arguments USER,VOL,FLAGS; example [**,*,ln,p,r]")
|
||||||
ap2.add_argument("--salt", type=u, default="hunter2", help="up2k file-hash salt")
|
ap2.add_argument("--salt", type=u, default="hunter2", help="up2k file-hash salt; used to generate unpredictable internal identifiers for uploads -- doesn't really matter")
|
||||||
ap2.add_argument("--fk-salt", metavar="SALT", type=u, default=fk_salt, help="per-file accesskey salt")
|
ap2.add_argument("--fk-salt", metavar="SALT", type=u, default=fk_salt, help="per-file accesskey salt; used to generate unpredictable URLs for hidden files -- this one DOES matter")
|
||||||
ap2.add_argument("--no-dot-mv", action="store_true", help="disallow moving dotfiles; makes it impossible to move folders containing dotfiles")
|
ap2.add_argument("--no-dot-mv", action="store_true", help="disallow moving dotfiles; makes it impossible to move folders containing dotfiles")
|
||||||
ap2.add_argument("--no-dot-ren", action="store_true", help="disallow renaming dotfiles; makes it impossible to make something a dotfile")
|
ap2.add_argument("--no-dot-ren", action="store_true", help="disallow renaming dotfiles; makes it impossible to make something a dotfile")
|
||||||
ap2.add_argument("--no-logues", action="store_true", help="disable rendering .prologue/.epilogue.html into directory listings")
|
ap2.add_argument("--no-logues", action="store_true", help="disable rendering .prologue/.epilogue.html into directory listings")
|
||||||
ap2.add_argument("--no-readme", action="store_true", help="disable rendering readme.md into directory listings")
|
ap2.add_argument("--no-readme", action="store_true", help="disable rendering readme.md into directory listings")
|
||||||
ap2.add_argument("--vague-403", action="store_true", help="send 404 instead of 403 (security through ambiguity, very enterprise)")
|
ap2.add_argument("--vague-403", action="store_true", help="send 404 instead of 403 (security through ambiguity, very enterprise)")
|
||||||
ap2.add_argument("--force-js", action="store_true", help="don't send HTML folder listings, force clients to use the embedded json instead")
|
ap2.add_argument("--force-js", action="store_true", help="don't send folder listings as HTML, force clients to use the embedded json instead -- slight protection against misbehaving search engines which ignore --no-robots")
|
||||||
ap2.add_argument("--no-robots", action="store_true", help="adds http and html headers asking search engines to not index anything")
|
ap2.add_argument("--no-robots", action="store_true", help="adds http and html headers asking search engines to not index anything")
|
||||||
|
ap2.add_argument("--logout", metavar="H", type=float, default="8086", help="logout clients after H hours of inactivity (0.0028=10sec, 0.1=6min, 24=day, 168=week, 720=month, 8760=year)")
|
||||||
|
|
||||||
ap2 = ap.add_argument_group('yolo options')
|
ap2 = ap.add_argument_group('yolo options')
|
||||||
ap2.add_argument("--ign-ebind", action="store_true", help="continue running even if it's impossible to listen on some of the requested endpoints")
|
ap2.add_argument("--ign-ebind", action="store_true", help="continue running even if it's impossible to listen on some of the requested endpoints")
|
||||||
@@ -484,8 +497,8 @@ def run_argparse(argv, formatter):
|
|||||||
ap2.add_argument("-q", action="store_true", help="quiet")
|
ap2.add_argument("-q", action="store_true", help="quiet")
|
||||||
ap2.add_argument("-lo", metavar="PATH", type=u, help="logfile, example: cpp-%%Y-%%m%%d-%%H%%M%%S.txt.xz")
|
ap2.add_argument("-lo", metavar="PATH", type=u, help="logfile, example: cpp-%%Y-%%m%%d-%%H%%M%%S.txt.xz")
|
||||||
ap2.add_argument("--no-voldump", action="store_true", help="do not list volumes and permissions on startup")
|
ap2.add_argument("--no-voldump", action="store_true", help="do not list volumes and permissions on startup")
|
||||||
ap2.add_argument("--log-conn", action="store_true", help="print tcp-server msgs")
|
ap2.add_argument("--log-conn", action="store_true", help="debug: print tcp-server msgs")
|
||||||
ap2.add_argument("--log-htp", action="store_true", help="print http-server threadpool scaling")
|
ap2.add_argument("--log-htp", action="store_true", help="debug: print http-server threadpool scaling")
|
||||||
ap2.add_argument("--ihead", metavar="HEADER", type=u, action='append', help="dump incoming header")
|
ap2.add_argument("--ihead", metavar="HEADER", type=u, action='append', help="dump incoming header")
|
||||||
ap2.add_argument("--lf-url", metavar="RE", type=u, default=r"^/\.cpr/|\?th=[wj]$", help="dont log URLs matching")
|
ap2.add_argument("--lf-url", metavar="RE", type=u, default=r"^/\.cpr/|\?th=[wj]$", help="dont log URLs matching")
|
||||||
|
|
||||||
@@ -502,15 +515,15 @@ def run_argparse(argv, formatter):
|
|||||||
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=int, default=60, help="conversion timeout in seconds")
|
ap2.add_argument("--th-convt", metavar="SEC", type=int, default=60, help="conversion timeout in seconds")
|
||||||
ap2.add_argument("--th-no-crop", action="store_true", help="dynamic height; show full image")
|
ap2.add_argument("--th-no-crop", action="store_true", help="dynamic height; show full image")
|
||||||
ap2.add_argument("--th-dec", metavar="LIBS", default="vips,pil,ff", help="decoders, in order of preference")
|
ap2.add_argument("--th-dec", metavar="LIBS", default="vips,pil,ff", help="image decoders, in order of preference")
|
||||||
ap2.add_argument("--th-no-jpg", action="store_true", help="disable jpg output")
|
ap2.add_argument("--th-no-jpg", action="store_true", help="disable jpg output")
|
||||||
ap2.add_argument("--th-no-webp", action="store_true", help="disable webp output")
|
ap2.add_argument("--th-no-webp", action="store_true", help="disable webp output")
|
||||||
ap2.add_argument("--th-ff-jpg", action="store_true", help="force jpg for video thumbs")
|
ap2.add_argument("--th-ff-jpg", action="store_true", help="force jpg output for video thumbs")
|
||||||
ap2.add_argument("--th-ff-swr", action="store_true", help="use swresample instead of soxr for audio thumbs")
|
ap2.add_argument("--th-ff-swr", action="store_true", help="use swresample instead of soxr for audio thumbs")
|
||||||
ap2.add_argument("--th-poke", metavar="SEC", type=int, default=300, help="activity labeling cooldown")
|
ap2.add_argument("--th-poke", metavar="SEC", type=int, default=300, help="activity labeling cooldown -- avoids doing keepalive pokes (updating the mtime) on thumbnail folders more often than SEC seconds")
|
||||||
ap2.add_argument("--th-clean", metavar="SEC", type=int, default=43200, help="cleanup interval; 0=disabled")
|
ap2.add_argument("--th-clean", metavar="SEC", type=int, default=43200, help="cleanup interval; 0=disabled")
|
||||||
ap2.add_argument("--th-maxage", metavar="SEC", type=int, default=604800, help="max folder age")
|
ap2.add_argument("--th-maxage", metavar="SEC", type=int, default=604800, help="max folder age -- folders which haven't been poked for longer than --th-poke seconds will get deleted every --th-clean seconds")
|
||||||
ap2.add_argument("--th-covers", metavar="N,N", type=u, default="folder.png,folder.jpg,cover.png,cover.jpg", help="folder thumbnails to stat for")
|
ap2.add_argument("--th-covers", metavar="N,N", type=u, default="folder.png,folder.jpg,cover.png,cover.jpg", help="folder thumbnails to stat/look for")
|
||||||
# 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:'
|
||||||
@@ -522,46 +535,48 @@ def run_argparse(argv, formatter):
|
|||||||
|
|
||||||
ap2 = ap.add_argument_group('transcoding options')
|
ap2 = ap.add_argument_group('transcoding options')
|
||||||
ap2.add_argument("--no-acode", action="store_true", help="disable audio transcoding")
|
ap2.add_argument("--no-acode", action="store_true", help="disable audio transcoding")
|
||||||
ap2.add_argument("--ac-maxage", metavar="SEC", type=int, default=86400, help="delete transcode output after SEC seconds")
|
ap2.add_argument("--ac-maxage", metavar="SEC", type=int, default=86400, help="delete cached transcode output after SEC seconds")
|
||||||
|
|
||||||
ap2 = ap.add_argument_group('general db options')
|
ap2 = ap.add_argument_group('general db options')
|
||||||
ap2.add_argument("-e2d", action="store_true", help="enable up2k database")
|
ap2.add_argument("-e2d", action="store_true", help="enable up2k database, making files searchable + enables upload deduplocation")
|
||||||
ap2.add_argument("-e2ds", action="store_true", help="enable up2k db-scanner, sets -e2d")
|
ap2.add_argument("-e2ds", action="store_true", help="scan writable folders for new files on startup; sets -e2d")
|
||||||
ap2.add_argument("-e2dsa", action="store_true", help="scan all folders (for search), sets -e2ds")
|
ap2.add_argument("-e2dsa", action="store_true", help="scans all folders on startup; sets -e2ds")
|
||||||
ap2.add_argument("--hist", metavar="PATH", type=u, help="where to store volume data (db, thumbs)")
|
ap2.add_argument("--hist", metavar="PATH", type=u, help="where to store volume data (db, thumbs)")
|
||||||
ap2.add_argument("--no-hash", metavar="PTN", type=u, help="regex: disable hashing of matching paths during e2ds folder scans")
|
ap2.add_argument("--no-hash", metavar="PTN", type=u, help="regex: disable hashing of matching paths during e2ds folder scans")
|
||||||
ap2.add_argument("--no-idx", metavar="PTN", type=u, help="regex: disable indexing of matching paths during e2ds folder scans")
|
ap2.add_argument("--no-idx", metavar="PTN", type=u, help="regex: disable indexing of matching paths during e2ds folder scans")
|
||||||
ap2.add_argument("--re-maxage", metavar="SEC", type=int, default=0, help="disk rescan volume interval, 0=off, can be set per-volume with the 'scan' volflag")
|
ap2.add_argument("--re-maxage", metavar="SEC", type=int, default=0, help="disk rescan volume interval, 0=off, can be set per-volume with the 'scan' volflag")
|
||||||
ap2.add_argument("--srch-time", metavar="SEC", type=int, default=30, help="search deadline")
|
ap2.add_argument("--srch-time", metavar="SEC", type=int, default=30, help="search deadline -- terminate searches running for more than SEC seconds")
|
||||||
ap2.add_argument("--srch-hits", metavar="N", type=int, default=1000, help="max search results")
|
ap2.add_argument("--srch-hits", metavar="N", type=int, default=7999, help="max search results to allow clients to fetch; 125 results will be shown initially")
|
||||||
|
|
||||||
ap2 = ap.add_argument_group('metadata db options')
|
ap2 = ap.add_argument_group('metadata db options')
|
||||||
ap2.add_argument("-e2t", action="store_true", help="enable metadata indexing")
|
ap2.add_argument("-e2t", action="store_true", help="enable metadata indexing; makes it possible to search for artist/title/codec/resolution/...")
|
||||||
ap2.add_argument("-e2ts", action="store_true", help="enable metadata scanner, sets -e2t")
|
ap2.add_argument("-e2ts", action="store_true", help="scan existing files on startup; sets -e2t")
|
||||||
ap2.add_argument("-e2tsr", action="store_true", help="rescan all metadata, sets -e2ts")
|
ap2.add_argument("-e2tsr", action="store_true", help="delete all metadata from DB and do a full rescan; sets -e2ts")
|
||||||
ap2.add_argument("--no-mutagen", action="store_true", help="use FFprobe for tags instead")
|
ap2.add_argument("--no-mutagen", action="store_true", help="use FFprobe for tags instead; will catch more tags")
|
||||||
ap2.add_argument("--no-mtag-ff", action="store_true", help="never use FFprobe as tag reader")
|
ap2.add_argument("--no-mtag-ff", action="store_true", help="never use FFprobe as tag reader; is probably safer")
|
||||||
ap2.add_argument("--mtag-mt", metavar="CORES", type=int, default=cores, help="num cpu cores to use for tag scanning")
|
ap2.add_argument("--mtag-mt", metavar="CORES", type=int, default=cores, help="num cpu cores to use for tag scanning")
|
||||||
ap2.add_argument("-mtm", metavar="M=t,t,t", type=u, action="append", help="add/replace metadata mapping")
|
ap2.add_argument("-mtm", metavar="M=t,t,t", type=u, action="append", help="add/replace metadata mapping")
|
||||||
ap2.add_argument("-mte", metavar="M,M,M", type=u, help="tags to index/display (comma-sep.)",
|
ap2.add_argument("-mte", metavar="M,M,M", type=u, help="tags to index/display (comma-sep.)",
|
||||||
default="circle,album,.tn,artist,title,.bpm,key,.dur,.q,.vq,.aq,vc,ac,res,.fps,ahash,vhash")
|
default="circle,album,.tn,artist,title,.bpm,key,.dur,.q,.vq,.aq,vc,ac,res,.fps,ahash,vhash")
|
||||||
ap2.add_argument("-mth", metavar="M,M,M", type=u, help="tags to hide by default (comma-sep.)",
|
ap2.add_argument("-mth", metavar="M,M,M", type=u, help="tags to hide by default (comma-sep.)",
|
||||||
default=".vq,.aq,vc,ac,res,.fps")
|
default=".vq,.aq,vc,ac,res,.fps")
|
||||||
ap2.add_argument("-mtp", metavar="M=[f,]bin", type=u, action="append", help="read tag M using bin")
|
ap2.add_argument("-mtp", metavar="M=[f,]BIN", type=u, action="append", help="read tag M using program BIN to parse the file")
|
||||||
|
|
||||||
ap2 = ap.add_argument_group('ui options')
|
ap2 = ap.add_argument_group('ui options')
|
||||||
|
ap2.add_argument("--lang", metavar="LANG", type=u, default="eng", help="language")
|
||||||
ap2.add_argument("--theme", metavar="NUM", type=int, default=0, help="default theme to use")
|
ap2.add_argument("--theme", metavar="NUM", type=int, default=0, help="default theme to use")
|
||||||
ap2.add_argument("--themes", metavar="NUM", type=int, default=4, help="number of themes installed")
|
ap2.add_argument("--themes", metavar="NUM", type=int, default=6, help="number of themes installed")
|
||||||
ap2.add_argument("--js-browser", metavar="L", type=u, help="URL to additional JS to include")
|
ap2.add_argument("--js-browser", metavar="L", type=u, help="URL to additional JS to include")
|
||||||
ap2.add_argument("--css-browser", metavar="L", type=u, help="URL to additional CSS to include")
|
ap2.add_argument("--css-browser", metavar="L", type=u, help="URL to additional CSS to include")
|
||||||
ap2.add_argument("--html-head", metavar="TXT", type=u, default="", help="text to append to the <head> of all HTML pages")
|
ap2.add_argument("--html-head", metavar="TXT", type=u, default="", help="text to append to the <head> of all HTML pages")
|
||||||
ap2.add_argument("--textfiles", metavar="CSV", type=u, default="txt,nfo,diz,cue,readme", help="file extensions to present as plaintext")
|
ap2.add_argument("--textfiles", metavar="CSV", type=u, default="txt,nfo,diz,cue,readme", help="file extensions to present as plaintext")
|
||||||
|
ap2.add_argument("--txt-max", metavar="KiB", type=int, default=64, help="max size of embedded textfiles on ?doc= (anything bigger will be lazy-loaded by JS)")
|
||||||
ap2.add_argument("--doctitle", metavar="TXT", type=u, default="copyparty", help="title / service-name to show in html documents")
|
ap2.add_argument("--doctitle", metavar="TXT", type=u, default="copyparty", help="title / service-name to show in html documents")
|
||||||
|
|
||||||
ap2 = ap.add_argument_group('debug options')
|
ap2 = ap.add_argument_group('debug options')
|
||||||
ap2.add_argument("--no-sendfile", action="store_true", help="disable sendfile")
|
ap2.add_argument("--no-sendfile", action="store_true", help="disable sendfile; instead using a traditional file read loop")
|
||||||
ap2.add_argument("--no-scandir", action="store_true", help="disable scandir")
|
ap2.add_argument("--no-scandir", action="store_true", help="disable scandir; instead using listdir + stat on each file")
|
||||||
ap2.add_argument("--no-fastboot", action="store_true", help="wait for up2k indexing")
|
ap2.add_argument("--no-fastboot", action="store_true", help="wait for up2k indexing before starting the httpd")
|
||||||
ap2.add_argument("--no-htp", action="store_true", help="disable httpserver threadpool, create threads as-needed instead")
|
ap2.add_argument("--no-htp", action="store_true", help="disable httpserver threadpool, create threads as-needed instead")
|
||||||
ap2.add_argument("--stackmon", metavar="P,S", type=u, help="write stacktrace to Path every S second")
|
ap2.add_argument("--stackmon", metavar="P,S", type=u, help="write stacktrace to Path every S second")
|
||||||
ap2.add_argument("--log-thrs", metavar="SEC", type=float, help="list active threads every SEC")
|
ap2.add_argument("--log-thrs", metavar="SEC", type=float, help="list active threads every SEC")
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
# coding: utf-8
|
# coding: utf-8
|
||||||
|
|
||||||
VERSION = (1, 2, 6)
|
VERSION = (1, 3, 0)
|
||||||
CODENAME = "ftp btw"
|
CODENAME = "god dag"
|
||||||
BUILD_DT = (2022, 4, 15)
|
BUILD_DT = (2022, 5, 22)
|
||||||
|
|
||||||
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)
|
||||||
|
|||||||
@@ -11,12 +11,13 @@ import hashlib
|
|||||||
import threading
|
import threading
|
||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
|
|
||||||
from .__init__ import WINDOWS
|
from .__init__ import ANYWIN, WINDOWS
|
||||||
from .util import (
|
from .util import (
|
||||||
IMPLICATIONS,
|
IMPLICATIONS,
|
||||||
META_NOBOTS,
|
META_NOBOTS,
|
||||||
uncyg,
|
uncyg,
|
||||||
undot,
|
undot,
|
||||||
|
relchk,
|
||||||
unhumanize,
|
unhumanize,
|
||||||
absreal,
|
absreal,
|
||||||
Pebkac,
|
Pebkac,
|
||||||
@@ -335,6 +336,12 @@ class VFS(object):
|
|||||||
):
|
):
|
||||||
# type: (str, str, bool, bool, bool, bool, bool) -> tuple[VFS, str]
|
# type: (str, str, bool, bool, bool, bool, bool) -> tuple[VFS, str]
|
||||||
"""returns [vfsnode,fs_remainder] if user has the requested permissions"""
|
"""returns [vfsnode,fs_remainder] if user has the requested permissions"""
|
||||||
|
if ANYWIN:
|
||||||
|
mod = relchk(vpath)
|
||||||
|
if mod:
|
||||||
|
self.log("vfs", "invalid relpath [{}]".format(vpath))
|
||||||
|
raise Pebkac(404)
|
||||||
|
|
||||||
vn, rem = self._find(vpath)
|
vn, rem = self._find(vpath)
|
||||||
c = vn.axs
|
c = vn.axs
|
||||||
|
|
||||||
@@ -404,7 +411,7 @@ class VFS(object):
|
|||||||
|
|
||||||
return [abspath, real, virt_vis]
|
return [abspath, real, virt_vis]
|
||||||
|
|
||||||
def walk(self, rel, rem, seen, uname, permsets, dots, scandir, lstat):
|
def walk(self, rel, rem, seen, uname, permsets, dots, scandir, lstat, subvols=True):
|
||||||
"""
|
"""
|
||||||
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)
|
||||||
@@ -437,9 +444,14 @@ class VFS(object):
|
|||||||
|
|
||||||
wrel = (rel + "/" + rdir).lstrip("/")
|
wrel = (rel + "/" + rdir).lstrip("/")
|
||||||
wrem = (rem + "/" + rdir).lstrip("/")
|
wrem = (rem + "/" + rdir).lstrip("/")
|
||||||
for x in self.walk(wrel, wrem, seen, uname, permsets, dots, scandir, lstat):
|
for x in self.walk(
|
||||||
|
wrel, wrem, seen, uname, permsets, dots, scandir, lstat, subvols
|
||||||
|
):
|
||||||
yield x
|
yield x
|
||||||
|
|
||||||
|
if not subvols:
|
||||||
|
return
|
||||||
|
|
||||||
for n, vfs in sorted(vfs_virt.items()):
|
for n, vfs in sorted(vfs_virt.items()):
|
||||||
if not dots and n.startswith("."):
|
if not dots and n.startswith("."):
|
||||||
continue
|
continue
|
||||||
@@ -1100,7 +1112,7 @@ class AuthSrv(object):
|
|||||||
flag_p = "p" in flags
|
flag_p = "p" in flags
|
||||||
flag_r = "r" in flags
|
flag_r = "r" in flags
|
||||||
|
|
||||||
n_bads = 0
|
bads = []
|
||||||
for v in vols:
|
for v in vols:
|
||||||
v = v[1:]
|
v = v[1:]
|
||||||
vtop = "/{}/".format(v) if v else "/"
|
vtop = "/{}/".format(v) if v else "/"
|
||||||
@@ -1112,10 +1124,19 @@ class AuthSrv(object):
|
|||||||
continue
|
continue
|
||||||
|
|
||||||
atop = vn.realpath
|
atop = vn.realpath
|
||||||
|
safeabs = atop + os.sep
|
||||||
g = vn.walk(
|
g = vn.walk(
|
||||||
vn.vpath, "", [], u, [[True]], True, not self.args.no_scandir, False
|
vn.vpath,
|
||||||
|
"",
|
||||||
|
[],
|
||||||
|
u,
|
||||||
|
[[True]],
|
||||||
|
True,
|
||||||
|
not self.args.no_scandir,
|
||||||
|
False,
|
||||||
|
False,
|
||||||
)
|
)
|
||||||
for _, _, vpath, apath, files, _, _ in g:
|
for _, _, vpath, apath, files, dirs, _ in g:
|
||||||
fnames = [n[0] for n in files]
|
fnames = [n[0] for n in files]
|
||||||
vpaths = [vpath + "/" + n for n in fnames] if vpath else fnames
|
vpaths = [vpath + "/" + n for n in fnames] if vpath else fnames
|
||||||
vpaths = [vtop + x for x in vpaths]
|
vpaths = [vtop + x for x in vpaths]
|
||||||
@@ -1123,21 +1144,28 @@ class AuthSrv(object):
|
|||||||
files = [[vpath + "/", apath + os.sep]] + list(zip(vpaths, apaths))
|
files = [[vpath + "/", apath + os.sep]] + list(zip(vpaths, apaths))
|
||||||
|
|
||||||
if flag_ln:
|
if flag_ln:
|
||||||
files = [x for x in files if not x[1].startswith(atop + os.sep)]
|
files = [x for x in files if not x[1].startswith(safeabs)]
|
||||||
n_bads += len(files)
|
if files:
|
||||||
|
dirs[:] = [] # stop recursion
|
||||||
|
bads.append(files[0][0])
|
||||||
|
|
||||||
if flag_v:
|
if not files:
|
||||||
msg = [
|
continue
|
||||||
|
elif flag_v:
|
||||||
|
msg = [""] + [
|
||||||
'# user "{}", vpath "{}"\n{}'.format(u, vp, ap)
|
'# user "{}", vpath "{}"\n{}'.format(u, vp, ap)
|
||||||
for vp, ap in files
|
for vp, ap in files
|
||||||
]
|
]
|
||||||
else:
|
else:
|
||||||
msg = [x[1] for x in files]
|
msg = ["user {}, vol {}: {} =>".format(u, vtop, files[0][0])]
|
||||||
|
msg += [x[1] for x in files]
|
||||||
|
|
||||||
if msg:
|
self.log("\n".join(msg))
|
||||||
self.log("\n" + "\n".join(msg))
|
|
||||||
|
|
||||||
if n_bads and flag_p:
|
if bads:
|
||||||
|
self.log("\n ".join(["found symlinks leaving volume:"] + bads))
|
||||||
|
|
||||||
|
if bads and flag_p:
|
||||||
raise Exception("found symlink leaving volume, and strict is set")
|
raise Exception("found symlink leaving volume, and strict is set")
|
||||||
|
|
||||||
if not flag_r:
|
if not flag_r:
|
||||||
|
|||||||
@@ -121,6 +121,12 @@ class HttpCli(object):
|
|||||||
|
|
||||||
try:
|
try:
|
||||||
self.mode, self.req, self.http_ver = headerlines[0].split(" ")
|
self.mode, self.req, self.http_ver = headerlines[0].split(" ")
|
||||||
|
|
||||||
|
# normalize incoming headers to lowercase;
|
||||||
|
# outgoing headers however are Correct-Case
|
||||||
|
for header_line in headerlines[1:]:
|
||||||
|
k, v = header_line.split(":", 1)
|
||||||
|
self.headers[k.lower()] = v.strip()
|
||||||
except:
|
except:
|
||||||
msg = " ]\n#[ ".join(headerlines)
|
msg = " ]\n#[ ".join(headerlines)
|
||||||
raise Pebkac(400, "bad headers:\n#[ " + msg + " ]")
|
raise Pebkac(400, "bad headers:\n#[ " + msg + " ]")
|
||||||
@@ -137,11 +143,9 @@ class HttpCli(object):
|
|||||||
if self.args.rsp_slp:
|
if self.args.rsp_slp:
|
||||||
time.sleep(self.args.rsp_slp)
|
time.sleep(self.args.rsp_slp)
|
||||||
|
|
||||||
# normalize incoming headers to lowercase;
|
self.ua = self.headers.get("user-agent", "")
|
||||||
# outgoing headers however are Correct-Case
|
self.is_rclone = self.ua.startswith("rclone/")
|
||||||
for header_line in headerlines[1:]:
|
self.is_ancient = self.ua.startswith("Mozilla/4.")
|
||||||
k, v = header_line.split(":", 1)
|
|
||||||
self.headers[k.lower()] = v.strip()
|
|
||||||
|
|
||||||
v = self.headers.get("connection", "").lower()
|
v = self.headers.get("connection", "").lower()
|
||||||
self.keepalive = not v.startswith("close") and self.http_ver != "HTTP/1.0"
|
self.keepalive = not v.startswith("close") and self.http_ver != "HTTP/1.0"
|
||||||
@@ -211,6 +215,14 @@ class HttpCli(object):
|
|||||||
self.cookies = cookies
|
self.cookies = cookies
|
||||||
self.vpath = unquotep(vpath) # not query, so + means +
|
self.vpath = unquotep(vpath) # not query, so + means +
|
||||||
|
|
||||||
|
ok = "\x00" not in self.vpath
|
||||||
|
if ANYWIN:
|
||||||
|
ok = ok and not relchk(self.vpath)
|
||||||
|
|
||||||
|
if not ok:
|
||||||
|
self.log("invalid relpath [{}]".format(self.vpath))
|
||||||
|
return self.tx_404() and self.keepalive
|
||||||
|
|
||||||
pwd = None
|
pwd = None
|
||||||
ba = self.headers.get("authorization")
|
ba = self.headers.get("authorization")
|
||||||
if ba:
|
if ba:
|
||||||
@@ -233,11 +245,9 @@ class HttpCli(object):
|
|||||||
self.dvol = self.asrv.vfs.adel[self.uname]
|
self.dvol = self.asrv.vfs.adel[self.uname]
|
||||||
self.gvol = self.asrv.vfs.aget[self.uname]
|
self.gvol = self.asrv.vfs.aget[self.uname]
|
||||||
|
|
||||||
if pwd and "pw" in self.ouparam and pwd != cookies.get("cppwd"):
|
if pwd:
|
||||||
self.out_headerlist.append(("Set-Cookie", self.get_pwd_cookie(pwd)[0]))
|
self.out_headerlist.append(("Set-Cookie", self.get_pwd_cookie(pwd)[0]))
|
||||||
|
|
||||||
self.ua = self.headers.get("user-agent", "")
|
|
||||||
self.is_rclone = self.ua.startswith("rclone/")
|
|
||||||
if self.is_rclone:
|
if self.is_rclone:
|
||||||
uparam["raw"] = False
|
uparam["raw"] = False
|
||||||
uparam["dots"] = False
|
uparam["dots"] = False
|
||||||
@@ -274,10 +284,11 @@ class HttpCli(object):
|
|||||||
msg = str(ex) if pex == ex else min_ex()
|
msg = str(ex) if pex == ex else min_ex()
|
||||||
self.log("{}\033[0m, {}".format(msg, self.vpath), 3)
|
self.log("{}\033[0m, {}".format(msg, self.vpath), 3)
|
||||||
|
|
||||||
msg = "<pre>{}\r\nURL: {}\r\n".format(str(ex), self.vpath)
|
msg = "{}\r\nURL: {}\r\n".format(str(ex), self.vpath)
|
||||||
if self.hint:
|
if self.hint:
|
||||||
msg += "hint: {}\r\n".format(self.hint)
|
msg += "hint: {}\r\n".format(self.hint)
|
||||||
|
|
||||||
|
msg = "<pre>" + html_escape(msg)
|
||||||
self.reply(msg.encode("utf-8", "replace"), status=pex.code, volsan=True)
|
self.reply(msg.encode("utf-8", "replace"), status=pex.code, volsan=True)
|
||||||
return self.keepalive
|
return self.keepalive
|
||||||
except Pebkac:
|
except Pebkac:
|
||||||
@@ -344,8 +355,11 @@ class HttpCli(object):
|
|||||||
return body
|
return body
|
||||||
|
|
||||||
def loud_reply(self, body, *args, **kwargs):
|
def loud_reply(self, body, *args, **kwargs):
|
||||||
|
if not kwargs.get("mime"):
|
||||||
|
kwargs["mime"] = "text/plain; charset=utf-8"
|
||||||
|
|
||||||
self.log(body.rstrip())
|
self.log(body.rstrip())
|
||||||
self.reply(b"<pre>" + body.encode("utf-8") + b"\r\n", *list(args), **kwargs)
|
self.reply(body.encode("utf-8") + b"\r\n", *list(args), **kwargs)
|
||||||
|
|
||||||
def urlq(self, add, rm):
|
def urlq(self, add, rm):
|
||||||
"""
|
"""
|
||||||
@@ -864,8 +878,9 @@ class HttpCli(object):
|
|||||||
else:
|
else:
|
||||||
# search by query params
|
# search by query params
|
||||||
q = body["q"]
|
q = body["q"]
|
||||||
self.log("qj: " + q)
|
n = body.get("n", self.args.srch_hits)
|
||||||
hits, taglist = idx.search(vols, q)
|
self.log("qj: {} |{}|".format(q, n))
|
||||||
|
hits, taglist = idx.search(vols, q, n)
|
||||||
msg = len(hits)
|
msg = len(hits)
|
||||||
|
|
||||||
idx.p_end = time.time()
|
idx.p_end = time.time()
|
||||||
@@ -995,9 +1010,15 @@ class HttpCli(object):
|
|||||||
pwd = self.parser.require("cppwd", 64)
|
pwd = self.parser.require("cppwd", 64)
|
||||||
self.parser.drop()
|
self.parser.drop()
|
||||||
|
|
||||||
dst = "/?h"
|
self.out_headerlist = [
|
||||||
|
x
|
||||||
|
for x in self.out_headerlist
|
||||||
|
if x[0] != "Set-Cookie" or "cppwd=" not in x[1]
|
||||||
|
]
|
||||||
|
|
||||||
|
dst = "/"
|
||||||
if self.vpath:
|
if self.vpath:
|
||||||
dst = "/" + quotep(self.vpath)
|
dst += quotep(self.vpath)
|
||||||
|
|
||||||
ck, msg = self.get_pwd_cookie(pwd)
|
ck, msg = self.get_pwd_cookie(pwd)
|
||||||
html = self.j2("msg", h1=msg, h2='<a href="' + dst + '">ack</a>', redir=dst)
|
html = self.j2("msg", h1=msg, h2='<a href="' + dst + '">ack</a>', redir=dst)
|
||||||
@@ -1007,13 +1028,17 @@ class HttpCli(object):
|
|||||||
def get_pwd_cookie(self, pwd):
|
def get_pwd_cookie(self, pwd):
|
||||||
if pwd in self.asrv.iacct:
|
if pwd in self.asrv.iacct:
|
||||||
msg = "login ok"
|
msg = "login ok"
|
||||||
dur = 60 * 60 * 24 * 365
|
dur = int(60 * 60 * self.args.logout)
|
||||||
else:
|
else:
|
||||||
msg = "naw dude"
|
msg = "naw dude"
|
||||||
pwd = "x" # nosec
|
pwd = "x" # nosec
|
||||||
dur = None
|
dur = None
|
||||||
|
|
||||||
return [gencookie("cppwd", pwd, dur), msg]
|
r = gencookie("cppwd", pwd, dur)
|
||||||
|
if self.is_ancient:
|
||||||
|
r = r.rsplit(" ", 1)[0]
|
||||||
|
|
||||||
|
return [r, msg]
|
||||||
|
|
||||||
def handle_mkdir(self):
|
def handle_mkdir(self):
|
||||||
new_dir = self.parser.require("name", 512)
|
new_dir = self.parser.require("name", 512)
|
||||||
@@ -1046,6 +1071,7 @@ class HttpCli(object):
|
|||||||
raise Pebkac(500, min_ex())
|
raise Pebkac(500, min_ex())
|
||||||
|
|
||||||
vpath = "{}/{}".format(self.vpath, sanitized).lstrip("/")
|
vpath = "{}/{}".format(self.vpath, sanitized).lstrip("/")
|
||||||
|
self.out_headers["X-New-Dir"] = quotep(sanitized)
|
||||||
self.redirect(vpath)
|
self.redirect(vpath)
|
||||||
return True
|
return True
|
||||||
|
|
||||||
@@ -1801,15 +1827,17 @@ class HttpCli(object):
|
|||||||
self.redirect("", "?h#cc")
|
self.redirect("", "?h#cc")
|
||||||
|
|
||||||
def tx_404(self, is_403=False):
|
def tx_404(self, is_403=False):
|
||||||
|
rc = 404
|
||||||
if self.args.vague_403:
|
if self.args.vague_403:
|
||||||
m = '<h1>404 not found ┐( ´ -`)┌</h1><p>or maybe you don\'t have access -- try logging in or <a href="/?h">go home</a></p>'
|
m = '<h1 id="n">404 not found ┐( ´ -`)┌</h1><p id="o">or maybe you don\'t have access -- try logging in or <a href="/?h">go home</a></p>'
|
||||||
elif is_403:
|
elif is_403:
|
||||||
m = '<h1>403 forbiddena ~┻━┻</h1><p>you\'ll have to log in or <a href="/?h">go home</a></p>'
|
m = '<h1 id="p">403 forbiddena ~┻━┻</h1><p id="q">you\'ll have to log in or <a href="/?h">go home</a></p>'
|
||||||
|
rc = 403
|
||||||
else:
|
else:
|
||||||
m = '<h1>404 not found ┐( ´ -`)┌</h1><p><a href="/?h">go home</a></p>'
|
m = '<h1 id="n">404 not found ┐( ´ -`)┌</h1><p><a id="r" href="/?h">go home</a></p>'
|
||||||
|
|
||||||
html = self.j2("splash", this=self, qvpath=quotep(self.vpath), msg=m)
|
html = self.j2("splash", this=self, qvpath=quotep(self.vpath), msg=m)
|
||||||
self.reply(html.encode("utf-8"), status=404)
|
self.reply(html.encode("utf-8"), status=rc)
|
||||||
return True
|
return True
|
||||||
|
|
||||||
def scanvol(self):
|
def scanvol(self):
|
||||||
@@ -1852,7 +1880,7 @@ class HttpCli(object):
|
|||||||
if self.args.no_stack:
|
if self.args.no_stack:
|
||||||
raise Pebkac(403, "the stackdump feature is disabled in server config")
|
raise Pebkac(403, "the stackdump feature is disabled in server config")
|
||||||
|
|
||||||
ret = "<pre>{}\n{}".format(time.time(), alltrace())
|
ret = "<pre>{}\n{}".format(time.time(), html_escape(alltrace()))
|
||||||
self.reply(ret.encode("utf-8"))
|
self.reply(ret.encode("utf-8"))
|
||||||
|
|
||||||
def tx_tree(self):
|
def tx_tree(self):
|
||||||
@@ -2165,7 +2193,7 @@ class HttpCli(object):
|
|||||||
if self.can_get:
|
if self.can_get:
|
||||||
perms.append("get")
|
perms.append("get")
|
||||||
|
|
||||||
url_suf = self.urlq({}, [])
|
url_suf = self.urlq({}, ["k"])
|
||||||
is_ls = "ls" in self.uparam
|
is_ls = "ls" in self.uparam
|
||||||
is_js = self.args.force_js or self.cookies.get("js") == "y"
|
is_js = self.args.force_js or self.cookies.get("js") == "y"
|
||||||
|
|
||||||
@@ -2224,8 +2252,10 @@ class HttpCli(object):
|
|||||||
"readme": readme,
|
"readme": readme,
|
||||||
"title": html_escape(self.vpath, crlf=True),
|
"title": html_escape(self.vpath, crlf=True),
|
||||||
"srv_info": srv_info,
|
"srv_info": srv_info,
|
||||||
|
"lang": self.args.lang,
|
||||||
"dtheme": self.args.theme,
|
"dtheme": self.args.theme,
|
||||||
"themes": self.args.themes,
|
"themes": self.args.themes,
|
||||||
|
"turbolvl": self.args.turbo,
|
||||||
}
|
}
|
||||||
if not self.can_read:
|
if not self.can_read:
|
||||||
if is_ls:
|
if is_ls:
|
||||||
@@ -2384,7 +2414,7 @@ class HttpCli(object):
|
|||||||
continue
|
continue
|
||||||
|
|
||||||
w = r[0][:16]
|
w = r[0][:16]
|
||||||
q = "select k, v from mt where w = ? and k != 'x'"
|
q = "select k, v from mt where w = ? and +k != 'x'"
|
||||||
try:
|
try:
|
||||||
for k, v in icur.execute(q, (w,)):
|
for k, v in icur.execute(q, (w,)):
|
||||||
taglist[k] = True
|
taglist[k] = True
|
||||||
@@ -2409,14 +2439,19 @@ class HttpCli(object):
|
|||||||
if doc:
|
if doc:
|
||||||
doc = unquotep(doc.replace("+", " ").split("?")[0])
|
doc = unquotep(doc.replace("+", " ").split("?")[0])
|
||||||
j2a["docname"] = doc
|
j2a["docname"] = doc
|
||||||
|
doctxt = None
|
||||||
if next((x for x in files if x["name"] == doc), None):
|
if next((x for x in files if x["name"] == doc), None):
|
||||||
with open(os.path.join(abspath, doc), "rb") as f:
|
docpath = os.path.join(abspath, doc)
|
||||||
doc = f.read().decode("utf-8", "replace")
|
sz = bos.path.getsize(docpath)
|
||||||
|
if sz < 1024 * self.args.txt_max:
|
||||||
|
with open(fsenc(docpath), "rb") as f:
|
||||||
|
doctxt = f.read().decode("utf-8", "replace")
|
||||||
else:
|
else:
|
||||||
self.log("doc 404: [{}]".format(doc), c=6)
|
self.log("doc 404: [{}]".format(doc), c=6)
|
||||||
doc = "( textfile not found )"
|
doctxt = "( textfile not found )"
|
||||||
|
|
||||||
j2a["doc"] = doc
|
if doctxt is not None:
|
||||||
|
j2a["doc"] = doctxt
|
||||||
|
|
||||||
if not self.conn.hsrv.prism:
|
if not self.conn.hsrv.prism:
|
||||||
j2a["no_prism"] = True
|
j2a["no_prism"] = True
|
||||||
|
|||||||
@@ -18,6 +18,7 @@ from .httpcli import HttpCli
|
|||||||
from .u2idx import U2idx
|
from .u2idx import U2idx
|
||||||
from .th_cli import ThumbCli
|
from .th_cli import ThumbCli
|
||||||
from .th_srv import HAVE_PIL, HAVE_VIPS
|
from .th_srv import HAVE_PIL, HAVE_VIPS
|
||||||
|
from .mtag import HAVE_FFMPEG
|
||||||
from .ico import Ico
|
from .ico import Ico
|
||||||
|
|
||||||
|
|
||||||
@@ -38,7 +39,7 @@ class HttpConn(object):
|
|||||||
self.cert_path = hsrv.cert_path
|
self.cert_path = hsrv.cert_path
|
||||||
self.u2fh = hsrv.u2fh
|
self.u2fh = hsrv.u2fh
|
||||||
|
|
||||||
enth = (HAVE_PIL or HAVE_VIPS) and not self.args.no_thumb
|
enth = (HAVE_PIL or HAVE_VIPS or HAVE_FFMPEG) and not self.args.no_thumb
|
||||||
self.thumbcli = ThumbCli(hsrv) if enth else None
|
self.thumbcli = ThumbCli(hsrv) if enth else None
|
||||||
self.ico = Ico(self.args)
|
self.ico = Ico(self.args)
|
||||||
|
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ import shutil
|
|||||||
import subprocess as sp
|
import subprocess as sp
|
||||||
|
|
||||||
from .__init__ import PY2, WINDOWS, unicode
|
from .__init__ import PY2, WINDOWS, unicode
|
||||||
from .util import fsenc, fsdec, uncyg, runcmd, REKOBO_LKEY
|
from .util import fsenc, fsdec, uncyg, runcmd, retchk, REKOBO_LKEY
|
||||||
from .bos import bos
|
from .bos import bos
|
||||||
|
|
||||||
|
|
||||||
@@ -82,8 +82,9 @@ def ffprobe(abspath, timeout=10):
|
|||||||
b"--",
|
b"--",
|
||||||
fsenc(abspath),
|
fsenc(abspath),
|
||||||
]
|
]
|
||||||
rc = runcmd(cmd, timeout=timeout)
|
rc, so, se = runcmd(cmd, timeout=timeout)
|
||||||
return parse_ffprobe(rc[1])
|
retchk(rc, cmd, se)
|
||||||
|
return parse_ffprobe(so)
|
||||||
|
|
||||||
|
|
||||||
def parse_ffprobe(txt):
|
def parse_ffprobe(txt):
|
||||||
@@ -491,12 +492,14 @@ class MTag(object):
|
|||||||
cmd = ["nice"] + cmd
|
cmd = ["nice"] + cmd
|
||||||
|
|
||||||
cmd = [fsenc(x) for x in cmd]
|
cmd = [fsenc(x) for x in cmd]
|
||||||
v = sp.check_output(cmd, **args).strip()
|
rc, v, err = runcmd(cmd, **args)
|
||||||
|
retchk(rc, cmd, err, self.log, 5)
|
||||||
|
v = v.strip()
|
||||||
if not v:
|
if not v:
|
||||||
continue
|
continue
|
||||||
|
|
||||||
if "," not in tagname:
|
if "," not in tagname:
|
||||||
ret[tagname] = v.decode("utf-8")
|
ret[tagname] = v
|
||||||
else:
|
else:
|
||||||
v = json.loads(v)
|
v = json.loads(v)
|
||||||
for tag in tagname.split(","):
|
for tag in tagname.split(","):
|
||||||
|
|||||||
@@ -136,13 +136,16 @@ class SvcHub(object):
|
|||||||
self.broker = Broker(self)
|
self.broker = Broker(self)
|
||||||
|
|
||||||
def thr_httpsrv_up(self):
|
def thr_httpsrv_up(self):
|
||||||
time.sleep(5)
|
time.sleep(1 if self.args.ign_ebind_all else 5)
|
||||||
expected = self.broker.num_workers * self.tcpsrv.nsrv
|
expected = self.broker.num_workers * self.tcpsrv.nsrv
|
||||||
failed = expected - self.httpsrv_up
|
failed = expected - self.httpsrv_up
|
||||||
if not failed:
|
if not failed:
|
||||||
return
|
return
|
||||||
|
|
||||||
if self.args.ign_ebind_all:
|
if self.args.ign_ebind_all:
|
||||||
|
if not self.tcpsrv.srv:
|
||||||
|
for _ in range(self.broker.num_workers):
|
||||||
|
self.broker.put(False, "cb_httpsrv_up")
|
||||||
return
|
return
|
||||||
|
|
||||||
if self.args.ign_ebind and self.tcpsrv.srv:
|
if self.args.ign_ebind and self.tcpsrv.srv:
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ from __future__ import print_function, unicode_literals
|
|||||||
|
|
||||||
import time
|
import time
|
||||||
import zlib
|
import zlib
|
||||||
from datetime import datetime
|
import calendar
|
||||||
|
|
||||||
from .sutil import errdesc
|
from .sutil import errdesc
|
||||||
from .util import yieldfile, sanitize_fn, spack, sunpack, min_ex
|
from .util import yieldfile, sanitize_fn, spack, sunpack, min_ex
|
||||||
@@ -25,12 +25,12 @@ def dostime2unix(buf):
|
|||||||
tf = "{:04d}-{:02d}-{:02d} {:02d}:{:02d}:{:02d}"
|
tf = "{:04d}-{:02d}-{:02d} {:02d}:{:02d}:{:02d}"
|
||||||
iso = tf.format(*tt)
|
iso = tf.format(*tt)
|
||||||
|
|
||||||
dt = datetime.strptime(iso, "%Y-%m-%d %H:%M:%S")
|
dt = time.strptime(iso, "%Y-%m-%d %H:%M:%S")
|
||||||
return int(dt.timestamp())
|
return int(calendar.timegm(dt))
|
||||||
|
|
||||||
|
|
||||||
def unixtime2dos(ts):
|
def unixtime2dos(ts):
|
||||||
tt = time.gmtime(ts)
|
tt = time.gmtime(ts + 1)
|
||||||
dy, dm, dd, th, tm, ts = list(tt)[:6]
|
dy, dm, dd, th, tm, ts = list(tt)[:6]
|
||||||
|
|
||||||
bd = ((dy - 1980) << 9) + (dm << 5) + dd
|
bd = ((dy - 1980) << 9) + (dm << 5) + dd
|
||||||
@@ -76,7 +76,7 @@ def gen_hdr(h_pos, fn, sz, lastmod, utf8, crc32, pre_crc):
|
|||||||
# 4b magic, 2b min-ver
|
# 4b magic, 2b min-ver
|
||||||
ret = b"\x50\x4b\x03\x04" + req_ver
|
ret = b"\x50\x4b\x03\x04" + req_ver
|
||||||
else:
|
else:
|
||||||
# 4b magic, 2b spec-ver, 2b min-ver
|
# 4b magic, 2b spec-ver (1b compat, 1b os (00 dos, 03 unix)), 2b min-ver
|
||||||
ret = b"\x50\x4b\x01\x02\x1e\x03" + req_ver
|
ret = b"\x50\x4b\x01\x02\x1e\x03" + req_ver
|
||||||
|
|
||||||
ret += b"\x00" if pre_crc else b"\x08" # streaming
|
ret += b"\x00" if pre_crc else b"\x08" # streaming
|
||||||
@@ -95,23 +95,34 @@ def gen_hdr(h_pos, fn, sz, lastmod, utf8, crc32, pre_crc):
|
|||||||
fn = sanitize_fn(fn, "/", [])
|
fn = sanitize_fn(fn, "/", [])
|
||||||
bfn = fn.encode("utf-8" if utf8 else "cp437", "replace").replace(b"?", b"_")
|
bfn = fn.encode("utf-8" if utf8 else "cp437", "replace").replace(b"?", b"_")
|
||||||
|
|
||||||
|
# add ntfs (0x24) and/or unix (0x10) extrafields for utc, add z64 if requested
|
||||||
z64_len = len(z64v) * 8 + 4 if z64v else 0
|
z64_len = len(z64v) * 8 + 4 if z64v else 0
|
||||||
ret += spack(b"<HH", len(bfn), z64_len)
|
ret += spack(b"<HH", len(bfn), 0x10 + z64_len)
|
||||||
|
|
||||||
if h_pos is not None:
|
if h_pos is not None:
|
||||||
# 2b comment, 2b diskno
|
# 2b comment, 2b diskno
|
||||||
ret += b"\x00" * 4
|
ret += b"\x00" * 4
|
||||||
|
|
||||||
# 2b internal.attr, 4b external.attr
|
# 2b internal.attr, 4b external.attr
|
||||||
# infozip-macos: 0100 0000 a481 file:644
|
# infozip-macos: 0100 0000 a481 (spec-ver 1e03) file:644
|
||||||
# infozip-macos: 0100 0100 0080 file:000
|
# infozip-macos: 0100 0100 0080 (spec-ver 1e03) file:000
|
||||||
ret += b"\x01\x00\x00\x00\xa4\x81"
|
# win10-zip: 0000 2000 0000 (spec-ver xx00) FILE_ATTRIBUTE_ARCHIVE
|
||||||
|
ret += b"\x00\x00\x00\x00\xa4\x81" # unx
|
||||||
|
# ret += b"\x00\x00\x20\x00\x00\x00" # fat
|
||||||
|
|
||||||
# 4b local-header-ofs
|
# 4b local-header-ofs
|
||||||
ret += spack(b"<L", min(h_pos, 0xFFFFFFFF))
|
ret += spack(b"<L", min(h_pos, 0xFFFFFFFF))
|
||||||
|
|
||||||
ret += bfn
|
ret += bfn
|
||||||
|
|
||||||
|
# ntfs: type 0a, size 20, rsvd, attr1, len 18, mtime, atime, ctime
|
||||||
|
# b"\xa3\x2f\x82\x41\x55\x68\xd8\x01" 1652616838.798941100 ~5.861518 132970904387989411 ~58615181
|
||||||
|
# nt = int((lastmod + 11644473600) * 10000000)
|
||||||
|
# ret += spack(b"<HHLHHQQQ", 0xA, 0x20, 0, 1, 0x18, nt, nt, nt)
|
||||||
|
|
||||||
|
# unix: type 0d, size 0c, atime, mtime, uid, gid
|
||||||
|
ret += spack(b"<HHLLHH", 0xD, 0xC, int(lastmod), int(lastmod), 1000, 1000)
|
||||||
|
|
||||||
if z64v:
|
if z64v:
|
||||||
ret += spack(b"<HH" + b"Q" * len(z64v), 1, len(z64v) * 8, *z64v)
|
ret += spack(b"<HH" + b"Q" * len(z64v), 1, len(z64v) * 8, *z64v)
|
||||||
|
|
||||||
@@ -205,7 +216,7 @@ class StreamZip(object):
|
|||||||
st = f["st"]
|
st = f["st"]
|
||||||
|
|
||||||
sz = st.st_size
|
sz = st.st_size
|
||||||
ts = st.st_mtime + 1
|
ts = st.st_mtime
|
||||||
|
|
||||||
crc = None
|
crc = None
|
||||||
if self.pre_crc:
|
if self.pre_crc:
|
||||||
|
|||||||
@@ -253,7 +253,9 @@ class ThumbSrv(object):
|
|||||||
fun(abspath, tpath)
|
fun(abspath, tpath)
|
||||||
except:
|
except:
|
||||||
msg = "{} could not create thumbnail of {}\n{}"
|
msg = "{} could not create thumbnail of {}\n{}"
|
||||||
self.log(msg.format(fun.__name__, abspath, min_ex()), "1;30")
|
msg = msg.format(fun.__name__, abspath, min_ex())
|
||||||
|
c = 1 if "<Signals.SIG" in msg else "1;30"
|
||||||
|
self.log(msg, c)
|
||||||
with open(tpath, "wb") as _:
|
with open(tpath, "wb") as _:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
@@ -343,6 +345,8 @@ class ThumbSrv(object):
|
|||||||
|
|
||||||
def conv_ffmpeg(self, abspath, tpath):
|
def conv_ffmpeg(self, abspath, tpath):
|
||||||
ret, _ = ffprobe(abspath)
|
ret, _ = ffprobe(abspath)
|
||||||
|
if not ret:
|
||||||
|
return
|
||||||
|
|
||||||
ext = abspath.rsplit(".")[-1].lower()
|
ext = abspath.rsplit(".")[-1].lower()
|
||||||
if ext in ["h264", "h265"] or ext in self.fmt_ffi:
|
if ext in ["h264", "h265"] or ext in self.fmt_ffi:
|
||||||
@@ -417,8 +421,15 @@ class ThumbSrv(object):
|
|||||||
m = "FFmpeg failed because it was compiled without libsox; you must set --th-ff-swr to force swr resampling:\n"
|
m = "FFmpeg failed because it was compiled without libsox; you must set --th-ff-swr to force swr resampling:\n"
|
||||||
c = 1
|
c = 1
|
||||||
|
|
||||||
m += "\n".join(["ff: {}".format(x) for x in serr.split("\n")])
|
lines = serr.strip("\n").split("\n")
|
||||||
self.log(m, c=c)
|
if len(lines) > 50:
|
||||||
|
lines = lines[:25] + ["[...]"] + lines[-25:]
|
||||||
|
|
||||||
|
txt = "\n".join(["ff: " + str(x) for x in lines])
|
||||||
|
if len(txt) > 5000:
|
||||||
|
txt = txt[:2500] + "...\nff: [...]\nff: ..." + txt[-2500:]
|
||||||
|
|
||||||
|
self.log(m + txt, c=c)
|
||||||
raise sp.CalledProcessError(ret, (cmd[0], b"...", cmd[-1]))
|
raise sp.CalledProcessError(ret, (cmd[0], b"...", cmd[-1]))
|
||||||
|
|
||||||
def conv_spec(self, abspath, tpath):
|
def conv_spec(self, abspath, tpath):
|
||||||
|
|||||||
@@ -4,8 +4,8 @@ from __future__ import print_function, unicode_literals
|
|||||||
import re
|
import re
|
||||||
import os
|
import os
|
||||||
import time
|
import time
|
||||||
|
import calendar
|
||||||
import threading
|
import threading
|
||||||
from datetime import datetime
|
|
||||||
from operator import itemgetter
|
from operator import itemgetter
|
||||||
|
|
||||||
from .__init__ import ANYWIN, unicode
|
from .__init__ import ANYWIN, unicode
|
||||||
@@ -21,6 +21,12 @@ except:
|
|||||||
HAVE_SQLITE3 = False
|
HAVE_SQLITE3 = False
|
||||||
|
|
||||||
|
|
||||||
|
try:
|
||||||
|
from pathlib import Path
|
||||||
|
except:
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
class U2idx(object):
|
class U2idx(object):
|
||||||
def __init__(self, conn):
|
def __init__(self, conn):
|
||||||
self.log_func = conn.log_func
|
self.log_func = conn.log_func
|
||||||
@@ -55,7 +61,7 @@ class U2idx(object):
|
|||||||
uv = [wark[:16], wark]
|
uv = [wark[:16], wark]
|
||||||
|
|
||||||
try:
|
try:
|
||||||
return self.run_query(vols, uq, uv, True, False)[0]
|
return self.run_query(vols, uq, uv, True, False, 99999)[0]
|
||||||
except:
|
except:
|
||||||
raise Pebkac(500, min_ex())
|
raise Pebkac(500, min_ex())
|
||||||
|
|
||||||
@@ -76,11 +82,26 @@ class U2idx(object):
|
|||||||
if not bos.path.exists(db_path):
|
if not bos.path.exists(db_path):
|
||||||
return None
|
return None
|
||||||
|
|
||||||
cur = sqlite3.connect(db_path, 2).cursor()
|
cur = None
|
||||||
|
if ANYWIN:
|
||||||
|
uri = ""
|
||||||
|
try:
|
||||||
|
uri = "{}?mode=ro&nolock=1".format(Path(db_path).as_uri())
|
||||||
|
cur = sqlite3.connect(uri, 2, uri=True).cursor()
|
||||||
|
self.log("ro: {}".format(db_path))
|
||||||
|
except:
|
||||||
|
self.log("could not open read-only: {}\n{}".format(uri, min_ex()))
|
||||||
|
|
||||||
|
if not cur:
|
||||||
|
# on windows, this steals the write-lock from up2k.deferred_init --
|
||||||
|
# seen on win 10.0.17763.2686, py 3.10.4, sqlite 3.37.2
|
||||||
|
cur = sqlite3.connect(db_path, 2).cursor()
|
||||||
|
self.log("opened {}".format(db_path))
|
||||||
|
|
||||||
self.cur[ptop] = cur
|
self.cur[ptop] = cur
|
||||||
return cur
|
return cur
|
||||||
|
|
||||||
def search(self, vols, uq):
|
def search(self, vols, uq, lim):
|
||||||
"""search by query params"""
|
"""search by query params"""
|
||||||
if not HAVE_SQLITE3:
|
if not HAVE_SQLITE3:
|
||||||
return []
|
return []
|
||||||
@@ -169,18 +190,17 @@ class U2idx(object):
|
|||||||
|
|
||||||
if is_date:
|
if is_date:
|
||||||
is_date = False
|
is_date = False
|
||||||
v = v.upper().rstrip("Z").replace(",", " ").replace("T", " ")
|
v = re.sub(r"[tzTZ, ]+", " ", v).strip()
|
||||||
while " " in v:
|
|
||||||
v = v.replace(" ", " ")
|
|
||||||
|
|
||||||
for fmt in [
|
for fmt in [
|
||||||
"%Y-%m-%d %H:%M:%S",
|
"%Y-%m-%d %H:%M:%S",
|
||||||
"%Y-%m-%d %H:%M",
|
"%Y-%m-%d %H:%M",
|
||||||
"%Y-%m-%d %H",
|
"%Y-%m-%d %H",
|
||||||
"%Y-%m-%d",
|
"%Y-%m-%d",
|
||||||
|
"%Y-%m",
|
||||||
|
"%Y",
|
||||||
]:
|
]:
|
||||||
try:
|
try:
|
||||||
v = datetime.strptime(v, fmt).timestamp()
|
v = calendar.timegm(time.strptime(v, fmt))
|
||||||
break
|
break
|
||||||
except:
|
except:
|
||||||
pass
|
pass
|
||||||
@@ -222,11 +242,11 @@ class U2idx(object):
|
|||||||
q += " lower({}) {} ? ) ".format(field, oper)
|
q += " lower({}) {} ? ) ".format(field, oper)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
return self.run_query(vols, q, va, have_up, have_mt)
|
return self.run_query(vols, q, va, have_up, have_mt, lim)
|
||||||
except Exception as ex:
|
except Exception as ex:
|
||||||
raise Pebkac(500, repr(ex))
|
raise Pebkac(500, repr(ex))
|
||||||
|
|
||||||
def run_query(self, vols, uq, uv, have_up, have_mt):
|
def run_query(self, vols, uq, uv, have_up, have_mt, lim):
|
||||||
done_flag = []
|
done_flag = []
|
||||||
self.active_id = "{:.6f}_{}".format(
|
self.active_id = "{:.6f}_{}".format(
|
||||||
time.time(), threading.current_thread().ident
|
time.time(), threading.current_thread().ident
|
||||||
@@ -255,7 +275,7 @@ class U2idx(object):
|
|||||||
self.log("qs: {!r} {!r}".format(uq, uv))
|
self.log("qs: {!r} {!r}".format(uq, uv))
|
||||||
|
|
||||||
ret = []
|
ret = []
|
||||||
lim = int(self.args.srch_hits)
|
lim = min(lim, int(self.args.srch_hits))
|
||||||
taglist = {}
|
taglist = {}
|
||||||
for (vtop, ptop, flags) in vols:
|
for (vtop, ptop, flags) in vols:
|
||||||
cur = self.get_cur(ptop)
|
cur = self.get_cur(ptop)
|
||||||
@@ -278,7 +298,7 @@ class U2idx(object):
|
|||||||
for hit in c:
|
for hit in c:
|
||||||
w, ts, sz, rd, fn, ip, at = hit[:7]
|
w, ts, sz, rd, fn, ip, at = hit[:7]
|
||||||
lim -= 1
|
lim -= 1
|
||||||
if lim <= 0:
|
if lim < 0:
|
||||||
break
|
break
|
||||||
|
|
||||||
if rd.startswith("//") or fn.startswith("//"):
|
if rd.startswith("//") or fn.startswith("//"):
|
||||||
@@ -307,7 +327,7 @@ class U2idx(object):
|
|||||||
w = hit["w"]
|
w = hit["w"]
|
||||||
del hit["w"]
|
del hit["w"]
|
||||||
tags = {}
|
tags = {}
|
||||||
q2 = "select k, v from mt where w = ? and k != 'x'"
|
q2 = "select k, v from mt where w = ? and +k != 'x'"
|
||||||
for k, v2 in cur.execute(q2, (w,)):
|
for k, v2 in cur.execute(q2, (w,)):
|
||||||
taglist[k] = True
|
taglist[k] = True
|
||||||
tags[k] = v2
|
tags[k] = v2
|
||||||
|
|||||||
@@ -95,7 +95,7 @@ class Up2k(object):
|
|||||||
|
|
||||||
if ANYWIN:
|
if ANYWIN:
|
||||||
# usually fails to set lastmod too quickly
|
# usually fails to set lastmod too quickly
|
||||||
self.lastmod_q = Queue()
|
self.lastmod_q = []
|
||||||
thr = threading.Thread(target=self._lastmodder, name="up2k-lastmod")
|
thr = threading.Thread(target=self._lastmodder, name="up2k-lastmod")
|
||||||
thr.daemon = True
|
thr.daemon = True
|
||||||
thr.start()
|
thr.start()
|
||||||
@@ -554,12 +554,16 @@ class Up2k(object):
|
|||||||
for d in all_vols
|
for d in all_vols
|
||||||
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 += list(self.asrv.vfs.histtab.values())
|
||||||
if WINDOWS:
|
if WINDOWS:
|
||||||
excl = [x.replace("/", "\\") for x in excl]
|
excl = [x.replace("/", "\\") for x in excl]
|
||||||
|
|
||||||
|
excl = set(excl)
|
||||||
|
rtop = absreal(top)
|
||||||
n_add = n_rm = 0
|
n_add = n_rm = 0
|
||||||
try:
|
try:
|
||||||
n_add = self._build_dir(dbw, top, set(excl), top, rei, reh, [])
|
n_add = self._build_dir(dbw, top, excl, top, rtop, rei, reh, [])
|
||||||
n_rm = self._drop_lost(dbw[0], top)
|
n_rm = self._drop_lost(dbw[0], top)
|
||||||
except:
|
except:
|
||||||
m = "failed to index volume [{}]:\n{}"
|
m = "failed to index volume [{}]:\n{}"
|
||||||
@@ -572,8 +576,7 @@ class Up2k(object):
|
|||||||
|
|
||||||
return True, n_add or n_rm or do_vac
|
return True, n_add or n_rm or do_vac
|
||||||
|
|
||||||
def _build_dir(self, dbw, top, excl, cdir, rei, reh, seen):
|
def _build_dir(self, dbw, top, excl, cdir, rcdir, rei, reh, seen):
|
||||||
rcdir = absreal(cdir) # a bit expensive but worth
|
|
||||||
if rcdir in seen:
|
if rcdir in seen:
|
||||||
m = "bailing from symlink loop,\n prev: {}\n curr: {}\n from: {}"
|
m = "bailing from symlink loop,\n prev: {}\n curr: {}\n from: {}"
|
||||||
self.log(m.format(seen[-1], rcdir, cdir), 3)
|
self.log(m.format(seen[-1], rcdir, cdir), 3)
|
||||||
@@ -581,11 +584,12 @@ class Up2k(object):
|
|||||||
|
|
||||||
seen = seen + [rcdir]
|
seen = seen + [rcdir]
|
||||||
self.pp.msg = "a{} {}".format(self.pp.n, cdir)
|
self.pp.msg = "a{} {}".format(self.pp.n, cdir)
|
||||||
histpath = self.asrv.vfs.histtab[top]
|
|
||||||
ret = 0
|
ret = 0
|
||||||
seen_files = {}
|
seen_files = {} # != inames; files-only for dropcheck
|
||||||
g = statdir(self.log_func, not self.args.no_scandir, False, cdir)
|
g = statdir(self.log_func, not self.args.no_scandir, False, cdir)
|
||||||
for iname, inf in sorted(g):
|
g = sorted(g)
|
||||||
|
inames = {x[0]: 1 for x in g}
|
||||||
|
for iname, inf in g:
|
||||||
abspath = os.path.join(cdir, iname)
|
abspath = os.path.join(cdir, iname)
|
||||||
if rei and rei.search(abspath):
|
if rei and rei.search(abspath):
|
||||||
continue
|
continue
|
||||||
@@ -594,17 +598,20 @@ class Up2k(object):
|
|||||||
lmod = int(inf.st_mtime)
|
lmod = int(inf.st_mtime)
|
||||||
sz = inf.st_size
|
sz = inf.st_size
|
||||||
if stat.S_ISDIR(inf.st_mode):
|
if stat.S_ISDIR(inf.st_mode):
|
||||||
if abspath in excl or abspath == histpath:
|
rap = absreal(abspath)
|
||||||
|
if abspath in excl or rap in excl:
|
||||||
continue
|
continue
|
||||||
if iname == ".th" and bos.path.isdir(os.path.join(abspath, "top")):
|
if iname == ".th" and bos.path.isdir(os.path.join(abspath, "top")):
|
||||||
# abandoned or foreign, skip
|
# abandoned or foreign, skip
|
||||||
continue
|
continue
|
||||||
# self.log(" dir: {}".format(abspath))
|
# self.log(" dir: {}".format(abspath))
|
||||||
try:
|
try:
|
||||||
ret += self._build_dir(dbw, top, excl, abspath, rei, reh, seen)
|
ret += self._build_dir(dbw, top, excl, abspath, rap, rei, reh, seen)
|
||||||
except:
|
except:
|
||||||
m = "failed to index subdir [{}]:\n{}"
|
m = "failed to index subdir [{}]:\n{}"
|
||||||
self.log(m.format(abspath, min_ex()), c=1)
|
self.log(m.format(abspath, min_ex()), c=1)
|
||||||
|
elif not stat.S_ISREG(inf.st_mode):
|
||||||
|
self.log("skip type-{:x} file [{}]".format(inf.st_mode, abspath))
|
||||||
else:
|
else:
|
||||||
# self.log("file: {}".format(abspath))
|
# self.log("file: {}".format(abspath))
|
||||||
seen_files[iname] = 1
|
seen_files[iname] = 1
|
||||||
@@ -612,6 +619,17 @@ class Up2k(object):
|
|||||||
if WINDOWS:
|
if WINDOWS:
|
||||||
rp = rp.replace("\\", "/").strip("/")
|
rp = rp.replace("\\", "/").strip("/")
|
||||||
|
|
||||||
|
if rp.endswith(".PARTIAL") and time.time() - lmod < 60:
|
||||||
|
# rescan during upload
|
||||||
|
continue
|
||||||
|
|
||||||
|
if not sz and (
|
||||||
|
"{}.PARTIAL".format(iname) in inames
|
||||||
|
or ".{}.PARTIAL".format(iname) in inames
|
||||||
|
):
|
||||||
|
# placeholder for unfinished upload
|
||||||
|
continue
|
||||||
|
|
||||||
rd, fn = rp.rsplit("/", 1) if "/" in rp else ["", rp]
|
rd, fn = rp.rsplit("/", 1) if "/" in rp else ["", rp]
|
||||||
sql = "select w, mt, sz from up where rd = ? and fn = ?"
|
sql = "select w, mt, sz from up where rd = ? and fn = ?"
|
||||||
try:
|
try:
|
||||||
@@ -781,6 +799,7 @@ class Up2k(object):
|
|||||||
if self.mtag.prefer_mt and self.args.mtag_mt > 1:
|
if self.mtag.prefer_mt and self.args.mtag_mt > 1:
|
||||||
mpool = self._start_mpool()
|
mpool = self._start_mpool()
|
||||||
|
|
||||||
|
# TODO blocks writes to registry cursor; do chunks instead
|
||||||
conn = sqlite3.connect(db_path, timeout=15)
|
conn = sqlite3.connect(db_path, timeout=15)
|
||||||
cur = conn.cursor()
|
cur = conn.cursor()
|
||||||
c2 = conn.cursor()
|
c2 = conn.cursor()
|
||||||
@@ -806,8 +825,8 @@ class Up2k(object):
|
|||||||
n_tags = self._tag_file(c3, *args)
|
n_tags = self._tag_file(c3, *args)
|
||||||
else:
|
else:
|
||||||
mpool.put(["mtag"] + args)
|
mpool.put(["mtag"] + args)
|
||||||
with self.mutex:
|
# not registry cursor; do not self.mutex:
|
||||||
n_tags = len(self._flush_mpool(c3))
|
n_tags = len(self._flush_mpool(c3))
|
||||||
|
|
||||||
n_add += n_tags
|
n_add += n_tags
|
||||||
n_buf += n_tags
|
n_buf += n_tags
|
||||||
@@ -830,9 +849,6 @@ class Up2k(object):
|
|||||||
cur.close()
|
cur.close()
|
||||||
conn.close()
|
conn.close()
|
||||||
|
|
||||||
with self.mutex:
|
|
||||||
gcur.connection.commit()
|
|
||||||
|
|
||||||
return n_add, n_rm, True
|
return n_add, n_rm, True
|
||||||
|
|
||||||
def _flush_mpool(self, wcur):
|
def _flush_mpool(self, wcur):
|
||||||
@@ -933,7 +949,7 @@ class Up2k(object):
|
|||||||
n_done += 1
|
n_done += 1
|
||||||
|
|
||||||
for w in to_delete.keys():
|
for w in to_delete.keys():
|
||||||
q = "delete from mt where w = ? and k = 't:mtp'"
|
q = "delete from mt where w = ? and +k = 't:mtp'"
|
||||||
cur.execute(q, (w,))
|
cur.execute(q, (w,))
|
||||||
|
|
||||||
to_delete = {}
|
to_delete = {}
|
||||||
@@ -971,7 +987,7 @@ class Up2k(object):
|
|||||||
with self.mutex:
|
with self.mutex:
|
||||||
done = self._flush_mpool(wcur)
|
done = self._flush_mpool(wcur)
|
||||||
for w in done:
|
for w in done:
|
||||||
q = "delete from mt where w = ? and k = 't:mtp'"
|
q = "delete from mt where w = ? and +k = 't:mtp'"
|
||||||
cur.execute(q, (w,))
|
cur.execute(q, (w,))
|
||||||
|
|
||||||
cur.connection.commit()
|
cur.connection.commit()
|
||||||
@@ -1069,18 +1085,20 @@ class Up2k(object):
|
|||||||
if parser == "mtag":
|
if parser == "mtag":
|
||||||
parser = self.mtag.backend
|
parser = self.mtag.backend
|
||||||
|
|
||||||
msg = "{} failed to read tags from {}:\n{}"
|
self._log_tag_err(parser, abspath, ex)
|
||||||
self.log(msg.format(parser, abspath, ex), c=3)
|
|
||||||
|
|
||||||
q.task_done()
|
q.task_done()
|
||||||
|
|
||||||
|
def _log_tag_err(self, parser, abspath, ex):
|
||||||
|
msg = "{} failed to read tags from {}:\n{}".format(parser, abspath, ex)
|
||||||
|
self.log(msg.lstrip(), c=1 if "<Signals.SIG" in msg else 3)
|
||||||
|
|
||||||
def _tag_file(self, write_cur, entags, wark, abspath, tags=None):
|
def _tag_file(self, write_cur, entags, wark, abspath, tags=None):
|
||||||
if tags is None:
|
if tags is None:
|
||||||
try:
|
try:
|
||||||
tags = self.mtag.get(abspath)
|
tags = self.mtag.get(abspath)
|
||||||
except Exception as ex:
|
except Exception as ex:
|
||||||
msg = "failed to read tags from {}:\n{}"
|
self._log_tag_err("", abspath, ex)
|
||||||
self.log(msg.format(abspath, ex), c=3)
|
|
||||||
return 0
|
return 0
|
||||||
|
|
||||||
if not bos.path.isfile(abspath):
|
if not bos.path.isfile(abspath):
|
||||||
@@ -1097,7 +1115,7 @@ class Up2k(object):
|
|||||||
|
|
||||||
for k in tags.keys():
|
for k in tags.keys():
|
||||||
q = "delete from mt where w = ? and ({})".format(
|
q = "delete from mt where w = ? and ({})".format(
|
||||||
" or ".join(["k = ?"] * len(tags))
|
" or ".join(["+k = ?"] * len(tags))
|
||||||
)
|
)
|
||||||
args = [wark[:16]] + list(tags.keys())
|
args = [wark[:16]] + list(tags.keys())
|
||||||
write_cur.execute(q, tuple(args))
|
write_cur.execute(q, tuple(args))
|
||||||
@@ -1111,7 +1129,8 @@ class Up2k(object):
|
|||||||
return ret
|
return ret
|
||||||
|
|
||||||
def _orz(self, db_path):
|
def _orz(self, db_path):
|
||||||
return sqlite3.connect(db_path, check_same_thread=False).cursor()
|
timeout = int(max(self.args.srch_time, 5) * 1.2)
|
||||||
|
return sqlite3.connect(db_path, timeout, check_same_thread=False).cursor()
|
||||||
# x.set_trace_callback(trace)
|
# x.set_trace_callback(trace)
|
||||||
|
|
||||||
def _open_db(self, db_path):
|
def _open_db(self, db_path):
|
||||||
@@ -1480,7 +1499,7 @@ class Up2k(object):
|
|||||||
if lmod and (not linked or SYMTIME):
|
if lmod and (not linked or SYMTIME):
|
||||||
times = (int(time.time()), int(lmod))
|
times = (int(time.time()), int(lmod))
|
||||||
if ANYWIN:
|
if ANYWIN:
|
||||||
self.lastmod_q.put([dst, 0, times])
|
self.lastmod_q.append([dst, 0, times])
|
||||||
else:
|
else:
|
||||||
bos.utime(dst, times, False)
|
bos.utime(dst, times, False)
|
||||||
|
|
||||||
@@ -1574,9 +1593,15 @@ class Up2k(object):
|
|||||||
# self.log("--- " + wark + " " + dst + " finish_upload atomic " + dst, 4)
|
# self.log("--- " + wark + " " + dst + " finish_upload atomic " + dst, 4)
|
||||||
atomic_move(src, dst)
|
atomic_move(src, dst)
|
||||||
|
|
||||||
|
times = (int(time.time()), int(job["lmod"]))
|
||||||
if ANYWIN:
|
if ANYWIN:
|
||||||
a = [dst, job["size"], (int(time.time()), int(job["lmod"]))]
|
a = [dst, job["size"], times]
|
||||||
self.lastmod_q.put(a)
|
self.lastmod_q.append(a)
|
||||||
|
elif not job["hash"]:
|
||||||
|
try:
|
||||||
|
bos.utime(dst, times)
|
||||||
|
except:
|
||||||
|
pass
|
||||||
|
|
||||||
a = [job[x] for x in "ptop wark prel name lmod size addr".split()]
|
a = [job[x] for x in "ptop wark prel name lmod size addr".split()]
|
||||||
a += [job.get("at") or time.time()]
|
a += [job.get("at") or time.time()]
|
||||||
@@ -1675,12 +1700,12 @@ class Up2k(object):
|
|||||||
vn, rem = vn.get_dbv(rem)
|
vn, rem = vn.get_dbv(rem)
|
||||||
unpost = False
|
unpost = False
|
||||||
except:
|
except:
|
||||||
# unpost with missing permissions? try read+write and verify with db
|
# unpost with missing permissions? verify with db
|
||||||
if not self.args.unpost:
|
if not self.args.unpost:
|
||||||
raise Pebkac(400, "the unpost feature is disabled in server config")
|
raise Pebkac(400, "the unpost feature is disabled in server config")
|
||||||
|
|
||||||
unpost = True
|
unpost = True
|
||||||
permsets = [[True, True]]
|
permsets = [[False, True]]
|
||||||
vn, rem = self.asrv.vfs.get(vpath, uname, *permsets[0])
|
vn, rem = self.asrv.vfs.get(vpath, uname, *permsets[0])
|
||||||
vn, rem = vn.get_dbv(rem)
|
vn, rem = vn.get_dbv(rem)
|
||||||
_, _, _, _, dip, dat = self._find_from_vpath(vn.realpath, rem)
|
_, _, _, _, dip, dat = self._find_from_vpath(vn.realpath, rem)
|
||||||
@@ -2064,9 +2089,8 @@ class Up2k(object):
|
|||||||
|
|
||||||
def _lastmodder(self):
|
def _lastmodder(self):
|
||||||
while True:
|
while True:
|
||||||
ready = []
|
ready = self.lastmod_q
|
||||||
while not self.lastmod_q.empty():
|
self.lastmod_q = []
|
||||||
ready.append(self.lastmod_q.get())
|
|
||||||
|
|
||||||
# self.log("lmod: got {}".format(len(ready)))
|
# self.log("lmod: got {}".format(len(ready)))
|
||||||
time.sleep(5)
|
time.sleep(5)
|
||||||
@@ -2171,8 +2195,7 @@ class Up2k(object):
|
|||||||
if parsers:
|
if parsers:
|
||||||
tags.update(self.mtag.get_bin(parsers, abspath))
|
tags.update(self.mtag.get_bin(parsers, abspath))
|
||||||
except Exception as ex:
|
except Exception as ex:
|
||||||
msg = "failed to read tags from {}:\n{}"
|
self._log_tag_err("", abspath, ex)
|
||||||
self.log(msg.format(abspath, ex), c=3)
|
|
||||||
continue
|
continue
|
||||||
|
|
||||||
with self.mutex:
|
with self.mutex:
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ import time
|
|||||||
import base64
|
import base64
|
||||||
import select
|
import select
|
||||||
import struct
|
import struct
|
||||||
|
import signal
|
||||||
import hashlib
|
import hashlib
|
||||||
import platform
|
import platform
|
||||||
import traceback
|
import traceback
|
||||||
@@ -912,6 +913,9 @@ def sanitize_fn(fn, ok, bad):
|
|||||||
if "/" not in ok:
|
if "/" not in ok:
|
||||||
fn = fn.replace("\\", "/").split("/")[-1]
|
fn = fn.replace("\\", "/").split("/")[-1]
|
||||||
|
|
||||||
|
if fn.lower() in bad:
|
||||||
|
fn = "_" + fn
|
||||||
|
|
||||||
if ANYWIN:
|
if ANYWIN:
|
||||||
remap = [
|
remap = [
|
||||||
["<", "<"],
|
["<", "<"],
|
||||||
@@ -927,16 +931,26 @@ def sanitize_fn(fn, ok, bad):
|
|||||||
for a, b in [x for x in remap if x[0] not in ok]:
|
for a, b in [x for x in remap if x[0] not in ok]:
|
||||||
fn = fn.replace(a, b)
|
fn = fn.replace(a, b)
|
||||||
|
|
||||||
bad.extend(["con", "prn", "aux", "nul"])
|
bad = ["con", "prn", "aux", "nul"]
|
||||||
for n in range(1, 10):
|
for n in range(1, 10):
|
||||||
bad += "com{0} lpt{0}".format(n).split(" ")
|
bad += "com{0} lpt{0}".format(n).split(" ")
|
||||||
|
|
||||||
if fn.lower() in bad:
|
if fn.lower().split(".")[0] in bad:
|
||||||
fn = "_" + fn
|
fn = "_" + fn
|
||||||
|
|
||||||
return fn.strip()
|
return fn.strip()
|
||||||
|
|
||||||
|
|
||||||
|
def relchk(rp):
|
||||||
|
if ANYWIN:
|
||||||
|
if "\n" in rp or "\r" in rp:
|
||||||
|
return "x\nx"
|
||||||
|
|
||||||
|
p = re.sub(r'[\\:*?"<>|]', "", rp)
|
||||||
|
if p != rp:
|
||||||
|
return "[{}]".format(p)
|
||||||
|
|
||||||
|
|
||||||
def absreal(fpath):
|
def absreal(fpath):
|
||||||
try:
|
try:
|
||||||
return fsdec(os.path.abspath(os.path.realpath(fsenc(fpath))))
|
return fsdec(os.path.abspath(os.path.realpath(fsenc(fpath))))
|
||||||
@@ -1337,8 +1351,8 @@ def guess_mime(url, fallback="application/octet-stream"):
|
|||||||
return ret
|
return ret
|
||||||
|
|
||||||
|
|
||||||
def runcmd(argv, timeout=None):
|
def runcmd(argv, timeout=None, **ka):
|
||||||
p = sp.Popen(argv, stdout=sp.PIPE, stderr=sp.PIPE)
|
p = sp.Popen(argv, stdout=sp.PIPE, stderr=sp.PIPE, **ka)
|
||||||
if not timeout or PY2:
|
if not timeout or PY2:
|
||||||
stdout, stderr = p.communicate()
|
stdout, stderr = p.communicate()
|
||||||
else:
|
else:
|
||||||
@@ -1353,9 +1367,10 @@ def runcmd(argv, timeout=None):
|
|||||||
return [p.returncode, stdout, stderr]
|
return [p.returncode, stdout, stderr]
|
||||||
|
|
||||||
|
|
||||||
def chkcmd(argv):
|
def chkcmd(argv, **ka):
|
||||||
ok, sout, serr = runcmd(argv)
|
ok, sout, serr = runcmd(argv, **ka)
|
||||||
if ok != 0:
|
if ok != 0:
|
||||||
|
retchk(ok, argv, serr)
|
||||||
raise Exception(serr)
|
raise Exception(serr)
|
||||||
|
|
||||||
return sout, serr
|
return sout, serr
|
||||||
@@ -1372,6 +1387,46 @@ def mchkcmd(argv, timeout=10):
|
|||||||
raise sp.CalledProcessError(rv, (argv[0], b"...", argv[-1]))
|
raise sp.CalledProcessError(rv, (argv[0], b"...", argv[-1]))
|
||||||
|
|
||||||
|
|
||||||
|
def retchk(rc, cmd, serr, logger=None, color=None):
|
||||||
|
if rc < 0:
|
||||||
|
rc = 128 - rc
|
||||||
|
|
||||||
|
if rc < 126:
|
||||||
|
return
|
||||||
|
|
||||||
|
s = None
|
||||||
|
if rc > 128:
|
||||||
|
try:
|
||||||
|
s = str(signal.Signals(rc - 128))
|
||||||
|
except:
|
||||||
|
pass
|
||||||
|
elif rc == 126:
|
||||||
|
s = "invalid program"
|
||||||
|
elif rc == 127:
|
||||||
|
s = "program not found"
|
||||||
|
else:
|
||||||
|
s = "invalid retcode"
|
||||||
|
|
||||||
|
if s:
|
||||||
|
m = "{} <{}>".format(rc, s)
|
||||||
|
else:
|
||||||
|
m = str(rc)
|
||||||
|
|
||||||
|
try:
|
||||||
|
c = " ".join([fsdec(x) for x in cmd])
|
||||||
|
except:
|
||||||
|
c = str(cmd)
|
||||||
|
|
||||||
|
m = "error {} from [{}]".format(m, c)
|
||||||
|
if serr:
|
||||||
|
m += "\n" + serr
|
||||||
|
|
||||||
|
if logger:
|
||||||
|
logger(m, color)
|
||||||
|
else:
|
||||||
|
raise Exception(m)
|
||||||
|
|
||||||
|
|
||||||
def gzip_orig_sz(fn):
|
def gzip_orig_sz(fn):
|
||||||
with open(fsenc(fn), "rb") as f:
|
with open(fsenc(fn), "rb") as f:
|
||||||
f.seek(-4, 2)
|
f.seek(-4, 2)
|
||||||
|
|||||||
@@ -35,8 +35,8 @@ window.baguetteBox = (function () {
|
|||||||
documentLastFocus = null,
|
documentLastFocus = null,
|
||||||
isFullscreen = false,
|
isFullscreen = false,
|
||||||
vmute = false,
|
vmute = false,
|
||||||
vloop = false,
|
vloop = sread('vmode') == 'L',
|
||||||
vnext = false,
|
vnext = sread('vmode') == 'C',
|
||||||
resume_mp = false;
|
resume_mp = false;
|
||||||
|
|
||||||
var onFSC = function (e) {
|
var onFSC = function (e) {
|
||||||
@@ -320,6 +320,7 @@ window.baguetteBox = (function () {
|
|||||||
btnVmode.setAttribute('aria-label', msg);
|
btnVmode.setAttribute('aria-label', msg);
|
||||||
btnVmode.setAttribute('tt', msg + tts);
|
btnVmode.setAttribute('tt', msg + tts);
|
||||||
btnVmode.textContent = lbl;
|
btnVmode.textContent = lbl;
|
||||||
|
swrite('vmode', lbl[0]);
|
||||||
|
|
||||||
v.loop = vloop
|
v.loop = vloop
|
||||||
if (vloop && v.paused)
|
if (vloop && v.paused)
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -68,7 +68,7 @@
|
|||||||
<div id="op_cfg" class="opview opbox opwide"></div>
|
<div id="op_cfg" class="opview opbox opwide"></div>
|
||||||
|
|
||||||
<h1 id="path">
|
<h1 id="path">
|
||||||
<a href="#" id="entree" tt="show navpane (directory tree sidebar)$NHotkey: B">🌲</a>
|
<a href="#" id="entree">🌲</a>
|
||||||
{%- for n in vpnodes %}
|
{%- for n in vpnodes %}
|
||||||
<a href="/{{ n[0] }}">{{ n[1] }}</a>
|
<a href="/{{ n[0] }}">{{ n[1] }}</a>
|
||||||
{%- endfor %}
|
{%- endfor %}
|
||||||
@@ -120,7 +120,7 @@
|
|||||||
|
|
||||||
<div id="epi" class="logue">{{ logues[1] }}</div>
|
<div id="epi" class="logue">{{ logues[1] }}</div>
|
||||||
|
|
||||||
<h2><a href="/?h">control-panel</a></h2>
|
<h2><a href="/?h" id="goh">control-panel</a></h2>
|
||||||
|
|
||||||
<a href="#" id="repl">π</a>
|
<a href="#" id="repl">π</a>
|
||||||
|
|
||||||
@@ -138,6 +138,7 @@
|
|||||||
themes = {{ themes }},
|
themes = {{ themes }},
|
||||||
dtheme = "{{ dtheme }}",
|
dtheme = "{{ dtheme }}",
|
||||||
srvinf = "{{ srv_info }}",
|
srvinf = "{{ srv_info }}",
|
||||||
|
lang = "{{ lang }}",
|
||||||
def_hcols = {{ def_hcols|tojson }},
|
def_hcols = {{ def_hcols|tojson }},
|
||||||
have_up2k_idx = {{ have_up2k_idx|tojson }},
|
have_up2k_idx = {{ have_up2k_idx|tojson }},
|
||||||
have_tags_idx = {{ have_tags_idx|tojson }},
|
have_tags_idx = {{ have_tags_idx|tojson }},
|
||||||
@@ -146,12 +147,13 @@
|
|||||||
have_del = {{ have_del|tojson }},
|
have_del = {{ have_del|tojson }},
|
||||||
have_unpost = {{ have_unpost|tojson }},
|
have_unpost = {{ have_unpost|tojson }},
|
||||||
have_zip = {{ have_zip|tojson }},
|
have_zip = {{ have_zip|tojson }},
|
||||||
|
turbolvl = {{ turbolvl|tojson }},
|
||||||
txt_ext = "{{ txt_ext }}",
|
txt_ext = "{{ txt_ext }}",
|
||||||
{% if no_prism %}no_prism = 1,{% endif %}
|
{% if no_prism %}no_prism = 1,{% endif %}
|
||||||
readme = {{ readme|tojson }},
|
readme = {{ readme|tojson }},
|
||||||
ls0 = {{ ls0|tojson }};
|
ls0 = {{ ls0|tojson }};
|
||||||
|
|
||||||
document.documentElement.setAttribute("class", localStorage.theme || dtheme);
|
document.documentElement.className = localStorage.theme || dtheme;
|
||||||
</script>
|
</script>
|
||||||
<script src="/.cpr/util.js?_={{ ts }}"></script>
|
<script src="/.cpr/util.js?_={{ ts }}"></script>
|
||||||
<script src="/.cpr/baguettebox.js?_={{ ts }}"></script>
|
<script src="/.cpr/baguettebox.js?_={{ ts }}"></script>
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -45,7 +45,9 @@
|
|||||||
<tr><td></td><td><a href="../{{ url_suf }}">parent folder</a></td><td>-</td><td>-</td></tr>
|
<tr><td></td><td><a href="../{{ url_suf }}">parent folder</a></td><td>-</td><td>-</td></tr>
|
||||||
|
|
||||||
{%- for f in files %}
|
{%- for f in files %}
|
||||||
<tr><td>{{ f.lead }}</td><td><a href="{{ f.href }}{{ url_suf }}">{{ f.name|e }}</a></td><td>{{ f.sz }}</td><td>{{ f.dt }}</td></tr>
|
<tr><td>{{ f.lead }}</td><td><a href="{{ f.href }}{{
|
||||||
|
'&' + url_suf[1:] if url_suf[:1] == '?' and '?' in f.href else url_suf
|
||||||
|
}}">{{ f.name|e }}</a></td><td>{{ f.sz }}</td><td>{{ f.dt }}</td></tr>
|
||||||
{%- endfor %}
|
{%- endfor %}
|
||||||
|
|
||||||
</tbody>
|
</tbody>
|
||||||
|
|||||||
@@ -161,7 +161,7 @@ blink {
|
|||||||
height: 1.05em;
|
height: 1.05em;
|
||||||
margin: -.2em .3em -.2em -.4em;
|
margin: -.2em .3em -.2em -.4em;
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
border: 1px solid rgba(0,0,0,0.2);
|
border: 1px solid rgba(154,154,154,0.6);
|
||||||
border-width: .2em .2em 0 0;
|
border-width: .2em .2em 0 0;
|
||||||
transform: rotate(45deg);
|
transform: rotate(45deg);
|
||||||
}
|
}
|
||||||
@@ -236,9 +236,6 @@ blink {
|
|||||||
html.z #toc li {
|
html.z #toc li {
|
||||||
border-width: 0;
|
border-width: 0;
|
||||||
}
|
}
|
||||||
html.z #mn a:not(:last-child)::after {
|
|
||||||
border-color: rgba(255,255,255,0.3);
|
|
||||||
}
|
|
||||||
html.z #mn a {
|
html.z #mn a {
|
||||||
color: #ccc;
|
color: #ccc;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -135,22 +135,22 @@ var md_opt = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
(function () {
|
(function () {
|
||||||
var l = localStorage,
|
var l = localStorage,
|
||||||
drk = l.light != 1,
|
drk = l.light != 1,
|
||||||
btn = document.getElementById("lightswitch"),
|
btn = document.getElementById("lightswitch"),
|
||||||
f = function (e) {
|
f = function (e) {
|
||||||
if (e) { e.preventDefault(); drk = !drk; }
|
if (e) { e.preventDefault(); drk = !drk; }
|
||||||
document.documentElement.setAttribute("class", drk? "z":"y");
|
document.documentElement.className = drk? "z":"y";
|
||||||
btn.innerHTML = "go " + (drk ? "light":"dark");
|
btn.innerHTML = "go " + (drk ? "light":"dark");
|
||||||
l.light = drk? 0:1;
|
l.light = drk? 0:1;
|
||||||
};
|
};
|
||||||
|
|
||||||
btn.onclick = f;
|
btn.onclick = f;
|
||||||
f();
|
f();
|
||||||
})();
|
})();
|
||||||
|
|
||||||
</script>
|
</script>
|
||||||
<script src="/.cpr/util.js?_={{ ts }}"></script>
|
<script src="/.cpr/util.js?_={{ ts }}"></script>
|
||||||
<script src="/.cpr/deps/marked.js?_={{ ts }}"></script>
|
<script src="/.cpr/deps/marked.js?_={{ ts }}"></script>
|
||||||
<script src="/.cpr/md.js?_={{ ts }}"></script>
|
<script src="/.cpr/md.js?_={{ ts }}"></script>
|
||||||
{%- if edit %}
|
{%- if edit %}
|
||||||
|
|||||||
@@ -278,7 +278,7 @@ function convert_markdown(md_text, dest_dom) {
|
|||||||
if (!txt)
|
if (!txt)
|
||||||
nodes[a].textContent = href;
|
nodes[a].textContent = href;
|
||||||
else if (href !== txt)
|
else if (href !== txt)
|
||||||
nodes[a].setAttribute('class', 'vis');
|
nodes[a].className = 'vis';
|
||||||
}
|
}
|
||||||
|
|
||||||
// todo-lists (should probably be a marked extension)
|
// todo-lists (should probably be a marked extension)
|
||||||
@@ -294,7 +294,7 @@ function convert_markdown(md_text, dest_dom) {
|
|||||||
var clas = done ? 'done' : 'pend';
|
var clas = done ? 'done' : 'pend';
|
||||||
var char = done ? 'Y' : 'N';
|
var char = done ? 'Y' : 'N';
|
||||||
|
|
||||||
dom_li.setAttribute('class', 'task-list-item');
|
dom_li.className = 'task-list-item';
|
||||||
dom_li.style.listStyleType = 'none';
|
dom_li.style.listStyleType = 'none';
|
||||||
var html = dom_li.innerHTML;
|
var html = dom_li.innerHTML;
|
||||||
dom_li.innerHTML =
|
dom_li.innerHTML =
|
||||||
@@ -468,11 +468,11 @@ function init_toc() {
|
|||||||
for (var a = 0; a < anchors.length; a++) {
|
for (var a = 0; a < anchors.length; a++) {
|
||||||
if (anchors[a].active) {
|
if (anchors[a].active) {
|
||||||
anchors[a].active = false;
|
anchors[a].active = false;
|
||||||
links[a].setAttribute('class', '');
|
links[a].className = '';
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
anchors[hit].active = true;
|
anchors[hit].active = true;
|
||||||
links[hit].setAttribute('class', 'act');
|
links[hit].className = 'act';
|
||||||
}
|
}
|
||||||
|
|
||||||
var pane_height = parseInt(getComputedStyle(dom_toc).height);
|
var pane_height = parseInt(getComputedStyle(dom_toc).height);
|
||||||
|
|||||||
@@ -144,16 +144,16 @@ redraw = (function () {
|
|||||||
map_pre = genmap(dom_pre, map_pre);
|
map_pre = genmap(dom_pre, map_pre);
|
||||||
}
|
}
|
||||||
function setsbs() {
|
function setsbs() {
|
||||||
dom_wrap.setAttribute('class', '');
|
dom_wrap.className = '';
|
||||||
dom_swrap.setAttribute('class', '');
|
dom_swrap.className = '';
|
||||||
onresize();
|
onresize();
|
||||||
}
|
}
|
||||||
function modetoggle() {
|
function modetoggle() {
|
||||||
var mode = dom_nsbs.innerHTML;
|
var mode = dom_nsbs.innerHTML;
|
||||||
dom_nsbs.innerHTML = mode == 'editor' ? 'preview' : 'editor';
|
dom_nsbs.innerHTML = mode == 'editor' ? 'preview' : 'editor';
|
||||||
mode += ' single';
|
mode += ' single';
|
||||||
dom_wrap.setAttribute('class', mode);
|
dom_wrap.className = mode;
|
||||||
dom_swrap.setAttribute('class', mode);
|
dom_swrap.className = mode;
|
||||||
onresize();
|
onresize();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -255,10 +255,10 @@ function Modpoll() {
|
|||||||
|
|
||||||
console.log('modpoll...');
|
console.log('modpoll...');
|
||||||
var url = (document.location + '').split('?')[0] + '?raw&_=' + Date.now();
|
var url = (document.location + '').split('?')[0] + '?raw&_=' + Date.now();
|
||||||
var xhr = new XMLHttpRequest();
|
var xhr = new XHR();
|
||||||
xhr.open('GET', url, true);
|
xhr.open('GET', url, true);
|
||||||
xhr.responseType = 'text';
|
xhr.responseType = 'text';
|
||||||
xhr.onreadystatechange = r.cb;
|
xhr.onload = xhr.onerror = r.cb;
|
||||||
xhr.send();
|
xhr.send();
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -268,9 +268,6 @@ function Modpoll() {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this.readyState != XMLHttpRequest.DONE)
|
|
||||||
return;
|
|
||||||
|
|
||||||
if (this.status !== 200) {
|
if (this.status !== 200) {
|
||||||
console.log('modpoll err ' + this.status + ": " + this.responseText);
|
console.log('modpoll err ' + this.status + ": " + this.responseText);
|
||||||
return;
|
return;
|
||||||
@@ -309,7 +306,7 @@ var modpoll = new Modpoll();
|
|||||||
|
|
||||||
|
|
||||||
window.onbeforeunload = function (e) {
|
window.onbeforeunload = function (e) {
|
||||||
if ((ebi("save").getAttribute('class') + '').indexOf('disabled') >= 0)
|
if ((ebi("save").className + '').indexOf('disabled') >= 0)
|
||||||
return; //nice (todo)
|
return; //nice (todo)
|
||||||
|
|
||||||
e.preventDefault(); //ff
|
e.preventDefault(); //ff
|
||||||
@@ -321,7 +318,7 @@ window.onbeforeunload = function (e) {
|
|||||||
function save(e) {
|
function save(e) {
|
||||||
if (e) e.preventDefault();
|
if (e) e.preventDefault();
|
||||||
var save_btn = ebi("save"),
|
var save_btn = ebi("save"),
|
||||||
save_cls = save_btn.getAttribute('class') + '';
|
save_cls = save_btn.className + '';
|
||||||
|
|
||||||
if (save_cls.indexOf('disabled') >= 0)
|
if (save_cls.indexOf('disabled') >= 0)
|
||||||
return toast.inf(2, "no changes");
|
return toast.inf(2, "no changes");
|
||||||
@@ -336,10 +333,10 @@ function save(e) {
|
|||||||
fd.append("body", txt);
|
fd.append("body", txt);
|
||||||
|
|
||||||
var url = (document.location + '').split('?')[0];
|
var url = (document.location + '').split('?')[0];
|
||||||
var xhr = new XMLHttpRequest();
|
var xhr = new XHR();
|
||||||
xhr.open('POST', url, true);
|
xhr.open('POST', url, true);
|
||||||
xhr.responseType = 'text';
|
xhr.responseType = 'text';
|
||||||
xhr.onreadystatechange = save_cb;
|
xhr.onload = xhr.onerror = save_cb;
|
||||||
xhr.btn = save_btn;
|
xhr.btn = save_btn;
|
||||||
xhr.txt = txt;
|
xhr.txt = txt;
|
||||||
|
|
||||||
@@ -356,9 +353,6 @@ function save(e) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function save_cb() {
|
function save_cb() {
|
||||||
if (this.readyState != XMLHttpRequest.DONE)
|
|
||||||
return;
|
|
||||||
|
|
||||||
if (this.status !== 200)
|
if (this.status !== 200)
|
||||||
return toast.err(0, 'Error! The file was NOT saved.\n\n' + this.status + ": " + (this.responseText + '').replace(/^<pre>/, ""));
|
return toast.err(0, 'Error! The file was NOT saved.\n\n' + this.status + ": " + (this.responseText + '').replace(/^<pre>/, ""));
|
||||||
|
|
||||||
@@ -397,10 +391,10 @@ function save_cb() {
|
|||||||
function run_savechk(lastmod, txt, btn, ntry) {
|
function run_savechk(lastmod, txt, btn, ntry) {
|
||||||
// download the saved doc from the server and compare
|
// download the saved doc from the server and compare
|
||||||
var url = (document.location + '').split('?')[0] + '?raw&_=' + Date.now();
|
var url = (document.location + '').split('?')[0] + '?raw&_=' + Date.now();
|
||||||
var xhr = new XMLHttpRequest();
|
var xhr = new XHR();
|
||||||
xhr.open('GET', url, true);
|
xhr.open('GET', url, true);
|
||||||
xhr.responseType = 'text';
|
xhr.responseType = 'text';
|
||||||
xhr.onreadystatechange = savechk_cb;
|
xhr.onload = xhr.onerror = savechk_cb;
|
||||||
xhr.lastmod = lastmod;
|
xhr.lastmod = lastmod;
|
||||||
xhr.txt = txt;
|
xhr.txt = txt;
|
||||||
xhr.btn = btn;
|
xhr.btn = btn;
|
||||||
@@ -409,9 +403,6 @@ function run_savechk(lastmod, txt, btn, ntry) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function savechk_cb() {
|
function savechk_cb() {
|
||||||
if (this.readyState != XMLHttpRequest.DONE)
|
|
||||||
return;
|
|
||||||
|
|
||||||
if (this.status !== 200)
|
if (this.status !== 200)
|
||||||
return toast.err(0, 'Error! The file was NOT saved.\n\n' + this.status + ": " + (this.responseText + '').replace(/^<pre>/, ""));
|
return toast.err(0, 'Error! The file was NOT saved.\n\n' + this.status + ": " + (this.responseText + '').replace(/^<pre>/, ""));
|
||||||
|
|
||||||
@@ -678,7 +669,7 @@ function reLastIndexOf(txt, ptn, end) {
|
|||||||
// table formatter
|
// table formatter
|
||||||
function fmt_table(e) {
|
function fmt_table(e) {
|
||||||
if (e) e.preventDefault();
|
if (e) e.preventDefault();
|
||||||
//dom_tbox.setAttribute('class', '');
|
//dom_tbox.className = '';
|
||||||
|
|
||||||
var txt = dom_src.value,
|
var txt = dom_src.value,
|
||||||
ofs = dom_src.selectionStart,
|
ofs = dom_src.selectionStart,
|
||||||
@@ -829,7 +820,7 @@ function fmt_table(e) {
|
|||||||
// show unicode
|
// show unicode
|
||||||
function mark_uni(e) {
|
function mark_uni(e) {
|
||||||
if (e) e.preventDefault();
|
if (e) e.preventDefault();
|
||||||
dom_tbox.setAttribute('class', '');
|
dom_tbox.className = '';
|
||||||
|
|
||||||
var txt = dom_src.value,
|
var txt = dom_src.value,
|
||||||
ptn = new RegExp('([^' + js_uni_whitelist + ']+)', 'g'),
|
ptn = new RegExp('([^' + js_uni_whitelist + ']+)', 'g'),
|
||||||
@@ -989,14 +980,14 @@ var set_lno = (function () {
|
|||||||
|
|
||||||
ebi('tools').onclick = function (e) {
|
ebi('tools').onclick = function (e) {
|
||||||
if (e) e.preventDefault();
|
if (e) e.preventDefault();
|
||||||
var is_open = dom_tbox.getAttribute('class') != 'open';
|
var is_open = dom_tbox.className != 'open';
|
||||||
dom_tbox.setAttribute('class', is_open ? 'open' : '');
|
dom_tbox.className = is_open ? 'open' : '';
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
ebi('help').onclick = function (e) {
|
ebi('help').onclick = function (e) {
|
||||||
if (e) e.preventDefault();
|
if (e) e.preventDefault();
|
||||||
dom_tbox.setAttribute('class', '');
|
dom_tbox.className = '';
|
||||||
|
|
||||||
var dom = ebi('helpbox');
|
var dom = ebi('helpbox');
|
||||||
var dtxt = dom.getElementsByTagName('textarea');
|
var dtxt = dom.getElementsByTagName('textarea');
|
||||||
|
|||||||
@@ -37,7 +37,7 @@ var lightswitch = (function () {
|
|||||||
drk = l.light != 1,
|
drk = l.light != 1,
|
||||||
f = function (e) {
|
f = function (e) {
|
||||||
if (e) drk = !drk;
|
if (e) drk = !drk;
|
||||||
document.documentElement.setAttribute("class", drk? "z":"y");
|
document.documentElement.className = drk? "z":"y";
|
||||||
l.light = drk? 0:1;
|
l.light = drk? 0:1;
|
||||||
};
|
};
|
||||||
f();
|
f();
|
||||||
@@ -45,7 +45,7 @@ l.light = drk? 0:1;
|
|||||||
})();
|
})();
|
||||||
|
|
||||||
</script>
|
</script>
|
||||||
<script src="/.cpr/util.js?_={{ ts }}"></script>
|
<script src="/.cpr/util.js?_={{ ts }}"></script>
|
||||||
<script src="/.cpr/deps/marked.js?_={{ ts }}"></script>
|
<script src="/.cpr/deps/marked.js?_={{ ts }}"></script>
|
||||||
<script src="/.cpr/deps/easymde.js?_={{ ts }}"></script>
|
<script src="/.cpr/deps/easymde.js?_={{ ts }}"></script>
|
||||||
<script src="/.cpr/mde.js?_={{ ts }}"></script>
|
<script src="/.cpr/mde.js?_={{ ts }}"></script>
|
||||||
|
|||||||
@@ -114,10 +114,10 @@ function save(mde) {
|
|||||||
fd.append("body", txt);
|
fd.append("body", txt);
|
||||||
|
|
||||||
var url = (document.location + '').split('?')[0];
|
var url = (document.location + '').split('?')[0];
|
||||||
var xhr = new XMLHttpRequest();
|
var xhr = new XHR();
|
||||||
xhr.open('POST', url, true);
|
xhr.open('POST', url, true);
|
||||||
xhr.responseType = 'text';
|
xhr.responseType = 'text';
|
||||||
xhr.onreadystatechange = save_cb;
|
xhr.onload = xhr.onerror = save_cb;
|
||||||
xhr.btn = save_btn;
|
xhr.btn = save_btn;
|
||||||
xhr.mde = mde;
|
xhr.mde = mde;
|
||||||
xhr.txt = txt;
|
xhr.txt = txt;
|
||||||
@@ -133,9 +133,6 @@ function save(mde) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function save_cb() {
|
function save_cb() {
|
||||||
if (this.readyState != XMLHttpRequest.DONE)
|
|
||||||
return;
|
|
||||||
|
|
||||||
if (this.status !== 200)
|
if (this.status !== 200)
|
||||||
return toast.err(0, 'Error! The file was NOT saved.\n\n' + this.status + ": " + (this.responseText + '').replace(/^<pre>/, ""));
|
return toast.err(0, 'Error! The file was NOT saved.\n\n' + this.status + ": " + (this.responseText + '').replace(/^<pre>/, ""));
|
||||||
|
|
||||||
@@ -170,10 +167,10 @@ function save_cb() {
|
|||||||
|
|
||||||
// download the saved doc from the server and compare
|
// download the saved doc from the server and compare
|
||||||
var url = (document.location + '').split('?')[0] + '?raw';
|
var url = (document.location + '').split('?')[0] + '?raw';
|
||||||
var xhr = new XMLHttpRequest();
|
var xhr = new XHR();
|
||||||
xhr.open('GET', url, true);
|
xhr.open('GET', url, true);
|
||||||
xhr.responseType = 'text';
|
xhr.responseType = 'text';
|
||||||
xhr.onreadystatechange = save_chk;
|
xhr.onload = xhr.onerror = save_chk;
|
||||||
xhr.btn = this.save_btn;
|
xhr.btn = this.save_btn;
|
||||||
xhr.mde = this.mde;
|
xhr.mde = this.mde;
|
||||||
xhr.txt = this.txt;
|
xhr.txt = this.txt;
|
||||||
@@ -182,9 +179,6 @@ function save_cb() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function save_chk() {
|
function save_chk() {
|
||||||
if (this.readyState != XMLHttpRequest.DONE)
|
|
||||||
return;
|
|
||||||
|
|
||||||
if (this.status !== 200)
|
if (this.status !== 200)
|
||||||
return toast.err(0, 'Error! The file was NOT saved.\n\n' + this.status + ": " + (this.responseText + '').replace(/^<pre>/, ""));
|
return toast.err(0, 'Error! The file was NOT saved.\n\n' + this.status + ": " + (this.responseText + '').replace(/^<pre>/, ""));
|
||||||
|
|
||||||
|
|||||||
@@ -2,49 +2,49 @@
|
|||||||
<html lang="en">
|
<html lang="en">
|
||||||
|
|
||||||
<head>
|
<head>
|
||||||
<meta charset="utf-8">
|
<meta charset="utf-8">
|
||||||
<title>{{ svcname }}</title>
|
<title>{{ svcname }}</title>
|
||||||
<meta http-equiv="X-UA-Compatible" content="IE=edge">
|
<meta http-equiv="X-UA-Compatible" content="IE=edge">
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=0.8">
|
<meta name="viewport" content="width=device-width, initial-scale=0.8">
|
||||||
{{ html_head }}
|
{{ html_head }}
|
||||||
<link rel="stylesheet" media="screen" href="/.cpr/msg.css?_={{ ts }}">
|
<link rel="stylesheet" media="screen" href="/.cpr/msg.css?_={{ ts }}">
|
||||||
</head>
|
</head>
|
||||||
|
|
||||||
<body>
|
<body>
|
||||||
<div id="box">
|
<div id="box">
|
||||||
|
|
||||||
{%- if h1 %}
|
{%- if h1 %}
|
||||||
<h1>{{ h1 }}</h1>
|
<h1>{{ h1 }}</h1>
|
||||||
{%- endif %}
|
{%- endif %}
|
||||||
|
|
||||||
{%- if h2 %}
|
{%- if h2 %}
|
||||||
<h2>{{ h2 }}</h2>
|
<h2>{{ h2 }}</h2>
|
||||||
{%- endif %}
|
{%- endif %}
|
||||||
|
|
||||||
{%- if p %}
|
{%- if p %}
|
||||||
<p>{{ p }}</p>
|
<p>{{ p }}</p>
|
||||||
{%- endif %}
|
{%- endif %}
|
||||||
|
|
||||||
{%- if pre %}
|
{%- if pre %}
|
||||||
<pre>{{ pre }}</pre>
|
<pre>{{ pre }}</pre>
|
||||||
{%- endif %}
|
{%- endif %}
|
||||||
|
|
||||||
{%- if html %}
|
{%- if html %}
|
||||||
{{ html }}
|
{{ html }}
|
||||||
{%- endif %}
|
{%- endif %}
|
||||||
|
|
||||||
{%- if click %}
|
{%- if click %}
|
||||||
<script>document.getElementsByTagName("a")[0].click()</script>
|
<script>document.getElementsByTagName("a")[0].click()</script>
|
||||||
{%- endif %}
|
{%- endif %}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{%- if redir %}
|
{%- if redir %}
|
||||||
<script>
|
<script>
|
||||||
setTimeout(function() {
|
setTimeout(function() {
|
||||||
window.location.replace("{{ redir }}");
|
window.location.replace("{{ redir }}");
|
||||||
}, 1000);
|
}, 1000);
|
||||||
</script>
|
</script>
|
||||||
{%- endif %}
|
{%- endif %}
|
||||||
</body>
|
</body>
|
||||||
|
|
||||||
</html>
|
</html>
|
||||||
@@ -1,9 +1,7 @@
|
|||||||
html, body, #wrap {
|
html {
|
||||||
color: #333;
|
color: #333;
|
||||||
background: #f7f7f7;
|
background: #f7f7f7;
|
||||||
font-family: sans-serif;
|
font-family: sans-serif;
|
||||||
}
|
|
||||||
html {
|
|
||||||
touch-action: manipulation;
|
touch-action: manipulation;
|
||||||
}
|
}
|
||||||
#wrap {
|
#wrap {
|
||||||
@@ -38,7 +36,6 @@ a+a {
|
|||||||
margin: -.2em 0 0 .5em;
|
margin: -.2em 0 0 .5em;
|
||||||
}
|
}
|
||||||
.logout,
|
.logout,
|
||||||
.btns a,
|
|
||||||
a.r {
|
a.r {
|
||||||
color: #c04;
|
color: #c04;
|
||||||
border-color: #c7a;
|
border-color: #c7a;
|
||||||
@@ -88,9 +85,7 @@ blockquote {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
html.z,
|
html.z {
|
||||||
html.z body,
|
|
||||||
html.z #wrap {
|
|
||||||
background: #222;
|
background: #222;
|
||||||
color: #ccc;
|
color: #ccc;
|
||||||
}
|
}
|
||||||
@@ -103,7 +98,6 @@ html.z a {
|
|||||||
border-color: #37a;
|
border-color: #37a;
|
||||||
}
|
}
|
||||||
html.z .logout,
|
html.z .logout,
|
||||||
html.z .btns a,
|
|
||||||
html.z a.r {
|
html.z a.r {
|
||||||
background: #804;
|
background: #804;
|
||||||
border-color: #c28;
|
border-color: #c28;
|
||||||
@@ -120,3 +114,9 @@ html.z input {
|
|||||||
html.z .num {
|
html.z .num {
|
||||||
border-color: #777;
|
border-color: #777;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
html.bz {
|
||||||
|
color: #bbd;
|
||||||
|
background: #11121d;
|
||||||
|
}
|
||||||
|
|||||||
@@ -2,105 +2,106 @@
|
|||||||
<html lang="en">
|
<html lang="en">
|
||||||
|
|
||||||
<head>
|
<head>
|
||||||
<meta charset="utf-8">
|
<meta charset="utf-8">
|
||||||
<title>{{ svcname }}</title>
|
<title>{{ svcname }}</title>
|
||||||
<meta http-equiv="X-UA-Compatible" content="IE=edge">
|
<meta http-equiv="X-UA-Compatible" content="IE=edge">
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=0.8">
|
<meta name="viewport" content="width=device-width, initial-scale=0.8">
|
||||||
{{ html_head }}
|
{{ html_head }}
|
||||||
<link rel="stylesheet" media="screen" href="/.cpr/splash.css?_={{ ts }}">
|
<link rel="stylesheet" media="screen" href="/.cpr/splash.css?_={{ ts }}">
|
||||||
<link rel="stylesheet" media="screen" href="/.cpr/ui.css?_={{ ts }}">
|
<link rel="stylesheet" media="screen" href="/.cpr/ui.css?_={{ ts }}">
|
||||||
</head>
|
</head>
|
||||||
|
|
||||||
<body>
|
<body>
|
||||||
<div id="wrap">
|
<div id="wrap">
|
||||||
<a href="/?h" class="refresh">refresh</a>
|
<a id="a" href="/?h" class="refresh">refresh</a>
|
||||||
|
|
||||||
{%- if this.uname == '*' %}
|
{%- if this.uname == '*' %}
|
||||||
<p>howdy stranger <small>(you're not logged in)</small></p>
|
<p id="b">howdy stranger <small>(you're not logged in)</small></p>
|
||||||
{%- else %}
|
{%- else %}
|
||||||
<a href="/?pw=x" class="logout">logout</a>
|
<a id="c" href="/?pw=x" class="logout">logout</a>
|
||||||
<p>welcome back, <strong>{{ this.uname }}</strong></p>
|
<p><span id="m">welcome back,</span> <strong>{{ this.uname }}</strong></p>
|
||||||
{%- endif %}
|
{%- endif %}
|
||||||
|
|
||||||
{%- if msg %}
|
{%- if msg %}
|
||||||
<div id="msg">
|
<div id="msg">
|
||||||
{{ msg }}
|
{{ msg }}
|
||||||
</div>
|
</div>
|
||||||
{%- endif %}
|
{%- endif %}
|
||||||
|
|
||||||
{%- if avol %}
|
{%- if avol %}
|
||||||
<h1>admin panel:</h1>
|
<h1>admin panel:</h1>
|
||||||
<table><tr><td> <!-- hehehe -->
|
<table><tr><td> <!-- hehehe -->
|
||||||
<table class="num">
|
<table class="num">
|
||||||
<tr><td>scanning</td><td>{{ scanning }}</td></tr>
|
<tr><td>scanning</td><td>{{ scanning }}</td></tr>
|
||||||
<tr><td>hash-q</td><td>{{ hashq }}</td></tr>
|
<tr><td>hash-q</td><td>{{ hashq }}</td></tr>
|
||||||
<tr><td>tag-q</td><td>{{ tagq }}</td></tr>
|
<tr><td>tag-q</td><td>{{ tagq }}</td></tr>
|
||||||
<tr><td>mtp-q</td><td>{{ mtpq }}</td></tr>
|
<tr><td>mtp-q</td><td>{{ mtpq }}</td></tr>
|
||||||
</table>
|
</table>
|
||||||
</td><td>
|
</td><td>
|
||||||
<table class="vols">
|
<table class="vols">
|
||||||
<thead><tr><th>vol</th><th>action</th><th>status</th></tr></thead>
|
<thead><tr><th>vol</th><th id="t">action</th><th>status</th></tr></thead>
|
||||||
<tbody>
|
<tbody>
|
||||||
{% for mp in avol %}
|
{% for mp in avol %}
|
||||||
{%- if mp in vstate and vstate[mp] %}
|
{%- if mp in vstate and vstate[mp] %}
|
||||||
<tr><td><a href="{{ mp }}{{ url_suf }}">{{ mp }}</a></td><td><a href="{{ mp }}?scan">rescan</a></td><td>{{ vstate[mp] }}</td></tr>
|
<tr><td><a href="{{ mp }}{{ url_suf }}">{{ mp }}</a></td><td><a class="s" href="{{ mp }}?scan">rescan</a></td><td>{{ vstate[mp] }}</td></tr>
|
||||||
{%- endif %}
|
{%- endif %}
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
</td></tr></table>
|
</td></tr></table>
|
||||||
<div class="btns">
|
<div class="btns">
|
||||||
<a href="/?stack" tt="shows the state of all active threads">dump stack</a>
|
<a id="d" href="/?stack" tt="shows the state of all active threads">dump stack</a>
|
||||||
<a href="/?reload=cfg" tt="reload config files (accounts/volumes/volflags),$Nand rescan all e2ds volumes">reload cfg</a>
|
<a id="e" href="/?reload=cfg" tt="reload config files (accounts/volumes/volflags),$Nand rescan all e2ds volumes">reload cfg</a>
|
||||||
</div>
|
</div>
|
||||||
{%- endif %}
|
{%- endif %}
|
||||||
|
|
||||||
{%- if rvol %}
|
{%- if rvol %}
|
||||||
<h1>you can browse these:</h1>
|
<h1 id="f">you can browse:</h1>
|
||||||
<ul>
|
<ul>
|
||||||
{% for mp in rvol %}
|
{% for mp in rvol %}
|
||||||
<li><a href="{{ mp }}{{ url_suf }}">{{ mp }}</a></li>
|
<li><a href="{{ mp }}{{ url_suf }}">{{ mp }}</a></li>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</ul>
|
</ul>
|
||||||
{%- endif %}
|
{%- endif %}
|
||||||
|
|
||||||
{%- if wvol %}
|
{%- if wvol %}
|
||||||
<h1>you can upload to:</h1>
|
<h1 id="g">you can upload to:</h1>
|
||||||
<ul>
|
<ul>
|
||||||
{% for mp in wvol %}
|
{% for mp in wvol %}
|
||||||
<li><a href="{{ mp }}{{ url_suf }}">{{ mp }}</a></li>
|
<li><a href="{{ mp }}{{ url_suf }}">{{ mp }}</a></li>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</ul>
|
</ul>
|
||||||
{%- endif %}
|
{%- endif %}
|
||||||
|
|
||||||
<h1 id="cc">client config:</h1>
|
<h1 id="cc">client config:</h1>
|
||||||
<ul>
|
<ul>
|
||||||
{% if k304 %}
|
{% if k304 %}
|
||||||
<li><a href="/?k304=n">disable k304</a> (currently enabled)
|
<li><a id="h" href="/?k304=n">disable k304</a> (currently enabled)
|
||||||
{%- else %}
|
{%- else %}
|
||||||
<li><a href="/?k304=y" class="r">enable k304</a> (currently disabled)
|
<li><a id="i" href="/?k304=y" class="r">enable k304</a> (currently disabled)
|
||||||
{% endif %}
|
{% endif %}
|
||||||
<blockquote>enabling this will disconnect your client on every HTTP 304, which can prevent some buggy browsers/proxies from getting stuck (suddenly not being able to load pages), <em>but</em> it will also make things slower in general</blockquote></li>
|
<blockquote id="j">enabling this will disconnect your client on every HTTP 304, which can prevent some buggy proxies from getting stuck (suddenly not loading pages), <em>but</em> it will also make things slower in general</blockquote></li>
|
||||||
|
|
||||||
<li><a href="/?reset" class="r" onclick="localStorage.clear();return true">reset client settings</a></li>
|
<li><a id="k" href="/?reset" class="r" onclick="localStorage.clear();return true">reset client settings</a></li>
|
||||||
</ul>
|
</ul>
|
||||||
|
|
||||||
<h1>login for more:</h1>
|
<h1 id="l">login for more:</h1>
|
||||||
<ul>
|
<ul>
|
||||||
<form method="post" enctype="multipart/form-data" action="/{{ qvpath }}">
|
<form method="post" enctype="multipart/form-data" action="/{{ qvpath }}">
|
||||||
<input type="hidden" name="act" value="login" />
|
<input type="hidden" name="act" value="login" />
|
||||||
<input type="password" name="cppwd" />
|
<input type="password" name="cppwd" />
|
||||||
<input type="submit" value="Login" />
|
<input type="submit" value="Login" />
|
||||||
</form>
|
</form>
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
<a href="#" id="repl">π</a>
|
<a href="#" id="repl">π</a>
|
||||||
<script>
|
<script>
|
||||||
|
|
||||||
document.documentElement.setAttribute("class", localStorage.light == 1 ? "y" : "z");
|
var lang="{{ this.args.lang }}";
|
||||||
|
document.documentElement.className=localStorage.theme||"{{ this.args.theme }}";
|
||||||
|
|
||||||
</script>
|
</script>
|
||||||
<script src="/.cpr/util.js?_={{ ts }}"></script>
|
<script src="/.cpr/util.js?_={{ ts }}"></script>
|
||||||
<script>tt.init();</script>
|
<script src="/.cpr/splash.js?_={{ ts }}"></script>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|||||||
44
copyparty/web/splash.js
Normal file
44
copyparty/web/splash.js
Normal file
@@ -0,0 +1,44 @@
|
|||||||
|
var Ls = {
|
||||||
|
"nor": {
|
||||||
|
"a1": "oppdater",
|
||||||
|
"b1": "halloien <small>(du er ikke logget inn)</small>",
|
||||||
|
"c1": "logg ut",
|
||||||
|
"d1": "tilstand",
|
||||||
|
"d2": "vis tilstanden til alle tråder",
|
||||||
|
"e1": "last innst.",
|
||||||
|
"e2": "leser inn konfigurasjonsfiler på nytt$N(kontoer, volumer, volumbrytere)$Nog kartlegger alle e2ds-volumer",
|
||||||
|
"f1": "du kan betrakte:",
|
||||||
|
"g1": "du kan laste opp til:",
|
||||||
|
"cc1": "klient-konfigurasjon",
|
||||||
|
"h1": "skru av k304",
|
||||||
|
"i1": "skru på k304",
|
||||||
|
"j1": "k304 bryter tilkoplingen for hver HTTP 304. Dette hjelper visse mellomtjenere som kan sette seg fast / plutselig slutter å laste sider, men det reduserer også ytelsen betydelig",
|
||||||
|
"k1": "nullstill innstillinger",
|
||||||
|
"l1": "logg inn:",
|
||||||
|
"m1": "velkommen tilbake,",
|
||||||
|
"n1": "404: filen finnes ikke ┐( ´ -`)┌",
|
||||||
|
"o1": 'eller kanskje du ikke har tilgang? prøv å logge inn eller <a href="/?h">gå hjem</a>',
|
||||||
|
"p1": "403: tilgang nektet ~┻━┻",
|
||||||
|
"q1": 'du må logge inn eller <a href="/?h">gå hjem</a>',
|
||||||
|
"r1": "gå hjem",
|
||||||
|
".s1": "kartlegg",
|
||||||
|
"t1": "handling",
|
||||||
|
}
|
||||||
|
},
|
||||||
|
d = Ls[sread("lang") || lang];
|
||||||
|
|
||||||
|
for (var k in (d || {})) {
|
||||||
|
var f = k.slice(-1),
|
||||||
|
i = k.slice(0, -1),
|
||||||
|
o = QSA(i.startsWith('.') ? i : '#' + i);
|
||||||
|
|
||||||
|
for (var a = 0; a < o.length; a++)
|
||||||
|
if (f == 1)
|
||||||
|
o[a].innerHTML = d[k];
|
||||||
|
else if (f == 2)
|
||||||
|
o[a].setAttribute("tt", d[k]);
|
||||||
|
}
|
||||||
|
|
||||||
|
tt.init();
|
||||||
|
if (!ebi('c'))
|
||||||
|
QS('input[name="cppwd"]').focus();
|
||||||
@@ -11,6 +11,7 @@ html {
|
|||||||
max-width: 34em;
|
max-width: 34em;
|
||||||
max-width: min(34em, 90%);
|
max-width: min(34em, 90%);
|
||||||
max-width: min(34em, calc(100% - 7em));
|
max-width: min(34em, calc(100% - 7em));
|
||||||
|
color: #ddd;
|
||||||
background: #333;
|
background: #333;
|
||||||
border: 0 solid #777;
|
border: 0 solid #777;
|
||||||
box-shadow: 0 .2em .5em #111;
|
box-shadow: 0 .2em .5em #111;
|
||||||
@@ -74,6 +75,9 @@ html {
|
|||||||
margin-right: -1.2em;
|
margin-right: -1.2em;
|
||||||
padding-right: .7em;
|
padding-right: .7em;
|
||||||
}
|
}
|
||||||
|
#toast.r #toastb {
|
||||||
|
text-align: right;
|
||||||
|
}
|
||||||
#toast pre {
|
#toast pre {
|
||||||
margin: 0;
|
margin: 0;
|
||||||
}
|
}
|
||||||
@@ -158,18 +162,27 @@ html {
|
|||||||
color: #f6a;
|
color: #f6a;
|
||||||
}
|
}
|
||||||
html.y #tt {
|
html.y #tt {
|
||||||
|
color: #333;
|
||||||
background: #fff;
|
background: #fff;
|
||||||
border-color: #888 #000 #777 #000;
|
border-color: #888 #000 #777 #000;
|
||||||
}
|
}
|
||||||
|
html.bz #tt {
|
||||||
|
background: #202231;
|
||||||
|
border-color: #3b3f58;
|
||||||
|
}
|
||||||
html.y #tt,
|
html.y #tt,
|
||||||
html.y #toast {
|
html.y #toast {
|
||||||
box-shadow: 0 .3em 1em rgba(0,0,0,0.4);
|
box-shadow: 0 .3em 1em rgba(0,0,0,0.4);
|
||||||
}
|
}
|
||||||
#modalc code,
|
|
||||||
html.y #tt code {
|
html.y #tt code {
|
||||||
background: #060;
|
background: #060;
|
||||||
color: #fff;
|
color: #fff;
|
||||||
}
|
}
|
||||||
|
#modalc code {
|
||||||
|
color: #060;
|
||||||
|
background: transparent;
|
||||||
|
border: 1px solid #ccc;
|
||||||
|
}
|
||||||
html.y #tt em {
|
html.y #tt em {
|
||||||
color: #d38;
|
color: #d38;
|
||||||
}
|
}
|
||||||
@@ -476,10 +489,20 @@ html.y textarea:focus {
|
|||||||
border-top: .4em solid #b80;
|
border-top: .4em solid #b80;
|
||||||
border-bottom: .4em solid #4c4c4c;
|
border-bottom: .4em solid #4c4c4c;
|
||||||
}
|
}
|
||||||
|
html.bz .mdo h1 {
|
||||||
|
background: #202231;
|
||||||
|
border: 1px solid #2d2f45;
|
||||||
|
border-width: 0 0 .4em 0;
|
||||||
|
}
|
||||||
html.z .mdo h2 {
|
html.z .mdo h2 {
|
||||||
background: #444;
|
background: #444;
|
||||||
border-bottom: .22em solid #555;
|
border-bottom: .22em solid #555;
|
||||||
}
|
}
|
||||||
|
html.bz .mdo h2,
|
||||||
|
html.bz .mdo h3 {
|
||||||
|
background: transparent;
|
||||||
|
border-color: #3b3f58;
|
||||||
|
}
|
||||||
html.z .mdo td,
|
html.z .mdo td,
|
||||||
html.z .mdo th {
|
html.z .mdo th {
|
||||||
border-color: #444;
|
border-color: #444;
|
||||||
|
|||||||
@@ -135,7 +135,7 @@ function up2k_flagbus() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
function U2pvis(act, btns) {
|
function U2pvis(act, btns, uc) {
|
||||||
var r = this;
|
var r = this;
|
||||||
r.act = act;
|
r.act = act;
|
||||||
r.ctr = { "ok": 0, "ng": 0, "bz": 0, "q": 0 };
|
r.ctr = { "ok": 0, "ng": 0, "bz": 0, "q": 0 };
|
||||||
@@ -425,7 +425,9 @@ function U2pvis(act, btns) {
|
|||||||
html.push(r.genrow(a, true).replace(/><td>/, "><td>b "));
|
html.push(r.genrow(a, true).replace(/><td>/, "><td>b "));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
ebi('u2tab').tBodies[0].innerHTML = html.join('\n');
|
var el = ebi('u2tab');
|
||||||
|
el.tBodies[0].innerHTML = html.join('\n');
|
||||||
|
el.className = (uc.fsearch ? 'srch ' : 'up ') + r.act;
|
||||||
};
|
};
|
||||||
|
|
||||||
r.genrow = function (nfile, as_html) {
|
r.genrow = function (nfile, as_html) {
|
||||||
@@ -566,12 +568,12 @@ function Donut(uc, st) {
|
|||||||
|
|
||||||
function fsearch_explain(n) {
|
function fsearch_explain(n) {
|
||||||
if (n)
|
if (n)
|
||||||
return toast.inf(60, 'your access to this folder is Read-Only\n\n' + (acct == '*' ? 'you are currently not logged in' : 'you are currently logged in as "' + acct + '"'));
|
return toast.inf(60, L.ue_ro + (acct == '*' ? L.ue_nl : L.ue_la).format(acct));
|
||||||
|
|
||||||
if (bcfg_get('fsearch', false))
|
if (bcfg_get('fsearch', false))
|
||||||
return toast.inf(60, 'you are currently in file-search mode\n\nswitch to upload-mode by clicking the green magnifying glass (next to the big yellow search button), and try uploading again\n\nsorry');
|
return toast.inf(60, L.ue_sr);
|
||||||
|
|
||||||
return toast.inf(60, 'try again, it should work now');
|
return toast.inf(60, L.ue_ta);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -590,14 +592,7 @@ function up2k_init(subtle) {
|
|||||||
ebi('u2notbtn').innerHTML = '';
|
ebi('u2notbtn').innerHTML = '';
|
||||||
}
|
}
|
||||||
|
|
||||||
var suggest_up2k = 'this is the basic uploader; <a href="#" id="u2yea">up2k</a> is better';
|
var suggest_up2k = L.u_su2k;
|
||||||
|
|
||||||
var shame = 'your browser <a href="https://www.chromium.org/blink/webcrypto">disables sha512</a> unless you <a href="' + (window.location + '').replace(':', 's:') + '">use https</a>',
|
|
||||||
is_https = (window.location + '').indexOf('https:') === 0;
|
|
||||||
|
|
||||||
if (is_https)
|
|
||||||
// chrome<37 firefox<34 edge<12 opera<24 safari<7
|
|
||||||
shame = 'your browser is impressively ancient';
|
|
||||||
|
|
||||||
function got_deps() {
|
function got_deps() {
|
||||||
return subtle || window.asmCrypto || window.hashwasm;
|
return subtle || window.asmCrypto || window.hashwasm;
|
||||||
@@ -606,15 +601,18 @@ function up2k_init(subtle) {
|
|||||||
var loading_deps = false;
|
var loading_deps = false;
|
||||||
function init_deps() {
|
function init_deps() {
|
||||||
if (!loading_deps && !got_deps()) {
|
if (!loading_deps && !got_deps()) {
|
||||||
var fn = 'sha512.' + sha_js + '.js';
|
var fn = 'sha512.' + sha_js + '.js',
|
||||||
showmodal('<h1>loading ' + fn + '</h1><h2>since ' + shame + '</h2><h4>thanks chrome</h4>');
|
m = L.u_https1 + ' <a href="' + (window.location + '').replace(':', 's:') + '">' + L.u_https2 + '</a> ' + L.u_https3;
|
||||||
|
|
||||||
|
showmodal('<h1>loading ' + fn + '</h1>');
|
||||||
import_js('/.cpr/deps/' + fn, unmodal);
|
import_js('/.cpr/deps/' + fn, unmodal);
|
||||||
|
|
||||||
if (is_https)
|
if (is_https) {
|
||||||
ebi('u2foot').innerHTML = shame + ' so <em>this</em> uploader will do like 500 KiB/s at best';
|
// chrome<37 firefox<34 edge<12 opera<24 safari<7
|
||||||
else
|
m = L.u_ancient;
|
||||||
ebi('u2foot').innerHTML = 'seems like ' + shame + ' so do that if you want more performance <span style="color:#' +
|
setmsg('');
|
||||||
(sha_js == 'ac' ? 'c84">(expecting 20' : '8a5">(but dont worry too much, expect 100') + ' MiB/s)</span>';
|
}
|
||||||
|
ebi('u2foot').innerHTML = '<big>' + m + '</big>';
|
||||||
}
|
}
|
||||||
loading_deps = true;
|
loading_deps = true;
|
||||||
}
|
}
|
||||||
@@ -624,11 +622,11 @@ function up2k_init(subtle) {
|
|||||||
|
|
||||||
function setmsg(msg, type) {
|
function setmsg(msg, type) {
|
||||||
if (msg !== undefined) {
|
if (msg !== undefined) {
|
||||||
ebi('u2err').setAttribute('class', type);
|
ebi('u2err').className = type;
|
||||||
ebi('u2err').innerHTML = msg;
|
ebi('u2err').innerHTML = msg;
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
ebi('u2err').setAttribute('class', '');
|
ebi('u2err').className = '';
|
||||||
ebi('u2err').innerHTML = '';
|
ebi('u2err').innerHTML = '';
|
||||||
}
|
}
|
||||||
if (msg == suggest_up2k) {
|
if (msg == suggest_up2k) {
|
||||||
@@ -646,16 +644,6 @@ function up2k_init(subtle) {
|
|||||||
|
|
||||||
setmsg(suggest_up2k, 'msg');
|
setmsg(suggest_up2k, 'msg');
|
||||||
|
|
||||||
if (!String.prototype.format) {
|
|
||||||
String.prototype.format = function () {
|
|
||||||
var args = arguments;
|
|
||||||
return this.replace(/{(\d+)}/g, function (match, number) {
|
|
||||||
return typeof args[number] != 'undefined' ?
|
|
||||||
args[number] : match;
|
|
||||||
});
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
var parallel_uploads = icfg_get('nthread'),
|
var parallel_uploads = icfg_get('nthread'),
|
||||||
uc = {},
|
uc = {},
|
||||||
fdom_ctr = 0,
|
fdom_ctr = 0,
|
||||||
@@ -665,8 +653,8 @@ function up2k_init(subtle) {
|
|||||||
bcfg_bind(uc, 'ask_up', 'ask_up', true, null, false);
|
bcfg_bind(uc, 'ask_up', 'ask_up', true, null, false);
|
||||||
bcfg_bind(uc, 'flag_en', 'flag_en', false, apply_flag_cfg);
|
bcfg_bind(uc, 'flag_en', 'flag_en', false, apply_flag_cfg);
|
||||||
bcfg_bind(uc, 'fsearch', 'fsearch', false, set_fsearch, false);
|
bcfg_bind(uc, 'fsearch', 'fsearch', false, set_fsearch, false);
|
||||||
bcfg_bind(uc, 'turbo', 'u2turbo', false, draw_turbo, false);
|
bcfg_bind(uc, 'turbo', 'u2turbo', turbolvl > 1, draw_turbo, false);
|
||||||
bcfg_bind(uc, 'datechk', 'u2tdate', true, null, false);
|
bcfg_bind(uc, 'datechk', 'u2tdate', turbolvl < 3, null, false);
|
||||||
|
|
||||||
var st = {
|
var st = {
|
||||||
"files": [],
|
"files": [],
|
||||||
@@ -705,7 +693,7 @@ function up2k_init(subtle) {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
var pvis = new U2pvis("bz", '#u2cards'),
|
var pvis = new U2pvis("bz", '#u2cards', uc),
|
||||||
donut = new Donut(uc, st);
|
donut = new Donut(uc, st);
|
||||||
|
|
||||||
var bobslice = null;
|
var bobslice = null;
|
||||||
@@ -713,7 +701,7 @@ function up2k_init(subtle) {
|
|||||||
bobslice = File.prototype.slice || File.prototype.mozSlice || File.prototype.webkitSlice;
|
bobslice = File.prototype.slice || File.prototype.mozSlice || File.prototype.webkitSlice;
|
||||||
|
|
||||||
if (!bobslice || !window.FileReader || !window.FileList)
|
if (!bobslice || !window.FileReader || !window.FileList)
|
||||||
return un2k("this is the basic uploader; up2k needs at least<br />chrome 21 // firefox 13 // edge 12 // opera 12 // safari 5.1");
|
return un2k(L.u_ever);
|
||||||
|
|
||||||
var flag = false;
|
var flag = false;
|
||||||
apply_flag_cfg();
|
apply_flag_cfg();
|
||||||
@@ -735,14 +723,14 @@ function up2k_init(subtle) {
|
|||||||
var mup, up = QS('#up_zd');
|
var mup, up = QS('#up_zd');
|
||||||
var msr, sr = QS('#srch_zd');
|
var msr, sr = QS('#srch_zd');
|
||||||
if (!has(perms, 'write'))
|
if (!has(perms, 'write'))
|
||||||
mup = 'you do not have write-access to this folder';
|
mup = L.u_ewrite;
|
||||||
if (!has(perms, 'read'))
|
if (!has(perms, 'read'))
|
||||||
msr = 'you do not have read-access to this folder';
|
msr = L.u_eread;
|
||||||
if (!have_up2k_idx)
|
if (!have_up2k_idx)
|
||||||
msr = 'file-search is not enabled in server config';
|
msr = L.u_enoi;
|
||||||
|
|
||||||
up.querySelector('span').textContent = mup || 'drop it here';
|
up.querySelector('span').textContent = mup || L.udt_drop;
|
||||||
sr.querySelector('span').textContent = msr || 'drop it here';
|
sr.querySelector('span').textContent = msr || L.udt_drop;
|
||||||
clmod(up, 'err', mup);
|
clmod(up, 'err', mup);
|
||||||
clmod(sr, 'err', msr);
|
clmod(sr, 'err', msr);
|
||||||
clmod(up, 'ok', !mup);
|
clmod(up, 'ok', !mup);
|
||||||
@@ -968,22 +956,22 @@ function up2k_init(subtle) {
|
|||||||
function gotallfiles(good_files, nil_files, bad_files) {
|
function gotallfiles(good_files, nil_files, bad_files) {
|
||||||
var ntot = good_files.concat(nil_files, bad_files).length;
|
var ntot = good_files.concat(nil_files, bad_files).length;
|
||||||
if (bad_files.length) {
|
if (bad_files.length) {
|
||||||
var msg = 'These {0} files (of {1} total) were skipped, possibly due to filesystem permissions:\n'.format(bad_files.length, ntot);
|
var msg = L.u_badf.format(bad_files.length, ntot);
|
||||||
for (var a = 0, aa = Math.min(20, bad_files.length); a < aa; a++)
|
for (var a = 0, aa = Math.min(20, bad_files.length); a < aa; a++)
|
||||||
msg += '-- ' + bad_files[a][1] + '\n';
|
msg += '-- ' + bad_files[a][1] + '\n';
|
||||||
|
|
||||||
msg += '\nMaybe it works better if you select just one file';
|
msg += L.u_just1;
|
||||||
return modal.alert(msg, function () {
|
return modal.alert(msg, function () {
|
||||||
gotallfiles(good_files, nil_files, []);
|
gotallfiles(good_files, nil_files, []);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
if (nil_files.length) {
|
if (nil_files.length) {
|
||||||
var msg = 'These {0} files (of {1} total) are blank/empty; upload them anyways?\n'.format(nil_files.length, ntot);
|
var msg = L.u_blankf.format(nil_files.length, ntot);
|
||||||
for (var a = 0, aa = Math.min(20, nil_files.length); a < aa; a++)
|
for (var a = 0, aa = Math.min(20, nil_files.length); a < aa; a++)
|
||||||
msg += '-- ' + nil_files[a][1] + '\n';
|
msg += '-- ' + nil_files[a][1] + '\n';
|
||||||
|
|
||||||
msg += '\nMaybe it works better if you select just one file';
|
msg += L.u_just1;
|
||||||
return modal.confirm(msg, function () {
|
return modal.confirm(msg, function () {
|
||||||
gotallfiles(good_files.concat(nil_files), [], []);
|
gotallfiles(good_files.concat(nil_files), [], []);
|
||||||
}, function () {
|
}, function () {
|
||||||
@@ -997,12 +985,15 @@ function up2k_init(subtle) {
|
|||||||
return a < b ? -1 : a > b ? 1 : 0;
|
return a < b ? -1 : a > b ? 1 : 0;
|
||||||
});
|
});
|
||||||
|
|
||||||
var msg = ['{0} these {1} files?<ul>'.format(uc.fsearch ? 'search' : 'upload', good_files.length)];
|
var msg = [L.u_asku.format(good_files.length, esc(get_vpath())) + '<ul>'];
|
||||||
for (var a = 0, aa = Math.min(20, good_files.length); a < aa; a++)
|
for (var a = 0, aa = Math.min(20, good_files.length); a < aa; a++)
|
||||||
msg.push('<li>' + esc(good_files[a][1]) + '</li>');
|
msg.push('<li>' + esc(good_files[a][1]) + '</li>');
|
||||||
|
|
||||||
if (uc.ask_up && !uc.fsearch)
|
if (uc.ask_up && !uc.fsearch)
|
||||||
return modal.confirm(msg.join('') + '</ul>', function () { up_them(good_files); }, null);
|
return modal.confirm(msg.join('') + '</ul>', function () {
|
||||||
|
up_them(good_files);
|
||||||
|
toast.inf(15, L.u_unpt);
|
||||||
|
}, null);
|
||||||
|
|
||||||
up_them(good_files);
|
up_them(good_files);
|
||||||
}
|
}
|
||||||
@@ -1054,7 +1045,7 @@ function up2k_init(subtle) {
|
|||||||
pvis.addfile([
|
pvis.addfile([
|
||||||
uc.fsearch ? esc(entry.name) : linksplit(
|
uc.fsearch ? esc(entry.name) : linksplit(
|
||||||
entry.purl + uricom_enc(entry.name)).join(' '),
|
entry.purl + uricom_enc(entry.name)).join(' '),
|
||||||
'📐 hash',
|
'📐 ' + L.u_hashing,
|
||||||
''
|
''
|
||||||
], fobj.size, draw_each);
|
], fobj.size, draw_each);
|
||||||
|
|
||||||
@@ -1102,11 +1093,11 @@ function up2k_init(subtle) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (!nhash)
|
if (!nhash)
|
||||||
ebi('u2etah').innerHTML = 'Done ({0}, {1} files)'.format(humansize(st.bytes.hashed), pvis.ctr["ok"] + pvis.ctr["ng"]);
|
ebi('u2etah').innerHTML = L.u_etadone.format(humansize(st.bytes.hashed), pvis.ctr["ok"] + pvis.ctr["ng"]);
|
||||||
|
|
||||||
if (!nsend && !nhash)
|
if (!nsend && !nhash)
|
||||||
ebi('u2etau').innerHTML = ebi('u2etat').innerHTML = (
|
ebi('u2etau').innerHTML = ebi('u2etat').innerHTML = (
|
||||||
'Done ({0}, {1} files)'.format(humansize(st.bytes.uploaded), pvis.ctr["ok"] + pvis.ctr["ng"]));
|
L.u_etadone.format(humansize(st.bytes.uploaded), pvis.ctr["ok"] + pvis.ctr["ng"]));
|
||||||
|
|
||||||
if (!st.busy.hash.length && !hashing_permitted())
|
if (!st.busy.hash.length && !hashing_permitted())
|
||||||
nhash = 0;
|
nhash = 0;
|
||||||
@@ -1127,7 +1118,7 @@ function up2k_init(subtle) {
|
|||||||
}
|
}
|
||||||
if ((nhash || nsend) && !uc.fsearch) {
|
if ((nhash || nsend) && !uc.fsearch) {
|
||||||
if (!st.bytes.finished) {
|
if (!st.bytes.finished) {
|
||||||
ebi('u2etat').innerHTML = '(preparing to upload)';
|
ebi('u2etat').innerHTML = L.u_etaprep;
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
st.time.busy += td;
|
st.time.busy += td;
|
||||||
@@ -1140,7 +1131,7 @@ function up2k_init(subtle) {
|
|||||||
eta = Math.floor(rem / bps);
|
eta = Math.floor(rem / bps);
|
||||||
|
|
||||||
if (t[a][1] < 1024 || t[a][3] < 0.1) {
|
if (t[a][1] < 1024 || t[a][3] < 0.1) {
|
||||||
ebi(t[a][0]).innerHTML = '(preparing to upload)';
|
ebi(t[a][0]).innerHTML = L.u_etaprep;
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1258,24 +1249,21 @@ function up2k_init(subtle) {
|
|||||||
donut.on(is_busy);
|
donut.on(is_busy);
|
||||||
|
|
||||||
if (!is_busy) {
|
if (!is_busy) {
|
||||||
var k = uc.fsearch ? 'searches' : 'uploads',
|
var sr = uc.fsearch,
|
||||||
ks = uc.fsearch ? 'Search' : 'Upload',
|
|
||||||
tok = uc.fsearch ? 'successful (found on server)' : 'completed successfully',
|
|
||||||
tng = uc.fsearch ? 'failed (NOT found on server)' : 'failed, sorry',
|
|
||||||
ok = pvis.ctr["ok"],
|
ok = pvis.ctr["ok"],
|
||||||
ng = pvis.ctr["ng"],
|
ng = pvis.ctr["ng"],
|
||||||
t = uc.ask_up ? 0 : 10;
|
t = uc.ask_up ? 0 : 10;
|
||||||
|
|
||||||
if (ok && ng)
|
if (ok && ng)
|
||||||
toast.warn(t, 'Finished, but some {0} failed:\n{1} {2},\n{3} {4}'.format(k, ok, tok, ng, tng));
|
toast.warn(t, (sr ? L.ur_sm : L.ur_um).format(ok, ng));
|
||||||
else if (ok > 1)
|
else if (ok > 1)
|
||||||
toast.ok(t, 'All {1} {0} {2}'.format(k, ok, tok));
|
toast.ok(t, (sr ? L.ur_aso : L.ur_auo).format(ok));
|
||||||
else if (ok)
|
else if (ok)
|
||||||
toast.ok(t, '{0} {1}'.format(ks, tok));
|
toast.ok(t, sr ? L.ur_1so : L.ur_1uo);
|
||||||
else if (ng > 1)
|
else if (ng > 1)
|
||||||
toast.err(t, 'All {1} {0} {2}'.format(k, ng, tng));
|
toast.err(t, (sr ? L.ur_asn : L.ur_aun).format(ng));
|
||||||
else if (ng)
|
else if (ng)
|
||||||
toast.err(t, '{0} {1}'.format(ks, tng));
|
toast.err(t, sr ? L.ur_1sn : L.ur_1un);
|
||||||
|
|
||||||
timer.rm(etafun);
|
timer.rm(etafun);
|
||||||
timer.rm(donut.do);
|
timer.rm(donut.do);
|
||||||
@@ -1528,7 +1516,7 @@ function up2k_init(subtle) {
|
|||||||
|
|
||||||
t.t_hashed = Date.now();
|
t.t_hashed = Date.now();
|
||||||
|
|
||||||
pvis.seth(t.n, 2, 'hashing done');
|
pvis.seth(t.n, 2, L.u_hashdone);
|
||||||
pvis.seth(t.n, 1, '📦 wait');
|
pvis.seth(t.n, 1, '📦 wait');
|
||||||
apop(st.busy.hash, t);
|
apop(st.busy.hash, t);
|
||||||
st.todo.handshake.push(t);
|
st.todo.handshake.push(t);
|
||||||
@@ -1656,8 +1644,8 @@ function up2k_init(subtle) {
|
|||||||
|
|
||||||
if (!response || !response.hits || !response.hits.length) {
|
if (!response || !response.hits || !response.hits.length) {
|
||||||
smsg = '404';
|
smsg = '404';
|
||||||
msg = ('not found on server <a href="#" onclick="fsearch_explain(' +
|
msg = (L.u_s404 + ' <a href="#" onclick="fsearch_explain(' +
|
||||||
(has(perms, 'write') ? '0' : '1') + ')" class="fsearch_explain">(explain)</a>');
|
(has(perms, 'write') ? '0' : '1') + ')" class="fsearch_explain">(' + L.u_expl + ')</a>');
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
smsg = 'found';
|
smsg = 'found';
|
||||||
@@ -1732,7 +1720,7 @@ function up2k_init(subtle) {
|
|||||||
'npart': t.postlist[a]
|
'npart': t.postlist[a]
|
||||||
});
|
});
|
||||||
|
|
||||||
msg = 'uploading';
|
msg = L.u_upping;
|
||||||
done = false;
|
done = false;
|
||||||
|
|
||||||
if (sort)
|
if (sort)
|
||||||
@@ -1809,11 +1797,8 @@ function up2k_init(subtle) {
|
|||||||
tasker();
|
tasker();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
toast.err(0, "server broke; hs-err {0} on file [{1}]:\n".format(
|
err = t.t_uploading ? L.u_ehsfin : t.srch ? L.u_ehssrch : L.u_ehsinit;
|
||||||
xhr.status, t.name) + (
|
xhrchk(xhr, err + ";\n\nfile: " + t.name + "\n\nerror ", "404, target folder not found");
|
||||||
(xhr.response && xhr.response.err) ||
|
|
||||||
(xhr.responseText && xhr.responseText) ||
|
|
||||||
"no further information"));
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
xhr.onload = function (e) {
|
xhr.onload = function (e) {
|
||||||
@@ -1872,8 +1857,7 @@ function up2k_init(subtle) {
|
|||||||
console.log("ignoring dupe-segment error", t);
|
console.log("ignoring dupe-segment error", t);
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
toast.err(0, "server broke; cu-err {0} on file [{1}]:\n".format(
|
xhrchk(xhr, L.u_cuerr2.format(npart, Math.ceil(t.size / chunksize), t.name), "404, target folder not found (???)");
|
||||||
xhr.status, t.name) + (txt || "no further information"));
|
|
||||||
|
|
||||||
chill(t);
|
chill(t);
|
||||||
}
|
}
|
||||||
@@ -1902,7 +1886,7 @@ function up2k_init(subtle) {
|
|||||||
return;
|
return;
|
||||||
|
|
||||||
if (!toast.visible)
|
if (!toast.visible)
|
||||||
toast.warn(9.98, "failed to upload a chunk;\nprobably harmless, continuing\n\n" + t.name);
|
toast.warn(9.98, L.u_cuerr.format(npart, Math.ceil(t.size / chunksize), t.name));
|
||||||
|
|
||||||
console.log('chunkpit onerror,', ++tries, t);
|
console.log('chunkpit onerror,', ++tries, t);
|
||||||
orz2(xhr);
|
orz2(xhr);
|
||||||
@@ -1938,19 +1922,17 @@ function up2k_init(subtle) {
|
|||||||
|
|
||||||
if (btn.parentNode !== parent) {
|
if (btn.parentNode !== parent) {
|
||||||
parent.appendChild(btn);
|
parent.appendChild(btn);
|
||||||
ebi('u2conf').setAttribute('class', wide);
|
ebi('u2conf').className = ebi('u2cards').className = ebi('u2etaw').className = wide;
|
||||||
ebi('u2cards').setAttribute('class', wide);
|
|
||||||
ebi('u2etaw').setAttribute('class', wide);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
wide = write && wem > 78 ? 'ww' : wide;
|
wide = write && wem > 78 ? 'ww' : wide;
|
||||||
parent = ebi(wide == 'ww' && write ? 'u2c3w' : 'u2c3t');
|
parent = ebi(wide == 'ww' && write ? 'u2c3w' : 'u2c3t');
|
||||||
var its = [ebi('u2etaw'), ebi('u2cards')];
|
var its = [ebi('u2etaw'), ebi('u2cards')];
|
||||||
if (its[0].parentNode !== parent) {
|
if (its[0].parentNode !== parent) {
|
||||||
ebi('u2conf').setAttribute('class', wide);
|
ebi('u2conf').className = wide;
|
||||||
for (var a = 0; a < 2; a++) {
|
for (var a = 0; a < 2; a++) {
|
||||||
parent.appendChild(its[a]);
|
parent.appendChild(its[a]);
|
||||||
its[a].setAttribute('class', wide);
|
its[a].className = wide;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -2017,17 +1999,18 @@ function up2k_init(subtle) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function draw_turbo() {
|
function draw_turbo() {
|
||||||
var msgu = '<p class="warn">WARNING: turbo enabled, <span> client may not detect and resume incomplete uploads; see turbo-button tooltip</span></p>',
|
var msg = uc.fsearch ? L.u_ts : L.u_tu,
|
||||||
msgs = '<p class="warn">WARNING: turbo enabled, <span> search results can be incorrect; see turbo-button tooltip</span></p>',
|
omsg = uc.fsearch ? L.u_tu : L.u_ts,
|
||||||
msg = uc.fsearch ? msgs : msgu,
|
|
||||||
omsg = uc.fsearch ? msgu : msgs,
|
|
||||||
html = ebi('u2foot').innerHTML,
|
html = ebi('u2foot').innerHTML,
|
||||||
ohtml = html;
|
ohtml = html;
|
||||||
|
|
||||||
if (uc.turbo && html.indexOf(msg) === -1)
|
if (turbolvl || !uc.turbo)
|
||||||
|
msg = null;
|
||||||
|
|
||||||
|
if (msg && html.indexOf(msg) === -1)
|
||||||
html = html.replace(omsg, '') + msg;
|
html = html.replace(omsg, '') + msg;
|
||||||
else if (!uc.turbo)
|
else if (!msg)
|
||||||
html = html.replace(msgu, '').replace(msgs, '');
|
html = html.replace(L.u_tu, '').replace(L.u_ts, '');
|
||||||
|
|
||||||
if (html !== ohtml)
|
if (html !== ohtml)
|
||||||
ebi('u2foot').innerHTML = html;
|
ebi('u2foot').innerHTML = html;
|
||||||
@@ -2063,13 +2046,15 @@ function up2k_init(subtle) {
|
|||||||
|
|
||||||
try {
|
try {
|
||||||
var ico = uc.fsearch ? '🔎' : '🚀',
|
var ico = uc.fsearch ? '🔎' : '🚀',
|
||||||
desc = uc.fsearch ? 'S E A R C H' : 'U P L O A D';
|
desc = uc.fsearch ? L.ul_btns : L.ul_btnu;
|
||||||
|
|
||||||
clmod(ebi('op_up2k'), 'srch', uc.fsearch);
|
clmod(ebi('op_up2k'), 'srch', uc.fsearch);
|
||||||
ebi('u2bm').innerHTML = ico + ' <sup>' + desc + '</sup>';
|
ebi('u2bm').innerHTML = ico + ' <sup>' + desc + '</sup>';
|
||||||
}
|
}
|
||||||
catch (ex) { }
|
catch (ex) { }
|
||||||
|
|
||||||
|
ebi('u2tab').className = (uc.fsearch ? 'srch ' : 'up ') + pvis.act;
|
||||||
|
|
||||||
draw_turbo();
|
draw_turbo();
|
||||||
onresize();
|
onresize();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ if (!window['console'])
|
|||||||
|
|
||||||
|
|
||||||
var is_touch = 'ontouchstart' in window,
|
var is_touch = 'ontouchstart' in window,
|
||||||
|
is_https = (window.location + '').indexOf('https:') === 0,
|
||||||
IPHONE = is_touch && /iPhone|iPad|iPod/i.test(navigator.userAgent),
|
IPHONE = is_touch && /iPhone|iPad|iPod/i.test(navigator.userAgent),
|
||||||
WINDOWS = navigator.platform ? navigator.platform == 'Win32' : /Windows/.test(navigator.userAgent);
|
WINDOWS = navigator.platform ? navigator.platform == 'Win32' : /Windows/.test(navigator.userAgent);
|
||||||
|
|
||||||
@@ -14,7 +15,8 @@ var is_touch = 'ontouchstart' in window,
|
|||||||
var ebi = document.getElementById.bind(document),
|
var ebi = document.getElementById.bind(document),
|
||||||
QS = document.querySelector.bind(document),
|
QS = document.querySelector.bind(document),
|
||||||
QSA = document.querySelectorAll.bind(document),
|
QSA = document.querySelectorAll.bind(document),
|
||||||
mknod = document.createElement.bind(document);
|
mknod = document.createElement.bind(document),
|
||||||
|
XHR = XMLHttpRequest;
|
||||||
|
|
||||||
|
|
||||||
function qsr(sel) {
|
function qsr(sel) {
|
||||||
@@ -89,6 +91,9 @@ function vis_exh(msg, url, lineNo, columnNo, error) {
|
|||||||
if ((msg + '').indexOf('l2d.js') !== -1)
|
if ((msg + '').indexOf('l2d.js') !== -1)
|
||||||
return; // `t` undefined in tapEvent -> hitTestSimpleCustom
|
return; // `t` undefined in tapEvent -> hitTestSimpleCustom
|
||||||
|
|
||||||
|
if (!/\.js($|\?)/.exec('' + url))
|
||||||
|
return; // chrome debugger
|
||||||
|
|
||||||
var ekey = url + '\n' + lineNo + '\n' + msg;
|
var ekey = url + '\n' + lineNo + '\n' + msg;
|
||||||
if (ignexd[ekey] || crashed)
|
if (ignexd[ekey] || crashed)
|
||||||
return;
|
return;
|
||||||
@@ -249,6 +254,14 @@ if (!Element.prototype.closest)
|
|||||||
} while (el !== null && el.nodeType === 1);
|
} while (el !== null && el.nodeType === 1);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
if (!String.prototype.format)
|
||||||
|
String.prototype.format = function () {
|
||||||
|
var args = arguments;
|
||||||
|
return this.replace(/{(\d+)}/g, function (match, number) {
|
||||||
|
return typeof args[number] != 'undefined' ?
|
||||||
|
args[number] : match;
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
// https://stackoverflow.com/a/950146
|
// https://stackoverflow.com/a/950146
|
||||||
function import_js(url, cb) {
|
function import_js(url, cb) {
|
||||||
@@ -312,7 +325,7 @@ function clmod(el, cls, add) {
|
|||||||
|
|
||||||
var n2 = n1.replace(re, ' ') + (add ? ' ' + cls : '');
|
var n2 = n1.replace(re, ' ') + (add ? ' ' + cls : '');
|
||||||
|
|
||||||
if (!n1 == !n2)
|
if (n1 == n2)
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
el.className = n2;
|
el.className = n2;
|
||||||
@@ -327,7 +340,7 @@ function clgot(el, cls) {
|
|||||||
if (el.classList)
|
if (el.classList)
|
||||||
return el.classList.contains(cls);
|
return el.classList.contains(cls);
|
||||||
|
|
||||||
var lst = (el.getAttribute('class') + '').split(/ /g);
|
var lst = (el.className + '').split(/ /g);
|
||||||
return has(lst, cls);
|
return has(lst, cls);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -458,7 +471,7 @@ function linksplit(rp, id) {
|
|||||||
q = '?' + q[1];
|
q = '?' + q[1];
|
||||||
}
|
}
|
||||||
|
|
||||||
if (rp && rp.charAt(0) == '/')
|
if (rp && rp[0] == '/')
|
||||||
rp = rp.slice(1);
|
rp = rp.slice(1);
|
||||||
|
|
||||||
while (rp) {
|
while (rp) {
|
||||||
@@ -1182,6 +1195,9 @@ var modal = (function () {
|
|||||||
return ok();
|
return ok();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if ((k == 'ArrowLeft' || k == 'ArrowRight') && eng && (ae == eok || ae == eng))
|
||||||
|
return (ae == eok ? eng : eok).focus() || ev(e);
|
||||||
|
|
||||||
if (k == 'Escape')
|
if (k == 'Escape')
|
||||||
return ng();
|
return ng();
|
||||||
}
|
}
|
||||||
@@ -1383,3 +1399,18 @@ var favico = (function () {
|
|||||||
r.to = setTimeout(r.init, 100);
|
r.to = setTimeout(r.init, 100);
|
||||||
return r;
|
return r;
|
||||||
})();
|
})();
|
||||||
|
|
||||||
|
|
||||||
|
function xhrchk(xhr, prefix, e404) {
|
||||||
|
if (xhr.status < 400 && xhr.status >= 200)
|
||||||
|
return true;
|
||||||
|
|
||||||
|
if (xhr.status == 403)
|
||||||
|
return toast.err(0, prefix + (window.L && L.xhr403 || "403: access denied\n\ntry pressing F5, maybe you got logged out"));
|
||||||
|
|
||||||
|
if (xhr.status == 404)
|
||||||
|
return toast.err(0, prefix + e404);
|
||||||
|
|
||||||
|
return toast.err(0, prefix + xhr.status + ": " + (
|
||||||
|
(xhr.response && xhr.response.err) || xhr.responseText));
|
||||||
|
}
|
||||||
|
|||||||
4
docs/notes.bat
Normal file
4
docs/notes.bat
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
rem appending a static ip to a dhcp nic on windows 10-1703 or later
|
||||||
|
netsh interface ipv4 show interface
|
||||||
|
netsh interface ipv4 set interface interface="Ethernet 2" dhcpstaticipcoexistence=enabled
|
||||||
|
netsh interface ipv4 add address "Ethernet 2" 10.1.2.4 255.255.255.0
|
||||||
@@ -2,9 +2,9 @@ FROM alpine:3.15
|
|||||||
WORKDIR /z
|
WORKDIR /z
|
||||||
ENV ver_asmcrypto=5b994303a9d3e27e0915f72a10b6c2c51535a4dc \
|
ENV ver_asmcrypto=5b994303a9d3e27e0915f72a10b6c2c51535a4dc \
|
||||||
ver_hashwasm=4.9.0 \
|
ver_hashwasm=4.9.0 \
|
||||||
ver_marked=4.0.12 \
|
ver_marked=4.0.16 \
|
||||||
ver_mde=2.16.1 \
|
ver_mde=2.16.1 \
|
||||||
ver_codemirror=5.65.2 \
|
ver_codemirror=5.65.4 \
|
||||||
ver_fontawesome=5.13.0 \
|
ver_fontawesome=5.13.0 \
|
||||||
ver_zopfli=1.0.3
|
ver_zopfli=1.0.3
|
||||||
|
|
||||||
@@ -43,8 +43,6 @@ RUN mkdir -p /z/dist/no-pk \
|
|||||||
|
|
||||||
|
|
||||||
# todo
|
# todo
|
||||||
# https://cdn.jsdelivr.net/gh/highlightjs/cdn-release/build/highlight.min.js
|
|
||||||
# https://cdn.jsdelivr.net/gh/highlightjs/cdn-release/build/styles/default.min.css
|
|
||||||
# https://prismjs.com/download.html#themes=prism-funky&languages=markup+css+clike+javascript+autohotkey+bash+basic+batch+c+csharp+cpp+cmake+diff+docker+go+ini+java+json+kotlin+latex+less+lisp+lua+makefile+objectivec+perl+powershell+python+r+jsx+ruby+rust+sass+scss+sql+swift+systemd+toml+typescript+vbnet+verilog+vhdl+yaml&plugins=line-highlight+line-numbers+autolinker
|
# https://prismjs.com/download.html#themes=prism-funky&languages=markup+css+clike+javascript+autohotkey+bash+basic+batch+c+csharp+cpp+cmake+diff+docker+go+ini+java+json+kotlin+latex+less+lisp+lua+makefile+objectivec+perl+powershell+python+r+jsx+ruby+rust+sass+scss+sql+swift+systemd+toml+typescript+vbnet+verilog+vhdl+yaml&plugins=line-highlight+line-numbers+autolinker
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -3,13 +3,13 @@ adds linetracking to marked.js v4.0.6;
|
|||||||
add data-ln="%d" to most tags, %d is the source markdown line
|
add data-ln="%d" to most tags, %d is the source markdown line
|
||||||
--- a/src/Lexer.js
|
--- a/src/Lexer.js
|
||||||
+++ b/src/Lexer.js
|
+++ b/src/Lexer.js
|
||||||
@@ -50,4 +50,5 @@ function mangle(text) {
|
@@ -52,4 +52,5 @@ function mangle(text) {
|
||||||
export class Lexer {
|
export class Lexer {
|
||||||
constructor(options) {
|
constructor(options) {
|
||||||
+ this.ln = 1; // like most editors, start couting from 1
|
+ this.ln = 1; // like most editors, start couting from 1
|
||||||
this.tokens = [];
|
this.tokens = [];
|
||||||
this.tokens.links = Object.create(null);
|
this.tokens.links = Object.create(null);
|
||||||
@@ -127,4 +128,15 @@ export class Lexer {
|
@@ -128,4 +129,15 @@ export class Lexer {
|
||||||
}
|
}
|
||||||
|
|
||||||
+ set_ln(token, ln = this.ln) {
|
+ set_ln(token, ln = this.ln) {
|
||||||
@@ -25,9 +25,9 @@ add data-ln="%d" to most tags, %d is the source markdown line
|
|||||||
+
|
+
|
||||||
/**
|
/**
|
||||||
* Lexing
|
* Lexing
|
||||||
@@ -134,7 +146,11 @@ export class Lexer {
|
@@ -140,7 +152,11 @@ export class Lexer {
|
||||||
src = src.replace(/^ +$/gm, '');
|
|
||||||
}
|
}
|
||||||
|
|
||||||
- let token, lastToken, cutSrc, lastParagraphClipped;
|
- let token, lastToken, cutSrc, lastParagraphClipped;
|
||||||
+ let token, lastToken, cutSrc, lastParagraphClipped, ln;
|
+ let token, lastToken, cutSrc, lastParagraphClipped, ln;
|
||||||
|
|
||||||
@@ -38,111 +38,112 @@ add data-ln="%d" to most tags, %d is the source markdown line
|
|||||||
+
|
+
|
||||||
if (this.options.extensions
|
if (this.options.extensions
|
||||||
&& this.options.extensions.block
|
&& this.options.extensions.block
|
||||||
@@ -142,4 +158,5 @@ export class Lexer {
|
@@ -148,4 +164,5 @@ export class Lexer {
|
||||||
if (token = extTokenizer.call({ lexer: this }, src, tokens)) {
|
if (token = extTokenizer.call({ lexer: this }, src, tokens)) {
|
||||||
src = src.substring(token.raw.length);
|
src = src.substring(token.raw.length);
|
||||||
+ this.set_ln(token, ln);
|
+ this.set_ln(token, ln);
|
||||||
tokens.push(token);
|
tokens.push(token);
|
||||||
return true;
|
return true;
|
||||||
@@ -153,4 +170,5 @@ export class Lexer {
|
@@ -159,4 +176,5 @@ export class Lexer {
|
||||||
if (token = this.tokenizer.space(src)) {
|
if (token = this.tokenizer.space(src)) {
|
||||||
src = src.substring(token.raw.length);
|
src = src.substring(token.raw.length);
|
||||||
+ this.set_ln(token, ln); // is \n if not type
|
+ this.set_ln(token, ln); // is \n if not type
|
||||||
if (token.type) {
|
if (token.raw.length === 1 && tokens.length > 0) {
|
||||||
tokens.push(token);
|
// if there's a single \n as a spacer, it's terminating the last line,
|
||||||
@@ -162,4 +180,5 @@ export class Lexer {
|
@@ -172,4 +190,5 @@ export class Lexer {
|
||||||
if (token = this.tokenizer.code(src)) {
|
if (token = this.tokenizer.code(src)) {
|
||||||
src = src.substring(token.raw.length);
|
src = src.substring(token.raw.length);
|
||||||
+ this.set_ln(token, ln);
|
+ this.set_ln(token, ln);
|
||||||
lastToken = tokens[tokens.length - 1];
|
lastToken = tokens[tokens.length - 1];
|
||||||
// An indented code block cannot interrupt a paragraph.
|
// An indented code block cannot interrupt a paragraph.
|
||||||
@@ -177,4 +196,5 @@ export class Lexer {
|
@@ -187,4 +206,5 @@ export class Lexer {
|
||||||
if (token = this.tokenizer.fences(src)) {
|
if (token = this.tokenizer.fences(src)) {
|
||||||
src = src.substring(token.raw.length);
|
src = src.substring(token.raw.length);
|
||||||
+ this.set_ln(token, ln);
|
+ this.set_ln(token, ln);
|
||||||
tokens.push(token);
|
tokens.push(token);
|
||||||
continue;
|
continue;
|
||||||
@@ -184,4 +204,5 @@ export class Lexer {
|
@@ -194,4 +214,5 @@ export class Lexer {
|
||||||
if (token = this.tokenizer.heading(src)) {
|
if (token = this.tokenizer.heading(src)) {
|
||||||
src = src.substring(token.raw.length);
|
src = src.substring(token.raw.length);
|
||||||
+ this.set_ln(token, ln);
|
+ this.set_ln(token, ln);
|
||||||
tokens.push(token);
|
tokens.push(token);
|
||||||
continue;
|
continue;
|
||||||
@@ -191,4 +212,5 @@ export class Lexer {
|
@@ -201,4 +222,5 @@ export class Lexer {
|
||||||
if (token = this.tokenizer.hr(src)) {
|
if (token = this.tokenizer.hr(src)) {
|
||||||
src = src.substring(token.raw.length);
|
src = src.substring(token.raw.length);
|
||||||
+ this.set_ln(token, ln);
|
+ this.set_ln(token, ln);
|
||||||
tokens.push(token);
|
tokens.push(token);
|
||||||
continue;
|
continue;
|
||||||
@@ -198,4 +220,5 @@ export class Lexer {
|
@@ -208,4 +230,5 @@ export class Lexer {
|
||||||
if (token = this.tokenizer.blockquote(src)) {
|
if (token = this.tokenizer.blockquote(src)) {
|
||||||
src = src.substring(token.raw.length);
|
src = src.substring(token.raw.length);
|
||||||
+ this.set_ln(token, ln);
|
+ this.set_ln(token, ln);
|
||||||
tokens.push(token);
|
tokens.push(token);
|
||||||
continue;
|
continue;
|
||||||
@@ -205,4 +228,5 @@ export class Lexer {
|
@@ -215,4 +238,5 @@ export class Lexer {
|
||||||
if (token = this.tokenizer.list(src)) {
|
if (token = this.tokenizer.list(src)) {
|
||||||
src = src.substring(token.raw.length);
|
src = src.substring(token.raw.length);
|
||||||
+ this.set_ln(token, ln);
|
+ this.set_ln(token, ln);
|
||||||
tokens.push(token);
|
tokens.push(token);
|
||||||
continue;
|
continue;
|
||||||
@@ -212,4 +236,5 @@ export class Lexer {
|
@@ -222,4 +246,5 @@ export class Lexer {
|
||||||
if (token = this.tokenizer.html(src)) {
|
if (token = this.tokenizer.html(src)) {
|
||||||
src = src.substring(token.raw.length);
|
src = src.substring(token.raw.length);
|
||||||
+ this.set_ln(token, ln);
|
+ this.set_ln(token, ln);
|
||||||
tokens.push(token);
|
tokens.push(token);
|
||||||
continue;
|
continue;
|
||||||
@@ -219,4 +244,5 @@ export class Lexer {
|
@@ -229,4 +254,5 @@ export class Lexer {
|
||||||
if (token = this.tokenizer.def(src)) {
|
if (token = this.tokenizer.def(src)) {
|
||||||
src = src.substring(token.raw.length);
|
src = src.substring(token.raw.length);
|
||||||
+ this.set_ln(token, ln);
|
+ this.set_ln(token, ln);
|
||||||
lastToken = tokens[tokens.length - 1];
|
lastToken = tokens[tokens.length - 1];
|
||||||
if (lastToken && (lastToken.type === 'paragraph' || lastToken.type === 'text')) {
|
if (lastToken && (lastToken.type === 'paragraph' || lastToken.type === 'text')) {
|
||||||
@@ -236,4 +262,5 @@ export class Lexer {
|
@@ -246,4 +272,5 @@ export class Lexer {
|
||||||
if (token = this.tokenizer.table(src)) {
|
if (token = this.tokenizer.table(src)) {
|
||||||
src = src.substring(token.raw.length);
|
src = src.substring(token.raw.length);
|
||||||
+ this.set_ln(token, ln);
|
+ this.set_ln(token, ln);
|
||||||
tokens.push(token);
|
tokens.push(token);
|
||||||
continue;
|
continue;
|
||||||
@@ -243,4 +270,5 @@ export class Lexer {
|
@@ -253,4 +280,5 @@ export class Lexer {
|
||||||
if (token = this.tokenizer.lheading(src)) {
|
if (token = this.tokenizer.lheading(src)) {
|
||||||
src = src.substring(token.raw.length);
|
src = src.substring(token.raw.length);
|
||||||
+ this.set_ln(token, ln);
|
+ this.set_ln(token, ln);
|
||||||
tokens.push(token);
|
tokens.push(token);
|
||||||
continue;
|
continue;
|
||||||
@@ -263,4 +291,5 @@ export class Lexer {
|
@@ -273,4 +301,5 @@ export class Lexer {
|
||||||
}
|
}
|
||||||
if (this.state.top && (token = this.tokenizer.paragraph(cutSrc))) {
|
if (this.state.top && (token = this.tokenizer.paragraph(cutSrc))) {
|
||||||
+ this.set_ln(token, ln);
|
+ this.set_ln(token, ln);
|
||||||
lastToken = tokens[tokens.length - 1];
|
lastToken = tokens[tokens.length - 1];
|
||||||
if (lastParagraphClipped && lastToken.type === 'paragraph') {
|
if (lastParagraphClipped && lastToken.type === 'paragraph') {
|
||||||
@@ -280,4 +309,6 @@ export class Lexer {
|
@@ -290,4 +319,6 @@ export class Lexer {
|
||||||
if (token = this.tokenizer.text(src)) {
|
if (token = this.tokenizer.text(src)) {
|
||||||
src = src.substring(token.raw.length);
|
src = src.substring(token.raw.length);
|
||||||
+ this.set_ln(token, ln);
|
+ this.set_ln(token, ln);
|
||||||
+ this.ln++;
|
+ this.ln++;
|
||||||
lastToken = tokens[tokens.length - 1];
|
lastToken = tokens[tokens.length - 1];
|
||||||
if (lastToken && lastToken.type === 'text') {
|
if (lastToken && lastToken.type === 'text') {
|
||||||
@@ -355,4 +386,5 @@ export class Lexer {
|
@@ -365,4 +396,5 @@ export class Lexer {
|
||||||
if (token = extTokenizer.call({ lexer: this }, src, tokens)) {
|
if (token = extTokenizer.call({ lexer: this }, src, tokens)) {
|
||||||
src = src.substring(token.raw.length);
|
src = src.substring(token.raw.length);
|
||||||
+ this.ln = token.ln || this.ln;
|
+ this.ln = token.ln || this.ln;
|
||||||
tokens.push(token);
|
tokens.push(token);
|
||||||
return true;
|
return true;
|
||||||
@@ -420,4 +452,6 @@ export class Lexer {
|
@@ -430,4 +462,6 @@ export class Lexer {
|
||||||
if (token = this.tokenizer.br(src)) {
|
if (token = this.tokenizer.br(src)) {
|
||||||
src = src.substring(token.raw.length);
|
src = src.substring(token.raw.length);
|
||||||
+ // no need to reset (no more blockTokens anyways)
|
+ // no need to reset (no more blockTokens anyways)
|
||||||
+ token.ln = this.ln++;
|
+ token.ln = this.ln++;
|
||||||
tokens.push(token);
|
tokens.push(token);
|
||||||
continue;
|
continue;
|
||||||
@@ -462,4 +496,5 @@ export class Lexer {
|
@@ -472,4 +506,5 @@ export class Lexer {
|
||||||
if (token = this.tokenizer.inlineText(cutSrc, smartypants)) {
|
if (token = this.tokenizer.inlineText(cutSrc, smartypants)) {
|
||||||
src = src.substring(token.raw.length);
|
src = src.substring(token.raw.length);
|
||||||
+ this.ln = token.ln || this.ln;
|
+ this.ln = token.ln || this.ln;
|
||||||
if (token.raw.slice(-1) !== '_') { // Track prevChar before string of ____ started
|
if (token.raw.slice(-1) !== '_') { // Track prevChar before string of ____ started
|
||||||
prevChar = token.raw.slice(-1);
|
prevChar = token.raw.slice(-1);
|
||||||
diff --git a/src/Parser.js b/src/Parser.js
|
diff --git a/src/Parser.js b/src/Parser.js
|
||||||
|
index a22a2bc..884ad66 100644
|
||||||
--- a/src/Parser.js
|
--- a/src/Parser.js
|
||||||
+++ b/src/Parser.js
|
+++ b/src/Parser.js
|
||||||
@@ -18,4 +18,5 @@ export class Parser {
|
@@ -18,4 +18,5 @@ export class Parser {
|
||||||
@@ -205,6 +206,7 @@ diff --git a/src/Parser.js b/src/Parser.js
|
|||||||
// Run any renderer extensions
|
// Run any renderer extensions
|
||||||
if (this.options.extensions && this.options.extensions.renderers && this.options.extensions.renderers[token.type]) {
|
if (this.options.extensions && this.options.extensions.renderers && this.options.extensions.renderers[token.type]) {
|
||||||
diff --git a/src/Renderer.js b/src/Renderer.js
|
diff --git a/src/Renderer.js b/src/Renderer.js
|
||||||
|
index 7c36a75..aa1a53a 100644
|
||||||
--- a/src/Renderer.js
|
--- a/src/Renderer.js
|
||||||
+++ b/src/Renderer.js
|
+++ b/src/Renderer.js
|
||||||
@@ -11,6 +11,12 @@ export class Renderer {
|
@@ -11,6 +11,12 @@ export class Renderer {
|
||||||
@@ -214,7 +216,7 @@ diff --git a/src/Renderer.js b/src/Renderer.js
|
|||||||
}
|
}
|
||||||
|
|
||||||
+ tag_ln(n) {
|
+ tag_ln(n) {
|
||||||
+ this.ln = ' data-ln="' + n + '"';
|
+ this.ln = ` data-ln="${n}"`;
|
||||||
+ return this;
|
+ return this;
|
||||||
+ };
|
+ };
|
||||||
+
|
+
|
||||||
@@ -233,65 +235,65 @@ diff --git a/src/Renderer.js b/src/Renderer.js
|
|||||||
+ return '<pre' + this.ln + '><code class="'
|
+ return '<pre' + this.ln + '><code class="'
|
||||||
+ this.options.langPrefix
|
+ this.options.langPrefix
|
||||||
+ escape(lang, true)
|
+ escape(lang, true)
|
||||||
@@ -40,5 +46,5 @@ export class Renderer {
|
@@ -43,5 +49,5 @@ export class Renderer {
|
||||||
|
*/
|
||||||
blockquote(quote) {
|
blockquote(quote) {
|
||||||
- return '<blockquote>\n' + quote + '</blockquote>\n';
|
- return `<blockquote>\n${quote}</blockquote>\n`;
|
||||||
+ return '<blockquote' + this.ln + '>\n' + quote + '</blockquote>\n';
|
+ return `<blockquote${this.ln}>\n${quote}</blockquote>\n`;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -51,4 +57,5 @@ export class Renderer {
|
@@ -59,9 +65,9 @@ export class Renderer {
|
||||||
return '<h'
|
if (this.options.headerIds) {
|
||||||
+ level
|
const id = this.options.headerPrefix + slugger.slug(raw);
|
||||||
+ + this.ln
|
- return `<h${level} id="${id}">${text}</h${level}>\n`;
|
||||||
+ ' id="'
|
+ return `<h${level}${this.ln} id="${id}">${text}</h${level}>\n`;
|
||||||
+ this.options.headerPrefix
|
|
||||||
@@ -61,5 +68,5 @@ export class Renderer {
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// ignore IDs
|
// ignore IDs
|
||||||
- return '<h' + level + '>' + text + '</h' + level + '>\n';
|
- return `<h${level}>${text}</h${level}>\n`;
|
||||||
+ return '<h' + level + this.ln + '>' + text + '</h' + level + '>\n';
|
+ return `<h${level}${this.ln}>${text}</h${level}>\n`;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -75,5 +82,5 @@ export class Renderer {
|
@@ -80,5 +86,5 @@ export class Renderer {
|
||||||
|
*/
|
||||||
listitem(text) {
|
listitem(text) {
|
||||||
- return '<li>' + text + '</li>\n';
|
- return `<li>${text}</li>\n`;
|
||||||
+ return '<li' + this.ln + '>' + text + '</li>\n';
|
+ return `<li${this.ln}>${text}</li>\n`;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -87,5 +94,5 @@ export class Renderer {
|
@@ -95,5 +101,5 @@ export class Renderer {
|
||||||
|
*/
|
||||||
paragraph(text) {
|
paragraph(text) {
|
||||||
- return '<p>' + text + '</p>\n';
|
- return `<p>${text}</p>\n`;
|
||||||
+ return '<p' + this.ln + '>' + text + '</p>\n';
|
+ return `<p${this.ln}>${text}</p>\n`;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -102,5 +109,5 @@ export class Renderer {
|
@@ -117,5 +123,5 @@ export class Renderer {
|
||||||
|
*/
|
||||||
tablerow(content) {
|
tablerow(content) {
|
||||||
- return '<tr>\n' + content + '</tr>\n';
|
- return `<tr>\n${content}</tr>\n`;
|
||||||
+ return '<tr' + this.ln + '>\n' + content + '</tr>\n';
|
+ return `<tr${this.ln}>\n${content}</tr>\n`;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -127,5 +134,5 @@ export class Renderer {
|
@@ -151,5 +157,5 @@ export class Renderer {
|
||||||
|
|
||||||
br() {
|
br() {
|
||||||
- return this.options.xhtml ? '<br/>' : '<br>';
|
- return this.options.xhtml ? '<br/>' : '<br>';
|
||||||
+ return this.options.xhtml ? '<br' + this.ln + '/>' : '<br' + this.ln + '>';
|
+ return this.options.xhtml ? `<br${this.ln}/>` : `<br${this.ln}>`;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -153,5 +160,5 @@ export class Renderer {
|
@@ -190,5 +196,5 @@ export class Renderer {
|
||||||
}
|
}
|
||||||
|
|
||||||
- let out = '<img src="' + href + '" alt="' + text + '"';
|
- let out = `<img src="${href}" alt="${text}"`;
|
||||||
+ let out = '<img' + this.ln + ' src="' + href + '" alt="' + text + '"';
|
+ let out = `<img${this.ln} src="${href}" alt="${text}"`;
|
||||||
if (title) {
|
if (title) {
|
||||||
out += ' title="' + title + '"';
|
out += ` title="${title}"`;
|
||||||
diff --git a/src/Tokenizer.js b/src/Tokenizer.js
|
diff --git a/src/Tokenizer.js b/src/Tokenizer.js
|
||||||
|
index e8a69b6..2cc772b 100644
|
||||||
--- a/src/Tokenizer.js
|
--- a/src/Tokenizer.js
|
||||||
+++ b/src/Tokenizer.js
|
+++ b/src/Tokenizer.js
|
||||||
@@ -297,4 +297,7 @@ export class Tokenizer {
|
@@ -302,4 +302,7 @@ export class Tokenizer {
|
||||||
const l = list.items.length;
|
const l = list.items.length;
|
||||||
|
|
||||||
+ // each nested list gets +1 ahead; this hack makes every listgroup -1 but atleast it doesn't get infinitely bad
|
+ // each nested list gets +1 ahead; this hack makes every listgroup -1 but atleast it doesn't get infinitely bad
|
||||||
|
|||||||
@@ -14,6 +14,9 @@ help() { exec cat <<'EOF'
|
|||||||
#
|
#
|
||||||
# `gz` creates a gzip-compressed python sfx instead of bzip2
|
# `gz` creates a gzip-compressed python sfx instead of bzip2
|
||||||
#
|
#
|
||||||
|
# `lang` limits which languages/translations to include,
|
||||||
|
# for example `lang eng` or `lang eng|nor`
|
||||||
|
#
|
||||||
# `no-cm` saves ~82k by removing easymde/codemirror
|
# `no-cm` saves ~82k by removing easymde/codemirror
|
||||||
# (the fancy markdown editor)
|
# (the fancy markdown editor)
|
||||||
#
|
#
|
||||||
@@ -61,6 +64,7 @@ pybin=$(command -v python3 || command -v python) || {
|
|||||||
exit 1
|
exit 1
|
||||||
}
|
}
|
||||||
|
|
||||||
|
langs=
|
||||||
use_gz=
|
use_gz=
|
||||||
zopf=2560
|
zopf=2560
|
||||||
while [ ! -z "$1" ]; do
|
while [ ! -z "$1" ]; do
|
||||||
@@ -73,6 +77,7 @@ while [ ! -z "$1" ]; do
|
|||||||
no-dd) no_dd=1 ; ;;
|
no-dd) no_dd=1 ; ;;
|
||||||
no-cm) no_cm=1 ; ;;
|
no-cm) no_cm=1 ; ;;
|
||||||
fast) zopf=100 ; ;;
|
fast) zopf=100 ; ;;
|
||||||
|
lang) shift;langs="$1"; ;;
|
||||||
*) help ; ;;
|
*) help ; ;;
|
||||||
esac
|
esac
|
||||||
shift
|
shift
|
||||||
@@ -262,6 +267,13 @@ rm have
|
|||||||
tmv "$f"
|
tmv "$f"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[ $langs ] &&
|
||||||
|
for f in copyparty/web/{browser.js,splash.js}; do
|
||||||
|
gzip -d "$f.gz" || true
|
||||||
|
awk '/^\}/{l=0} !l; /^var Ls =/{l=1;next} o; /^\t["}]/{o=0} /^\t"'"$langs"'"/{o=1;print}' <$f >t
|
||||||
|
tmv "$f"
|
||||||
|
done
|
||||||
|
|
||||||
[ $repack ] ||
|
[ $repack ] ||
|
||||||
find | grep -E '\.py$' |
|
find | grep -E '\.py$' |
|
||||||
grep -vE '__version__' |
|
grep -vE '__version__' |
|
||||||
|
|||||||
@@ -71,6 +71,7 @@ copyparty/web/msg.css,
|
|||||||
copyparty/web/msg.html,
|
copyparty/web/msg.html,
|
||||||
copyparty/web/splash.css,
|
copyparty/web/splash.css,
|
||||||
copyparty/web/splash.html,
|
copyparty/web/splash.html,
|
||||||
|
copyparty/web/splash.js,
|
||||||
copyparty/web/ui.css,
|
copyparty/web/ui.css,
|
||||||
copyparty/web/up2k.js,
|
copyparty/web/up2k.js,
|
||||||
copyparty/web/util.js,
|
copyparty/web/util.js,
|
||||||
|
|||||||
@@ -38,6 +38,9 @@ class Cfg(Namespace):
|
|||||||
no_mv=False,
|
no_mv=False,
|
||||||
no_del=False,
|
no_del=False,
|
||||||
no_zip=False,
|
no_zip=False,
|
||||||
|
no_thumb=False,
|
||||||
|
no_athumb=False,
|
||||||
|
no_vthumb=False,
|
||||||
no_voldump=True,
|
no_voldump=True,
|
||||||
no_scandir=False,
|
no_scandir=False,
|
||||||
no_sendfile=True,
|
no_sendfile=True,
|
||||||
@@ -53,6 +56,10 @@ class Cfg(Namespace):
|
|||||||
textfiles="",
|
textfiles="",
|
||||||
doctitle="",
|
doctitle="",
|
||||||
html_head="",
|
html_head="",
|
||||||
|
theme=0,
|
||||||
|
themes=0,
|
||||||
|
turbo=0,
|
||||||
|
logout=573,
|
||||||
hist=None,
|
hist=None,
|
||||||
no_idx=None,
|
no_idx=None,
|
||||||
no_hash=None,
|
no_hash=None,
|
||||||
|
|||||||
@@ -17,7 +17,7 @@ from copyparty import util
|
|||||||
|
|
||||||
class Cfg(Namespace):
|
class Cfg(Namespace):
|
||||||
def __init__(self, a=None, v=None, c=None):
|
def __init__(self, a=None, v=None, c=None):
|
||||||
ex = "nw e2d e2ds e2dsa e2t e2ts e2tsr no_logues no_readme no_acode force_js no_robots"
|
ex = "nw e2d e2ds e2dsa e2t e2ts e2tsr no_logues no_readme no_acode force_js no_robots no_thumb no_athumb no_vthumb"
|
||||||
ex = {k: False for k in ex.split()}
|
ex = {k: False for k in ex.split()}
|
||||||
ex2 = {
|
ex2 = {
|
||||||
"mtp": [],
|
"mtp": [],
|
||||||
@@ -36,6 +36,10 @@ class Cfg(Namespace):
|
|||||||
"rsp_slp": 0,
|
"rsp_slp": 0,
|
||||||
"s_wr_slp": 0,
|
"s_wr_slp": 0,
|
||||||
"s_wr_sz": 512 * 1024,
|
"s_wr_sz": 512 * 1024,
|
||||||
|
"theme": 0,
|
||||||
|
"themes": 0,
|
||||||
|
"turbo": 0,
|
||||||
|
"logout": 573,
|
||||||
}
|
}
|
||||||
ex.update(ex2)
|
ex.update(ex2)
|
||||||
super(Cfg, self).__init__(a=a or [], v=v or [], c=c, **ex)
|
super(Cfg, self).__init__(a=a or [], v=v or [], c=c, **ex)
|
||||||
|
|||||||
Reference in New Issue
Block a user