Compare commits
463 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
b90e1200d7 | ||
|
|
4493a0a804 | ||
|
|
58835b2b42 | ||
|
|
427597b603 | ||
|
|
7d64879ba8 | ||
|
|
bb715704b7 | ||
|
|
d67e9cc507 | ||
|
|
2927bbb2d6 | ||
|
|
0527b59180 | ||
|
|
a5ce1032d3 | ||
|
|
1c2acdc985 | ||
|
|
4e75534ef8 | ||
|
|
7a573cafd1 | ||
|
|
844194ee29 | ||
|
|
609c5921d4 | ||
|
|
c79eaa089a | ||
|
|
e9d962f273 | ||
|
|
b5405174ec | ||
|
|
6eee601521 | ||
|
|
2fac2bee7c | ||
|
|
c140eeee6b | ||
|
|
c5988a04f9 | ||
|
|
a2e0f98693 | ||
|
|
1111153f06 | ||
|
|
e5a836cb7d | ||
|
|
b0de84cbc5 | ||
|
|
cbb718e10d | ||
|
|
b5ad9369fe | ||
|
|
4401de0413 | ||
|
|
6e671c5245 | ||
|
|
08848be784 | ||
|
|
b599fbae97 | ||
|
|
a8dabc99f6 | ||
|
|
f1130db131 | ||
|
|
735ec35546 | ||
|
|
5a009a2a64 | ||
|
|
d9e9526247 | ||
|
|
5a8c3b8be0 | ||
|
|
1c9c17fb9b | ||
|
|
7f82449179 | ||
|
|
e455ec994e | ||
|
|
c111027420 | ||
|
|
abcdf479e6 | ||
|
|
ad2371f810 | ||
|
|
c4e2b0f95f | ||
|
|
3da62ec234 | ||
|
|
01233991f3 | ||
|
|
ee35974273 | ||
|
|
7037e7365e | ||
|
|
03b13e8a1c | ||
|
|
cdd2da0208 | ||
|
|
cec0e0cf02 | ||
|
|
8122ddedfe | ||
|
|
55a77c5e89 | ||
|
|
461f31582d | ||
|
|
f356faa278 | ||
|
|
9f034d9c4c | ||
|
|
ba52590ae4 | ||
|
|
92edea1de5 | ||
|
|
7ff46966da | ||
|
|
fca70b3508 | ||
|
|
70009cd984 | ||
|
|
8d8b88c4fd | ||
|
|
c4b0cccefd | ||
|
|
7c2beba555 | ||
|
|
7d8d94388b | ||
|
|
0b46b1a614 | ||
|
|
5153db6bff | ||
|
|
b0af4b3712 | ||
|
|
c8f4aeaefa | ||
|
|
00da74400c | ||
|
|
83fb569d61 | ||
|
|
5a62cb4869 | ||
|
|
687df2fabd | ||
|
|
cdd0794d6e | ||
|
|
dcc988135e | ||
|
|
3db117d85f | ||
|
|
ee9aad82dd | ||
|
|
2d6eb63fce | ||
|
|
ca001c8504 | ||
|
|
4e581c59da | ||
|
|
dbd42bc6bf | ||
|
|
c862ec1b64 | ||
|
|
f709140571 | ||
|
|
ef1c4b7a20 | ||
|
|
6c94a63f1c | ||
|
|
20669c73d3 | ||
|
|
0da719f4c2 | ||
|
|
373194c38a | ||
|
|
3d245431fc | ||
|
|
250c8c56f0 | ||
|
|
e136231c8e | ||
|
|
98ffaadf52 | ||
|
|
ebb1981803 | ||
|
|
72361c99e1 | ||
|
|
d5c9c8ebbd | ||
|
|
746229846d | ||
|
|
ffd7cd3ca8 | ||
|
|
b3cecabca3 | ||
|
|
662541c64c | ||
|
|
225bd80ea8 | ||
|
|
85e54980cc | ||
|
|
a19a0fa9f3 | ||
|
|
9bb6e0dc62 | ||
|
|
15ddcf53e7 | ||
|
|
6b54972ec0 | ||
|
|
0219eada23 | ||
|
|
8916bce306 | ||
|
|
99edba4fd9 | ||
|
|
64de3e01e8 | ||
|
|
8222ccc40b | ||
|
|
dc449bf8b0 | ||
|
|
ef0ecf878b | ||
|
|
53f1e3c91d | ||
|
|
eeef80919f | ||
|
|
987bce2182 | ||
|
|
b511d686f0 | ||
|
|
132a83501e | ||
|
|
e565ad5f55 | ||
|
|
f955d2bd58 | ||
|
|
5953399090 | ||
|
|
d26a944d95 | ||
|
|
50dac15568 | ||
|
|
ac1e11e4ce | ||
|
|
d749683d48 | ||
|
|
84e8e1ddfb | ||
|
|
6e58514b84 | ||
|
|
803e156509 | ||
|
|
c06aa683eb | ||
|
|
6644ceef49 | ||
|
|
bd3b3863ae | ||
|
|
ffd4f9c8b9 | ||
|
|
760ff2db72 | ||
|
|
f37187a041 | ||
|
|
1cdb170290 | ||
|
|
d5de3f2fe0 | ||
|
|
d76673e62d | ||
|
|
c549f367c1 | ||
|
|
927c3bce96 | ||
|
|
d75a2c77da | ||
|
|
e6c55d7ff9 | ||
|
|
4c2cb26991 | ||
|
|
dfe7f1d9af | ||
|
|
666297f6fb | ||
|
|
55a011b9c1 | ||
|
|
27aff12a1e | ||
|
|
9a87ee2fe4 | ||
|
|
0a9f4c6074 | ||
|
|
7219331057 | ||
|
|
2fd12a839c | ||
|
|
8c73e0cbc2 | ||
|
|
52e06226a2 | ||
|
|
452592519d | ||
|
|
c9281f8912 | ||
|
|
36d6d29a0c | ||
|
|
db6059e100 | ||
|
|
aab57cb24b | ||
|
|
f00b939402 | ||
|
|
bef9617638 | ||
|
|
692175f5b0 | ||
|
|
5ad65450c4 | ||
|
|
60c96f990a | ||
|
|
07b2bf1104 | ||
|
|
ac1bc232a9 | ||
|
|
5919607ad0 | ||
|
|
07ea629ca5 | ||
|
|
b629d18df6 | ||
|
|
566cbb6507 | ||
|
|
400d700845 | ||
|
|
82ce6862ee | ||
|
|
38e4fdfe03 | ||
|
|
c04662798d | ||
|
|
19d156ff4e | ||
|
|
87c60a1ec9 | ||
|
|
2c92dab165 | ||
|
|
5c1e23907d | ||
|
|
925c7f0a57 | ||
|
|
feed08deb2 | ||
|
|
560d7b6672 | ||
|
|
565daee98b | ||
|
|
e396c5c2b5 | ||
|
|
1ee2cdd089 | ||
|
|
beacedab50 | ||
|
|
25139a4358 | ||
|
|
f8491970fd | ||
|
|
da091aec85 | ||
|
|
e9eb5affcd | ||
|
|
c1918bc36c | ||
|
|
fdda567f50 | ||
|
|
603d0ed72b | ||
|
|
b15a4ef79f | ||
|
|
48a6789d36 | ||
|
|
36f2c446af | ||
|
|
69517e4624 | ||
|
|
ea270ab9f2 | ||
|
|
b6cf2d3089 | ||
|
|
e8db3dd37f | ||
|
|
27485a4cb1 | ||
|
|
253a414443 | ||
|
|
f6e693f0f5 | ||
|
|
c5f7cfc355 | ||
|
|
bc2c1e427a | ||
|
|
95d9e693c6 | ||
|
|
70a3cf36d1 | ||
|
|
aa45fccf11 | ||
|
|
42d00050c1 | ||
|
|
4bb0e6e75a | ||
|
|
2f7f9de3f5 | ||
|
|
f31ac90932 | ||
|
|
439cb7f85b | ||
|
|
af193ee834 | ||
|
|
c06126cc9d | ||
|
|
897ffbbbd0 | ||
|
|
8244d3b4fc | ||
|
|
74266af6d1 | ||
|
|
8c552f1ad1 | ||
|
|
bf5850785f | ||
|
|
feecb3e0b8 | ||
|
|
08d8c82167 | ||
|
|
5239e7ac0c | ||
|
|
9937c2e755 | ||
|
|
f1e947f37d | ||
|
|
a70a49b9c9 | ||
|
|
fe700dcf1a | ||
|
|
c8e3ed3aae | ||
|
|
b8733653a3 | ||
|
|
b772a4f8bb | ||
|
|
9e5253ef87 | ||
|
|
7b94e4edf3 | ||
|
|
da26ec36ca | ||
|
|
443acf2f8b | ||
|
|
6c90e3893d | ||
|
|
ea002ee71d | ||
|
|
ab18893cd2 | ||
|
|
844d16b9e5 | ||
|
|
989cc613ef | ||
|
|
4f0cad5468 | ||
|
|
f89de6b35d | ||
|
|
e0bcb88ee7 | ||
|
|
a0022805d1 | ||
|
|
853adb5d04 | ||
|
|
7744226b5c | ||
|
|
d94b5b3fc9 | ||
|
|
e6ba065bc2 | ||
|
|
59a53ba9ac | ||
|
|
b88cc7b5ce | ||
|
|
5ab54763c6 | ||
|
|
59f815ff8c | ||
|
|
9c42cbec6f | ||
|
|
f471b05aa4 | ||
|
|
34c32e3e89 | ||
|
|
a080759a03 | ||
|
|
0ae12868e5 | ||
|
|
ef52e2c06c | ||
|
|
32c912bb16 | ||
|
|
20870fda79 | ||
|
|
bdfe2c1a5f | ||
|
|
cb99fbf442 | ||
|
|
bccc44dc21 | ||
|
|
2f20d29edd | ||
|
|
c6acd3a904 | ||
|
|
2b24c50eb7 | ||
|
|
d30ae8453d | ||
|
|
8e5c436bef | ||
|
|
f500e55e68 | ||
|
|
9700a12366 | ||
|
|
2b6a34dc5c | ||
|
|
ee80cdb9cf | ||
|
|
2def4cd248 | ||
|
|
0287c7baa5 | ||
|
|
51d31588e6 | ||
|
|
32553e4520 | ||
|
|
211a30da38 | ||
|
|
bdbcbbb002 | ||
|
|
e78af02241 | ||
|
|
115020ba60 | ||
|
|
66abf17bae | ||
|
|
b377791be7 | ||
|
|
78919e65d6 | ||
|
|
84b52ea8c5 | ||
|
|
fd89f7ecb9 | ||
|
|
2ebfdc2562 | ||
|
|
dbf1cbc8af | ||
|
|
a259704596 | ||
|
|
04b55f1a1d | ||
|
|
206af8f151 | ||
|
|
645bb5c990 | ||
|
|
f8966222e4 | ||
|
|
d71f844b43 | ||
|
|
e8b7f65f82 | ||
|
|
f193f398c1 | ||
|
|
b6554a7f8c | ||
|
|
3f05b6655c | ||
|
|
51a83b04a0 | ||
|
|
0c03921965 | ||
|
|
2527e90325 | ||
|
|
7f08f10c37 | ||
|
|
1c011ff0bb | ||
|
|
a1ad608267 | ||
|
|
547a486387 | ||
|
|
7741870dc7 | ||
|
|
8785d2f9fe | ||
|
|
d744f3ff8f | ||
|
|
8ca996e2f7 | ||
|
|
096de50889 | ||
|
|
bec3fee9ee | ||
|
|
8413ed6d1f | ||
|
|
055302b5be | ||
|
|
8016e6711b | ||
|
|
c8ea4066b1 | ||
|
|
6cc7101d31 | ||
|
|
263adec70a | ||
|
|
ac96fd9c96 | ||
|
|
e5582605cd | ||
|
|
1b52ef1f8a | ||
|
|
503face974 | ||
|
|
13e77777d7 | ||
|
|
89c6c2e0d9 | ||
|
|
14af136fcd | ||
|
|
d39a99c929 | ||
|
|
43ee6b9f5b | ||
|
|
8a38101e48 | ||
|
|
5026b21226 | ||
|
|
d07859e8e6 | ||
|
|
df7219d3b6 | ||
|
|
ad9be54f55 | ||
|
|
eeecc50757 | ||
|
|
8ff7094e4d | ||
|
|
58ae38c613 | ||
|
|
7f1c992601 | ||
|
|
fbfdd8338b | ||
|
|
bbc379906a | ||
|
|
33f41f3e61 | ||
|
|
655f6d00f8 | ||
|
|
fd552842d4 | ||
|
|
6bd087ddc5 | ||
|
|
0504b010a1 | ||
|
|
39cc92d4bc | ||
|
|
a0da0122b9 | ||
|
|
879e83e24f | ||
|
|
64ad585318 | ||
|
|
f262aee800 | ||
|
|
d4da386172 | ||
|
|
5d92f4df49 | ||
|
|
6f8a588c4d | ||
|
|
7c8e368721 | ||
|
|
f7a43a8e46 | ||
|
|
02879713a2 | ||
|
|
acbb8267e1 | ||
|
|
8796c09f56 | ||
|
|
d636316a19 | ||
|
|
a96d9ac6cb | ||
|
|
643e222986 | ||
|
|
ed524d84bb | ||
|
|
f0cdd9f25d | ||
|
|
4e797a7156 | ||
|
|
136c0fdc2b | ||
|
|
35165f8472 | ||
|
|
cab999978e | ||
|
|
fabeebd96b | ||
|
|
b1cf588452 | ||
|
|
c354a38b4c | ||
|
|
a17c267d87 | ||
|
|
c1180d6f9c | ||
|
|
d3db6d296f | ||
|
|
caf7e93f5e | ||
|
|
eefa0518db | ||
|
|
945170e271 | ||
|
|
6c2c6090dc | ||
|
|
b2e233403d | ||
|
|
e397ec2e48 | ||
|
|
fade751a3e | ||
|
|
0f386c4b08 | ||
|
|
14bccbe45f | ||
|
|
55eb692134 | ||
|
|
b32d65207b | ||
|
|
64cac003d8 | ||
|
|
6dbfcddcda | ||
|
|
b4e0a34193 | ||
|
|
01c82b54a7 | ||
|
|
4ef3106009 | ||
|
|
aa3a971961 | ||
|
|
b9d0c8536b | ||
|
|
3313503ea5 | ||
|
|
d999d3a921 | ||
|
|
e7d00bae39 | ||
|
|
650e41c717 | ||
|
|
140f6e0389 | ||
|
|
5e111ba5ee | ||
|
|
95a599961e | ||
|
|
a55e0d6eb8 | ||
|
|
2fd2c6b948 | ||
|
|
7a936ea01e | ||
|
|
226c7c3045 | ||
|
|
a4239a466b | ||
|
|
d0eb014c38 | ||
|
|
e01ba8552a | ||
|
|
024303592a | ||
|
|
86419b8f47 | ||
|
|
f1358dbaba | ||
|
|
e8a653ca0c | ||
|
|
9bc09ce949 | ||
|
|
dc8e621d7c | ||
|
|
dee0950f74 | ||
|
|
143f72fe36 | ||
|
|
a7889fb6a2 | ||
|
|
987caec15d | ||
|
|
ab40ff5051 | ||
|
|
bed133d3dd | ||
|
|
829c8fca96 | ||
|
|
5b26ab0096 | ||
|
|
39554b4bc3 | ||
|
|
97d9c149f1 | ||
|
|
59688bc8d7 | ||
|
|
a18f63895f | ||
|
|
27433d6214 | ||
|
|
374c535cfa | ||
|
|
ac7815a0ae | ||
|
|
10bc2d9205 | ||
|
|
0c50ea1757 | ||
|
|
c057c5e8e8 | ||
|
|
46d667716e | ||
|
|
cba2e10d29 | ||
|
|
b1693f95cb | ||
|
|
3f00073256 | ||
|
|
d15000062d | ||
|
|
6cb3b35a54 | ||
|
|
b4031e8d43 | ||
|
|
a3ca0638cb | ||
|
|
a360ac29da | ||
|
|
9672b8c9b3 | ||
|
|
e70ecd98ef | ||
|
|
5f7ce78d7f | ||
|
|
2077dca66f | ||
|
|
91f010290c | ||
|
|
395e3386b7 | ||
|
|
a1dce0f24e | ||
|
|
c7770904e6 | ||
|
|
1690889ed8 | ||
|
|
842817d9e3 | ||
|
|
5fc04152bd | ||
|
|
1be85bdb26 | ||
|
|
2eafaa88a2 | ||
|
|
900cc463c3 | ||
|
|
97b999c463 | ||
|
|
a7cef91b8b | ||
|
|
a4a112c0ee | ||
|
|
e6bcee28d6 | ||
|
|
626b5770a5 | ||
|
|
c2f92cacc1 | ||
|
|
4f8a1f5f6a | ||
|
|
4a98b73915 | ||
|
|
00812cb1da | ||
|
|
16766e702e | ||
|
|
5e932a9504 | ||
|
|
ccab44daf2 | ||
|
|
8c52b88767 | ||
|
|
c9fd26255b | ||
|
|
0b9b8dbe72 | ||
|
|
b7723ac245 | ||
|
|
35b75c3db1 | ||
|
|
f902779050 | ||
|
|
fdddd36a5d |
1
.gitignore
vendored
1
.gitignore
vendored
@@ -12,6 +12,7 @@ copyparty.egg-info/
|
|||||||
/dist/
|
/dist/
|
||||||
/py2/
|
/py2/
|
||||||
/sfx*
|
/sfx*
|
||||||
|
/pyz/
|
||||||
/unt/
|
/unt/
|
||||||
/log/
|
/log/
|
||||||
|
|
||||||
|
|||||||
3
.vscode/launch.json
vendored
3
.vscode/launch.json
vendored
@@ -19,8 +19,7 @@
|
|||||||
"-emp",
|
"-emp",
|
||||||
"-e2dsa",
|
"-e2dsa",
|
||||||
"-e2ts",
|
"-e2ts",
|
||||||
"-mtp",
|
"-mtp=.bpm=f,bin/mtag/audio-bpm.py",
|
||||||
".bpm=f,bin/mtag/audio-bpm.py",
|
|
||||||
"-aed:wark",
|
"-aed:wark",
|
||||||
"-vsrv::r:rw,ed:c,dupe",
|
"-vsrv::r:rw,ed:c,dupe",
|
||||||
"-vdist:dist:r"
|
"-vdist:dist:r"
|
||||||
|
|||||||
24
.vscode/settings.json
vendored
24
.vscode/settings.json
vendored
@@ -22,6 +22,9 @@
|
|||||||
"terminal.ansiBrightCyan": "#9cf0ed",
|
"terminal.ansiBrightCyan": "#9cf0ed",
|
||||||
"terminal.ansiBrightWhite": "#ffffff",
|
"terminal.ansiBrightWhite": "#ffffff",
|
||||||
},
|
},
|
||||||
|
"python.terminal.activateEnvironment": false,
|
||||||
|
"python.analysis.enablePytestSupport": false,
|
||||||
|
"python.analysis.typeCheckingMode": "standard",
|
||||||
"python.testing.pytestEnabled": false,
|
"python.testing.pytestEnabled": false,
|
||||||
"python.testing.unittestEnabled": true,
|
"python.testing.unittestEnabled": true,
|
||||||
"python.testing.unittestArgs": [
|
"python.testing.unittestArgs": [
|
||||||
@@ -31,23 +34,8 @@
|
|||||||
"-p",
|
"-p",
|
||||||
"test_*.py"
|
"test_*.py"
|
||||||
],
|
],
|
||||||
"python.linting.pylintEnabled": true,
|
// python3 -m isort --py=27 --profile=black ~/dev/copyparty/{copyparty,tests}/*.py && python3 -m black -t py27 ~/dev/copyparty/{copyparty,tests,bin}/*.py $(find ~/dev/copyparty/copyparty/stolen -iname '*.py')
|
||||||
"python.linting.flake8Enabled": true,
|
"editor.formatOnSave": false,
|
||||||
"python.linting.banditEnabled": true,
|
|
||||||
"python.linting.mypyEnabled": true,
|
|
||||||
"python.linting.flake8Args": [
|
|
||||||
"--max-line-length=120",
|
|
||||||
"--ignore=E722,F405,E203,W503,W293,E402,E501,E128,E226",
|
|
||||||
],
|
|
||||||
"python.linting.banditArgs": [
|
|
||||||
"--ignore=B104,B110,B112"
|
|
||||||
],
|
|
||||||
// python3 -m isort --py=27 --profile=black copyparty/
|
|
||||||
"python.formatting.provider": "none",
|
|
||||||
"[python]": {
|
|
||||||
"editor.defaultFormatter": "ms-python.black-formatter"
|
|
||||||
},
|
|
||||||
"editor.formatOnSave": true,
|
|
||||||
"[html]": {
|
"[html]": {
|
||||||
"editor.formatOnSave": false,
|
"editor.formatOnSave": false,
|
||||||
"editor.autoIndent": "keep",
|
"editor.autoIndent": "keep",
|
||||||
@@ -58,6 +46,4 @@
|
|||||||
"files.associations": {
|
"files.associations": {
|
||||||
"*.makefile": "makefile"
|
"*.makefile": "makefile"
|
||||||
},
|
},
|
||||||
"python.linting.enabled": true,
|
|
||||||
"python.pythonPath": "/usr/bin/python3"
|
|
||||||
}
|
}
|
||||||
2
LICENSE
2
LICENSE
@@ -1,6 +1,6 @@
|
|||||||
MIT License
|
MIT License
|
||||||
|
|
||||||
Copyright (c) 2019 ed
|
Copyright (c) 2019 ed <oss@ocv.me>
|
||||||
|
|
||||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
of this software and associated documentation files (the "Software"), to deal
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
|
|||||||
@@ -207,7 +207,7 @@ def examples():
|
|||||||
|
|
||||||
|
|
||||||
def main():
|
def main():
|
||||||
global NC, BY_PATH
|
global NC, BY_PATH # pylint: disable=global-statement
|
||||||
os.system("")
|
os.system("")
|
||||||
print()
|
print()
|
||||||
|
|
||||||
@@ -282,7 +282,8 @@ def main():
|
|||||||
if ver == "corrupt":
|
if ver == "corrupt":
|
||||||
die("{} database appears to be corrupt, sorry")
|
die("{} database appears to be corrupt, sorry")
|
||||||
|
|
||||||
if ver < DB_VER1 or ver > DB_VER2:
|
iver = int(ver)
|
||||||
|
if iver < DB_VER1 or iver > DB_VER2:
|
||||||
m = f"{n} db is version {ver}, this tool only supports versions between {DB_VER1} and {DB_VER2}, please upgrade it with copyparty first"
|
m = f"{n} db is version {ver}, this tool only supports versions between {DB_VER1} and {DB_VER2}, please upgrade it with copyparty first"
|
||||||
die(m)
|
die(m)
|
||||||
|
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ standalone programs which are executed by copyparty when an event happens (uploa
|
|||||||
|
|
||||||
these programs either take zero arguments, or a filepath (the affected file), or a json message with filepath + additional info
|
these programs either take zero arguments, or a filepath (the affected file), or a json message with filepath + additional info
|
||||||
|
|
||||||
run copyparty with `--help-hooks` for usage details / hook type explanations (xbu/xau/xiu/xbr/xar/xbd/xad)
|
run copyparty with `--help-hooks` for usage details / hook type explanations (xm/xbu/xau/xiu/xbr/xar/xbd/xad/xban)
|
||||||
|
|
||||||
> **note:** in addition to event hooks (the stuff described here), copyparty has another api to run your programs/scripts while providing way more information such as audio tags / video codecs / etc and optionally daisychaining data between scripts in a processing pipeline; if that's what you want then see [mtp plugins](../mtag/) instead
|
> **note:** in addition to event hooks (the stuff described here), copyparty has another api to run your programs/scripts while providing way more information such as audio tags / video codecs / etc and optionally daisychaining data between scripts in a processing pipeline; if that's what you want then see [mtp plugins](../mtag/) instead
|
||||||
|
|
||||||
@@ -13,6 +13,7 @@ run copyparty with `--help-hooks` for usage details / hook type explanations (xb
|
|||||||
* [image-noexif.py](image-noexif.py) removes image exif by overwriting / directly editing the uploaded file
|
* [image-noexif.py](image-noexif.py) removes image exif by overwriting / directly editing the uploaded file
|
||||||
* [discord-announce.py](discord-announce.py) announces new uploads on discord using webhooks ([example](https://user-images.githubusercontent.com/241032/215304439-1c1cb3c8-ec6f-4c17-9f27-81f969b1811a.png))
|
* [discord-announce.py](discord-announce.py) announces new uploads on discord using webhooks ([example](https://user-images.githubusercontent.com/241032/215304439-1c1cb3c8-ec6f-4c17-9f27-81f969b1811a.png))
|
||||||
* [reject-mimetype.py](reject-mimetype.py) rejects uploads unless the mimetype is acceptable
|
* [reject-mimetype.py](reject-mimetype.py) rejects uploads unless the mimetype is acceptable
|
||||||
|
* [into-the-cache-it-goes.py](into-the-cache-it-goes.py) avoids bugs in caching proxies by immediately downloading each file that is uploaded
|
||||||
|
|
||||||
|
|
||||||
# upload batches
|
# upload batches
|
||||||
@@ -23,7 +24,10 @@ these are `--xiu` hooks; unlike `xbu` and `xau` (which get executed on every sin
|
|||||||
|
|
||||||
# before upload
|
# before upload
|
||||||
* [reject-extension.py](reject-extension.py) rejects uploads if they match a list of file extensions
|
* [reject-extension.py](reject-extension.py) rejects uploads if they match a list of file extensions
|
||||||
|
* [reloc-by-ext.py](reloc-by-ext.py) redirects an upload to another destination based on the file extension
|
||||||
|
|
||||||
|
|
||||||
# on message
|
# on message
|
||||||
* [wget.py](wget.py) lets you download files by POSTing URLs to copyparty
|
* [wget.py](wget.py) lets you download files by POSTing URLs to copyparty
|
||||||
|
* [qbittorrent-magnet.py](qbittorrent-magnet.py) starts downloading a torrent if you post a magnet url
|
||||||
|
* [msg-log.py](msg-log.py) is a guestbook; logs messages to a doc in the same folder
|
||||||
|
|||||||
@@ -12,19 +12,28 @@ announces a new upload on discord
|
|||||||
example usage as global config:
|
example usage as global config:
|
||||||
--xau f,t5,j,bin/hooks/discord-announce.py
|
--xau f,t5,j,bin/hooks/discord-announce.py
|
||||||
|
|
||||||
|
parameters explained,
|
||||||
|
xau = execute after upload
|
||||||
|
f = fork; don't delay other hooks while this is running
|
||||||
|
t5 = timeout if it's still running after 5 sec
|
||||||
|
j = this hook needs upload information as json (not just the filename)
|
||||||
|
|
||||||
example usage as a volflag (per-volume config):
|
example usage as a volflag (per-volume config):
|
||||||
-v srv/inc:inc:r:rw,ed:c,xau=f,t5,j,bin/hooks/discord-announce.py
|
-v srv/inc:inc:r:rw,ed:c,xau=f,t5,j,bin/hooks/discord-announce.py
|
||||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||||
|
|
||||||
(share filesystem-path srv/inc as volume /inc,
|
(share filesystem-path srv/inc as volume /inc,
|
||||||
readable by everyone, read-write for user 'ed',
|
readable by everyone, read-write for user 'ed',
|
||||||
running this plugin on all uploads with the params listed below)
|
running this plugin on all uploads with the params explained above)
|
||||||
|
|
||||||
parameters explained,
|
example usage as a volflag in a copyparty config file:
|
||||||
xbu = execute after upload
|
[/inc]
|
||||||
f = fork; don't wait for it to finish
|
srv/inc
|
||||||
t5 = timeout if it's still running after 5 sec
|
accs:
|
||||||
j = provide upload information as json; not just the filename
|
r: *
|
||||||
|
rw: ed
|
||||||
|
flags:
|
||||||
|
xau: f,t5,j,bin/hooks/discord-announce.py
|
||||||
|
|
||||||
replace "xau" with "xbu" to announce Before upload starts instead of After completion
|
replace "xau" with "xbu" to announce Before upload starts instead of After completion
|
||||||
|
|
||||||
|
|||||||
140
bin/hooks/into-the-cache-it-goes.py
Normal file
140
bin/hooks/into-the-cache-it-goes.py
Normal file
@@ -0,0 +1,140 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
|
||||||
|
import sys
|
||||||
|
import json
|
||||||
|
import shutil
|
||||||
|
import platform
|
||||||
|
import subprocess as sp
|
||||||
|
from urllib.parse import quote
|
||||||
|
|
||||||
|
|
||||||
|
_ = r"""
|
||||||
|
try to avoid race conditions in caching proxies
|
||||||
|
(primarily cloudflare, but probably others too)
|
||||||
|
by means of the most obvious solution possible:
|
||||||
|
|
||||||
|
just as each file has finished uploading, use
|
||||||
|
the server's external URL to download the file
|
||||||
|
so that it ends up in the cache, warm and snug
|
||||||
|
|
||||||
|
this intentionally delays the upload response
|
||||||
|
as it waits for the file to finish downloading
|
||||||
|
before copyparty is allowed to return the URL
|
||||||
|
|
||||||
|
NOTE: you must edit this script before use,
|
||||||
|
replacing https://example.com with your URL
|
||||||
|
|
||||||
|
NOTE: if the files are only accessible with a
|
||||||
|
password and/or filekey, you must also add
|
||||||
|
a cromulent password in the PASSWORD field
|
||||||
|
|
||||||
|
NOTE: needs either wget, curl, or "requests":
|
||||||
|
python3 -m pip install --user -U requests
|
||||||
|
|
||||||
|
|
||||||
|
example usage as global config:
|
||||||
|
--xau j,t10,bin/hooks/into-the-cache-it-goes.py
|
||||||
|
|
||||||
|
parameters explained,
|
||||||
|
xau = execute after upload
|
||||||
|
j = this hook needs upload information as json (not just the filename)
|
||||||
|
t10 = abort download and continue if it takes longer than 10sec
|
||||||
|
|
||||||
|
example usage as a volflag (per-volume config):
|
||||||
|
-v srv/inc:inc:r:rw,ed:c,xau=j,t10,bin/hooks/into-the-cache-it-goes.py
|
||||||
|
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||||
|
|
||||||
|
(share filesystem-path srv/inc as volume /inc,
|
||||||
|
readable by everyone, read-write for user 'ed',
|
||||||
|
running this plugin on all uploads with params explained above)
|
||||||
|
|
||||||
|
example usage as a volflag in a copyparty config file:
|
||||||
|
[/inc]
|
||||||
|
srv/inc
|
||||||
|
accs:
|
||||||
|
r: *
|
||||||
|
rw: ed
|
||||||
|
flags:
|
||||||
|
xau: j,t10,bin/hooks/into-the-cache-it-goes.py
|
||||||
|
"""
|
||||||
|
|
||||||
|
|
||||||
|
# replace this with your site's external URL
|
||||||
|
# (including the :portnumber if necessary)
|
||||||
|
SITE_URL = "https://example.com"
|
||||||
|
|
||||||
|
# if downloading is protected by passwords or filekeys,
|
||||||
|
# specify a valid password between the quotes below:
|
||||||
|
PASSWORD = ""
|
||||||
|
|
||||||
|
# if file is larger than this, skip download
|
||||||
|
MAX_MEGABYTES = 8
|
||||||
|
|
||||||
|
# =============== END OF CONFIG ===============
|
||||||
|
|
||||||
|
|
||||||
|
WINDOWS = platform.system() == "Windows"
|
||||||
|
|
||||||
|
|
||||||
|
def main():
|
||||||
|
fun = download_with_python
|
||||||
|
if shutil.which("curl"):
|
||||||
|
fun = download_with_curl
|
||||||
|
elif shutil.which("wget"):
|
||||||
|
fun = download_with_wget
|
||||||
|
|
||||||
|
inf = json.loads(sys.argv[1])
|
||||||
|
|
||||||
|
if inf["sz"] > 1024 * 1024 * MAX_MEGABYTES:
|
||||||
|
print("[into-the-cache] file is too large; will not download")
|
||||||
|
return
|
||||||
|
|
||||||
|
file_url = "/"
|
||||||
|
if inf["vp"]:
|
||||||
|
file_url += inf["vp"] + "/"
|
||||||
|
file_url += inf["ap"].replace("\\", "/").split("/")[-1]
|
||||||
|
file_url = SITE_URL.rstrip("/") + quote(file_url, safe=b"/")
|
||||||
|
|
||||||
|
print("[into-the-cache] %s(%s)" % (fun.__name__, file_url))
|
||||||
|
fun(file_url, PASSWORD.strip())
|
||||||
|
|
||||||
|
print("[into-the-cache] Download OK")
|
||||||
|
|
||||||
|
|
||||||
|
def download_with_curl(url, pw):
|
||||||
|
cmd = ["curl"]
|
||||||
|
|
||||||
|
if pw:
|
||||||
|
cmd += ["-HPW:%s" % (pw,)]
|
||||||
|
|
||||||
|
nah = sp.DEVNULL
|
||||||
|
sp.check_call(cmd + [url], stdout=nah, stderr=nah)
|
||||||
|
|
||||||
|
|
||||||
|
def download_with_wget(url, pw):
|
||||||
|
cmd = ["wget", "-O"]
|
||||||
|
|
||||||
|
cmd += ["nul" if WINDOWS else "/dev/null"]
|
||||||
|
|
||||||
|
if pw:
|
||||||
|
cmd += ["--header=PW:%s" % (pw,)]
|
||||||
|
|
||||||
|
nah = sp.DEVNULL
|
||||||
|
sp.check_call(cmd + [url], stdout=nah, stderr=nah)
|
||||||
|
|
||||||
|
|
||||||
|
def download_with_python(url, pw):
|
||||||
|
import requests
|
||||||
|
|
||||||
|
headers = {}
|
||||||
|
if pw:
|
||||||
|
headers["PW"] = pw
|
||||||
|
|
||||||
|
with requests.get(url, headers=headers, stream=True) as r:
|
||||||
|
r.raise_for_status()
|
||||||
|
for _ in r.iter_content(chunk_size=1024 * 256):
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
main()
|
||||||
@@ -14,19 +14,32 @@ except:
|
|||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
|
|
||||||
|
|
||||||
"""
|
_ = r"""
|
||||||
use copyparty as a dumb messaging server / guestbook thing;
|
use copyparty as a dumb messaging server / guestbook thing;
|
||||||
|
accepts guestbook entries from 📟 (message-to-server-log) in the web-ui
|
||||||
initially contributed by @clach04 in https://github.com/9001/copyparty/issues/35 (thanks!)
|
initially contributed by @clach04 in https://github.com/9001/copyparty/issues/35 (thanks!)
|
||||||
|
|
||||||
Sample usage:
|
example usage as global config:
|
||||||
|
|
||||||
python copyparty-sfx.py --xm j,bin/hooks/msg-log.py
|
python copyparty-sfx.py --xm j,bin/hooks/msg-log.py
|
||||||
|
|
||||||
Where:
|
parameters explained,
|
||||||
|
xm = execute on message (📟)
|
||||||
|
j = this hook needs message information as json (not just the message-text)
|
||||||
|
|
||||||
xm = execute on message-to-server-log
|
example usage as a volflag (per-volume config):
|
||||||
j = provide message information as json; not just the text - this script REQUIRES json
|
python copyparty-sfx.py -v srv/log:log:r:c,xm=j,bin/hooks/msg-log.py
|
||||||
t10 = timeout and kill download after 10 secs
|
^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||||
|
|
||||||
|
(share filesystem-path srv/log as volume /log, readable by everyone,
|
||||||
|
running this plugin on all messages with the params explained above)
|
||||||
|
|
||||||
|
example usage as a volflag in a copyparty config file:
|
||||||
|
[/log]
|
||||||
|
srv/log
|
||||||
|
accs:
|
||||||
|
r: *
|
||||||
|
flags:
|
||||||
|
xm: j,bin/hooks/msg-log.py
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
128
bin/hooks/qbittorrent-magnet.py
Executable file
128
bin/hooks/qbittorrent-magnet.py
Executable file
@@ -0,0 +1,128 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
# coding: utf-8
|
||||||
|
|
||||||
|
import os
|
||||||
|
import sys
|
||||||
|
import json
|
||||||
|
import shutil
|
||||||
|
import subprocess as sp
|
||||||
|
|
||||||
|
|
||||||
|
_ = r"""
|
||||||
|
start downloading a torrent by POSTing a magnet URL to copyparty,
|
||||||
|
for example using 📟 (message-to-server-log) in the web-ui
|
||||||
|
|
||||||
|
by default it will download the torrent to the folder you were in
|
||||||
|
when you pasted the magnet into the message-to-server-log field
|
||||||
|
|
||||||
|
you can optionally specify another location by adding a whitespace
|
||||||
|
after the magnet URL followed by the name of the subfolder to DL into,
|
||||||
|
or for example "anime/airing" would download to /srv/media/anime/airing
|
||||||
|
because the keyword "anime" is in the DESTS config below
|
||||||
|
|
||||||
|
needs python3
|
||||||
|
|
||||||
|
example usage as global config (not a good idea):
|
||||||
|
python copyparty-sfx.py --xm aw,f,j,t60,bin/hooks/qbittorrent-magnet.py
|
||||||
|
|
||||||
|
parameters explained,
|
||||||
|
xm = execute on message (📟)
|
||||||
|
aw = only users with write-access can use this
|
||||||
|
f = fork; don't delay other hooks while this is running
|
||||||
|
j = provide message information as json (not just the text)
|
||||||
|
t60 = abort if qbittorrent has to think about it for more than 1 min
|
||||||
|
|
||||||
|
example usage as a volflag (per-volume config, much better):
|
||||||
|
-v srv/qb:qb:A,ed:c,xm=aw,f,j,t60,bin/hooks/qbittorrent-magnet.py
|
||||||
|
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||||
|
|
||||||
|
(share filesystem-path srv/qb as volume /qb with Admin for user 'ed',
|
||||||
|
running this plugin on all messages with the params explained above)
|
||||||
|
|
||||||
|
example usage as a volflag in a copyparty config file:
|
||||||
|
[/qb]
|
||||||
|
srv/qb
|
||||||
|
accs:
|
||||||
|
A: ed
|
||||||
|
flags:
|
||||||
|
xm: aw,f,j,t60,bin/hooks/qbittorrent-magnet.py
|
||||||
|
|
||||||
|
the volflag examples only kicks in if you send the torrent magnet
|
||||||
|
while you're in the /qb folder (or any folder below there)
|
||||||
|
"""
|
||||||
|
|
||||||
|
|
||||||
|
# list of usernames to allow
|
||||||
|
ALLOWLIST = [ "ed", "morpheus" ]
|
||||||
|
|
||||||
|
|
||||||
|
# list of destination aliases to translate into full filesystem
|
||||||
|
# paths; takes effect if the first folder component in the
|
||||||
|
# custom download location matches anything in this dict
|
||||||
|
DESTS = {
|
||||||
|
"iso": "/srv/pub/linux-isos",
|
||||||
|
"anime": "/srv/media/anime",
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
def main():
|
||||||
|
inf = json.loads(sys.argv[1])
|
||||||
|
url = inf["txt"]
|
||||||
|
if not url.lower().startswith("magnet:?"):
|
||||||
|
# not a magnet, abort
|
||||||
|
return
|
||||||
|
|
||||||
|
if inf["user"] not in ALLOWLIST:
|
||||||
|
print("🧲 denied for user", inf["user"])
|
||||||
|
return
|
||||||
|
|
||||||
|
# might as well run the command inside the filesystem folder
|
||||||
|
# which matches the URL that the magnet message was sent to
|
||||||
|
os.chdir(inf["ap"])
|
||||||
|
|
||||||
|
# is there is a custom download location in the url?
|
||||||
|
dst = ""
|
||||||
|
if " " in url:
|
||||||
|
url, dst = url.split(" ", 1)
|
||||||
|
|
||||||
|
# is the location in the predefined list of locations?
|
||||||
|
parts = dst.replace("\\", "/").split("/")
|
||||||
|
if parts[0] in DESTS:
|
||||||
|
dst = os.path.join(DESTS[parts[0]], *(parts[1:]))
|
||||||
|
|
||||||
|
else:
|
||||||
|
# nope, so download to the current folder instead;
|
||||||
|
# comment the dst line below to instead use the default
|
||||||
|
# download location from your qbittorrent settings
|
||||||
|
dst = inf["ap"]
|
||||||
|
pass
|
||||||
|
|
||||||
|
# archlinux has a -nox suffix for qbittorrent if headless
|
||||||
|
# so check if we should be using that
|
||||||
|
if shutil.which("qbittorrent-nox"):
|
||||||
|
torrent_bin = "qbittorrent-nox"
|
||||||
|
else:
|
||||||
|
torrent_bin = "qbittorrent"
|
||||||
|
|
||||||
|
# the command to add a new torrent, adjust if necessary
|
||||||
|
cmd = [torrent_bin, url]
|
||||||
|
if dst:
|
||||||
|
cmd += ["--save-path=%s" % (dst,)]
|
||||||
|
|
||||||
|
# if copyparty and qbittorrent are running as different users
|
||||||
|
# you may have to do something like the following
|
||||||
|
# (assuming qbittorrent* is nopasswd-allowed in sudoers):
|
||||||
|
#
|
||||||
|
# cmd = ["sudo", "-u", "qbitter"] + cmd
|
||||||
|
|
||||||
|
print("🧲", cmd)
|
||||||
|
|
||||||
|
try:
|
||||||
|
sp.check_call(cmd)
|
||||||
|
except:
|
||||||
|
print("🧲 FAILED TO ADD", url)
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
main()
|
||||||
|
|
||||||
127
bin/hooks/reloc-by-ext.py
Normal file
127
bin/hooks/reloc-by-ext.py
Normal file
@@ -0,0 +1,127 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
|
||||||
|
import json
|
||||||
|
import os
|
||||||
|
import re
|
||||||
|
import sys
|
||||||
|
|
||||||
|
|
||||||
|
_ = r"""
|
||||||
|
relocate/redirect incoming uploads according to file extension or name
|
||||||
|
|
||||||
|
example usage as global config:
|
||||||
|
--xbu j,c1,bin/hooks/reloc-by-ext.py
|
||||||
|
|
||||||
|
parameters explained,
|
||||||
|
xbu = execute before upload
|
||||||
|
j = this hook needs upload information as json (not just the filename)
|
||||||
|
c1 = this hook returns json on stdout, so tell copyparty to read that
|
||||||
|
|
||||||
|
example usage as a volflag (per-volume config):
|
||||||
|
-v srv/inc:inc:r:rw,ed:c,xbu=j,c1,bin/hooks/reloc-by-ext.py
|
||||||
|
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||||
|
|
||||||
|
(share filesystem-path srv/inc as volume /inc,
|
||||||
|
readable by everyone, read-write for user 'ed',
|
||||||
|
running this plugin on all uploads with the params explained above)
|
||||||
|
|
||||||
|
example usage as a volflag in a copyparty config file:
|
||||||
|
[/inc]
|
||||||
|
srv/inc
|
||||||
|
accs:
|
||||||
|
r: *
|
||||||
|
rw: ed
|
||||||
|
flags:
|
||||||
|
xbu: j,c1,bin/hooks/reloc-by-ext.py
|
||||||
|
|
||||||
|
note: this could also work as an xau hook (after-upload), but
|
||||||
|
because it doesn't need to read the file contents its better
|
||||||
|
as xbu (before-upload) since that's safer / less buggy,
|
||||||
|
and only xbu works with up2k (dragdrop into browser)
|
||||||
|
"""
|
||||||
|
|
||||||
|
|
||||||
|
PICS = "avif bmp gif heic heif jpeg jpg jxl png psd qoi tga tif tiff webp"
|
||||||
|
VIDS = "3gp asf avi flv mkv mov mp4 mpeg mpeg2 mpegts mpg mpg2 nut ogm ogv rm ts vob webm wmv"
|
||||||
|
MUSIC = "aac aif aiff alac amr ape dfpwm flac m4a mp3 ogg opus ra tak tta wav wma wv"
|
||||||
|
|
||||||
|
|
||||||
|
def main():
|
||||||
|
inf = json.loads(sys.argv[1])
|
||||||
|
vdir, fn = os.path.split(inf["vp"])
|
||||||
|
|
||||||
|
try:
|
||||||
|
fn, ext = fn.rsplit(".", 1)
|
||||||
|
except:
|
||||||
|
# no file extension; pretend it's "bin"
|
||||||
|
ext = "bin"
|
||||||
|
|
||||||
|
ext = ext.lower()
|
||||||
|
|
||||||
|
# this function must end by printing the action to perform;
|
||||||
|
# that's handled by the print(json.dumps(... at the bottom
|
||||||
|
#
|
||||||
|
# the action can contain the following keys:
|
||||||
|
# "vp" is the folder URL to move the upload to,
|
||||||
|
# "ap" is the filesystem-path to move it to (but "vp" is safer),
|
||||||
|
# "fn" overrides the final filename to use
|
||||||
|
|
||||||
|
##
|
||||||
|
## some example actions to take; pick one by
|
||||||
|
## selecting it inside the print at the end:
|
||||||
|
##
|
||||||
|
|
||||||
|
# create a subfolder named after the filetype and move it into there
|
||||||
|
into_subfolder = {"vp": ext}
|
||||||
|
|
||||||
|
# move it into a toplevel folder named after the filetype
|
||||||
|
into_toplevel = {"vp": "/" + ext}
|
||||||
|
|
||||||
|
# move it into a filetype-named folder next to the target folder
|
||||||
|
into_sibling = {"vp": "../" + ext}
|
||||||
|
|
||||||
|
# move images into "/just/pics", vids into "/just/vids",
|
||||||
|
# music into "/just/tunes", and anything else as-is
|
||||||
|
if ext in PICS.split():
|
||||||
|
by_category = {"vp": "/just/pics"}
|
||||||
|
elif ext in VIDS.split():
|
||||||
|
by_category = {"vp": "/just/vids"}
|
||||||
|
elif ext in MUSIC.split():
|
||||||
|
by_category = {"vp": "/just/tunes"}
|
||||||
|
else:
|
||||||
|
by_category = {} # no action
|
||||||
|
|
||||||
|
# now choose the default effect to apply; can be any of these:
|
||||||
|
# into_subfolder into_toplevel into_sibling by_category
|
||||||
|
effect = {"vp": "/junk"}
|
||||||
|
|
||||||
|
##
|
||||||
|
## but we can keep going, adding more speicifc rules
|
||||||
|
## which can take precedence, replacing the fallback
|
||||||
|
## effect we just specified:
|
||||||
|
##
|
||||||
|
|
||||||
|
fn = fn.lower() # lowercase filename to make this easier
|
||||||
|
|
||||||
|
if "screenshot" in fn:
|
||||||
|
effect = {"vp": "/ss"}
|
||||||
|
if "mpv_" in fn:
|
||||||
|
effect = {"vp": "/anishots"}
|
||||||
|
elif "debian" in fn or "biebian" in fn:
|
||||||
|
effect = {"vp": "/linux-ISOs"}
|
||||||
|
elif re.search(r"ep(isode |\.)?[0-9]", fn):
|
||||||
|
effect = {"vp": "/podcasts"}
|
||||||
|
|
||||||
|
# regex lets you grab a part of the matching
|
||||||
|
# text and use that in the upload path:
|
||||||
|
m = re.search(r"\b(op|ed)([^a-z]|$)", fn)
|
||||||
|
if m:
|
||||||
|
# the regex matched; use "anime-op" or "anime-ed"
|
||||||
|
effect = {"vp": "/anime-" + m[1]}
|
||||||
|
|
||||||
|
# aaand DO IT
|
||||||
|
print(json.dumps({"reloc": effect}))
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
main()
|
||||||
@@ -9,25 +9,38 @@ import subprocess as sp
|
|||||||
_ = r"""
|
_ = r"""
|
||||||
use copyparty as a file downloader by POSTing URLs as
|
use copyparty as a file downloader by POSTing URLs as
|
||||||
application/x-www-form-urlencoded (for example using the
|
application/x-www-form-urlencoded (for example using the
|
||||||
message/pager function on the website)
|
📟 message-to-server-log in the web-ui)
|
||||||
|
|
||||||
example usage as global config:
|
example usage as global config:
|
||||||
--xm f,j,t3600,bin/hooks/wget.py
|
--xm aw,f,j,t3600,bin/hooks/wget.py
|
||||||
|
|
||||||
example usage as a volflag (per-volume config):
|
|
||||||
-v srv/inc:inc:r:rw,ed:c,xm=f,j,t3600,bin/hooks/wget.py
|
|
||||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
|
||||||
|
|
||||||
(share filesystem-path srv/inc as volume /inc,
|
|
||||||
readable by everyone, read-write for user 'ed',
|
|
||||||
running this plugin on all messages with the params listed below)
|
|
||||||
|
|
||||||
parameters explained,
|
parameters explained,
|
||||||
xm = execute on message-to-server-log
|
xm = execute on message-to-server-log
|
||||||
f = fork so it doesn't block uploads
|
aw = only users with write-access can use this
|
||||||
j = provide message information as json; not just the text
|
f = fork; don't delay other hooks while this is running
|
||||||
|
j = provide message information as json (not just the text)
|
||||||
c3 = mute all output
|
c3 = mute all output
|
||||||
t3600 = timeout and kill download after 1 hour
|
t3600 = timeout and abort download after 1 hour
|
||||||
|
|
||||||
|
example usage as a volflag (per-volume config):
|
||||||
|
-v srv/inc:inc:r:rw,ed:c,xm=aw,f,j,t3600,bin/hooks/wget.py
|
||||||
|
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||||
|
|
||||||
|
(share filesystem-path srv/inc as volume /inc,
|
||||||
|
readable by everyone, read-write for user 'ed',
|
||||||
|
running this plugin on all messages with the params explained above)
|
||||||
|
|
||||||
|
example usage as a volflag in a copyparty config file:
|
||||||
|
[/inc]
|
||||||
|
srv/inc
|
||||||
|
accs:
|
||||||
|
r: *
|
||||||
|
rw: ed
|
||||||
|
flags:
|
||||||
|
xm: aw,f,j,t3600,bin/hooks/wget.py
|
||||||
|
|
||||||
|
the volflag examples only kicks in if you send the message
|
||||||
|
while you're in the /inc folder (or any folder below there)
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -223,12 +223,15 @@ install_vamp() {
|
|||||||
# use msys2 in mingw-w64 mode
|
# use msys2 in mingw-w64 mode
|
||||||
# pacman -S --needed mingw-w64-x86_64-{ffmpeg,python,python-pip,vamp-plugin-sdk}
|
# pacman -S --needed mingw-w64-x86_64-{ffmpeg,python,python-pip,vamp-plugin-sdk}
|
||||||
|
|
||||||
$pybin -m pip install --user vamp
|
$pybin -m pip install --user vamp || {
|
||||||
|
printf '\n\033[7malright, trying something else...\033[0m\n'
|
||||||
|
$pybin -m pip install --user --no-build-isolation vamp
|
||||||
|
}
|
||||||
|
|
||||||
cd "$td"
|
cd "$td"
|
||||||
echo '#include <vamp-sdk/Plugin.h>' | g++ -x c++ -c -o /dev/null - || [ -e ~/pe/vamp-sdk ] || {
|
echo '#include <vamp-sdk/Plugin.h>' | g++ -x c++ -c -o /dev/null - || [ -e ~/pe/vamp-sdk ] || {
|
||||||
printf '\033[33mcould not find the vamp-sdk, building from source\033[0m\n'
|
printf '\033[33mcould not find the vamp-sdk, building from source\033[0m\n'
|
||||||
(dl_files yolo https://code.soundsoftware.ac.uk/attachments/download/2691/vamp-plugin-sdk-2.10.0.tar.gz)
|
(dl_files yolo https://ocv.me/mirror/vamp-plugin-sdk-2.10.0.tar.gz)
|
||||||
sha512sum -c <(
|
sha512sum -c <(
|
||||||
echo "153b7f2fa01b77c65ad393ca0689742d66421017fd5931d216caa0fcf6909355fff74706fabbc062a3a04588a619c9b515a1dae00f21a57afd97902a355c48ed -"
|
echo "153b7f2fa01b77c65ad393ca0689742d66421017fd5931d216caa0fcf6909355fff74706fabbc062a3a04588a619c9b515a1dae00f21a57afd97902a355c48ed -"
|
||||||
) <vamp-plugin-sdk-2.10.0.tar.gz
|
) <vamp-plugin-sdk-2.10.0.tar.gz
|
||||||
@@ -244,7 +247,7 @@ install_vamp() {
|
|||||||
cd "$td"
|
cd "$td"
|
||||||
have_beatroot || {
|
have_beatroot || {
|
||||||
printf '\033[33mcould not find the vamp beatroot plugin, building from source\033[0m\n'
|
printf '\033[33mcould not find the vamp beatroot plugin, building from source\033[0m\n'
|
||||||
(dl_files yolo https://code.soundsoftware.ac.uk/attachments/download/885/beatroot-vamp-v1.0.tar.gz)
|
(dl_files yolo https://ocv.me/mirror/beatroot-vamp-v1.0.tar.gz)
|
||||||
sha512sum -c <(
|
sha512sum -c <(
|
||||||
echo "1f444d1d58ccf565c0adfe99f1a1aa62789e19f5071e46857e2adfbc9d453037bc1c4dcb039b02c16240e9b97f444aaff3afb625c86aa2470233e711f55b6874 -"
|
echo "1f444d1d58ccf565c0adfe99f1a1aa62789e19f5071e46857e2adfbc9d453037bc1c4dcb039b02c16240e9b97f444aaff3afb625c86aa2470233e711f55b6874 -"
|
||||||
) <beatroot-vamp-v1.0.tar.gz
|
) <beatroot-vamp-v1.0.tar.gz
|
||||||
|
|||||||
@@ -53,7 +53,13 @@ from urllib.parse import unquote_to_bytes as unquote
|
|||||||
WINDOWS = sys.platform == "win32"
|
WINDOWS = sys.platform == "win32"
|
||||||
MACOS = platform.system() == "Darwin"
|
MACOS = platform.system() == "Darwin"
|
||||||
UTC = timezone.utc
|
UTC = timezone.utc
|
||||||
info = log = dbg = None
|
|
||||||
|
|
||||||
|
def print(*args, **kwargs):
|
||||||
|
try:
|
||||||
|
builtins.print(*list(args), **kwargs)
|
||||||
|
except:
|
||||||
|
builtins.print(termsafe(" ".join(str(x) for x in args)), **kwargs)
|
||||||
|
|
||||||
|
|
||||||
print(
|
print(
|
||||||
@@ -65,6 +71,13 @@ print(
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def null_log(msg):
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
info = log = dbg = null_log
|
||||||
|
|
||||||
|
|
||||||
try:
|
try:
|
||||||
from fuse import FUSE, FuseOSError, Operations
|
from fuse import FUSE, FuseOSError, Operations
|
||||||
except:
|
except:
|
||||||
@@ -84,13 +97,6 @@ except:
|
|||||||
raise
|
raise
|
||||||
|
|
||||||
|
|
||||||
def print(*args, **kwargs):
|
|
||||||
try:
|
|
||||||
builtins.print(*list(args), **kwargs)
|
|
||||||
except:
|
|
||||||
builtins.print(termsafe(" ".join(str(x) for x in args)), **kwargs)
|
|
||||||
|
|
||||||
|
|
||||||
def termsafe(txt):
|
def termsafe(txt):
|
||||||
try:
|
try:
|
||||||
return txt.encode(sys.stdout.encoding, "backslashreplace").decode(
|
return txt.encode(sys.stdout.encoding, "backslashreplace").decode(
|
||||||
@@ -119,10 +125,6 @@ def fancy_log(msg):
|
|||||||
print("{:10.6f} {} {}\n".format(time.time() % 900, rice_tid(), msg), end="")
|
print("{:10.6f} {} {}\n".format(time.time() % 900, rice_tid(), msg), end="")
|
||||||
|
|
||||||
|
|
||||||
def null_log(msg):
|
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
def hexler(binary):
|
def hexler(binary):
|
||||||
return binary.replace("\r", "\\r").replace("\n", "\\n")
|
return binary.replace("\r", "\\r").replace("\n", "\\n")
|
||||||
return " ".join(["{}\033[36m{:02x}\033[0m".format(b, ord(b)) for b in binary])
|
return " ".join(["{}\033[36m{:02x}\033[0m".format(b, ord(b)) for b in binary])
|
||||||
|
|||||||
416
bin/u2c.py
416
bin/u2c.py
@@ -1,8 +1,8 @@
|
|||||||
#!/usr/bin/env python3
|
#!/usr/bin/env python3
|
||||||
from __future__ import print_function, unicode_literals
|
from __future__ import print_function, unicode_literals
|
||||||
|
|
||||||
S_VERSION = "1.11"
|
S_VERSION = "1.24"
|
||||||
S_BUILD_DT = "2023-11-11"
|
S_BUILD_DT = "2024-09-05"
|
||||||
|
|
||||||
"""
|
"""
|
||||||
u2c.py: upload to copyparty
|
u2c.py: upload to copyparty
|
||||||
@@ -20,6 +20,7 @@ import sys
|
|||||||
import stat
|
import stat
|
||||||
import math
|
import math
|
||||||
import time
|
import time
|
||||||
|
import json
|
||||||
import atexit
|
import atexit
|
||||||
import signal
|
import signal
|
||||||
import socket
|
import socket
|
||||||
@@ -29,7 +30,7 @@ import platform
|
|||||||
import threading
|
import threading
|
||||||
import datetime
|
import datetime
|
||||||
|
|
||||||
EXE = sys.executable.endswith("exe")
|
EXE = bool(getattr(sys, "frozen", False))
|
||||||
|
|
||||||
try:
|
try:
|
||||||
import argparse
|
import argparse
|
||||||
@@ -40,19 +41,25 @@ except:
|
|||||||
|
|
||||||
try:
|
try:
|
||||||
import requests
|
import requests
|
||||||
|
|
||||||
|
req_ses = requests.Session()
|
||||||
except ImportError as ex:
|
except ImportError as ex:
|
||||||
if EXE:
|
if "-" in sys.argv or "-h" in sys.argv:
|
||||||
|
m = ""
|
||||||
|
elif EXE:
|
||||||
raise
|
raise
|
||||||
elif sys.version_info > (2, 7):
|
elif sys.version_info > (2, 7):
|
||||||
m = "\nERROR: need 'requests'; please run this command:\n {0} -m pip install --user requests\n"
|
m = "\nERROR: need 'requests'{0}; please run this command:\n {1} -m pip install --user requests\n"
|
||||||
else:
|
else:
|
||||||
m = "requests/2.18.4 urllib3/1.23 chardet/3.0.4 certifi/2020.4.5.1 idna/2.7"
|
m = "requests/2.18.4 urllib3/1.23 chardet/3.0.4 certifi/2020.4.5.1 idna/2.7"
|
||||||
m = [" https://pypi.org/project/" + x + "/#files" for x in m.split()]
|
m = [" https://pypi.org/project/" + x + "/#files" for x in m.split()]
|
||||||
m = "\n ERROR: need these:\n" + "\n".join(m) + "\n"
|
m = "\n ERROR: need these{0}:\n" + "\n".join(m) + "\n"
|
||||||
m += "\n for f in *.whl; do unzip $f; done; rm -r *.dist-info\n"
|
m += "\n for f in *.whl; do unzip $f; done; rm -r *.dist-info\n"
|
||||||
|
|
||||||
print(m.format(sys.executable), "\nspecifically,", ex)
|
if m:
|
||||||
sys.exit(1)
|
t = " when not running with '-h' or url '-'"
|
||||||
|
print(m.format(t, sys.executable), "\nspecifically,", ex)
|
||||||
|
sys.exit(1)
|
||||||
|
|
||||||
|
|
||||||
# from copyparty/__init__.py
|
# from copyparty/__init__.py
|
||||||
@@ -75,16 +82,40 @@ else:
|
|||||||
VT100 = platform.system() != "Windows"
|
VT100 = platform.system() != "Windows"
|
||||||
|
|
||||||
|
|
||||||
req_ses = requests.Session()
|
try:
|
||||||
|
UTC = datetime.timezone.utc
|
||||||
|
except:
|
||||||
|
TD_ZERO = datetime.timedelta(0)
|
||||||
|
|
||||||
|
class _UTC(datetime.tzinfo):
|
||||||
|
def utcoffset(self, dt):
|
||||||
|
return TD_ZERO
|
||||||
|
|
||||||
|
def tzname(self, dt):
|
||||||
|
return "UTC"
|
||||||
|
|
||||||
|
def dst(self, dt):
|
||||||
|
return TD_ZERO
|
||||||
|
|
||||||
|
UTC = _UTC()
|
||||||
|
|
||||||
|
|
||||||
class Daemon(threading.Thread):
|
class Daemon(threading.Thread):
|
||||||
def __init__(self, target, name=None, a=None):
|
def __init__(self, target, name=None, a=None):
|
||||||
# type: (Any, Any, Any) -> None
|
threading.Thread.__init__(self, name=name)
|
||||||
threading.Thread.__init__(self, target=target, args=a or (), name=name)
|
self.a = a or ()
|
||||||
|
self.fun = target
|
||||||
self.daemon = True
|
self.daemon = True
|
||||||
self.start()
|
self.start()
|
||||||
|
|
||||||
|
def run(self):
|
||||||
|
try:
|
||||||
|
signal.pthread_sigmask(signal.SIG_BLOCK, [signal.SIGINT, signal.SIGTERM])
|
||||||
|
except:
|
||||||
|
pass
|
||||||
|
|
||||||
|
self.fun(*self.a)
|
||||||
|
|
||||||
|
|
||||||
class File(object):
|
class File(object):
|
||||||
"""an up2k upload task; represents a single file"""
|
"""an up2k upload task; represents a single file"""
|
||||||
@@ -101,18 +132,22 @@ class File(object):
|
|||||||
# set by get_hashlist
|
# set by get_hashlist
|
||||||
self.cids = [] # type: list[tuple[str, int, int]] # [ hash, ofs, sz ]
|
self.cids = [] # type: list[tuple[str, int, int]] # [ hash, ofs, sz ]
|
||||||
self.kchunks = {} # type: dict[str, tuple[int, int]] # hash: [ ofs, sz ]
|
self.kchunks = {} # type: dict[str, tuple[int, int]] # hash: [ ofs, sz ]
|
||||||
|
self.t_hash = 0.0 # type: float
|
||||||
|
|
||||||
# set by handshake
|
# set by handshake
|
||||||
self.recheck = False # duplicate; redo handshake after all files done
|
self.recheck = False # duplicate; redo handshake after all files done
|
||||||
self.ucids = [] # type: list[str] # chunks which need to be uploaded
|
self.ucids = [] # type: list[str] # chunks which need to be uploaded
|
||||||
self.wark = None # type: str
|
self.wark = "" # type: str
|
||||||
self.url = None # type: str
|
self.url = "" # type: str
|
||||||
self.nhs = 0
|
self.nhs = 0 # type: int
|
||||||
|
|
||||||
# set by upload
|
# set by upload
|
||||||
|
self.t0_up = 0.0 # type: float
|
||||||
|
self.t1_up = 0.0 # type: float
|
||||||
|
self.nojoin = 0 # type: int
|
||||||
self.up_b = 0 # type: int
|
self.up_b = 0 # type: int
|
||||||
self.up_c = 0 # type: int
|
self.up_c = 0 # type: int
|
||||||
self.cd = 0
|
self.cd = 0 # type: int
|
||||||
|
|
||||||
# t = "size({}) lmod({}) top({}) rel({}) abs({}) name({})\n"
|
# t = "size({}) lmod({}) top({}) rel({}) abs({}) name({})\n"
|
||||||
# eprint(t.format(self.size, self.lmod, self.top, self.rel, self.abs, self.name))
|
# eprint(t.format(self.size, self.lmod, self.top, self.rel, self.abs, self.name))
|
||||||
@@ -121,10 +156,20 @@ class File(object):
|
|||||||
class FileSlice(object):
|
class FileSlice(object):
|
||||||
"""file-like object providing a fixed window into a file"""
|
"""file-like object providing a fixed window into a file"""
|
||||||
|
|
||||||
def __init__(self, file, cid):
|
def __init__(self, file, cids):
|
||||||
# type: (File, str) -> None
|
# type: (File, str) -> None
|
||||||
|
|
||||||
self.car, self.len = file.kchunks[cid]
|
self.file = file
|
||||||
|
self.cids = cids
|
||||||
|
|
||||||
|
self.car, tlen = file.kchunks[cids[0]]
|
||||||
|
for cid in cids[1:]:
|
||||||
|
ofs, clen = file.kchunks[cid]
|
||||||
|
if ofs != self.car + tlen:
|
||||||
|
raise Exception(9)
|
||||||
|
tlen += clen
|
||||||
|
|
||||||
|
self.len = tlen
|
||||||
self.cdr = self.car + self.len
|
self.cdr = self.car + self.len
|
||||||
self.ofs = 0 # type: int
|
self.ofs = 0 # type: int
|
||||||
self.f = open(file.abs, "rb", 512 * 1024)
|
self.f = open(file.abs, "rb", 512 * 1024)
|
||||||
@@ -223,6 +268,7 @@ class MTHash(object):
|
|||||||
|
|
||||||
def hash_at(self, nch):
|
def hash_at(self, nch):
|
||||||
f = self.f
|
f = self.f
|
||||||
|
assert f
|
||||||
ofs = ofs0 = nch * self.csz
|
ofs = ofs0 = nch * self.csz
|
||||||
hashobj = hashlib.sha512()
|
hashobj = hashlib.sha512()
|
||||||
chunk_sz = chunk_rem = min(self.csz, self.sz - ofs)
|
chunk_sz = chunk_rem = min(self.csz, self.sz - ofs)
|
||||||
@@ -246,6 +292,12 @@ class MTHash(object):
|
|||||||
_print = print
|
_print = print
|
||||||
|
|
||||||
|
|
||||||
|
def safe_print(*a, **ka):
|
||||||
|
ka["end"] = ""
|
||||||
|
zs = " ".join([unicode(x) for x in a])
|
||||||
|
_print(zs + "\n", **ka)
|
||||||
|
|
||||||
|
|
||||||
def eprint(*a, **ka):
|
def eprint(*a, **ka):
|
||||||
ka["file"] = sys.stderr
|
ka["file"] = sys.stderr
|
||||||
ka["end"] = ""
|
ka["end"] = ""
|
||||||
@@ -259,18 +311,17 @@ def eprint(*a, **ka):
|
|||||||
|
|
||||||
def flushing_print(*a, **ka):
|
def flushing_print(*a, **ka):
|
||||||
try:
|
try:
|
||||||
_print(*a, **ka)
|
safe_print(*a, **ka)
|
||||||
except:
|
except:
|
||||||
v = " ".join(str(x) for x in a)
|
v = " ".join(str(x) for x in a)
|
||||||
v = v.encode("ascii", "replace").decode("ascii")
|
v = v.encode("ascii", "replace").decode("ascii")
|
||||||
_print(v, **ka)
|
safe_print(v, **ka)
|
||||||
|
|
||||||
if "flush" not in ka:
|
if "flush" not in ka:
|
||||||
sys.stdout.flush()
|
sys.stdout.flush()
|
||||||
|
|
||||||
|
|
||||||
if not VT100:
|
print = safe_print if VT100 else flushing_print
|
||||||
print = flushing_print
|
|
||||||
|
|
||||||
|
|
||||||
def termsize():
|
def termsize():
|
||||||
@@ -347,7 +398,7 @@ def undns(url):
|
|||||||
usp = urlsplit(url)
|
usp = urlsplit(url)
|
||||||
hn = usp.hostname
|
hn = usp.hostname
|
||||||
gai = None
|
gai = None
|
||||||
eprint("resolving host [{0}] ...".format(hn), end="")
|
eprint("resolving host [%s] ..." % (hn,))
|
||||||
try:
|
try:
|
||||||
gai = socket.getaddrinfo(hn, None)
|
gai = socket.getaddrinfo(hn, None)
|
||||||
hn = gai[0][4][0]
|
hn = gai[0][4][0]
|
||||||
@@ -365,7 +416,7 @@ def undns(url):
|
|||||||
|
|
||||||
usp = usp._replace(netloc=hn)
|
usp = usp._replace(netloc=hn)
|
||||||
url = urlunsplit(usp)
|
url = urlunsplit(usp)
|
||||||
eprint(" {0}".format(url))
|
eprint(" %s\n" % (url,))
|
||||||
return url
|
return url
|
||||||
|
|
||||||
|
|
||||||
@@ -463,7 +514,7 @@ def quotep(btxt):
|
|||||||
if not PY2:
|
if not PY2:
|
||||||
quot1 = quot1.encode("ascii")
|
quot1 = quot1.encode("ascii")
|
||||||
|
|
||||||
return quot1.replace(b" ", b"+")
|
return quot1.replace(b" ", b"+") # type: ignore
|
||||||
|
|
||||||
|
|
||||||
# from copyparty/util.py
|
# from copyparty/util.py
|
||||||
@@ -500,7 +551,7 @@ def up2k_chunksize(filesize):
|
|||||||
|
|
||||||
# mostly from copyparty/up2k.py
|
# mostly from copyparty/up2k.py
|
||||||
def get_hashlist(file, pcb, mth):
|
def get_hashlist(file, pcb, mth):
|
||||||
# type: (File, any, any) -> None
|
# type: (File, Any, Any) -> None
|
||||||
"""generates the up2k hashlist from file contents, inserts it into `file`"""
|
"""generates the up2k hashlist from file contents, inserts it into `file`"""
|
||||||
|
|
||||||
chunk_sz = up2k_chunksize(file.size)
|
chunk_sz = up2k_chunksize(file.size)
|
||||||
@@ -508,6 +559,8 @@ def get_hashlist(file, pcb, mth):
|
|||||||
file_ofs = 0
|
file_ofs = 0
|
||||||
ret = []
|
ret = []
|
||||||
with open(file.abs, "rb", 512 * 1024) as f:
|
with open(file.abs, "rb", 512 * 1024) as f:
|
||||||
|
t0 = time.time()
|
||||||
|
|
||||||
if mth and file.size >= 1024 * 512:
|
if mth and file.size >= 1024 * 512:
|
||||||
ret = mth.hash(f, file.size, chunk_sz, pcb, file)
|
ret = mth.hash(f, file.size, chunk_sz, pcb, file)
|
||||||
file_rem = 0
|
file_rem = 0
|
||||||
@@ -534,10 +587,12 @@ def get_hashlist(file, pcb, mth):
|
|||||||
if pcb:
|
if pcb:
|
||||||
pcb(file, file_ofs)
|
pcb(file, file_ofs)
|
||||||
|
|
||||||
|
file.t_hash = time.time() - t0
|
||||||
file.cids = ret
|
file.cids = ret
|
||||||
file.kchunks = {}
|
file.kchunks = {}
|
||||||
for k, v1, v2 in ret:
|
for k, v1, v2 in ret:
|
||||||
file.kchunks[k] = [v1, v2]
|
if k not in file.kchunks:
|
||||||
|
file.kchunks[k] = [v1, v2]
|
||||||
|
|
||||||
|
|
||||||
def handshake(ar, file, search):
|
def handshake(ar, file, search):
|
||||||
@@ -559,8 +614,11 @@ def handshake(ar, file, search):
|
|||||||
}
|
}
|
||||||
if search:
|
if search:
|
||||||
req["srch"] = 1
|
req["srch"] = 1
|
||||||
elif ar.dr:
|
else:
|
||||||
req["replace"] = True
|
if ar.touch:
|
||||||
|
req["umod"] = True
|
||||||
|
if ar.ow:
|
||||||
|
req["replace"] = True
|
||||||
|
|
||||||
headers = {"Content-Type": "text/plain"} # <=1.5.1 compat
|
headers = {"Content-Type": "text/plain"} # <=1.5.1 compat
|
||||||
if pw:
|
if pw:
|
||||||
@@ -576,7 +634,8 @@ def handshake(ar, file, search):
|
|||||||
sc = 600
|
sc = 600
|
||||||
txt = ""
|
txt = ""
|
||||||
try:
|
try:
|
||||||
r = req_ses.post(url, headers=headers, json=req)
|
zs = json.dumps(req, separators=(",\n", ": "))
|
||||||
|
r = req_ses.post(url, headers=headers, data=zs)
|
||||||
sc = r.status_code
|
sc = r.status_code
|
||||||
txt = r.text
|
txt = r.text
|
||||||
if sc < 400:
|
if sc < 400:
|
||||||
@@ -623,13 +682,20 @@ def handshake(ar, file, search):
|
|||||||
return r["hash"], r["sprs"]
|
return r["hash"], r["sprs"]
|
||||||
|
|
||||||
|
|
||||||
def upload(file, cid, pw, stats):
|
def upload(fsl, pw, stats):
|
||||||
# type: (File, str, str, str) -> None
|
# type: (FileSlice, str, str) -> None
|
||||||
"""upload one specific chunk, `cid` (a chunk-hash)"""
|
"""upload a range of file data, defined by one or more `cid` (chunk-hash)"""
|
||||||
|
|
||||||
|
ctxt = fsl.cids[0]
|
||||||
|
if len(fsl.cids) > 1:
|
||||||
|
n = 192 // len(fsl.cids)
|
||||||
|
n = 9 if n > 9 else 2 if n < 2 else n
|
||||||
|
zsl = [zs[:n] for zs in fsl.cids[1:]]
|
||||||
|
ctxt += ",%d,%s" % (n, "".join(zsl))
|
||||||
|
|
||||||
headers = {
|
headers = {
|
||||||
"X-Up2k-Hash": cid,
|
"X-Up2k-Hash": ctxt,
|
||||||
"X-Up2k-Wark": file.wark,
|
"X-Up2k-Wark": fsl.file.wark,
|
||||||
"Content-Type": "application/octet-stream",
|
"Content-Type": "application/octet-stream",
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -639,15 +705,24 @@ def upload(file, cid, pw, stats):
|
|||||||
if pw:
|
if pw:
|
||||||
headers["Cookie"] = "=".join(["cppwd", pw])
|
headers["Cookie"] = "=".join(["cppwd", pw])
|
||||||
|
|
||||||
f = FileSlice(file, cid)
|
|
||||||
try:
|
try:
|
||||||
r = req_ses.post(file.url, headers=headers, data=f)
|
r = req_ses.post(fsl.file.url, headers=headers, data=fsl)
|
||||||
|
|
||||||
|
if r.status_code == 400:
|
||||||
|
txt = r.text
|
||||||
|
if (
|
||||||
|
"already being written" in txt
|
||||||
|
or "already got that" in txt
|
||||||
|
or "only sibling chunks" in txt
|
||||||
|
):
|
||||||
|
fsl.file.nojoin = 1
|
||||||
|
|
||||||
if not r:
|
if not r:
|
||||||
raise Exception(repr(r))
|
raise Exception(repr(r))
|
||||||
|
|
||||||
_ = r.content
|
_ = r.content
|
||||||
finally:
|
finally:
|
||||||
f.f.close()
|
fsl.f.close()
|
||||||
|
|
||||||
|
|
||||||
class Ctl(object):
|
class Ctl(object):
|
||||||
@@ -711,6 +786,9 @@ class Ctl(object):
|
|||||||
if ar.safe:
|
if ar.safe:
|
||||||
self._safe()
|
self._safe()
|
||||||
else:
|
else:
|
||||||
|
self.at_hash = 0.0
|
||||||
|
self.at_up = 0.0
|
||||||
|
self.at_upr = 0.0
|
||||||
self.hash_f = 0
|
self.hash_f = 0
|
||||||
self.hash_c = 0
|
self.hash_c = 0
|
||||||
self.hash_b = 0
|
self.hash_b = 0
|
||||||
@@ -718,8 +796,6 @@ class Ctl(object):
|
|||||||
self.up_c = 0
|
self.up_c = 0
|
||||||
self.up_b = 0
|
self.up_b = 0
|
||||||
self.up_br = 0
|
self.up_br = 0
|
||||||
self.hasher_busy = 1
|
|
||||||
self.handshaker_busy = 0
|
|
||||||
self.uploader_busy = 0
|
self.uploader_busy = 0
|
||||||
self.serialized = False
|
self.serialized = False
|
||||||
|
|
||||||
@@ -729,8 +805,11 @@ class Ctl(object):
|
|||||||
self.eta = "99:99:99"
|
self.eta = "99:99:99"
|
||||||
|
|
||||||
self.mutex = threading.Lock()
|
self.mutex = threading.Lock()
|
||||||
|
self.exit_cond = threading.Condition()
|
||||||
|
self.uploader_alive = ar.j
|
||||||
|
self.handshaker_alive = ar.j
|
||||||
self.q_handshake = Queue() # type: Queue[File]
|
self.q_handshake = Queue() # type: Queue[File]
|
||||||
self.q_upload = Queue() # type: Queue[tuple[File, str]]
|
self.q_upload = Queue() # type: Queue[FileSlice]
|
||||||
|
|
||||||
self.st_hash = [None, "(idle, starting...)"] # type: tuple[File, int]
|
self.st_hash = [None, "(idle, starting...)"] # type: tuple[File, int]
|
||||||
self.st_up = [None, "(idle, starting...)"] # type: tuple[File, int]
|
self.st_up = [None, "(idle, starting...)"] # type: tuple[File, int]
|
||||||
@@ -775,7 +854,8 @@ class Ctl(object):
|
|||||||
for nc, cid in enumerate(hs):
|
for nc, cid in enumerate(hs):
|
||||||
print(" {0} up {1}".format(ncs - nc, cid))
|
print(" {0} up {1}".format(ncs - nc, cid))
|
||||||
stats = "{0}/0/0/{1}".format(nf, self.nfiles - nf)
|
stats = "{0}/0/0/{1}".format(nf, self.nfiles - nf)
|
||||||
upload(file, cid, self.ar.a, stats)
|
fslice = FileSlice(file, [cid])
|
||||||
|
upload(fslice, self.ar.a, stats)
|
||||||
|
|
||||||
print(" ok!")
|
print(" ok!")
|
||||||
if file.recheck:
|
if file.recheck:
|
||||||
@@ -784,7 +864,7 @@ class Ctl(object):
|
|||||||
if not self.recheck:
|
if not self.recheck:
|
||||||
return
|
return
|
||||||
|
|
||||||
eprint("finalizing {0} duplicate files".format(len(self.recheck)))
|
eprint("finalizing %d duplicate files\n" % (len(self.recheck),))
|
||||||
for file in self.recheck:
|
for file in self.recheck:
|
||||||
handshake(self.ar, file, search)
|
handshake(self.ar, file, search)
|
||||||
|
|
||||||
@@ -798,27 +878,21 @@ class Ctl(object):
|
|||||||
Daemon(self.handshaker)
|
Daemon(self.handshaker)
|
||||||
Daemon(self.uploader)
|
Daemon(self.uploader)
|
||||||
|
|
||||||
idles = 0
|
while True:
|
||||||
while idles < 3:
|
with self.exit_cond:
|
||||||
time.sleep(0.07)
|
self.exit_cond.wait(0.07)
|
||||||
with self.mutex:
|
with self.mutex:
|
||||||
if (
|
if not self.handshaker_alive and not self.uploader_alive:
|
||||||
self.q_handshake.empty()
|
break
|
||||||
and self.q_upload.empty()
|
st_hash = self.st_hash[:]
|
||||||
and not self.hasher_busy
|
st_up = self.st_up[:]
|
||||||
and not self.handshaker_busy
|
|
||||||
and not self.uploader_busy
|
|
||||||
):
|
|
||||||
idles += 1
|
|
||||||
else:
|
|
||||||
idles = 0
|
|
||||||
|
|
||||||
if VT100 and not self.ar.ns:
|
if VT100 and not self.ar.ns:
|
||||||
maxlen = ss.w - len(str(self.nfiles)) - 14
|
maxlen = ss.w - len(str(self.nfiles)) - 14
|
||||||
txt = "\033[s\033[{0}H".format(ss.g)
|
txt = "\033[s\033[{0}H".format(ss.g)
|
||||||
for y, k, st, f in [
|
for y, k, st, f in [
|
||||||
[0, "hash", self.st_hash, self.hash_f],
|
[0, "hash", st_hash, self.hash_f],
|
||||||
[1, "send", self.st_up, self.up_f],
|
[1, "send", st_up, self.up_f],
|
||||||
]:
|
]:
|
||||||
txt += "\033[{0}H{1}:".format(ss.g + y, k)
|
txt += "\033[{0}H{1}:".format(ss.g + y, k)
|
||||||
file, arg = st
|
file, arg = st
|
||||||
@@ -842,12 +916,12 @@ class Ctl(object):
|
|||||||
txt = " "
|
txt = " "
|
||||||
|
|
||||||
if not self.up_br:
|
if not self.up_br:
|
||||||
spd = self.hash_b / (time.time() - self.t0)
|
spd = self.hash_b / ((time.time() - self.t0) or 1)
|
||||||
eta = (self.nbytes - self.hash_b) / (spd + 1)
|
eta = (self.nbytes - self.hash_b) / (spd or 1)
|
||||||
else:
|
else:
|
||||||
spd = self.up_br / (time.time() - self.t0_up)
|
spd = self.up_br / ((time.time() - self.t0_up) or 1)
|
||||||
spd = self.spd = (self.spd or spd) * 0.9 + spd * 0.1
|
spd = self.spd = (self.spd or spd) * 0.9 + spd * 0.1
|
||||||
eta = (self.nbytes - self.up_b) / (spd + 1)
|
eta = (self.nbytes - self.up_b) / (spd or 1)
|
||||||
|
|
||||||
spd = humansize(spd)
|
spd = humansize(spd)
|
||||||
self.eta = str(datetime.timedelta(seconds=int(eta)))
|
self.eta = str(datetime.timedelta(seconds=int(eta)))
|
||||||
@@ -858,10 +932,17 @@ class Ctl(object):
|
|||||||
t = "{0} eta @ {1}/s, {2}, {3}# left".format(self.eta, spd, sleft, nleft)
|
t = "{0} eta @ {1}/s, {2}, {3}# left".format(self.eta, spd, sleft, nleft)
|
||||||
eprint(txt + "\033]0;{0}\033\\\r{0}{1}".format(t, tail))
|
eprint(txt + "\033]0;{0}\033\\\r{0}{1}".format(t, tail))
|
||||||
|
|
||||||
|
if self.hash_b and self.at_hash:
|
||||||
|
spd = humansize(self.hash_b / self.at_hash)
|
||||||
|
eprint("\nhasher: %.2f sec, %s/s\n" % (self.at_hash, spd))
|
||||||
|
if self.up_b and self.at_up:
|
||||||
|
spd = humansize(self.up_b / self.at_up)
|
||||||
|
eprint("upload: %.2f sec, %s/s\n" % (self.at_up, spd))
|
||||||
|
|
||||||
if not self.recheck:
|
if not self.recheck:
|
||||||
return
|
return
|
||||||
|
|
||||||
eprint("finalizing {0} duplicate files".format(len(self.recheck)))
|
eprint("finalizing %d duplicate files\n" % (len(self.recheck),))
|
||||||
for file in self.recheck:
|
for file in self.recheck:
|
||||||
handshake(self.ar, file, False)
|
handshake(self.ar, file, False)
|
||||||
|
|
||||||
@@ -873,6 +954,8 @@ class Ctl(object):
|
|||||||
self.st_hash = [file, ofs]
|
self.st_hash = [file, ofs]
|
||||||
|
|
||||||
def hasher(self):
|
def hasher(self):
|
||||||
|
ptn = re.compile(self.ar.x.encode("utf-8"), re.I) if self.ar.x else None
|
||||||
|
sep = "{0}".format(os.sep).encode("ascii")
|
||||||
prd = None
|
prd = None
|
||||||
ls = {}
|
ls = {}
|
||||||
for top, rel, inf in self.filegen:
|
for top, rel, inf in self.filegen:
|
||||||
@@ -905,13 +988,29 @@ class Ctl(object):
|
|||||||
if self.ar.drd:
|
if self.ar.drd:
|
||||||
dp = os.path.join(top, rd)
|
dp = os.path.join(top, rd)
|
||||||
lnodes = set(os.listdir(dp))
|
lnodes = set(os.listdir(dp))
|
||||||
bnames = [x for x in ls if x not in lnodes]
|
if ptn:
|
||||||
if bnames:
|
zs = dp.replace(sep, b"/").rstrip(b"/") + b"/"
|
||||||
vpath = self.ar.url.split("://")[-1].split("/", 1)[-1]
|
zls = [zs + x for x in lnodes]
|
||||||
names = [x.decode("utf-8", "replace") for x in bnames]
|
zls = [x for x in zls if not ptn.match(x)]
|
||||||
locs = [vpath + srd + "/" + x for x in names]
|
lnodes = [x.split(b"/")[-1] for x in zls]
|
||||||
print("DELETING ~{0}/#{1}".format(srd, len(names)))
|
bnames = [x for x in ls if x not in lnodes and x != b".hist"]
|
||||||
req_ses.post(self.ar.url + "?delete", json=locs)
|
vpath = self.ar.url.split("://")[-1].split("/", 1)[-1]
|
||||||
|
names = [x.decode("utf-8", "replace") for x in bnames]
|
||||||
|
locs = [vpath + srd + "/" + x for x in names]
|
||||||
|
while locs:
|
||||||
|
req = locs
|
||||||
|
while req:
|
||||||
|
print("DELETING ~%s/#%s" % (srd, len(req)))
|
||||||
|
r = req_ses.post(self.ar.url + "?delete", json=req)
|
||||||
|
if r.status_code == 413 and "json 2big" in r.text:
|
||||||
|
print(" (delete request too big; slicing...)")
|
||||||
|
req = req[: len(req) // 2]
|
||||||
|
continue
|
||||||
|
elif not r:
|
||||||
|
t = "delete request failed: %r %s"
|
||||||
|
raise Exception(t % (r, r.text))
|
||||||
|
break
|
||||||
|
locs = locs[len(req) :]
|
||||||
|
|
||||||
if isdir:
|
if isdir:
|
||||||
continue
|
continue
|
||||||
@@ -949,11 +1048,42 @@ class Ctl(object):
|
|||||||
self.hash_f += 1
|
self.hash_f += 1
|
||||||
self.hash_c += len(file.cids)
|
self.hash_c += len(file.cids)
|
||||||
self.hash_b += file.size
|
self.hash_b += file.size
|
||||||
|
if self.ar.wlist:
|
||||||
|
self.up_f = self.hash_f
|
||||||
|
self.up_c = self.hash_c
|
||||||
|
self.up_b = self.hash_b
|
||||||
|
|
||||||
|
if self.ar.wlist:
|
||||||
|
zsl = [self.ar.wsalt, str(file.size)] + [x[0] for x in file.kchunks]
|
||||||
|
zb = hashlib.sha512("\n".join(zsl).encode("utf-8")).digest()[:33]
|
||||||
|
wark = base64.urlsafe_b64encode(zb).decode("utf-8")
|
||||||
|
vp = file.rel.decode("utf-8")
|
||||||
|
if self.ar.jw:
|
||||||
|
print("%s %s" % (wark, vp))
|
||||||
|
else:
|
||||||
|
zd = datetime.datetime.fromtimestamp(file.lmod, UTC)
|
||||||
|
dt = "%04d-%02d-%02d %02d:%02d:%02d" % (
|
||||||
|
zd.year,
|
||||||
|
zd.month,
|
||||||
|
zd.day,
|
||||||
|
zd.hour,
|
||||||
|
zd.minute,
|
||||||
|
zd.second,
|
||||||
|
)
|
||||||
|
print("%s %12d %s %s" % (dt, file.size, wark, vp))
|
||||||
|
continue
|
||||||
|
|
||||||
self.q_handshake.put(file)
|
self.q_handshake.put(file)
|
||||||
|
|
||||||
self.hasher_busy = 0
|
|
||||||
self.st_hash = [None, "(finished)"]
|
self.st_hash = [None, "(finished)"]
|
||||||
|
self._check_if_done()
|
||||||
|
|
||||||
|
def _check_if_done(self):
|
||||||
|
with self.mutex:
|
||||||
|
if self.nfiles - self.up_f:
|
||||||
|
return
|
||||||
|
for _ in range(self.ar.j):
|
||||||
|
self.q_handshake.put(None)
|
||||||
|
|
||||||
def handshaker(self):
|
def handshaker(self):
|
||||||
search = self.ar.s
|
search = self.ar.s
|
||||||
@@ -961,8 +1091,10 @@ class Ctl(object):
|
|||||||
while True:
|
while True:
|
||||||
file = self.q_handshake.get()
|
file = self.q_handshake.get()
|
||||||
if not file:
|
if not file:
|
||||||
|
with self.mutex:
|
||||||
|
self.handshaker_alive -= 1
|
||||||
self.q_upload.put(None)
|
self.q_upload.put(None)
|
||||||
break
|
return
|
||||||
|
|
||||||
upath = file.abs.decode("utf-8", "replace")
|
upath = file.abs.decode("utf-8", "replace")
|
||||||
if not VT100:
|
if not VT100:
|
||||||
@@ -974,9 +1106,6 @@ class Ctl(object):
|
|||||||
self.errs += 1
|
self.errs += 1
|
||||||
continue
|
continue
|
||||||
|
|
||||||
with self.mutex:
|
|
||||||
self.handshaker_busy += 1
|
|
||||||
|
|
||||||
while time.time() < file.cd:
|
while time.time() < file.cd:
|
||||||
time.sleep(0.1)
|
time.sleep(0.1)
|
||||||
|
|
||||||
@@ -984,17 +1113,17 @@ class Ctl(object):
|
|||||||
if search:
|
if search:
|
||||||
if hs:
|
if hs:
|
||||||
for hit in hs:
|
for hit in hs:
|
||||||
t = "found: {0}\n {1}{2}\n"
|
t = "found: {0}\n {1}{2}"
|
||||||
print(t.format(upath, burl, hit["rp"]), end="")
|
print(t.format(upath, burl, hit["rp"]))
|
||||||
else:
|
else:
|
||||||
print("NOT found: {0}\n".format(upath), end="")
|
print("NOT found: {0}".format(upath))
|
||||||
|
|
||||||
with self.mutex:
|
with self.mutex:
|
||||||
self.up_f += 1
|
self.up_f += 1
|
||||||
self.up_c += len(file.cids)
|
self.up_c += len(file.cids)
|
||||||
self.up_b += file.size
|
self.up_b += file.size
|
||||||
self.handshaker_busy -= 1
|
|
||||||
|
|
||||||
|
self._check_if_done()
|
||||||
continue
|
continue
|
||||||
|
|
||||||
if file.recheck:
|
if file.recheck:
|
||||||
@@ -1026,58 +1155,110 @@ class Ctl(object):
|
|||||||
file.up_b -= sz
|
file.up_b -= sz
|
||||||
|
|
||||||
file.ucids = hs
|
file.ucids = hs
|
||||||
self.handshaker_busy -= 1
|
|
||||||
|
|
||||||
if not hs:
|
if not hs:
|
||||||
kw = "uploaded" if file.up_b else " found"
|
self.at_hash += file.t_hash
|
||||||
print("{0} {1}".format(kw, upath))
|
|
||||||
for cid in hs:
|
if self.ar.spd:
|
||||||
self.q_upload.put([file, cid])
|
if VT100:
|
||||||
|
c1 = "\033[36m"
|
||||||
|
c2 = "\033[0m"
|
||||||
|
else:
|
||||||
|
c1 = c2 = ""
|
||||||
|
|
||||||
|
spd_h = humansize(file.size / file.t_hash, True)
|
||||||
|
if file.up_b:
|
||||||
|
t_up = file.t1_up - file.t0_up
|
||||||
|
spd_u = humansize(file.size / t_up, True)
|
||||||
|
|
||||||
|
t = "uploaded %s %s(h:%.2fs,%s/s,up:%.2fs,%s/s)%s"
|
||||||
|
print(t % (upath, c1, file.t_hash, spd_h, t_up, spd_u, c2))
|
||||||
|
else:
|
||||||
|
t = " found %s %s(%.2fs,%s/s)%s"
|
||||||
|
print(t % (upath, c1, file.t_hash, spd_h, c2))
|
||||||
|
else:
|
||||||
|
kw = "uploaded" if file.up_b else " found"
|
||||||
|
print("{0} {1}".format(kw, upath))
|
||||||
|
|
||||||
|
self._check_if_done()
|
||||||
|
continue
|
||||||
|
|
||||||
|
chunksz = up2k_chunksize(file.size)
|
||||||
|
njoin = (self.ar.sz * 1024 * 1024) // chunksz
|
||||||
|
cs = hs[:]
|
||||||
|
while cs:
|
||||||
|
fsl = FileSlice(file, cs[:1])
|
||||||
|
try:
|
||||||
|
if file.nojoin:
|
||||||
|
raise Exception()
|
||||||
|
for n in range(2, min(len(cs), njoin + 1)):
|
||||||
|
fsl = FileSlice(file, cs[:n])
|
||||||
|
except:
|
||||||
|
pass
|
||||||
|
cs = cs[len(fsl.cids) :]
|
||||||
|
self.q_upload.put(fsl)
|
||||||
|
|
||||||
def uploader(self):
|
def uploader(self):
|
||||||
while True:
|
while True:
|
||||||
task = self.q_upload.get()
|
fsl = self.q_upload.get()
|
||||||
if not task:
|
if not fsl:
|
||||||
self.st_up = [None, "(finished)"]
|
done = False
|
||||||
break
|
with self.mutex:
|
||||||
|
self.uploader_alive -= 1
|
||||||
|
if not self.uploader_alive:
|
||||||
|
done = not self.handshaker_alive
|
||||||
|
self.st_up = [None, "(finished)"]
|
||||||
|
if done:
|
||||||
|
with self.exit_cond:
|
||||||
|
self.exit_cond.notify_all()
|
||||||
|
return
|
||||||
|
|
||||||
|
file = fsl.file
|
||||||
|
cids = fsl.cids
|
||||||
|
|
||||||
with self.mutex:
|
with self.mutex:
|
||||||
|
if not self.uploader_busy:
|
||||||
|
self.at_upr = time.time()
|
||||||
self.uploader_busy += 1
|
self.uploader_busy += 1
|
||||||
self.t0_up = self.t0_up or time.time()
|
if not file.t0_up:
|
||||||
|
file.t0_up = time.time()
|
||||||
|
if not self.t0_up:
|
||||||
|
self.t0_up = file.t0_up
|
||||||
|
|
||||||
zs = "{0}/{1}/{2}/{3} {4}/{5} {6}"
|
stats = "%d/%d/%d/%d %d/%d %s" % (
|
||||||
stats = zs.format(
|
|
||||||
self.up_f,
|
self.up_f,
|
||||||
len(self.recheck),
|
len(self.recheck),
|
||||||
self.uploader_busy,
|
self.uploader_busy,
|
||||||
self.nfiles - self.up_f,
|
self.nfiles - self.up_f,
|
||||||
int(self.nbytes / (1024 * 1024)),
|
self.nbytes // (1024 * 1024),
|
||||||
int((self.nbytes - self.up_b) / (1024 * 1024)),
|
(self.nbytes - self.up_b) // (1024 * 1024),
|
||||||
self.eta,
|
self.eta,
|
||||||
)
|
)
|
||||||
|
|
||||||
file, cid = task
|
|
||||||
try:
|
try:
|
||||||
upload(file, cid, self.ar.a, stats)
|
upload(fsl, self.ar.a, stats)
|
||||||
except Exception as ex:
|
except Exception as ex:
|
||||||
t = "upload failed, retrying: {0} #{1} ({2})\n"
|
t = "upload failed, retrying: %s #%s+%d (%s)\n"
|
||||||
eprint(t.format(file.name, cid[:8], ex))
|
eprint(t % (file.name, cids[0][:8], len(cids) - 1, ex))
|
||||||
file.cd = time.time() + self.ar.cd
|
file.cd = time.time() + self.ar.cd
|
||||||
# handshake will fix it
|
# handshake will fix it
|
||||||
|
|
||||||
with self.mutex:
|
with self.mutex:
|
||||||
sz = file.kchunks[cid][1]
|
sz = fsl.len
|
||||||
file.ucids = [x for x in file.ucids if x != cid]
|
file.ucids = [x for x in file.ucids if x not in cids]
|
||||||
if not file.ucids:
|
if not file.ucids:
|
||||||
|
file.t1_up = time.time()
|
||||||
self.q_handshake.put(file)
|
self.q_handshake.put(file)
|
||||||
|
|
||||||
self.st_up = [file, cid]
|
self.st_up = [file, cids[0]]
|
||||||
file.up_b += sz
|
file.up_b += sz
|
||||||
self.up_b += sz
|
self.up_b += sz
|
||||||
self.up_br += sz
|
self.up_br += sz
|
||||||
file.up_c += 1
|
file.up_c += 1
|
||||||
self.up_c += 1
|
self.up_c += 1
|
||||||
self.uploader_busy -= 1
|
self.uploader_busy -= 1
|
||||||
|
if not self.uploader_busy:
|
||||||
|
self.at_up += time.time() - self.at_upr
|
||||||
|
|
||||||
def up_done(self, file):
|
def up_done(self, file):
|
||||||
if self.ar.dl:
|
if self.ar.dl:
|
||||||
@@ -1114,10 +1295,13 @@ source file/folder selection uses rsync syntax, meaning that:
|
|||||||
ap.add_argument("url", type=unicode, help="server url, including destination folder")
|
ap.add_argument("url", type=unicode, help="server url, including destination folder")
|
||||||
ap.add_argument("files", type=unicode, nargs="+", help="files and/or folders to process")
|
ap.add_argument("files", type=unicode, nargs="+", help="files and/or folders to process")
|
||||||
ap.add_argument("-v", action="store_true", help="verbose")
|
ap.add_argument("-v", action="store_true", help="verbose")
|
||||||
ap.add_argument("-a", metavar="PASSWORD", help="password or $filepath")
|
ap.add_argument("-a", metavar="PASSWD", help="password or $filepath")
|
||||||
ap.add_argument("-s", action="store_true", help="file-search (disables upload)")
|
ap.add_argument("-s", action="store_true", help="file-search (disables upload)")
|
||||||
ap.add_argument("-x", type=unicode, metavar="REGEX", default="", help="skip file if filesystem-abspath matches REGEX, example: '.*/\.hist/.*'")
|
ap.add_argument("-x", type=unicode, metavar="REGEX", action="append", help="skip file if filesystem-abspath matches REGEX (option can be repeated), example: '.*/\\.hist/.*'")
|
||||||
ap.add_argument("--ok", action="store_true", help="continue even if some local files are inaccessible")
|
ap.add_argument("--ok", action="store_true", help="continue even if some local files are inaccessible")
|
||||||
|
ap.add_argument("--touch", action="store_true", help="if last-modified timestamps differ, push local to server (need write+delete perms)")
|
||||||
|
ap.add_argument("--ow", action="store_true", help="overwrite existing files instead of autorenaming")
|
||||||
|
ap.add_argument("--spd", action="store_true", help="print speeds for each file")
|
||||||
ap.add_argument("--version", action="store_true", help="show version and exit")
|
ap.add_argument("--version", action="store_true", help="show version and exit")
|
||||||
|
|
||||||
ap = app.add_argument_group("compatibility")
|
ap = app.add_argument_group("compatibility")
|
||||||
@@ -1126,12 +1310,17 @@ source file/folder selection uses rsync syntax, meaning that:
|
|||||||
|
|
||||||
ap = app.add_argument_group("folder sync")
|
ap = app.add_argument_group("folder sync")
|
||||||
ap.add_argument("--dl", action="store_true", help="delete local files after uploading")
|
ap.add_argument("--dl", action="store_true", help="delete local files after uploading")
|
||||||
ap.add_argument("--dr", action="store_true", help="delete remote files which don't exist locally")
|
ap.add_argument("--dr", action="store_true", help="delete remote files which don't exist locally (implies --ow)")
|
||||||
ap.add_argument("--drd", action="store_true", help="delete remote files during upload instead of afterwards; reduces peak disk space usage, but will reupload instead of detecting renames")
|
ap.add_argument("--drd", action="store_true", help="delete remote files during upload instead of afterwards; reduces peak disk space usage, but will reupload instead of detecting renames")
|
||||||
|
|
||||||
|
ap = app.add_argument_group("file-ID calculator; enable with url '-' to list warks (file identifiers) instead of upload/search")
|
||||||
|
ap.add_argument("--wsalt", type=unicode, metavar="S", default="hunter2", help="salt to use when creating warks; must match server config")
|
||||||
|
ap.add_argument("--jw", action="store_true", help="just identifier+filepath, not mtime/size too")
|
||||||
|
|
||||||
ap = app.add_argument_group("performance tweaks")
|
ap = app.add_argument_group("performance tweaks")
|
||||||
ap.add_argument("-j", type=int, metavar="THREADS", default=4, help="parallel connections")
|
ap.add_argument("-j", type=int, metavar="CONNS", default=2, help="parallel connections")
|
||||||
ap.add_argument("-J", type=int, metavar="THREADS", default=hcores, help="num cpu-cores to use for hashing; set 0 or 1 for single-core hashing")
|
ap.add_argument("-J", type=int, metavar="CORES", default=hcores, help="num cpu-cores to use for hashing; set 0 or 1 for single-core hashing")
|
||||||
|
ap.add_argument("--sz", type=int, metavar="MiB", default=64, help="try to make each POST this big")
|
||||||
ap.add_argument("-nh", action="store_true", help="disable hashing while uploading")
|
ap.add_argument("-nh", action="store_true", help="disable hashing while uploading")
|
||||||
ap.add_argument("-ns", action="store_true", help="no status panel (for slow consoles and macos)")
|
ap.add_argument("-ns", action="store_true", help="no status panel (for slow consoles and macos)")
|
||||||
ap.add_argument("--cd", type=float, metavar="SEC", default=5, help="delay before reattempting a failed handshake/upload")
|
ap.add_argument("--cd", type=float, metavar="SEC", default=5, help="delay before reattempting a failed handshake/upload")
|
||||||
@@ -1139,7 +1328,7 @@ source file/folder selection uses rsync syntax, meaning that:
|
|||||||
ap.add_argument("-z", action="store_true", help="ZOOMIN' (skip uploading files if they exist at the destination with the ~same last-modified timestamp, so same as yolo / turbo with date-chk but even faster)")
|
ap.add_argument("-z", action="store_true", help="ZOOMIN' (skip uploading files if they exist at the destination with the ~same last-modified timestamp, so same as yolo / turbo with date-chk but even faster)")
|
||||||
|
|
||||||
ap = app.add_argument_group("tls")
|
ap = app.add_argument_group("tls")
|
||||||
ap.add_argument("-te", metavar="PEM_FILE", help="certificate to expect/verify")
|
ap.add_argument("-te", metavar="PATH", help="path to ca.pem or cert.pem to expect/verify")
|
||||||
ap.add_argument("-td", action="store_true", help="disable certificate check")
|
ap.add_argument("-td", action="store_true", help="disable certificate check")
|
||||||
# fmt: on
|
# fmt: on
|
||||||
|
|
||||||
@@ -1156,7 +1345,14 @@ source file/folder selection uses rsync syntax, meaning that:
|
|||||||
if ar.drd:
|
if ar.drd:
|
||||||
ar.dr = True
|
ar.dr = True
|
||||||
|
|
||||||
for k in "dl dr drd".split():
|
if ar.dr:
|
||||||
|
ar.ow = True
|
||||||
|
|
||||||
|
ar.x = "|".join(ar.x or [])
|
||||||
|
|
||||||
|
setattr(ar, "wlist", ar.url == "-")
|
||||||
|
|
||||||
|
for k in "dl dr drd wlist".split():
|
||||||
errs = []
|
errs = []
|
||||||
if ar.safe and getattr(ar, k):
|
if ar.safe and getattr(ar, k):
|
||||||
errs.append(k)
|
errs.append(k)
|
||||||
@@ -1174,6 +1370,14 @@ source file/folder selection uses rsync syntax, meaning that:
|
|||||||
if "://" not in ar.url:
|
if "://" not in ar.url:
|
||||||
ar.url = "http://" + ar.url
|
ar.url = "http://" + ar.url
|
||||||
|
|
||||||
|
if "https://" in ar.url.lower():
|
||||||
|
try:
|
||||||
|
import ssl, zipfile
|
||||||
|
except:
|
||||||
|
t = "ERROR: https is not available for some reason; please use http"
|
||||||
|
print("\n\n %s\n\n" % (t,))
|
||||||
|
raise
|
||||||
|
|
||||||
if ar.a and ar.a.startswith("$"):
|
if ar.a and ar.a.startswith("$"):
|
||||||
fn = ar.a[1:]
|
fn = ar.a[1:]
|
||||||
print("reading password from file [{0}]".format(fn))
|
print("reading password from file [{0}]".format(fn))
|
||||||
|
|||||||
@@ -66,7 +66,7 @@ def main():
|
|||||||
ofs = ln.find("{")
|
ofs = ln.find("{")
|
||||||
j = json.loads(ln[ofs:])
|
j = json.loads(ln[ofs:])
|
||||||
except:
|
except:
|
||||||
pass
|
continue
|
||||||
|
|
||||||
w = j["wark"]
|
w = j["wark"]
|
||||||
if db.execute("select w from up where w = ?", (w,)).fetchone():
|
if db.execute("select w from up where w = ?", (w,)).fetchone():
|
||||||
|
|||||||
@@ -16,11 +16,16 @@
|
|||||||
* sharex config file to upload screenshots and grab the URL
|
* sharex config file to upload screenshots and grab the URL
|
||||||
* `RequestURL`: full URL to the target folder
|
* `RequestURL`: full URL to the target folder
|
||||||
* `pw`: password (remove the `pw` line if anon-write)
|
* `pw`: password (remove the `pw` line if anon-write)
|
||||||
|
* the `act:bput` thing is optional since copyparty v1.9.29
|
||||||
|
* using an older sharex version, maybe sharex v12.1.1 for example? dw fam i got your back 👉😎👉 [`sharex12.sxcu`](sharex12.sxcu)
|
||||||
|
|
||||||
however if your copyparty is behind a reverse-proxy, you may want to use [`sharex-html.sxcu`](sharex-html.sxcu) instead:
|
### [`flameshot.sh`](flameshot.sh)
|
||||||
* `RequestURL`: full URL to the target folder
|
* takes a screenshot with [flameshot](https://flameshot.org/) on Linux, uploads it, and writes the URL to clipboard
|
||||||
* `URL`: full URL to the root folder (with trailing slash) followed by `$regex:1|1$`
|
|
||||||
* `pw`: password (remove `Parameters` if anon-write)
|
### [`send-to-cpp.contextlet.json`](send-to-cpp.contextlet.json)
|
||||||
|
* browser integration, kind of? custom rightclick actions and stuff
|
||||||
|
* rightclick a pic and send it to copyparty straight from your browser
|
||||||
|
* for the [contextlet](https://addons.mozilla.org/en-US/firefox/addon/contextlets/) firefox extension
|
||||||
|
|
||||||
### [`media-osd-bgone.ps1`](media-osd-bgone.ps1)
|
### [`media-osd-bgone.ps1`](media-osd-bgone.ps1)
|
||||||
* disables the [windows OSD popup](https://user-images.githubusercontent.com/241032/122821375-0e08df80-d2dd-11eb-9fd9-184e8aacf1d0.png) (the thing on the left) which appears every time you hit media hotkeys to adjust volume or change song while playing music with the copyparty web-ui, or most other audio players really
|
* disables the [windows OSD popup](https://user-images.githubusercontent.com/241032/122821375-0e08df80-d2dd-11eb-9fd9-184e8aacf1d0.png) (the thing on the left) which appears every time you hit media hotkeys to adjust volume or change song while playing music with the copyparty web-ui, or most other audio players really
|
||||||
|
|||||||
14
contrib/flameshot.sh
Executable file
14
contrib/flameshot.sh
Executable file
@@ -0,0 +1,14 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
set -e
|
||||||
|
|
||||||
|
# take a screenshot with flameshot and send it to copyparty;
|
||||||
|
# the image url will be placed on your clipboard
|
||||||
|
|
||||||
|
password=wark
|
||||||
|
url=https://a.ocv.me/up/
|
||||||
|
filename=$(date +%Y-%m%d-%H%M%S).png
|
||||||
|
|
||||||
|
flameshot gui -s -r |
|
||||||
|
curl -T- $url$filename?pw=$password |
|
||||||
|
tail -n 1 |
|
||||||
|
xsel -ib
|
||||||
@@ -1,29 +1,60 @@
|
|||||||
# when running copyparty behind a reverse proxy,
|
# look for "max clients:" when starting copyparty, as nginx should
|
||||||
# the following arguments are recommended:
|
# not accept more consecutive clients than what copyparty is able to;
|
||||||
#
|
|
||||||
# -i 127.0.0.1 only accept connections from nginx
|
|
||||||
#
|
|
||||||
# -nc must match or exceed the webserver's max number of concurrent clients;
|
|
||||||
# copyparty default is 1024 if OS permits it (see "max clients:" on startup),
|
|
||||||
# nginx default is 512 (worker_processes 1, worker_connections 512)
|
# nginx default is 512 (worker_processes 1, worker_connections 512)
|
||||||
#
|
#
|
||||||
# you may also consider adding -j0 for CPU-intensive configurations
|
# rarely, in some extreme usecases, it can be good to add -j0
|
||||||
# (5'000 requests per second, or 20gbps upload/download in parallel)
|
# (40'000 requests per second, or 20gbps upload/download in parallel)
|
||||||
|
# but this is usually counterproductive and slightly buggy
|
||||||
#
|
#
|
||||||
# on fedora/rhel, remember to setsebool -P httpd_can_network_connect 1
|
# on fedora/rhel, remember to setsebool -P httpd_can_network_connect 1
|
||||||
|
#
|
||||||
|
# if you are behind cloudflare (or another protection service),
|
||||||
|
# remember to reject all connections which are not coming from your
|
||||||
|
# protection service -- for cloudflare in particular, you can
|
||||||
|
# generate the list of permitted IP ranges like so:
|
||||||
|
# (curl -s https://www.cloudflare.com/ips-v{4,6} | sed 's/^/allow /; s/$/;/'; echo; echo "deny all;") > /etc/nginx/cloudflare-only.conf
|
||||||
|
#
|
||||||
|
# and then enable it below by uncomenting the cloudflare-only.conf line
|
||||||
|
|
||||||
|
|
||||||
|
upstream cpp_tcp {
|
||||||
|
# alternative 1: connect to copyparty using tcp;
|
||||||
|
# cpp_uds is slightly faster and more secure, but
|
||||||
|
# cpp_tcp is easier to setup and "just works"
|
||||||
|
# ...you should however restrict copyparty to only
|
||||||
|
# accept connections from nginx by adding these args:
|
||||||
|
# -i 127.0.0.1
|
||||||
|
|
||||||
upstream cpp {
|
|
||||||
server 127.0.0.1:3923 fail_timeout=1s;
|
server 127.0.0.1:3923 fail_timeout=1s;
|
||||||
keepalive 1;
|
keepalive 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
upstream cpp_uds {
|
||||||
|
# alternative 2: unix-socket, aka. "unix domain socket";
|
||||||
|
# 5-10% faster, and better isolation from other software,
|
||||||
|
# but there must be at least one unix-group which both
|
||||||
|
# nginx and copyparty is a member of; if that group is
|
||||||
|
# "www" then run copyparty with the following args:
|
||||||
|
# -i unix:770:www:/tmp/party.sock
|
||||||
|
|
||||||
|
server unix:/tmp/party.sock fail_timeout=1s;
|
||||||
|
keepalive 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
server {
|
server {
|
||||||
listen 443 ssl;
|
listen 443 ssl;
|
||||||
listen [::]:443 ssl;
|
listen [::]:443 ssl;
|
||||||
|
|
||||||
server_name fs.example.com;
|
server_name fs.example.com;
|
||||||
|
|
||||||
|
# uncomment the following line to reject non-cloudflare connections, ensuring client IPs cannot be spoofed:
|
||||||
|
#include /etc/nginx/cloudflare-only.conf;
|
||||||
|
|
||||||
location / {
|
location / {
|
||||||
proxy_pass http://cpp;
|
# recommendation: replace cpp_tcp with cpp_uds below
|
||||||
|
proxy_pass http://cpp_tcp;
|
||||||
proxy_redirect off;
|
proxy_redirect off;
|
||||||
# disable buffering (next 4 lines)
|
# disable buffering (next 4 lines)
|
||||||
proxy_http_version 1.1;
|
proxy_http_version 1.1;
|
||||||
@@ -41,6 +72,7 @@ server {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
# default client_max_body_size (1M) blocks uploads larger than 256 MiB
|
# default client_max_body_size (1M) blocks uploads larger than 256 MiB
|
||||||
client_max_body_size 1024M;
|
client_max_body_size 1024M;
|
||||||
client_header_timeout 610m;
|
client_header_timeout 610m;
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
# Maintainer: icxes <dev.null@need.moe>
|
# Maintainer: icxes <dev.null@need.moe>
|
||||||
pkgname=copyparty
|
pkgname=copyparty
|
||||||
pkgver="1.9.20"
|
pkgver="1.15.1"
|
||||||
pkgrel=1
|
pkgrel=1
|
||||||
pkgdesc="File server with accelerated resumable uploads, dedup, WebDAV, FTP, zeroconf, media indexer, thumbnails++"
|
pkgdesc="File server with accelerated resumable uploads, dedup, WebDAV, FTP, TFTP, zeroconf, media indexer, thumbnails++"
|
||||||
arch=("any")
|
arch=("any")
|
||||||
url="https://github.com/9001/${pkgname}"
|
url="https://github.com/9001/${pkgname}"
|
||||||
license=('MIT')
|
license=('MIT')
|
||||||
@@ -21,7 +21,7 @@ optdepends=("ffmpeg: thumbnails for videos, images (slower) and audio, music tag
|
|||||||
)
|
)
|
||||||
source=("https://github.com/9001/${pkgname}/releases/download/v${pkgver}/${pkgname}-${pkgver}.tar.gz")
|
source=("https://github.com/9001/${pkgname}/releases/download/v${pkgver}/${pkgname}-${pkgver}.tar.gz")
|
||||||
backup=("etc/${pkgname}.d/init" )
|
backup=("etc/${pkgname}.d/init" )
|
||||||
sha256sums=("e0e267e222a22fc21491bd1a902318e095e90189ef8ece90a7aba000f730d279")
|
sha256sums=("5fb048fe7e2aa5ad18c9cdb333af3ee5e51c338efa74b34aa8aa444675eac913")
|
||||||
|
|
||||||
build() {
|
build() {
|
||||||
cd "${srcdir}/${pkgname}-${pkgver}"
|
cd "${srcdir}/${pkgname}-${pkgver}"
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
{
|
{
|
||||||
"url": "https://github.com/9001/copyparty/releases/download/v1.9.20/copyparty-sfx.py",
|
"url": "https://github.com/9001/copyparty/releases/download/v1.15.1/copyparty-sfx.py",
|
||||||
"version": "1.9.20",
|
"version": "1.15.1",
|
||||||
"hash": "sha256-KZIM5r/zi6+7I9MvYZXI9v+DQPROzg8ApGnPgEwx8BY="
|
"hash": "sha256-i4S/TmuAphv/wbndfoSUYztNqO+o+qh/v8GcslxWWUk="
|
||||||
}
|
}
|
||||||
@@ -20,6 +20,13 @@ point `--js-browser` to one of these by URL:
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
## example any-js
|
||||||
|
point `--js-browser` and/or `--js-other` to one of these by URL:
|
||||||
|
|
||||||
|
* [`banner.js`](banner.js) shows a very enterprise [legal-banner](https://github.com/user-attachments/assets/8ae8e087-b209-449c-b08d-74e040f0284b)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
## example browser-css
|
## example browser-css
|
||||||
point `--css-browser` to one of these by URL:
|
point `--css-browser` to one of these by URL:
|
||||||
|
|
||||||
|
|||||||
93
contrib/plugins/banner.js
Normal file
93
contrib/plugins/banner.js
Normal file
@@ -0,0 +1,93 @@
|
|||||||
|
(function() {
|
||||||
|
|
||||||
|
// usage: copy this to '.banner.js' in your webroot,
|
||||||
|
// and run copyparty with the following arguments:
|
||||||
|
// --js-browser /.banner.js --js-other /.banner.js
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
// had to pick the most chuuni one as the default
|
||||||
|
var bannertext = '' +
|
||||||
|
'<h3>You are accessing a U.S. Government (USG) Information System (IS) that is provided for USG-authorized use only.</h3>' +
|
||||||
|
'<p>By using this IS (which includes any device attached to this IS), you consent to the following conditions:</p>' +
|
||||||
|
'<ul>' +
|
||||||
|
'<li>The USG routinely intercepts and monitors communications on this IS for purposes including, but not limited to, penetration testing, COMSEC monitoring, network operations and defense, personnel misconduct (PM), law enforcement (LE), and counterintelligence (CI) investigations.</li>' +
|
||||||
|
'<li>At any time, the USG may inspect and seize data stored on this IS.</li>' +
|
||||||
|
'<li>Communications using, or data stored on, this IS are not private, are subject to routine monitoring, interception, and search, and may be disclosed or used for any USG-authorized purpose.</li>' +
|
||||||
|
'<li>This IS includes security measures (e.g., authentication and access controls) to protect USG interests -- not for your personal benefit or privacy.</li>' +
|
||||||
|
'<li>Notwithstanding the above, using this IS does not constitute consent to PM, LE or CI investigative searching or monitoring of the content of privileged communications, or work product, related to personal representation or services by attorneys, psychotherapists, or clergy, and their assistants. Such communications and work product are private and confidential. See User Agreement for details.</li>' +
|
||||||
|
'</ul>';
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
// fancy div to insert into pages
|
||||||
|
function bannerdiv(border) {
|
||||||
|
var ret = mknod('div', null, bannertext);
|
||||||
|
if (border)
|
||||||
|
ret.setAttribute("style", "border:1em solid var(--fg); border-width:.3em 0; margin:3em 0");
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
// keep all of these false and then selectively enable them in the if-blocks below
|
||||||
|
var show_msgbox = false,
|
||||||
|
login_top = false,
|
||||||
|
top = false,
|
||||||
|
bottom = false,
|
||||||
|
top_bordered = false,
|
||||||
|
bottom_bordered = false;
|
||||||
|
|
||||||
|
if (QS("h1#cc") && QS("a#k")) {
|
||||||
|
// this is the controlpanel
|
||||||
|
// (you probably want to keep just one of these enabled)
|
||||||
|
show_msgbox = true;
|
||||||
|
login_top = true;
|
||||||
|
bottom = true;
|
||||||
|
}
|
||||||
|
else if (ebi("swin") && ebi("smac")) {
|
||||||
|
// this is the connect-page, same deal here
|
||||||
|
show_msgbox = true;
|
||||||
|
top_bordered = true;
|
||||||
|
bottom_bordered = true;
|
||||||
|
}
|
||||||
|
else if (ebi("op_cfg") || ebi("div#mw") ) {
|
||||||
|
// we're running in the main filebrowser (op_cfg) or markdown-viewer/editor (div#mw),
|
||||||
|
// fragile pages which break if you do something too fancy
|
||||||
|
show_msgbox = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
// shows a fullscreen messagebox; works on all pages
|
||||||
|
if (show_msgbox) {
|
||||||
|
var now = Math.floor(Date.now() / 1000),
|
||||||
|
last_shown = sread("bannerts") || 0;
|
||||||
|
|
||||||
|
// 60 * 60 * 17 = 17 hour cooldown
|
||||||
|
if (now - last_shown > 60 * 60 * 17) {
|
||||||
|
swrite("bannerts", now);
|
||||||
|
modal.confirm(bannertext, null, function () {
|
||||||
|
location = 'https://this-page-intentionally-left-blank.org/';
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// show a message on the page footer; only works on the connect-page
|
||||||
|
if (top || top_bordered) {
|
||||||
|
var dst = ebi('wrap');
|
||||||
|
dst.insertBefore(bannerdiv(top_bordered), dst.firstChild);
|
||||||
|
}
|
||||||
|
|
||||||
|
// show a message on the page footer; only works on the controlpanel and connect-page
|
||||||
|
if (bottom || bottom_bordered) {
|
||||||
|
ebi('wrap').appendChild(bannerdiv(bottom_bordered));
|
||||||
|
}
|
||||||
|
|
||||||
|
// show a message on the top of the page; only works on the controlpanel
|
||||||
|
if (login_top) {
|
||||||
|
var dst = QS('h1');
|
||||||
|
dst.parentNode.insertBefore(bannerdiv(false), dst);
|
||||||
|
}
|
||||||
|
|
||||||
|
})();
|
||||||
11
contrib/send-to-cpp.contextlet.json
Normal file
11
contrib/send-to-cpp.contextlet.json
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
{
|
||||||
|
"code": "// https://addons.mozilla.org/en-US/firefox/addon/contextlets/\n// https://github.com/davidmhammond/contextlets\n\nvar url = 'http://partybox.local:3923/';\nvar pw = 'wark';\n\nvar xhr = new XMLHttpRequest();\nxhr.msg = this.info.linkUrl || this.info.srcUrl;\nxhr.open('POST', url, true);\nxhr.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded;charset=UTF-8');\nxhr.setRequestHeader('PW', pw);\nxhr.send('msg=' + xhr.msg);\n",
|
||||||
|
"contexts": [
|
||||||
|
"link"
|
||||||
|
],
|
||||||
|
"icons": null,
|
||||||
|
"patterns": "",
|
||||||
|
"scope": "background",
|
||||||
|
"title": "send to cpp",
|
||||||
|
"type": "normal"
|
||||||
|
}
|
||||||
@@ -1,19 +0,0 @@
|
|||||||
{
|
|
||||||
"Version": "13.5.0",
|
|
||||||
"Name": "copyparty-html",
|
|
||||||
"DestinationType": "ImageUploader",
|
|
||||||
"RequestMethod": "POST",
|
|
||||||
"RequestURL": "http://127.0.0.1:3923/sharex",
|
|
||||||
"Parameters": {
|
|
||||||
"pw": "wark"
|
|
||||||
},
|
|
||||||
"Body": "MultipartFormData",
|
|
||||||
"Arguments": {
|
|
||||||
"act": "bput"
|
|
||||||
},
|
|
||||||
"FileFormName": "f",
|
|
||||||
"RegexList": [
|
|
||||||
"bytes // <a href=\"/([^\"]+)\""
|
|
||||||
],
|
|
||||||
"URL": "http://127.0.0.1:3923/$regex:1|1$"
|
|
||||||
}
|
|
||||||
@@ -1,17 +1,19 @@
|
|||||||
{
|
{
|
||||||
"Version": "13.5.0",
|
"Version": "15.0.0",
|
||||||
"Name": "copyparty",
|
"Name": "copyparty",
|
||||||
"DestinationType": "ImageUploader",
|
"DestinationType": "ImageUploader",
|
||||||
"RequestMethod": "POST",
|
"RequestMethod": "POST",
|
||||||
"RequestURL": "http://127.0.0.1:3923/sharex",
|
"RequestURL": "http://127.0.0.1:3923/sharex",
|
||||||
"Parameters": {
|
"Parameters": {
|
||||||
"pw": "wark",
|
|
||||||
"j": null
|
"j": null
|
||||||
},
|
},
|
||||||
|
"Headers": {
|
||||||
|
"pw": "PUT_YOUR_PASSWORD_HERE_MY_DUDE"
|
||||||
|
},
|
||||||
"Body": "MultipartFormData",
|
"Body": "MultipartFormData",
|
||||||
"Arguments": {
|
"Arguments": {
|
||||||
"act": "bput"
|
"act": "bput"
|
||||||
},
|
},
|
||||||
"FileFormName": "f",
|
"FileFormName": "f",
|
||||||
"URL": "$json:files[0].url$"
|
"URL": "{json:files[0].url}"
|
||||||
}
|
}
|
||||||
|
|||||||
13
contrib/sharex12.sxcu
Normal file
13
contrib/sharex12.sxcu
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
{
|
||||||
|
"Name": "copyparty",
|
||||||
|
"DestinationType": "ImageUploader, TextUploader, FileUploader",
|
||||||
|
"RequestURL": "http://127.0.0.1:3923/sharex",
|
||||||
|
"FileFormName": "f",
|
||||||
|
"Arguments": {
|
||||||
|
"act": "bput"
|
||||||
|
},
|
||||||
|
"Headers": {
|
||||||
|
"accept": "url",
|
||||||
|
"pw": "PUT_YOUR_PASSWORD_HERE_MY_DUDE"
|
||||||
|
}
|
||||||
|
}
|
||||||
42
contrib/systemd/copyparty.conf
Normal file
42
contrib/systemd/copyparty.conf
Normal file
@@ -0,0 +1,42 @@
|
|||||||
|
# not actually YAML but lets pretend:
|
||||||
|
# -*- mode: yaml -*-
|
||||||
|
# vim: ft=yaml:
|
||||||
|
|
||||||
|
|
||||||
|
# put this file in /etc/
|
||||||
|
|
||||||
|
|
||||||
|
[global]
|
||||||
|
e2dsa # enable file indexing and filesystem scanning
|
||||||
|
e2ts # and enable multimedia indexing
|
||||||
|
ansi # and colors in log messages
|
||||||
|
|
||||||
|
# disable logging to stdout/journalctl and log to a file instead;
|
||||||
|
# $LOGS_DIRECTORY is usually /var/log/copyparty (comes from systemd)
|
||||||
|
# and copyparty replaces %Y-%m%d with Year-MonthDay, so the
|
||||||
|
# full path will be something like /var/log/copyparty/2023-1130.txt
|
||||||
|
# (note: enable compression by adding .xz at the end)
|
||||||
|
q, lo: $LOGS_DIRECTORY/%Y-%m%d.log
|
||||||
|
|
||||||
|
# p: 80,443,3923 # listen on 80/443 as well (requires CAP_NET_BIND_SERVICE)
|
||||||
|
# i: 127.0.0.1 # only allow connections from localhost (reverse-proxies)
|
||||||
|
# ftp: 3921 # enable ftp server on port 3921
|
||||||
|
# p: 3939 # listen on another port
|
||||||
|
# df: 16 # stop accepting uploads if less than 16 GB free disk space
|
||||||
|
# ver # show copyparty version in the controlpanel
|
||||||
|
# grid # show thumbnails/grid-view by default
|
||||||
|
# theme: 2 # monokai
|
||||||
|
# name: datasaver # change the server-name that's displayed in the browser
|
||||||
|
# stats, nos-dup # enable the prometheus endpoint, but disable the dupes counter (too slow)
|
||||||
|
# no-robots, force-js # make it harder for search engines to read your server
|
||||||
|
|
||||||
|
|
||||||
|
[accounts]
|
||||||
|
ed: wark # username: password
|
||||||
|
|
||||||
|
|
||||||
|
[/] # create a volume at "/" (the webroot), which will
|
||||||
|
/mnt # share the contents of the "/mnt" folder
|
||||||
|
accs:
|
||||||
|
rw: * # everyone gets read-write access, but
|
||||||
|
rwmda: ed # the user "ed" gets read-write-move-delete-admin
|
||||||
@@ -1,28 +1,34 @@
|
|||||||
# this will start `/usr/local/bin/copyparty-sfx.py`
|
# this will start `/usr/local/bin/copyparty-sfx.py` and
|
||||||
# and share '/mnt' with anonymous read+write
|
# read copyparty config from `/etc/copyparty.conf`, for example:
|
||||||
|
# https://github.com/9001/copyparty/blob/hovudstraum/contrib/systemd/copyparty.conf
|
||||||
#
|
#
|
||||||
# installation:
|
# installation:
|
||||||
# wget https://github.com/9001/copyparty/releases/latest/download/copyparty-sfx.py -O /usr/local/bin/copyparty-sfx.py
|
# wget https://github.com/9001/copyparty/releases/latest/download/copyparty-sfx.py -O /usr/local/bin/copyparty-sfx.py
|
||||||
# cp -pv copyparty.service /etc/systemd/system/
|
# useradd -r -s /sbin/nologin -m -d /var/lib/copyparty copyparty
|
||||||
# restorecon -vr /etc/systemd/system/copyparty.service # on fedora/rhel
|
# firewall-cmd --permanent --add-port=3923/tcp # --zone=libvirt
|
||||||
# firewall-cmd --permanent --add-port={80,443,3923}/tcp # --zone=libvirt
|
|
||||||
# firewall-cmd --reload
|
# firewall-cmd --reload
|
||||||
|
# cp -pv copyparty.service /etc/systemd/system/
|
||||||
|
# cp -pv copyparty.conf /etc/
|
||||||
|
# restorecon -vr /etc/systemd/system/copyparty.service # on fedora/rhel
|
||||||
# systemctl daemon-reload && systemctl enable --now copyparty
|
# systemctl daemon-reload && systemctl enable --now copyparty
|
||||||
#
|
#
|
||||||
|
# every time you edit this file, you must "systemctl daemon-reload"
|
||||||
|
# for the changes to take effect and then "systemctl restart copyparty"
|
||||||
|
#
|
||||||
# if it fails to start, first check this: systemctl status copyparty
|
# if it fails to start, first check this: systemctl status copyparty
|
||||||
# then try starting it while viewing logs: journalctl -fan 100
|
# then try starting it while viewing logs:
|
||||||
|
# journalctl -fan 100
|
||||||
|
# tail -Fn 100 /var/log/copyparty/$(date +%Y-%m%d.log)
|
||||||
|
#
|
||||||
|
# if you run into any issues, for example thumbnails not working,
|
||||||
|
# try removing the "some quick hardening" section and then please
|
||||||
|
# let me know if that actually helped so we can look into it
|
||||||
#
|
#
|
||||||
# you may want to:
|
# you may want to:
|
||||||
# change "User=cpp" and "/home/cpp/" to another user
|
# - change "User=copyparty" and "/var/lib/copyparty/" to another user
|
||||||
# remove the nft lines to only listen on port 3923
|
# - edit /etc/copyparty.conf to configure copyparty
|
||||||
# and in the ExecStart= line:
|
# 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
|
|
||||||
# add '-q' to disable logging on busy servers
|
|
||||||
# add '-i 127.0.0.1' to only allow local connections
|
|
||||||
# add '-e2dsa' to enable filesystem scanning + indexing
|
|
||||||
# add '-e2ts' to enable metadata indexing
|
|
||||||
# remove '--ansi' to disable colored logs
|
|
||||||
#
|
#
|
||||||
# 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.
|
||||||
@@ -30,11 +36,9 @@
|
|||||||
# 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
|
||||||
#
|
#
|
||||||
# unless you add -q to disable logging, you may want to remove the
|
########################################################################
|
||||||
# following line to allow buffering (slightly better performance):
|
########################################################################
|
||||||
# Environment=PYTHONUNBUFFERED=x
|
|
||||||
#
|
|
||||||
# keep ExecStartPre before ExecStart, at least on rhel8
|
|
||||||
|
|
||||||
[Unit]
|
[Unit]
|
||||||
Description=copyparty file server
|
Description=copyparty file server
|
||||||
@@ -44,23 +48,52 @@ Type=notify
|
|||||||
SyslogIdentifier=copyparty
|
SyslogIdentifier=copyparty
|
||||||
Environment=PYTHONUNBUFFERED=x
|
Environment=PYTHONUNBUFFERED=x
|
||||||
ExecReload=/bin/kill -s USR1 $MAINPID
|
ExecReload=/bin/kill -s USR1 $MAINPID
|
||||||
|
PermissionsStartOnly=true
|
||||||
|
|
||||||
# user to run as + where the TLS certificate is (if any)
|
## user to run as + where the TLS certificate is (if any)
|
||||||
User=cpp
|
##
|
||||||
Environment=XDG_CONFIG_HOME=/home/cpp/.config
|
User=copyparty
|
||||||
|
Group=copyparty
|
||||||
|
WorkingDirectory=/var/lib/copyparty
|
||||||
|
Environment=XDG_CONFIG_HOME=/var/lib/copyparty/.config
|
||||||
|
|
||||||
# OPTIONAL: setup forwarding from ports 80 and 443 to port 3923
|
## OPTIONAL: allow copyparty to listen on low ports (like 80/443);
|
||||||
ExecStartPre=+/bin/bash -c 'nft -n -a list table nat | awk "/ to :3923 /{print\$NF}" | xargs -rL1 nft delete rule nat prerouting handle; true'
|
## you need to uncomment the "p: 80,443,3923" in the config too
|
||||||
ExecStartPre=+nft add table ip nat
|
## ------------------------------------------------------------
|
||||||
ExecStartPre=+nft -- add chain ip nat prerouting { type nat hook prerouting priority -100 \; }
|
## a slightly safer alternative is to enable partyalone.service
|
||||||
ExecStartPre=+nft add rule ip nat prerouting tcp dport 80 redirect to :3923
|
## which does portforwarding with nftables instead, but an even
|
||||||
ExecStartPre=+nft add rule ip nat prerouting tcp dport 443 redirect to :3923
|
## better option is to use a reverse-proxy (nginx/caddy/...)
|
||||||
|
##
|
||||||
|
AmbientCapabilities=CAP_NET_BIND_SERVICE
|
||||||
|
|
||||||
# stop systemd-tmpfiles-clean.timer from deleting copyparty while it's running
|
## some quick hardening; TODO port more from the nixos package
|
||||||
ExecStartPre=+/bin/bash -c 'mkdir -p /run/tmpfiles.d/ && echo "x /tmp/pe-copyparty*" > /run/tmpfiles.d/copyparty.conf'
|
##
|
||||||
|
MemoryMax=50%
|
||||||
|
MemorySwapMax=50%
|
||||||
|
ProtectClock=true
|
||||||
|
ProtectControlGroups=true
|
||||||
|
ProtectHostname=true
|
||||||
|
ProtectKernelLogs=true
|
||||||
|
ProtectKernelModules=true
|
||||||
|
ProtectKernelTunables=true
|
||||||
|
ProtectProc=invisible
|
||||||
|
RemoveIPC=true
|
||||||
|
RestrictNamespaces=true
|
||||||
|
RestrictRealtime=true
|
||||||
|
RestrictSUIDSGID=true
|
||||||
|
|
||||||
# copyparty settings
|
## create a directory for logfiles;
|
||||||
ExecStart=/usr/bin/python3 /usr/local/bin/copyparty-sfx.py --ansi -e2d -v /mnt::rw
|
## this defines $LOGS_DIRECTORY which is used in copyparty.conf
|
||||||
|
##
|
||||||
|
LogsDirectory=copyparty
|
||||||
|
|
||||||
|
## finally, start copyparty and give it the config file:
|
||||||
|
##
|
||||||
|
ExecStart=/usr/bin/python3 /usr/local/bin/copyparty-sfx.py -c /etc/copyparty.conf
|
||||||
|
|
||||||
|
# NOTE: if you installed copyparty from an OS package repo (nice)
|
||||||
|
# then you probably want something like this instead:
|
||||||
|
#ExecStart=/usr/bin/copyparty -c /etc/copyparty.conf
|
||||||
|
|
||||||
[Install]
|
[Install]
|
||||||
WantedBy=multi-user.target
|
WantedBy=multi-user.target
|
||||||
|
|||||||
116
contrib/themes/bsod.css
Normal file
116
contrib/themes/bsod.css
Normal file
@@ -0,0 +1,116 @@
|
|||||||
|
/* copy bsod.* into a folder named ".themes" in your webroot and then
|
||||||
|
--themes=10 --theme=9 --css-browser=/.themes/bsod.css
|
||||||
|
*/
|
||||||
|
|
||||||
|
html.ey {
|
||||||
|
--w2: #3d7bbc;
|
||||||
|
--w3: #5fcbec;
|
||||||
|
|
||||||
|
--fg: #fff;
|
||||||
|
--fg-max: #fff;
|
||||||
|
--fg-weak: var(--w3);
|
||||||
|
|
||||||
|
--bg: #2067b2;
|
||||||
|
--bg-d3: var(--bg);
|
||||||
|
--bg-d2: var(--w2);
|
||||||
|
--bg-d1: var(--fg-weak);
|
||||||
|
--bg-u2: var(--bg);
|
||||||
|
--bg-u3: var(--bg);
|
||||||
|
--bg-u5: var(--w2);
|
||||||
|
|
||||||
|
--tab-alt: var(--fg-weak);
|
||||||
|
--row-alt: var(--w2);
|
||||||
|
|
||||||
|
--scroll: var(--w3);
|
||||||
|
|
||||||
|
--a: #fff;
|
||||||
|
--a-b: #fff;
|
||||||
|
--a-hil: #fff;
|
||||||
|
--a-h-bg: var(--fg-weak);
|
||||||
|
--a-dark: var(--a);
|
||||||
|
--a-gray: var(--fg-weak);
|
||||||
|
|
||||||
|
--btn-fg: var(--a);
|
||||||
|
--btn-bg: var(--w2);
|
||||||
|
--btn-h-fg: var(--w2);
|
||||||
|
--btn-1-fg: var(--bg);
|
||||||
|
--btn-1-bg: var(--a);
|
||||||
|
--txt-sh: a;
|
||||||
|
--txt-bg: var(--w2);
|
||||||
|
|
||||||
|
--u2-b1-bg: var(--w2);
|
||||||
|
--u2-b2-bg: var(--w2);
|
||||||
|
--u2-txt-bg: var(--w2);
|
||||||
|
--u2-tab-bg: a;
|
||||||
|
--u2-tab-1-bg: var(--w2);
|
||||||
|
|
||||||
|
--sort-1: var(--a);
|
||||||
|
--sort-1: var(--fg-weak);
|
||||||
|
|
||||||
|
--tree-bg: var(--bg);
|
||||||
|
|
||||||
|
--g-b1: a;
|
||||||
|
--g-b2: a;
|
||||||
|
--g-f-bg: var(--w2);
|
||||||
|
|
||||||
|
--f-sh1: 0.1;
|
||||||
|
--f-sh2: 0.02;
|
||||||
|
--f-sh3: 0.1;
|
||||||
|
--f-h-b1: a;
|
||||||
|
|
||||||
|
--srv-1: var(--a);
|
||||||
|
--srv-3: var(--a);
|
||||||
|
|
||||||
|
--mp-sh: a;
|
||||||
|
}
|
||||||
|
|
||||||
|
html.ey {
|
||||||
|
background: url('bsod.png') top 5em right 4.5em no-repeat fixed var(--bg);
|
||||||
|
}
|
||||||
|
html.ey body#b {
|
||||||
|
background: var(--bg); /*sandbox*/
|
||||||
|
}
|
||||||
|
html.ey #ops {
|
||||||
|
margin: 1.7em 1.5em 0 1.5em;
|
||||||
|
border-radius: .3em;
|
||||||
|
border-width: 1px 0;
|
||||||
|
}
|
||||||
|
html.ey #ops a {
|
||||||
|
text-shadow: 1px 1px 0 rgba(0,0,0,0.5);
|
||||||
|
}
|
||||||
|
html.ey .opbox {
|
||||||
|
margin: 1.5em 0 0 0;
|
||||||
|
}
|
||||||
|
html.ey #tree {
|
||||||
|
box-shadow: none;
|
||||||
|
}
|
||||||
|
html.ey #tt {
|
||||||
|
border-color: var(--w2);
|
||||||
|
background: var(--w2);
|
||||||
|
}
|
||||||
|
html.ey .mdo a {
|
||||||
|
background: none;
|
||||||
|
text-decoration: underline;
|
||||||
|
}
|
||||||
|
html.ey .mdo pre,
|
||||||
|
html.ey .mdo code {
|
||||||
|
color: #fff;
|
||||||
|
background: var(--w2);
|
||||||
|
border: none;
|
||||||
|
}
|
||||||
|
html.ey .mdo h1,
|
||||||
|
html.ey .mdo h2 {
|
||||||
|
background: none;
|
||||||
|
border-color: var(--w2);
|
||||||
|
}
|
||||||
|
html.ey .mdo ul ul,
|
||||||
|
html.ey .mdo ul ol,
|
||||||
|
html.ey .mdo ol ul,
|
||||||
|
html.ey .mdo ol ol {
|
||||||
|
border-color: var(--w2);
|
||||||
|
}
|
||||||
|
html.ey .mdo p>em,
|
||||||
|
html.ey .mdo li>em,
|
||||||
|
html.ey .mdo td>em {
|
||||||
|
color: #fd0;
|
||||||
|
}
|
||||||
BIN
contrib/themes/bsod.png
Normal file
BIN
contrib/themes/bsod.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 1.2 KiB |
@@ -19,11 +19,12 @@ if True:
|
|||||||
from typing import Any, Callable
|
from typing import Any, Callable
|
||||||
|
|
||||||
PY2 = sys.version_info < (3,)
|
PY2 = sys.version_info < (3,)
|
||||||
|
PY36 = sys.version_info > (3, 6)
|
||||||
if not PY2:
|
if not PY2:
|
||||||
unicode: Callable[[Any], str] = str
|
unicode: Callable[[Any], str] = str
|
||||||
else:
|
else:
|
||||||
sys.dont_write_bytecode = True
|
sys.dont_write_bytecode = True
|
||||||
unicode = unicode # noqa: F821 # pylint: disable=undefined-variable,self-assigning-variable
|
unicode = unicode # type: ignore
|
||||||
|
|
||||||
WINDOWS: Any = (
|
WINDOWS: Any = (
|
||||||
[int(x) for x in platform.version().split(".")]
|
[int(x) for x in platform.version().split(".")]
|
||||||
@@ -56,7 +57,6 @@ class EnvParams(object):
|
|||||||
self.t0 = time.time()
|
self.t0 = time.time()
|
||||||
self.mod = ""
|
self.mod = ""
|
||||||
self.cfg = ""
|
self.cfg = ""
|
||||||
self.ox = getattr(sys, "oxidized", None)
|
|
||||||
|
|
||||||
|
|
||||||
E = EnvParams()
|
E = EnvParams()
|
||||||
|
|||||||
709
copyparty/__main__.py
Executable file → Normal file
709
copyparty/__main__.py
Executable file → Normal file
File diff suppressed because it is too large
Load Diff
@@ -1,8 +1,8 @@
|
|||||||
# coding: utf-8
|
# coding: utf-8
|
||||||
|
|
||||||
VERSION = (1, 9, 21)
|
VERSION = (1, 15, 2)
|
||||||
CODENAME = "prometheable"
|
CODENAME = "fill the drives"
|
||||||
BUILD_DT = (2023, 11, 25)
|
BUILD_DT = (2024, 9, 16)
|
||||||
|
|
||||||
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)
|
||||||
|
|||||||
1108
copyparty/authsrv.py
1108
copyparty/authsrv.py
File diff suppressed because it is too large
Load Diff
@@ -43,6 +43,10 @@ def open(p: str, *a, **ka) -> int:
|
|||||||
return os.open(fsenc(p), *a, **ka)
|
return os.open(fsenc(p), *a, **ka)
|
||||||
|
|
||||||
|
|
||||||
|
def readlink(p: str) -> str:
|
||||||
|
return fsdec(os.readlink(fsenc(p)))
|
||||||
|
|
||||||
|
|
||||||
def rename(src: str, dst: str) -> None:
|
def rename(src: str, dst: str) -> None:
|
||||||
return os.rename(fsenc(src), fsenc(dst))
|
return os.rename(fsenc(src), fsenc(dst))
|
||||||
|
|
||||||
|
|||||||
@@ -9,14 +9,14 @@ import queue
|
|||||||
|
|
||||||
from .__init__ import CORES, TYPE_CHECKING
|
from .__init__ import CORES, TYPE_CHECKING
|
||||||
from .broker_mpw import MpWorker
|
from .broker_mpw import MpWorker
|
||||||
from .broker_util import ExceptionalQueue, try_exec
|
from .broker_util import ExceptionalQueue, NotExQueue, try_exec
|
||||||
from .util import Daemon, mp
|
from .util import Daemon, mp
|
||||||
|
|
||||||
if TYPE_CHECKING:
|
if TYPE_CHECKING:
|
||||||
from .svchub import SvcHub
|
from .svchub import SvcHub
|
||||||
|
|
||||||
if True: # pylint: disable=using-constant-test
|
if True: # pylint: disable=using-constant-test
|
||||||
from typing import Any
|
from typing import Any, Union
|
||||||
|
|
||||||
|
|
||||||
class MProcess(mp.Process):
|
class MProcess(mp.Process):
|
||||||
@@ -46,8 +46,8 @@ class BrokerMp(object):
|
|||||||
self.num_workers = self.args.j or CORES
|
self.num_workers = self.args.j or CORES
|
||||||
self.log("broker", "booting {} subprocesses".format(self.num_workers))
|
self.log("broker", "booting {} subprocesses".format(self.num_workers))
|
||||||
for n in range(1, self.num_workers + 1):
|
for n in range(1, self.num_workers + 1):
|
||||||
q_pend: queue.Queue[tuple[int, str, list[Any]]] = mp.Queue(1)
|
q_pend: queue.Queue[tuple[int, str, list[Any]]] = mp.Queue(1) # type: ignore
|
||||||
q_yield: queue.Queue[tuple[int, str, list[Any]]] = mp.Queue(64)
|
q_yield: queue.Queue[tuple[int, str, list[Any]]] = mp.Queue(64) # type: ignore
|
||||||
|
|
||||||
proc = MProcess(q_pend, q_yield, MpWorker, (q_pend, q_yield, self.args, n))
|
proc = MProcess(q_pend, q_yield, MpWorker, (q_pend, q_yield, self.args, n))
|
||||||
Daemon(self.collector, "mp-sink-{}".format(n), (proc,))
|
Daemon(self.collector, "mp-sink-{}".format(n), (proc,))
|
||||||
@@ -57,11 +57,8 @@ class BrokerMp(object):
|
|||||||
def shutdown(self) -> None:
|
def shutdown(self) -> None:
|
||||||
self.log("broker", "shutting down")
|
self.log("broker", "shutting down")
|
||||||
for n, proc in enumerate(self.procs):
|
for n, proc in enumerate(self.procs):
|
||||||
thr = threading.Thread(
|
name = "mp-shut-%d-%d" % (n, len(self.procs))
|
||||||
target=proc.q_pend.put((0, "shutdown", [])),
|
Daemon(proc.q_pend.put, name, ((0, "shutdown", []),))
|
||||||
name="mp-shutdown-{}-{}".format(n, len(self.procs)),
|
|
||||||
)
|
|
||||||
thr.start()
|
|
||||||
|
|
||||||
with self.mutex:
|
with self.mutex:
|
||||||
procs = self.procs
|
procs = self.procs
|
||||||
@@ -79,6 +76,10 @@ class BrokerMp(object):
|
|||||||
for _, proc in enumerate(self.procs):
|
for _, proc in enumerate(self.procs):
|
||||||
proc.q_pend.put((0, "reload", []))
|
proc.q_pend.put((0, "reload", []))
|
||||||
|
|
||||||
|
def reload_sessions(self) -> None:
|
||||||
|
for _, proc in enumerate(self.procs):
|
||||||
|
proc.q_pend.put((0, "reload_sessions", []))
|
||||||
|
|
||||||
def collector(self, proc: MProcess) -> None:
|
def collector(self, proc: MProcess) -> None:
|
||||||
"""receive message from hub in other process"""
|
"""receive message from hub in other process"""
|
||||||
while True:
|
while True:
|
||||||
@@ -107,7 +108,7 @@ class BrokerMp(object):
|
|||||||
if retq_id:
|
if retq_id:
|
||||||
proc.q_pend.put((retq_id, "retq", rv))
|
proc.q_pend.put((retq_id, "retq", rv))
|
||||||
|
|
||||||
def ask(self, dest: str, *args: Any) -> ExceptionalQueue:
|
def ask(self, dest: str, *args: Any) -> Union[ExceptionalQueue, NotExQueue]:
|
||||||
|
|
||||||
# new non-ipc invoking managed service in hub
|
# new non-ipc invoking managed service in hub
|
||||||
obj = self.hub
|
obj = self.hub
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ import queue
|
|||||||
|
|
||||||
from .__init__ import ANYWIN
|
from .__init__ import ANYWIN
|
||||||
from .authsrv import AuthSrv
|
from .authsrv import AuthSrv
|
||||||
from .broker_util import BrokerCli, ExceptionalQueue
|
from .broker_util import BrokerCli, ExceptionalQueue, NotExQueue
|
||||||
from .httpsrv import HttpSrv
|
from .httpsrv import HttpSrv
|
||||||
from .util import FAKE_MP, Daemon, HMaccas
|
from .util import FAKE_MP, Daemon, HMaccas
|
||||||
|
|
||||||
@@ -76,7 +76,7 @@ class MpWorker(BrokerCli):
|
|||||||
pass
|
pass
|
||||||
|
|
||||||
def logw(self, msg: str, c: Union[int, str] = 0) -> None:
|
def logw(self, msg: str, c: Union[int, str] = 0) -> None:
|
||||||
self.log("mp{}".format(self.n), msg, c)
|
self.log("mp%d" % (self.n,), msg, c)
|
||||||
|
|
||||||
def main(self) -> None:
|
def main(self) -> None:
|
||||||
while True:
|
while True:
|
||||||
@@ -94,6 +94,10 @@ class MpWorker(BrokerCli):
|
|||||||
self.asrv.reload()
|
self.asrv.reload()
|
||||||
self.logw("mpw.asrv reloaded")
|
self.logw("mpw.asrv reloaded")
|
||||||
|
|
||||||
|
elif dest == "reload_sessions":
|
||||||
|
with self.asrv.mutex:
|
||||||
|
self.asrv.load_sessions()
|
||||||
|
|
||||||
elif dest == "listen":
|
elif dest == "listen":
|
||||||
self.httpsrv.listen(args[0], args[1])
|
self.httpsrv.listen(args[0], args[1])
|
||||||
|
|
||||||
@@ -110,7 +114,7 @@ class MpWorker(BrokerCli):
|
|||||||
else:
|
else:
|
||||||
raise Exception("what is " + str(dest))
|
raise Exception("what is " + str(dest))
|
||||||
|
|
||||||
def ask(self, dest: str, *args: Any) -> ExceptionalQueue:
|
def ask(self, dest: str, *args: Any) -> Union[ExceptionalQueue, NotExQueue]:
|
||||||
retq = ExceptionalQueue(1)
|
retq = ExceptionalQueue(1)
|
||||||
retq_id = id(retq)
|
retq_id = id(retq)
|
||||||
with self.retpend_mutex:
|
with self.retpend_mutex:
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ import os
|
|||||||
import threading
|
import threading
|
||||||
|
|
||||||
from .__init__ import TYPE_CHECKING
|
from .__init__ import TYPE_CHECKING
|
||||||
from .broker_util import BrokerCli, ExceptionalQueue, try_exec
|
from .broker_util import BrokerCli, ExceptionalQueue, NotExQueue
|
||||||
from .httpsrv import HttpSrv
|
from .httpsrv import HttpSrv
|
||||||
from .util import HMaccas
|
from .util import HMaccas
|
||||||
|
|
||||||
@@ -13,7 +13,7 @@ if TYPE_CHECKING:
|
|||||||
from .svchub import SvcHub
|
from .svchub import SvcHub
|
||||||
|
|
||||||
if True: # pylint: disable=using-constant-test
|
if True: # pylint: disable=using-constant-test
|
||||||
from typing import Any
|
from typing import Any, Union
|
||||||
|
|
||||||
|
|
||||||
class BrokerThr(BrokerCli):
|
class BrokerThr(BrokerCli):
|
||||||
@@ -34,6 +34,7 @@ class BrokerThr(BrokerCli):
|
|||||||
self.iphash = HMaccas(os.path.join(self.args.E.cfg, "iphash"), 8)
|
self.iphash = HMaccas(os.path.join(self.args.E.cfg, "iphash"), 8)
|
||||||
self.httpsrv = HttpSrv(self, None)
|
self.httpsrv = HttpSrv(self, None)
|
||||||
self.reload = self.noop
|
self.reload = self.noop
|
||||||
|
self.reload_sessions = self.noop
|
||||||
|
|
||||||
def shutdown(self) -> None:
|
def shutdown(self) -> None:
|
||||||
# self.log("broker", "shutting down")
|
# self.log("broker", "shutting down")
|
||||||
@@ -42,19 +43,14 @@ class BrokerThr(BrokerCli):
|
|||||||
def noop(self) -> None:
|
def noop(self) -> None:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
def ask(self, dest: str, *args: Any) -> ExceptionalQueue:
|
def ask(self, dest: str, *args: Any) -> Union[ExceptionalQueue, NotExQueue]:
|
||||||
|
|
||||||
# new ipc invoking managed service in hub
|
# new ipc invoking managed service in hub
|
||||||
obj = self.hub
|
obj = self.hub
|
||||||
for node in dest.split("."):
|
for node in dest.split("."):
|
||||||
obj = getattr(obj, node)
|
obj = getattr(obj, node)
|
||||||
|
|
||||||
rv = try_exec(True, obj, *args)
|
return NotExQueue(obj(*args)) # type: ignore
|
||||||
|
|
||||||
# pretend we're broker_mp
|
|
||||||
retq = ExceptionalQueue(1)
|
|
||||||
retq.put(rv)
|
|
||||||
return retq
|
|
||||||
|
|
||||||
def say(self, dest: str, *args: Any) -> None:
|
def say(self, dest: str, *args: Any) -> None:
|
||||||
if dest == "listen":
|
if dest == "listen":
|
||||||
@@ -70,4 +66,4 @@ class BrokerThr(BrokerCli):
|
|||||||
for node in dest.split("."):
|
for node in dest.split("."):
|
||||||
obj = getattr(obj, node)
|
obj = getattr(obj, node)
|
||||||
|
|
||||||
try_exec(False, obj, *args)
|
obj(*args) # type: ignore
|
||||||
|
|||||||
@@ -28,11 +28,23 @@ class ExceptionalQueue(Queue, object):
|
|||||||
if rv[1] == "pebkac":
|
if rv[1] == "pebkac":
|
||||||
raise Pebkac(*rv[2:])
|
raise Pebkac(*rv[2:])
|
||||||
else:
|
else:
|
||||||
raise Exception(rv[2])
|
raise rv[2]
|
||||||
|
|
||||||
return rv
|
return rv
|
||||||
|
|
||||||
|
|
||||||
|
class NotExQueue(object):
|
||||||
|
"""
|
||||||
|
BrokerThr uses this instead of ExceptionalQueue; 7x faster
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(self, rv: Any) -> None:
|
||||||
|
self.rv = rv
|
||||||
|
|
||||||
|
def get(self) -> Any:
|
||||||
|
return self.rv
|
||||||
|
|
||||||
|
|
||||||
class BrokerCli(object):
|
class BrokerCli(object):
|
||||||
"""
|
"""
|
||||||
helps mypy understand httpsrv.broker but still fails a few levels deeper,
|
helps mypy understand httpsrv.broker but still fails a few levels deeper,
|
||||||
@@ -48,7 +60,7 @@ class BrokerCli(object):
|
|||||||
def __init__(self) -> None:
|
def __init__(self) -> None:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
def ask(self, dest: str, *args: Any) -> ExceptionalQueue:
|
def ask(self, dest: str, *args: Any) -> Union[ExceptionalQueue, NotExQueue]:
|
||||||
return ExceptionalQueue(1)
|
return ExceptionalQueue(1)
|
||||||
|
|
||||||
def say(self, dest: str, *args: Any) -> None:
|
def say(self, dest: str, *args: Any) -> None:
|
||||||
@@ -65,8 +77,8 @@ def try_exec(want_retval: Union[bool, int], func: Any, *args: list[Any]) -> Any:
|
|||||||
|
|
||||||
return ["exception", "pebkac", ex.code, str(ex)]
|
return ["exception", "pebkac", ex.code, str(ex)]
|
||||||
|
|
||||||
except:
|
except Exception as ex:
|
||||||
if not want_retval:
|
if not want_retval:
|
||||||
raise
|
raise
|
||||||
|
|
||||||
return ["exception", "stack", traceback.format_exc()]
|
return ["exception", "stack", ex]
|
||||||
|
|||||||
@@ -6,12 +6,19 @@ import os
|
|||||||
import shutil
|
import shutil
|
||||||
import time
|
import time
|
||||||
|
|
||||||
from .util import Netdev, runcmd
|
from .__init__ import ANYWIN
|
||||||
|
from .util import Netdev, runcmd, wrename, wunlink
|
||||||
|
|
||||||
HAVE_CFSSL = True
|
HAVE_CFSSL = not os.environ.get("PRTY_NO_CFSSL")
|
||||||
|
|
||||||
if True: # pylint: disable=using-constant-test
|
if True: # pylint: disable=using-constant-test
|
||||||
from .util import RootLogger
|
from .util import NamedLogger, RootLogger
|
||||||
|
|
||||||
|
|
||||||
|
if ANYWIN:
|
||||||
|
VF = {"mv_re_t": 5, "rm_re_t": 5, "mv_re_r": 0.1, "rm_re_r": 0.1}
|
||||||
|
else:
|
||||||
|
VF = {"mv_re_t": 0, "rm_re_t": 0}
|
||||||
|
|
||||||
|
|
||||||
def ensure_cert(log: "RootLogger", args) -> None:
|
def ensure_cert(log: "RootLogger", args) -> None:
|
||||||
@@ -76,6 +83,8 @@ def _read_crt(args, fn):
|
|||||||
|
|
||||||
|
|
||||||
def _gen_ca(log: "RootLogger", args):
|
def _gen_ca(log: "RootLogger", args):
|
||||||
|
nlog: "NamedLogger" = lambda msg, c=0: log("cert-gen-ca", msg, c)
|
||||||
|
|
||||||
expiry = _read_crt(args, "ca.pem")[0]
|
expiry = _read_crt(args, "ca.pem")[0]
|
||||||
if time.time() + args.crt_cdays * 60 * 60 * 24 * 0.1 < expiry:
|
if time.time() + args.crt_cdays * 60 * 60 * 24 * 0.1 < expiry:
|
||||||
return
|
return
|
||||||
@@ -105,13 +114,19 @@ def _gen_ca(log: "RootLogger", args):
|
|||||||
raise Exception("failed to translate ca-cert: {}, {}".format(rc, se), 3)
|
raise Exception("failed to translate ca-cert: {}, {}".format(rc, se), 3)
|
||||||
|
|
||||||
bname = os.path.join(args.crt_dir, "ca")
|
bname = os.path.join(args.crt_dir, "ca")
|
||||||
os.rename(bname + "-key.pem", bname + ".key")
|
try:
|
||||||
os.unlink(bname + ".csr")
|
wunlink(nlog, bname + ".key", VF)
|
||||||
|
except:
|
||||||
|
pass
|
||||||
|
wrename(nlog, bname + "-key.pem", bname + ".key", VF)
|
||||||
|
wunlink(nlog, bname + ".csr", VF)
|
||||||
|
|
||||||
log("cert", "new ca OK", 2)
|
log("cert", "new ca OK", 2)
|
||||||
|
|
||||||
|
|
||||||
def _gen_srv(log: "RootLogger", args, netdevs: dict[str, Netdev]):
|
def _gen_srv(log: "RootLogger", args, netdevs: dict[str, Netdev]):
|
||||||
|
nlog: "NamedLogger" = lambda msg, c=0: log("cert-gen-srv", msg, c)
|
||||||
|
|
||||||
names = args.crt_ns.split(",") if args.crt_ns else []
|
names = args.crt_ns.split(",") if args.crt_ns else []
|
||||||
if not args.crt_exact:
|
if not args.crt_exact:
|
||||||
for n in names[:]:
|
for n in names[:]:
|
||||||
@@ -185,11 +200,11 @@ def _gen_srv(log: "RootLogger", args, netdevs: dict[str, Netdev]):
|
|||||||
|
|
||||||
bname = os.path.join(args.crt_dir, "srv")
|
bname = os.path.join(args.crt_dir, "srv")
|
||||||
try:
|
try:
|
||||||
os.unlink(bname + ".key")
|
wunlink(nlog, bname + ".key", VF)
|
||||||
except:
|
except:
|
||||||
pass
|
pass
|
||||||
os.rename(bname + "-key.pem", bname + ".key")
|
wrename(nlog, bname + "-key.pem", bname + ".key", VF)
|
||||||
os.unlink(bname + ".csr")
|
wunlink(nlog, bname + ".csr", VF)
|
||||||
|
|
||||||
with open(os.path.join(args.crt_dir, "ca.pem"), "rb") as f:
|
with open(os.path.join(args.crt_dir, "ca.pem"), "rb") as f:
|
||||||
ca = f.read()
|
ca = f.read()
|
||||||
|
|||||||
@@ -9,19 +9,21 @@ onedash = set(zs.split())
|
|||||||
def vf_bmap() -> dict[str, str]:
|
def vf_bmap() -> dict[str, str]:
|
||||||
"""argv-to-volflag: simple bools"""
|
"""argv-to-volflag: simple bools"""
|
||||||
ret = {
|
ret = {
|
||||||
"never_symlink": "neversymlink",
|
"dav_auth": "davauth",
|
||||||
"no_dedup": "copydupes",
|
"dav_rt": "davrt",
|
||||||
|
"ed": "dots",
|
||||||
|
"hardlink_only": "hardlinkonly",
|
||||||
|
"no_dirsz": "nodirsz",
|
||||||
"no_dupe": "nodupe",
|
"no_dupe": "nodupe",
|
||||||
"no_forget": "noforget",
|
"no_forget": "noforget",
|
||||||
|
"no_pipe": "nopipe",
|
||||||
"no_robots": "norobots",
|
"no_robots": "norobots",
|
||||||
"no_thumb": "dthumb",
|
"no_thumb": "dthumb",
|
||||||
"no_vthumb": "dvthumb",
|
"no_vthumb": "dvthumb",
|
||||||
"no_athumb": "dathumb",
|
"no_athumb": "dathumb",
|
||||||
"th_no_crop": "nocrop",
|
|
||||||
"dav_auth": "davauth",
|
|
||||||
"dav_rt": "davrt",
|
|
||||||
}
|
}
|
||||||
for k in (
|
for k in (
|
||||||
|
"dedup",
|
||||||
"dotsrch",
|
"dotsrch",
|
||||||
"e2d",
|
"e2d",
|
||||||
"e2ds",
|
"e2ds",
|
||||||
@@ -34,10 +36,14 @@ def vf_bmap() -> dict[str, str]:
|
|||||||
"e2vp",
|
"e2vp",
|
||||||
"exp",
|
"exp",
|
||||||
"grid",
|
"grid",
|
||||||
|
"gsel",
|
||||||
"hardlink",
|
"hardlink",
|
||||||
"magic",
|
"magic",
|
||||||
"no_sb_md",
|
"no_sb_md",
|
||||||
"no_sb_lg",
|
"no_sb_lg",
|
||||||
|
"og",
|
||||||
|
"og_no_head",
|
||||||
|
"og_s_title",
|
||||||
"rand",
|
"rand",
|
||||||
"xdev",
|
"xdev",
|
||||||
"xlink",
|
"xlink",
|
||||||
@@ -53,16 +59,33 @@ def vf_vmap() -> dict[str, str]:
|
|||||||
"no_hash": "nohash",
|
"no_hash": "nohash",
|
||||||
"no_idx": "noidx",
|
"no_idx": "noidx",
|
||||||
"re_maxage": "scan",
|
"re_maxage": "scan",
|
||||||
|
"safe_dedup": "safededup",
|
||||||
"th_convt": "convt",
|
"th_convt": "convt",
|
||||||
"th_size": "thsize",
|
"th_size": "thsize",
|
||||||
|
"th_crop": "crop",
|
||||||
|
"th_x3": "th3x",
|
||||||
}
|
}
|
||||||
for k in (
|
for k in (
|
||||||
"dbd",
|
"dbd",
|
||||||
|
"html_head",
|
||||||
"lg_sbf",
|
"lg_sbf",
|
||||||
"md_sbf",
|
"md_sbf",
|
||||||
"nrand",
|
"nrand",
|
||||||
|
"og_desc",
|
||||||
|
"og_site",
|
||||||
|
"og_th",
|
||||||
|
"og_title",
|
||||||
|
"og_title_a",
|
||||||
|
"og_title_v",
|
||||||
|
"og_title_i",
|
||||||
|
"og_tpl",
|
||||||
|
"og_ua",
|
||||||
|
"mv_retry",
|
||||||
|
"rm_retry",
|
||||||
"sort",
|
"sort",
|
||||||
|
"tcolor",
|
||||||
"unlist",
|
"unlist",
|
||||||
|
"u2abort",
|
||||||
"u2ts",
|
"u2ts",
|
||||||
):
|
):
|
||||||
ret[k] = k
|
ret[k] = k
|
||||||
@@ -75,7 +98,6 @@ def vf_cmap() -> dict[str, str]:
|
|||||||
for k in (
|
for k in (
|
||||||
"exp_lg",
|
"exp_lg",
|
||||||
"exp_md",
|
"exp_md",
|
||||||
"html_head",
|
|
||||||
"mte",
|
"mte",
|
||||||
"mth",
|
"mth",
|
||||||
"mtp",
|
"mtp",
|
||||||
@@ -98,19 +120,23 @@ permdescs = {
|
|||||||
"w": 'write; upload files; need "r" to see the uploads',
|
"w": 'write; upload files; need "r" to see the uploads',
|
||||||
"m": 'move; move files and folders; need "w" at destination',
|
"m": 'move; move files and folders; need "w" at destination',
|
||||||
"d": "delete; permanently delete files and folders",
|
"d": "delete; permanently delete files and folders",
|
||||||
|
".": "dots; user can ask to show dotfiles in listings",
|
||||||
"g": "get; download files, but cannot see folder contents",
|
"g": "get; download files, but cannot see folder contents",
|
||||||
"G": 'upget; same as "g" but can see filekeys of their own uploads',
|
"G": 'upget; same as "g" but can see filekeys of their own uploads',
|
||||||
"h": 'html; same as "g" but folders return their index.html',
|
"h": 'html; same as "g" but folders return their index.html',
|
||||||
"a": "admin; can see uploader IPs, config-reload",
|
"a": "admin; can see uploader IPs, config-reload",
|
||||||
|
"A": "all; same as 'rwmda.' (read/write/move/delete/dotfiles)",
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
flagcats = {
|
flagcats = {
|
||||||
"uploads, general": {
|
"uploads, general": {
|
||||||
|
"dedup": "enable symlink-based file deduplication",
|
||||||
|
"hardlink": "enable hardlink-based file deduplication,\nwith fallback on symlinks when that is impossible",
|
||||||
|
"hardlinkonly": "dedup with hardlink only, never symlink;\nmake a full copy if hardlink is impossible",
|
||||||
|
"safededup": "verify on-disk data before using it for dedup",
|
||||||
"nodupe": "rejects existing files (instead of symlinking them)",
|
"nodupe": "rejects existing files (instead of symlinking them)",
|
||||||
"hardlink": "does dedup with hardlinks instead of symlinks",
|
"sparse": "force use of sparse files, mainly for s3-backed storage",
|
||||||
"neversymlink": "disables symlink fallback; full copy instead",
|
|
||||||
"copydupes": "disables dedup, always saves full copies of dupes",
|
|
||||||
"daw": "enable full WebDAV write support (dangerous);\nPUT-operations will now \033[1;31mOVERWRITE\033[0;35m existing files",
|
"daw": "enable full WebDAV write support (dangerous);\nPUT-operations will now \033[1;31mOVERWRITE\033[0;35m existing files",
|
||||||
"nosub": "forces all uploads into the top folder of the vfs",
|
"nosub": "forces all uploads into the top folder of the vfs",
|
||||||
"magic": "enables filetype detection for nameless uploads",
|
"magic": "enables filetype detection for nameless uploads",
|
||||||
@@ -122,9 +148,11 @@ flagcats = {
|
|||||||
"maxb=1g,300": "max 1 GiB over 5min (suffixes: b, k, m, g, t)",
|
"maxb=1g,300": "max 1 GiB over 5min (suffixes: b, k, m, g, t)",
|
||||||
"vmaxb=1g": "total volume size max 1 GiB (suffixes: b, k, m, g, t)",
|
"vmaxb=1g": "total volume size max 1 GiB (suffixes: b, k, m, g, t)",
|
||||||
"vmaxn=4k": "max 4096 files in volume (suffixes: b, k, m, g, t)",
|
"vmaxn=4k": "max 4096 files in volume (suffixes: b, k, m, g, t)",
|
||||||
|
"medialinks": "return medialinks for non-up2k uploads (not hotlinks)",
|
||||||
"rand": "force randomized filenames, 9 chars long by default",
|
"rand": "force randomized filenames, 9 chars long by default",
|
||||||
"nrand=N": "randomized filenames are N chars long",
|
"nrand=N": "randomized filenames are N chars long",
|
||||||
"u2ts=fc": "[f]orce [c]lient-last-modified or [u]pload-time",
|
"u2ts=fc": "[f]orce [c]lient-last-modified or [u]pload-time",
|
||||||
|
"u2abort=1": "allow aborting unfinished uploads? 0=no 1=strict 2=ip-chk 3=acct-chk",
|
||||||
"sz=1k-3m": "allow filesizes between 1 KiB and 3MiB",
|
"sz=1k-3m": "allow filesizes between 1 KiB and 3MiB",
|
||||||
"df=1g": "ensure 1 GiB free disk space",
|
"df=1g": "ensure 1 GiB free disk space",
|
||||||
},
|
},
|
||||||
@@ -134,7 +162,7 @@ flagcats = {
|
|||||||
"lifetime=3600": "uploads are deleted after 1 hour",
|
"lifetime=3600": "uploads are deleted after 1 hour",
|
||||||
},
|
},
|
||||||
"database, general": {
|
"database, general": {
|
||||||
"e2d": "enable database; makes files searchable + enables upload dedup",
|
"e2d": "enable database; makes files searchable + enables upload-undo",
|
||||||
"e2ds": "scan writable folders for new files on startup; also sets -e2d",
|
"e2ds": "scan writable folders for new files on startup; also sets -e2d",
|
||||||
"e2dsa": "scans all folders for new files on startup; also sets -e2d",
|
"e2dsa": "scans all folders for new files on startup; also sets -e2d",
|
||||||
"e2t": "enable multimedia indexing; makes it possible to search for tags",
|
"e2t": "enable multimedia indexing; makes it possible to search for tags",
|
||||||
@@ -152,7 +180,7 @@ flagcats = {
|
|||||||
"noforget": "don't forget files when deleted from disk",
|
"noforget": "don't forget files when deleted from disk",
|
||||||
"fat32": "avoid excessive reindexing on android sdcardfs",
|
"fat32": "avoid excessive reindexing on android sdcardfs",
|
||||||
"dbd=[acid|swal|wal|yolo]": "database speed-durability tradeoff",
|
"dbd=[acid|swal|wal|yolo]": "database speed-durability tradeoff",
|
||||||
"xlink": "cross-volume dupe detection / linking",
|
"xlink": "cross-volume dupe detection / linking (dangerous)",
|
||||||
"xdev": "do not descend into other filesystems",
|
"xdev": "do not descend into other filesystems",
|
||||||
"xvol": "do not follow symlinks leaving the volume root",
|
"xvol": "do not follow symlinks leaving the volume root",
|
||||||
"dotsrch": "show dotfiles in search results",
|
"dotsrch": "show dotfiles in search results",
|
||||||
@@ -167,8 +195,10 @@ flagcats = {
|
|||||||
"dvthumb": "disables video thumbnails",
|
"dvthumb": "disables video thumbnails",
|
||||||
"dathumb": "disables audio thumbnails (spectrograms)",
|
"dathumb": "disables audio thumbnails (spectrograms)",
|
||||||
"dithumb": "disables image thumbnails",
|
"dithumb": "disables image thumbnails",
|
||||||
|
"pngquant": "compress audio waveforms 33% better",
|
||||||
"thsize": "thumbnail res; WxH",
|
"thsize": "thumbnail res; WxH",
|
||||||
"nocrop": "disable center-cropping by default",
|
"crop": "center-cropping (y/n/fy/fn)",
|
||||||
|
"th3x": "3x resolution (y/n/fy/fn)",
|
||||||
"convt": "conversion timeout in seconds",
|
"convt": "conversion timeout in seconds",
|
||||||
},
|
},
|
||||||
"handlers\n(better explained in --help-handlers)": {
|
"handlers\n(better explained in --help-handlers)": {
|
||||||
@@ -188,9 +218,10 @@ flagcats = {
|
|||||||
},
|
},
|
||||||
"client and ux": {
|
"client and ux": {
|
||||||
"grid": "show grid/thumbnails by default",
|
"grid": "show grid/thumbnails by default",
|
||||||
|
"gsel": "select files in grid by ctrl-click",
|
||||||
"sort": "default sort order",
|
"sort": "default sort order",
|
||||||
"unlist": "dont list files matching REGEX",
|
"unlist": "dont list files matching REGEX",
|
||||||
"html_head=TXT": "includes TXT in the <head>",
|
"html_head=TXT": "includes TXT in the <head>, or @PATH for file at PATH",
|
||||||
"robots": "allows indexing by search engines (default)",
|
"robots": "allows indexing by search engines (default)",
|
||||||
"norobots": "kindly asks search engines to leave",
|
"norobots": "kindly asks search engines to leave",
|
||||||
"no_sb_md": "disable js sandbox for markdown files",
|
"no_sb_md": "disable js sandbox for markdown files",
|
||||||
@@ -202,8 +233,11 @@ flagcats = {
|
|||||||
"nohtml": "return html and markdown as text/html",
|
"nohtml": "return html and markdown as text/html",
|
||||||
},
|
},
|
||||||
"others": {
|
"others": {
|
||||||
|
"dots": "allow all users with read-access to\nenable the option to show dotfiles in listings",
|
||||||
"fk=8": 'generates per-file accesskeys,\nwhich are then required at the "g" permission;\nkeys are invalidated if filesize or inode changes',
|
"fk=8": 'generates per-file accesskeys,\nwhich are then required at the "g" permission;\nkeys are invalidated if filesize or inode changes',
|
||||||
"fka=8": 'generates slightly weaker per-file accesskeys,\nwhich are then required at the "g" permission;\nnot affected by filesize or inode numbers',
|
"fka=8": 'generates slightly weaker per-file accesskeys,\nwhich are then required at the "g" permission;\nnot affected by filesize or inode numbers',
|
||||||
|
"mv_retry": "ms-windows: timeout for renaming busy files",
|
||||||
|
"rm_retry": "ms-windows: timeout for deleting busy files",
|
||||||
"davauth": "ask webdav clients to login for all folders",
|
"davauth": "ask webdav clients to login for all folders",
|
||||||
"davrt": "show lastmod time of symlink destination, not the link itself\n(note: this option is always enabled for recursive listings)",
|
"davrt": "show lastmod time of symlink destination, not the link itself\n(note: this option is always enabled for recursive listings)",
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
# coding: utf-8
|
# coding: utf-8
|
||||||
from __future__ import print_function, unicode_literals
|
from __future__ import print_function, unicode_literals
|
||||||
|
|
||||||
|
import argparse
|
||||||
import os
|
import os
|
||||||
import re
|
import re
|
||||||
import time
|
import time
|
||||||
@@ -8,29 +9,35 @@ import time
|
|||||||
from .__init__ import ANYWIN, MACOS
|
from .__init__ import ANYWIN, MACOS
|
||||||
from .authsrv import AXS, VFS
|
from .authsrv import AXS, VFS
|
||||||
from .bos import bos
|
from .bos import bos
|
||||||
from .util import chkcmd, min_ex
|
from .util import chkcmd, min_ex, undot
|
||||||
|
|
||||||
if True: # pylint: disable=using-constant-test
|
if True: # pylint: disable=using-constant-test
|
||||||
from typing import Optional, Union
|
from typing import Optional, Union
|
||||||
|
|
||||||
from .util import RootLogger
|
from .util import RootLogger, undot
|
||||||
|
|
||||||
|
|
||||||
class Fstab(object):
|
class Fstab(object):
|
||||||
def __init__(self, log: "RootLogger"):
|
def __init__(self, log: "RootLogger", args: argparse.Namespace):
|
||||||
self.log_func = log
|
self.log_func = log
|
||||||
|
|
||||||
|
self.warned = False
|
||||||
self.trusted = False
|
self.trusted = False
|
||||||
self.tab: Optional[VFS] = None
|
self.tab: Optional[VFS] = None
|
||||||
|
self.oldtab: Optional[VFS] = None
|
||||||
|
self.srctab = "a"
|
||||||
self.cache: dict[str, str] = {}
|
self.cache: dict[str, str] = {}
|
||||||
self.age = 0.0
|
self.age = 0.0
|
||||||
|
self.maxage = args.mtab_age
|
||||||
|
|
||||||
def log(self, msg: str, c: Union[int, str] = 0) -> None:
|
def log(self, msg: str, c: Union[int, str] = 0) -> None:
|
||||||
self.log_func("fstab", msg, c)
|
self.log_func("fstab", msg, c)
|
||||||
|
|
||||||
def get(self, path: str) -> str:
|
def get(self, path: str) -> str:
|
||||||
if len(self.cache) > 9000:
|
now = time.time()
|
||||||
self.age = time.time()
|
if now - self.age > self.maxage or len(self.cache) > 9000:
|
||||||
|
self.age = now
|
||||||
|
self.oldtab = self.tab or self.oldtab
|
||||||
self.tab = None
|
self.tab = None
|
||||||
self.cache = {}
|
self.cache = {}
|
||||||
|
|
||||||
@@ -45,7 +52,7 @@ class Fstab(object):
|
|||||||
self.log(msg.format(path, fs, min_ex()), 3)
|
self.log(msg.format(path, fs, min_ex()), 3)
|
||||||
return fs
|
return fs
|
||||||
|
|
||||||
path = path.lstrip("/")
|
path = undot(path)
|
||||||
try:
|
try:
|
||||||
return self.cache[path]
|
return self.cache[path]
|
||||||
except:
|
except:
|
||||||
@@ -75,7 +82,7 @@ class Fstab(object):
|
|||||||
self.trusted = False
|
self.trusted = False
|
||||||
|
|
||||||
def build_tab(self) -> None:
|
def build_tab(self) -> None:
|
||||||
self.log("building tab")
|
self.log("inspecting mtab for changes")
|
||||||
|
|
||||||
sptn = r"^.*? on (.*) type ([^ ]+) \(.*"
|
sptn = r"^.*? on (.*) type ([^ ]+) \(.*"
|
||||||
if MACOS:
|
if MACOS:
|
||||||
@@ -84,6 +91,7 @@ class Fstab(object):
|
|||||||
ptn = re.compile(sptn)
|
ptn = re.compile(sptn)
|
||||||
so, _ = chkcmd(["mount"])
|
so, _ = chkcmd(["mount"])
|
||||||
tab1: list[tuple[str, str]] = []
|
tab1: list[tuple[str, str]] = []
|
||||||
|
atab = []
|
||||||
for ln in so.split("\n"):
|
for ln in so.split("\n"):
|
||||||
m = ptn.match(ln)
|
m = ptn.match(ln)
|
||||||
if not m:
|
if not m:
|
||||||
@@ -91,6 +99,15 @@ class Fstab(object):
|
|||||||
|
|
||||||
zs1, zs2 = m.groups()
|
zs1, zs2 = m.groups()
|
||||||
tab1.append((str(zs1), str(zs2)))
|
tab1.append((str(zs1), str(zs2)))
|
||||||
|
atab.append(ln)
|
||||||
|
|
||||||
|
# keep empirically-correct values if mounttab unchanged
|
||||||
|
srctab = "\n".join(sorted(atab))
|
||||||
|
if srctab == self.srctab:
|
||||||
|
self.tab = self.oldtab
|
||||||
|
return
|
||||||
|
|
||||||
|
self.log("mtab has changed; reevaluating support for sparse files")
|
||||||
|
|
||||||
tab1.sort(key=lambda x: (len(x[0]), x[0]))
|
tab1.sort(key=lambda x: (len(x[0]), x[0]))
|
||||||
path1, fs1 = tab1[0]
|
path1, fs1 = tab1[0]
|
||||||
@@ -99,14 +116,15 @@ class Fstab(object):
|
|||||||
tab.add(fs, path.lstrip("/"))
|
tab.add(fs, path.lstrip("/"))
|
||||||
|
|
||||||
self.tab = tab
|
self.tab = tab
|
||||||
|
self.srctab = srctab
|
||||||
|
|
||||||
def relabel(self, path: str, nval: str) -> None:
|
def relabel(self, path: str, nval: str) -> None:
|
||||||
assert self.tab
|
assert self.tab # !rm
|
||||||
self.cache = {}
|
self.cache = {}
|
||||||
if ANYWIN:
|
if ANYWIN:
|
||||||
path = self._winpath(path)
|
path = self._winpath(path)
|
||||||
|
|
||||||
path = path.lstrip("/")
|
path = undot(path)
|
||||||
ptn = re.compile(r"^[^\\/]*")
|
ptn = re.compile(r"^[^\\/]*")
|
||||||
vn, rem = self.tab._find(path)
|
vn, rem = self.tab._find(path)
|
||||||
if not self.trusted:
|
if not self.trusted:
|
||||||
@@ -133,10 +151,12 @@ class Fstab(object):
|
|||||||
self.trusted = True
|
self.trusted = True
|
||||||
except:
|
except:
|
||||||
# prisonparty or other restrictive environment
|
# prisonparty or other restrictive environment
|
||||||
self.log("failed to build tab:\n{}".format(min_ex()), 3)
|
if not self.warned:
|
||||||
|
self.warned = True
|
||||||
|
self.log("failed to build tab:\n{}".format(min_ex()), 3)
|
||||||
self.build_fallback()
|
self.build_fallback()
|
||||||
|
|
||||||
assert self.tab
|
assert self.tab # !rm
|
||||||
ret = self.tab._find(path)[0]
|
ret = self.tab._find(path)[0]
|
||||||
if self.trusted or path == ret.vpath:
|
if self.trusted or path == ret.vpath:
|
||||||
return ret.realpath.split("/")[0]
|
return ret.realpath.split("/")[0]
|
||||||
@@ -147,6 +167,6 @@ class Fstab(object):
|
|||||||
if not self.tab:
|
if not self.tab:
|
||||||
self.build_fallback()
|
self.build_fallback()
|
||||||
|
|
||||||
assert self.tab
|
assert self.tab # !rm
|
||||||
ret = self.tab._find(path)[0]
|
ret = self.tab._find(path)[0]
|
||||||
return ret.realpath
|
return ret.realpath
|
||||||
|
|||||||
@@ -15,11 +15,13 @@ from pyftpdlib.handlers import FTPHandler
|
|||||||
from pyftpdlib.ioloop import IOLoop
|
from pyftpdlib.ioloop import IOLoop
|
||||||
from pyftpdlib.servers import FTPServer
|
from pyftpdlib.servers import FTPServer
|
||||||
|
|
||||||
from .__init__ import ANYWIN, PY2, TYPE_CHECKING, E
|
from .__init__ import PY2, TYPE_CHECKING
|
||||||
from .authsrv import VFS
|
from .authsrv import VFS
|
||||||
from .bos import bos
|
from .bos import bos
|
||||||
from .util import (
|
from .util import (
|
||||||
|
VF_CAREFUL,
|
||||||
Daemon,
|
Daemon,
|
||||||
|
ODict,
|
||||||
Pebkac,
|
Pebkac,
|
||||||
exclude_dotfiles,
|
exclude_dotfiles,
|
||||||
fsenc,
|
fsenc,
|
||||||
@@ -29,6 +31,7 @@ from .util import (
|
|||||||
runhook,
|
runhook,
|
||||||
sanitize_fn,
|
sanitize_fn,
|
||||||
vjoin,
|
vjoin,
|
||||||
|
wunlink,
|
||||||
)
|
)
|
||||||
|
|
||||||
if TYPE_CHECKING:
|
if TYPE_CHECKING:
|
||||||
@@ -36,7 +39,10 @@ if TYPE_CHECKING:
|
|||||||
|
|
||||||
if True: # pylint: disable=using-constant-test
|
if True: # pylint: disable=using-constant-test
|
||||||
import typing
|
import typing
|
||||||
from typing import Any, Optional
|
from typing import Any, Optional, Union
|
||||||
|
|
||||||
|
if PY2:
|
||||||
|
range = xrange # type: ignore
|
||||||
|
|
||||||
|
|
||||||
class FSE(FilesystemError):
|
class FSE(FilesystemError):
|
||||||
@@ -73,6 +79,7 @@ class FtpAuth(DummyAuthorizer):
|
|||||||
asrv = self.hub.asrv
|
asrv = self.hub.asrv
|
||||||
uname = "*"
|
uname = "*"
|
||||||
if username != "anonymous":
|
if username != "anonymous":
|
||||||
|
uname = ""
|
||||||
for zs in (password, username):
|
for zs in (password, username):
|
||||||
zs = asrv.iacct.get(asrv.ah.hash(zs), "")
|
zs = asrv.iacct.get(asrv.ah.hash(zs), "")
|
||||||
if zs:
|
if zs:
|
||||||
@@ -88,8 +95,8 @@ class FtpAuth(DummyAuthorizer):
|
|||||||
bans[ip] = bonk
|
bans[ip] = bonk
|
||||||
try:
|
try:
|
||||||
# only possible if multiprocessing disabled
|
# only possible if multiprocessing disabled
|
||||||
self.hub.broker.httpsrv.bans[ip] = bonk
|
self.hub.broker.httpsrv.bans[ip] = bonk # type: ignore
|
||||||
self.hub.broker.httpsrv.nban += 1
|
self.hub.broker.httpsrv.nban += 1 # type: ignore
|
||||||
except:
|
except:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
@@ -132,11 +139,14 @@ class FtpFs(AbstractedFS):
|
|||||||
|
|
||||||
self.can_read = self.can_write = self.can_move = False
|
self.can_read = self.can_write = self.can_move = False
|
||||||
self.can_delete = self.can_get = self.can_upget = False
|
self.can_delete = self.can_get = self.can_upget = False
|
||||||
self.can_admin = False
|
self.can_admin = self.can_dot = False
|
||||||
|
|
||||||
self.listdirinfo = self.listdir
|
self.listdirinfo = self.listdir
|
||||||
self.chdir(".")
|
self.chdir(".")
|
||||||
|
|
||||||
|
def log(self, msg: str, c: Union[int, str] = 0) -> None:
|
||||||
|
self.hub.log("ftpd", msg, c)
|
||||||
|
|
||||||
def v2a(
|
def v2a(
|
||||||
self,
|
self,
|
||||||
vpath: str,
|
vpath: str,
|
||||||
@@ -167,7 +177,7 @@ class FtpFs(AbstractedFS):
|
|||||||
if not avfs:
|
if not avfs:
|
||||||
raise FSE(t.format(vpath), 1)
|
raise FSE(t.format(vpath), 1)
|
||||||
|
|
||||||
cr, cw, cm, cd, _, _, _ = avfs.can_access("", self.h.uname)
|
cr, cw, cm, cd, _, _, _, _ = avfs.can_access("", self.h.uname)
|
||||||
if r and not cr or w and not cw or m and not cm or d and not cd:
|
if r and not cr or w and not cw or m and not cm or d and not cd:
|
||||||
raise FSE(t.format(vpath), 1)
|
raise FSE(t.format(vpath), 1)
|
||||||
|
|
||||||
@@ -205,18 +215,38 @@ class FtpFs(AbstractedFS):
|
|||||||
w = "w" in mode or "a" in mode or "+" in mode
|
w = "w" in mode or "a" in mode or "+" in mode
|
||||||
|
|
||||||
ap = self.rv2a(filename, r, w)[0]
|
ap = self.rv2a(filename, r, w)[0]
|
||||||
|
self.validpath(ap)
|
||||||
if w:
|
if w:
|
||||||
try:
|
try:
|
||||||
st = bos.stat(ap)
|
st = bos.stat(ap)
|
||||||
td = time.time() - st.st_mtime
|
td = time.time() - st.st_mtime
|
||||||
|
need_unlink = True
|
||||||
except:
|
except:
|
||||||
|
need_unlink = False
|
||||||
td = 0
|
td = 0
|
||||||
|
|
||||||
if td < -1 or td > self.args.ftp_wt:
|
if w and need_unlink:
|
||||||
raise FSE("Cannot open existing file for writing")
|
if td >= -1 and td <= self.args.ftp_wt:
|
||||||
|
# within permitted timeframe; unlink and accept
|
||||||
|
do_it = True
|
||||||
|
elif self.args.no_del or self.args.ftp_no_ow:
|
||||||
|
# file too old, or overwrite not allowed; reject
|
||||||
|
do_it = False
|
||||||
|
else:
|
||||||
|
# allow overwrite if user has delete permission
|
||||||
|
# (avoids win2000 freaking out and deleting the server copy without uploading its own)
|
||||||
|
try:
|
||||||
|
self.rv2a(filename, False, True, False, True)
|
||||||
|
do_it = True
|
||||||
|
except:
|
||||||
|
do_it = False
|
||||||
|
|
||||||
self.validpath(ap)
|
if not do_it:
|
||||||
return open(fsenc(ap), mode)
|
raise FSE("File already exists")
|
||||||
|
|
||||||
|
wunlink(self.log, ap, VF_CAREFUL)
|
||||||
|
|
||||||
|
return open(fsenc(ap), mode, self.args.iobuf)
|
||||||
|
|
||||||
def chdir(self, path: str) -> None:
|
def chdir(self, path: str) -> None:
|
||||||
nwd = join(self.cwd, path)
|
nwd = join(self.cwd, path)
|
||||||
@@ -243,6 +273,7 @@ class FtpFs(AbstractedFS):
|
|||||||
self.can_get,
|
self.can_get,
|
||||||
self.can_upget,
|
self.can_upget,
|
||||||
self.can_admin,
|
self.can_admin,
|
||||||
|
self.can_dot,
|
||||||
) = avfs.can_access("", self.h.uname)
|
) = avfs.can_access("", self.h.uname)
|
||||||
|
|
||||||
def mkdir(self, path: str) -> None:
|
def mkdir(self, path: str) -> None:
|
||||||
@@ -265,7 +296,7 @@ class FtpFs(AbstractedFS):
|
|||||||
vfs_ls = [x[0] for x in vfs_ls1]
|
vfs_ls = [x[0] for x in vfs_ls1]
|
||||||
vfs_ls.extend(vfs_virt.keys())
|
vfs_ls.extend(vfs_virt.keys())
|
||||||
|
|
||||||
if not self.args.ed:
|
if not self.can_dot:
|
||||||
vfs_ls = exclude_dotfiles(vfs_ls)
|
vfs_ls = exclude_dotfiles(vfs_ls)
|
||||||
|
|
||||||
vfs_ls.sort()
|
vfs_ls.sort()
|
||||||
@@ -279,9 +310,20 @@ class FtpFs(AbstractedFS):
|
|||||||
# display write-only folders as empty
|
# display write-only folders as empty
|
||||||
return []
|
return []
|
||||||
|
|
||||||
# return list of volumes
|
# return list of accessible volumes
|
||||||
r = {x.split("/")[0]: 1 for x in self.hub.asrv.vfs.all_vols.keys()}
|
ret = []
|
||||||
return list(sorted(list(r.keys())))
|
for vn in self.hub.asrv.vfs.all_vols.values():
|
||||||
|
if "/" in vn.vpath or not vn.vpath:
|
||||||
|
continue # only include toplevel-mounted vols
|
||||||
|
|
||||||
|
try:
|
||||||
|
self.hub.asrv.vfs.get(vn.vpath, self.uname, True, False)
|
||||||
|
ret.append(vn.vpath)
|
||||||
|
except:
|
||||||
|
pass
|
||||||
|
|
||||||
|
ret.sort()
|
||||||
|
return ret
|
||||||
|
|
||||||
def rmdir(self, path: str) -> None:
|
def rmdir(self, path: str) -> None:
|
||||||
ap = self.rv2a(path, d=True)[0]
|
ap = self.rv2a(path, d=True)[0]
|
||||||
@@ -297,7 +339,7 @@ class FtpFs(AbstractedFS):
|
|||||||
|
|
||||||
vp = join(self.cwd, path).lstrip("/")
|
vp = join(self.cwd, path).lstrip("/")
|
||||||
try:
|
try:
|
||||||
self.hub.up2k.handle_rm(self.uname, self.h.cli_ip, [vp], [], False)
|
self.hub.up2k.handle_rm(self.uname, self.h.cli_ip, [vp], [], False, False)
|
||||||
except Exception as ex:
|
except Exception as ex:
|
||||||
raise FSE(str(ex))
|
raise FSE(str(ex))
|
||||||
|
|
||||||
@@ -311,7 +353,7 @@ class FtpFs(AbstractedFS):
|
|||||||
svp = join(self.cwd, src).lstrip("/")
|
svp = join(self.cwd, src).lstrip("/")
|
||||||
dvp = join(self.cwd, dst).lstrip("/")
|
dvp = join(self.cwd, dst).lstrip("/")
|
||||||
try:
|
try:
|
||||||
self.hub.up2k.handle_mv(self.uname, svp, dvp)
|
self.hub.up2k.handle_mv(self.uname, self.h.cli_ip, svp, dvp)
|
||||||
except Exception as ex:
|
except Exception as ex:
|
||||||
raise FSE(str(ex))
|
raise FSE(str(ex))
|
||||||
|
|
||||||
@@ -404,7 +446,16 @@ class FtpHandler(FTPHandler):
|
|||||||
super(FtpHandler, self).__init__(conn, server, ioloop)
|
super(FtpHandler, self).__init__(conn, server, ioloop)
|
||||||
|
|
||||||
cip = self.remote_ip
|
cip = self.remote_ip
|
||||||
self.cli_ip = cip[7:] if cip.startswith("::ffff:") else cip
|
if cip.startswith("::ffff:"):
|
||||||
|
cip = cip[7:]
|
||||||
|
|
||||||
|
if self.args.ftp_ipa_nm and not self.args.ftp_ipa_nm.map(cip):
|
||||||
|
logging.warning("client rejected (--ftp-ipa): %s", cip)
|
||||||
|
self.connected = False
|
||||||
|
conn.close()
|
||||||
|
return
|
||||||
|
|
||||||
|
self.cli_ip = cip
|
||||||
|
|
||||||
# abspath->vpath mapping to resolve log_transfer paths
|
# abspath->vpath mapping to resolve log_transfer paths
|
||||||
self.vfs_map: dict[str, str] = {}
|
self.vfs_map: dict[str, str] = {}
|
||||||
@@ -420,15 +471,19 @@ class FtpHandler(FTPHandler):
|
|||||||
xbu = vfs.flags.get("xbu")
|
xbu = vfs.flags.get("xbu")
|
||||||
if xbu and not runhook(
|
if xbu and not runhook(
|
||||||
None,
|
None,
|
||||||
|
None,
|
||||||
|
self.hub.up2k,
|
||||||
|
"xbu.ftpd",
|
||||||
xbu,
|
xbu,
|
||||||
ap,
|
ap,
|
||||||
vfs.canonical(rem),
|
vp,
|
||||||
"",
|
"",
|
||||||
self.uname,
|
self.uname,
|
||||||
|
self.hub.asrv.vfs.get_perms(vp, self.uname),
|
||||||
0,
|
0,
|
||||||
0,
|
0,
|
||||||
self.cli_ip,
|
self.cli_ip,
|
||||||
0,
|
time.time(),
|
||||||
"",
|
"",
|
||||||
):
|
):
|
||||||
raise FSE("Upload blocked by xbu server config")
|
raise FSE("Upload blocked by xbu server config")
|
||||||
@@ -531,9 +586,17 @@ class Ftpd(object):
|
|||||||
if "::" in ips:
|
if "::" in ips:
|
||||||
ips.append("0.0.0.0")
|
ips.append("0.0.0.0")
|
||||||
|
|
||||||
|
ips = [x for x in ips if "unix:" not in x]
|
||||||
|
|
||||||
if self.args.ftp4:
|
if self.args.ftp4:
|
||||||
ips = [x for x in ips if ":" not in x]
|
ips = [x for x in ips if ":" not in x]
|
||||||
|
|
||||||
|
if not ips:
|
||||||
|
lgr.fatal("cannot start ftp-server; no compatible IPs in -i")
|
||||||
|
return
|
||||||
|
|
||||||
|
ips = list(ODict.fromkeys(ips)) # dedup
|
||||||
|
|
||||||
ioloop = IOLoop()
|
ioloop = IOLoop()
|
||||||
for ip in ips:
|
for ip in ips:
|
||||||
for h, lp in hs:
|
for h, lp in hs:
|
||||||
|
|||||||
2111
copyparty/httpcli.py
2111
copyparty/httpcli.py
File diff suppressed because it is too large
Load Diff
@@ -9,6 +9,9 @@ import threading # typechk
|
|||||||
import time
|
import time
|
||||||
|
|
||||||
try:
|
try:
|
||||||
|
if os.environ.get("PRTY_NO_TLS"):
|
||||||
|
raise Exception()
|
||||||
|
|
||||||
HAVE_SSL = True
|
HAVE_SSL = True
|
||||||
import ssl
|
import ssl
|
||||||
except:
|
except:
|
||||||
@@ -23,7 +26,7 @@ from .mtag import HAVE_FFMPEG
|
|||||||
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 .u2idx import U2idx
|
from .u2idx import U2idx
|
||||||
from .util import HMaccas, shut_socket
|
from .util import HMaccas, NetMap, shut_socket
|
||||||
|
|
||||||
if True: # pylint: disable=using-constant-test
|
if True: # pylint: disable=using-constant-test
|
||||||
from typing import Optional, Pattern, Union
|
from typing import Optional, Pattern, Union
|
||||||
@@ -50,11 +53,15 @@ class HttpConn(object):
|
|||||||
self.addr = addr
|
self.addr = addr
|
||||||
self.hsrv = hsrv
|
self.hsrv = hsrv
|
||||||
|
|
||||||
self.mutex: threading.Lock = hsrv.mutex # mypy404
|
self.u2mutex: threading.Lock = hsrv.u2mutex # mypy404
|
||||||
self.args: argparse.Namespace = hsrv.args # mypy404
|
self.args: argparse.Namespace = hsrv.args # mypy404
|
||||||
self.E: EnvParams = self.args.E
|
self.E: EnvParams = self.args.E
|
||||||
self.asrv: AuthSrv = hsrv.asrv # mypy404
|
self.asrv: AuthSrv = hsrv.asrv # mypy404
|
||||||
self.u2fh: Util.FHC = hsrv.u2fh # mypy404
|
self.u2fh: Util.FHC = hsrv.u2fh # mypy404
|
||||||
|
self.pipes: Util.CachedDict = hsrv.pipes # mypy404
|
||||||
|
self.ipa_nm: Optional[NetMap] = hsrv.ipa_nm
|
||||||
|
self.xff_nm: Optional[NetMap] = hsrv.xff_nm
|
||||||
|
self.xff_lan: NetMap = hsrv.xff_lan # type: ignore
|
||||||
self.iphash: HMaccas = hsrv.broker.iphash
|
self.iphash: HMaccas = hsrv.broker.iphash
|
||||||
self.bans: dict[str, int] = hsrv.bans
|
self.bans: dict[str, int] = hsrv.bans
|
||||||
self.aclose: dict[str, int] = hsrv.aclose
|
self.aclose: dict[str, int] = hsrv.aclose
|
||||||
@@ -93,7 +100,7 @@ class HttpConn(object):
|
|||||||
self.rproxy = ip
|
self.rproxy = ip
|
||||||
|
|
||||||
self.ip = ip
|
self.ip = ip
|
||||||
self.log_src = "{} \033[{}m{}".format(ip, color, self.addr[1]).ljust(26)
|
self.log_src = ("%s \033[%dm%d" % (ip, color, self.addr[1])).ljust(26)
|
||||||
return self.log_src
|
return self.log_src
|
||||||
|
|
||||||
def respath(self, res_name: str) -> str:
|
def respath(self, res_name: str) -> str:
|
||||||
@@ -112,32 +119,30 @@ class HttpConn(object):
|
|||||||
return self.u2idx
|
return self.u2idx
|
||||||
|
|
||||||
def _detect_https(self) -> bool:
|
def _detect_https(self) -> bool:
|
||||||
method = None
|
try:
|
||||||
if True:
|
method = self.s.recv(4, socket.MSG_PEEK)
|
||||||
try:
|
except socket.timeout:
|
||||||
method = self.s.recv(4, socket.MSG_PEEK)
|
return False
|
||||||
except socket.timeout:
|
except AttributeError:
|
||||||
return False
|
# jython does not support msg_peek; forget about https
|
||||||
except AttributeError:
|
method = self.s.recv(4)
|
||||||
# jython does not support msg_peek; forget about https
|
self.sr = Util.Unrecv(self.s, self.log)
|
||||||
method = self.s.recv(4)
|
self.sr.buf = method
|
||||||
self.sr = Util.Unrecv(self.s, self.log)
|
|
||||||
self.sr.buf = method
|
|
||||||
|
|
||||||
# jython used to do this, they stopped since it's broken
|
# jython used to do this, they stopped since it's broken
|
||||||
# but reimplementing sendall is out of scope for now
|
# but reimplementing sendall is out of scope for now
|
||||||
if not getattr(self.s, "sendall", None):
|
if not getattr(self.s, "sendall", None):
|
||||||
self.s.sendall = self.s.send # type: ignore
|
self.s.sendall = self.s.send # type: ignore
|
||||||
|
|
||||||
if len(method) != 4:
|
if len(method) != 4:
|
||||||
err = "need at least 4 bytes in the first packet; got {}".format(
|
err = "need at least 4 bytes in the first packet; got {}".format(
|
||||||
len(method)
|
len(method)
|
||||||
)
|
)
|
||||||
if method:
|
if method:
|
||||||
self.log(err)
|
self.log(err)
|
||||||
|
|
||||||
self.s.send(b"HTTP/1.1 400 Bad Request\r\n\r\n" + err.encode("utf-8"))
|
self.s.send(b"HTTP/1.1 400 Bad Request\r\n\r\n" + err.encode("utf-8"))
|
||||||
return False
|
return False
|
||||||
|
|
||||||
return not method or not bool(PTN_HTTP.match(method))
|
return not method or not bool(PTN_HTTP.match(method))
|
||||||
|
|
||||||
@@ -178,14 +183,14 @@ class HttpConn(object):
|
|||||||
|
|
||||||
self.s = ctx.wrap_socket(self.s, server_side=True)
|
self.s = ctx.wrap_socket(self.s, server_side=True)
|
||||||
msg = [
|
msg = [
|
||||||
"\033[1;3{:d}m{}".format(c, s)
|
"\033[1;3%dm%s" % (c, s)
|
||||||
for c, s in zip([0, 5, 0], self.s.cipher()) # type: ignore
|
for c, s in zip([0, 5, 0], self.s.cipher()) # type: ignore
|
||||||
]
|
]
|
||||||
self.log(" ".join(msg) + "\033[0m")
|
self.log(" ".join(msg) + "\033[0m")
|
||||||
|
|
||||||
if self.args.ssl_dbg and hasattr(self.s, "shared_ciphers"):
|
if self.args.ssl_dbg and hasattr(self.s, "shared_ciphers"):
|
||||||
ciphers = self.s.shared_ciphers()
|
ciphers = self.s.shared_ciphers()
|
||||||
assert ciphers
|
assert ciphers # !rm
|
||||||
overlap = [str(y[::-1]) for y in ciphers]
|
overlap = [str(y[::-1]) for y in ciphers]
|
||||||
self.log("TLS cipher overlap:" + "\n".join(overlap))
|
self.log("TLS cipher overlap:" + "\n".join(overlap))
|
||||||
for k, v in [
|
for k, v in [
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
# coding: utf-8
|
# coding: utf-8
|
||||||
from __future__ import print_function, unicode_literals
|
from __future__ import print_function, unicode_literals
|
||||||
|
|
||||||
import base64
|
|
||||||
import math
|
import math
|
||||||
import os
|
import os
|
||||||
import re
|
import re
|
||||||
@@ -12,7 +11,7 @@ import time
|
|||||||
|
|
||||||
import queue
|
import queue
|
||||||
|
|
||||||
from .__init__ import ANYWIN, CORES, EXE, MACOS, TYPE_CHECKING, EnvParams
|
from .__init__ import ANYWIN, CORES, EXE, MACOS, PY2, TYPE_CHECKING, EnvParams, unicode
|
||||||
|
|
||||||
try:
|
try:
|
||||||
MNFE = ModuleNotFoundError
|
MNFE = ModuleNotFoundError
|
||||||
@@ -61,18 +60,21 @@ from .u2idx import U2idx
|
|||||||
from .util import (
|
from .util import (
|
||||||
E_SCK,
|
E_SCK,
|
||||||
FHC,
|
FHC,
|
||||||
|
CachedDict,
|
||||||
Daemon,
|
Daemon,
|
||||||
Garda,
|
Garda,
|
||||||
Magician,
|
Magician,
|
||||||
Netdev,
|
Netdev,
|
||||||
NetMap,
|
NetMap,
|
||||||
absreal,
|
absreal,
|
||||||
|
build_netmap,
|
||||||
ipnorm,
|
ipnorm,
|
||||||
min_ex,
|
min_ex,
|
||||||
shut_socket,
|
shut_socket,
|
||||||
spack,
|
spack,
|
||||||
start_log_thrs,
|
start_log_thrs,
|
||||||
start_stackmon,
|
start_stackmon,
|
||||||
|
ub64enc,
|
||||||
)
|
)
|
||||||
|
|
||||||
if TYPE_CHECKING:
|
if TYPE_CHECKING:
|
||||||
@@ -82,6 +84,12 @@ if TYPE_CHECKING:
|
|||||||
if True: # pylint: disable=using-constant-test
|
if True: # pylint: disable=using-constant-test
|
||||||
from typing import Any, Optional
|
from typing import Any, Optional
|
||||||
|
|
||||||
|
if PY2:
|
||||||
|
range = xrange # type: ignore
|
||||||
|
|
||||||
|
if not hasattr(socket, "AF_UNIX"):
|
||||||
|
setattr(socket, "AF_UNIX", -9001)
|
||||||
|
|
||||||
|
|
||||||
class HttpSrv(object):
|
class HttpSrv(object):
|
||||||
"""
|
"""
|
||||||
@@ -103,12 +111,13 @@ class HttpSrv(object):
|
|||||||
self.t0 = time.time()
|
self.t0 = time.time()
|
||||||
nsuf = "-n{}-i{:x}".format(nid, os.getpid()) if nid else ""
|
nsuf = "-n{}-i{:x}".format(nid, os.getpid()) if nid else ""
|
||||||
self.magician = Magician()
|
self.magician = Magician()
|
||||||
self.nm = NetMap([], {})
|
self.nm = NetMap([], [])
|
||||||
self.ssdp: Optional["SSDPr"] = None
|
self.ssdp: Optional["SSDPr"] = None
|
||||||
self.gpwd = Garda(self.args.ban_pw)
|
self.gpwd = Garda(self.args.ban_pw)
|
||||||
self.g404 = Garda(self.args.ban_404)
|
self.g404 = Garda(self.args.ban_404)
|
||||||
self.g403 = Garda(self.args.ban_403)
|
self.g403 = Garda(self.args.ban_403)
|
||||||
self.g422 = Garda(self.args.ban_422, False)
|
self.g422 = Garda(self.args.ban_422, False)
|
||||||
|
self.gmal = Garda(self.args.ban_422)
|
||||||
self.gurl = Garda(self.args.ban_url)
|
self.gurl = Garda(self.args.ban_url)
|
||||||
self.bans: dict[str, int] = {}
|
self.bans: dict[str, int] = {}
|
||||||
self.aclose: dict[str, int] = {}
|
self.aclose: dict[str, int] = {}
|
||||||
@@ -116,6 +125,7 @@ class HttpSrv(object):
|
|||||||
self.bound: set[tuple[str, int]] = set()
|
self.bound: set[tuple[str, int]] = set()
|
||||||
self.name = "hsrv" + nsuf
|
self.name = "hsrv" + nsuf
|
||||||
self.mutex = threading.Lock()
|
self.mutex = threading.Lock()
|
||||||
|
self.u2mutex = threading.Lock()
|
||||||
self.stopping = False
|
self.stopping = False
|
||||||
|
|
||||||
self.tp_nthr = 0 # actual
|
self.tp_nthr = 0 # actual
|
||||||
@@ -127,6 +137,7 @@ class HttpSrv(object):
|
|||||||
self.t_periodic: Optional[threading.Thread] = None
|
self.t_periodic: Optional[threading.Thread] = None
|
||||||
|
|
||||||
self.u2fh = FHC()
|
self.u2fh = FHC()
|
||||||
|
self.pipes = CachedDict(0.2)
|
||||||
self.metrics = Metrics(self)
|
self.metrics = Metrics(self)
|
||||||
self.nreq = 0
|
self.nreq = 0
|
||||||
self.nsus = 0
|
self.nsus = 0
|
||||||
@@ -143,11 +154,25 @@ class HttpSrv(object):
|
|||||||
|
|
||||||
env = jinja2.Environment()
|
env = jinja2.Environment()
|
||||||
env.loader = jinja2.FileSystemLoader(os.path.join(self.E.mod, "web"))
|
env.loader = jinja2.FileSystemLoader(os.path.join(self.E.mod, "web"))
|
||||||
jn = ["splash", "svcs", "browser", "browser2", "msg", "md", "mde", "cf"]
|
jn = [
|
||||||
|
"splash",
|
||||||
|
"shares",
|
||||||
|
"svcs",
|
||||||
|
"browser",
|
||||||
|
"browser2",
|
||||||
|
"msg",
|
||||||
|
"md",
|
||||||
|
"mde",
|
||||||
|
"cf",
|
||||||
|
]
|
||||||
self.j2 = {x: env.get_template(x + ".html") for x in jn}
|
self.j2 = {x: env.get_template(x + ".html") for x in jn}
|
||||||
zs = os.path.join(self.E.mod, "web", "deps", "prism.js.gz")
|
zs = os.path.join(self.E.mod, "web", "deps", "prism.js.gz")
|
||||||
self.prism = os.path.exists(zs)
|
self.prism = os.path.exists(zs)
|
||||||
|
|
||||||
|
self.ipa_nm = build_netmap(self.args.ipa)
|
||||||
|
self.xff_nm = build_netmap(self.args.xff_src)
|
||||||
|
self.xff_lan = build_netmap("lan")
|
||||||
|
|
||||||
self.statics: set[str] = set()
|
self.statics: set[str] = set()
|
||||||
self._build_statics()
|
self._build_statics()
|
||||||
|
|
||||||
@@ -189,7 +214,7 @@ class HttpSrv(object):
|
|||||||
for fn in df:
|
for fn in df:
|
||||||
ap = absreal(os.path.join(dp, fn))
|
ap = absreal(os.path.join(dp, fn))
|
||||||
self.statics.add(ap)
|
self.statics.add(ap)
|
||||||
if ap.endswith(".gz") or ap.endswith(".br"):
|
if ap.endswith(".gz"):
|
||||||
self.statics.add(ap[:-3])
|
self.statics.add(ap[:-3])
|
||||||
|
|
||||||
def set_netdevs(self, netdevs: dict[str, Netdev]) -> None:
|
def set_netdevs(self, netdevs: dict[str, Netdev]) -> None:
|
||||||
@@ -197,7 +222,7 @@ class HttpSrv(object):
|
|||||||
for ip, _ in self.bound:
|
for ip, _ in self.bound:
|
||||||
ips.add(ip)
|
ips.add(ip)
|
||||||
|
|
||||||
self.nm = NetMap(list(ips), netdevs)
|
self.nm = NetMap(list(ips), list(netdevs))
|
||||||
|
|
||||||
def start_threads(self, n: int) -> None:
|
def start_threads(self, n: int) -> None:
|
||||||
self.tp_nthr += n
|
self.tp_nthr += n
|
||||||
@@ -212,14 +237,14 @@ class HttpSrv(object):
|
|||||||
if self.args.log_htp:
|
if self.args.log_htp:
|
||||||
self.log(self.name, "workers -= {} = {}".format(n, self.tp_nthr), 6)
|
self.log(self.name, "workers -= {} = {}".format(n, self.tp_nthr), 6)
|
||||||
|
|
||||||
assert self.tp_q
|
assert self.tp_q # !rm
|
||||||
for _ in range(n):
|
for _ in range(n):
|
||||||
self.tp_q.put(None)
|
self.tp_q.put(None)
|
||||||
|
|
||||||
def periodic(self) -> None:
|
def periodic(self) -> None:
|
||||||
while True:
|
while True:
|
||||||
time.sleep(2 if self.tp_ncli or self.ncli else 10)
|
time.sleep(2 if self.tp_ncli or self.ncli else 10)
|
||||||
with self.mutex:
|
with self.u2mutex, self.mutex:
|
||||||
self.u2fh.clean()
|
self.u2fh.clean()
|
||||||
if self.tp_q:
|
if self.tp_q:
|
||||||
self.tp_ncli = max(self.ncli, self.tp_ncli - 2)
|
self.tp_ncli = max(self.ncli, self.tp_ncli - 2)
|
||||||
@@ -231,15 +256,24 @@ class HttpSrv(object):
|
|||||||
return
|
return
|
||||||
|
|
||||||
def listen(self, sck: socket.socket, nlisteners: int) -> None:
|
def listen(self, sck: socket.socket, nlisteners: int) -> None:
|
||||||
|
tcp = sck.family != socket.AF_UNIX
|
||||||
|
|
||||||
if self.args.j != 1:
|
if self.args.j != 1:
|
||||||
# lost in the pickle; redefine
|
# lost in the pickle; redefine
|
||||||
if not ANYWIN or self.args.reuseaddr:
|
if not ANYWIN or self.args.reuseaddr:
|
||||||
sck.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
|
sck.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
|
||||||
|
|
||||||
sck.setsockopt(socket.IPPROTO_TCP, socket.TCP_NODELAY, 1)
|
if tcp:
|
||||||
|
sck.setsockopt(socket.IPPROTO_TCP, socket.TCP_NODELAY, 1)
|
||||||
|
|
||||||
sck.settimeout(None) # < does not inherit, ^ opts above do
|
sck.settimeout(None) # < does not inherit, ^ opts above do
|
||||||
|
|
||||||
ip, port = sck.getsockname()[:2]
|
if tcp:
|
||||||
|
ip, port = sck.getsockname()[:2]
|
||||||
|
else:
|
||||||
|
ip = re.sub(r"\.[0-9]+$", "", sck.getsockname().split("/")[-1])
|
||||||
|
port = 0
|
||||||
|
|
||||||
self.srvs.append(sck)
|
self.srvs.append(sck)
|
||||||
self.bound.add((ip, port))
|
self.bound.add((ip, port))
|
||||||
self.nclimax = math.ceil(self.args.nc * 1.0 / nlisteners)
|
self.nclimax = math.ceil(self.args.nc * 1.0 / nlisteners)
|
||||||
@@ -251,16 +285,22 @@ class HttpSrv(object):
|
|||||||
|
|
||||||
def thr_listen(self, srv_sck: socket.socket) -> None:
|
def thr_listen(self, srv_sck: socket.socket) -> None:
|
||||||
"""listens on a shared tcp server"""
|
"""listens on a shared tcp server"""
|
||||||
ip, port = srv_sck.getsockname()[:2]
|
|
||||||
fno = srv_sck.fileno()
|
fno = srv_sck.fileno()
|
||||||
hip = "[{}]".format(ip) if ":" in ip else ip
|
if srv_sck.family == socket.AF_UNIX:
|
||||||
msg = "subscribed @ {}:{} f{} p{}".format(hip, port, fno, os.getpid())
|
ip = re.sub(r"\.[0-9]+$", "", srv_sck.getsockname())
|
||||||
|
msg = "subscribed @ %s f%d p%d" % (ip, fno, os.getpid())
|
||||||
|
ip = ip.split("/")[-1]
|
||||||
|
port = 0
|
||||||
|
tcp = False
|
||||||
|
else:
|
||||||
|
tcp = True
|
||||||
|
ip, port = srv_sck.getsockname()[:2]
|
||||||
|
hip = "[%s]" % (ip,) if ":" in ip else ip
|
||||||
|
msg = "subscribed @ %s:%d f%d p%d" % (hip, port, fno, os.getpid())
|
||||||
|
|
||||||
self.log(self.name, msg)
|
self.log(self.name, msg)
|
||||||
|
|
||||||
def fun() -> None:
|
Daemon(self.broker.say, "sig-hsrv-up1", ("cb_httpsrv_up",))
|
||||||
self.broker.say("cb_httpsrv_up")
|
|
||||||
|
|
||||||
threading.Thread(target=fun, name="sig-hsrv-up1").start()
|
|
||||||
|
|
||||||
while not self.stopping:
|
while not self.stopping:
|
||||||
if self.args.log_conn:
|
if self.args.log_conn:
|
||||||
@@ -329,11 +369,13 @@ class HttpSrv(object):
|
|||||||
|
|
||||||
try:
|
try:
|
||||||
sck, saddr = srv_sck.accept()
|
sck, saddr = srv_sck.accept()
|
||||||
cip, cport = saddr[:2]
|
if tcp:
|
||||||
if cip.startswith("::ffff:"):
|
cip = unicode(saddr[0])
|
||||||
cip = cip[7:]
|
if cip.startswith("::ffff:"):
|
||||||
|
cip = cip[7:]
|
||||||
addr = (cip, cport)
|
addr = (cip, saddr[1])
|
||||||
|
else:
|
||||||
|
addr = ("127.8.3.7", sck.fileno())
|
||||||
except (OSError, socket.error) as ex:
|
except (OSError, socket.error) as ex:
|
||||||
if self.stopping:
|
if self.stopping:
|
||||||
break
|
break
|
||||||
@@ -365,7 +407,7 @@ class HttpSrv(object):
|
|||||||
if not self.t_periodic:
|
if not self.t_periodic:
|
||||||
name = "hsrv-pt"
|
name = "hsrv-pt"
|
||||||
if self.nid:
|
if self.nid:
|
||||||
name += "-{}".format(self.nid)
|
name += "-%d" % (self.nid,)
|
||||||
|
|
||||||
self.t_periodic = Daemon(self.periodic, name)
|
self.t_periodic = Daemon(self.periodic, name)
|
||||||
|
|
||||||
@@ -384,12 +426,12 @@ class HttpSrv(object):
|
|||||||
|
|
||||||
Daemon(
|
Daemon(
|
||||||
self.thr_client,
|
self.thr_client,
|
||||||
"httpconn-{}-{}".format(addr[0].split(".", 2)[-1][-6:], addr[1]),
|
"httpconn-%s-%d" % (addr[0].split(".", 2)[-1][-6:], addr[1]),
|
||||||
(sck, addr),
|
(sck, addr),
|
||||||
)
|
)
|
||||||
|
|
||||||
def thr_poolw(self) -> None:
|
def thr_poolw(self) -> None:
|
||||||
assert self.tp_q
|
assert self.tp_q # !rm
|
||||||
while True:
|
while True:
|
||||||
task = self.tp_q.get()
|
task = self.tp_q.get()
|
||||||
if not task:
|
if not task:
|
||||||
@@ -401,9 +443,7 @@ class HttpSrv(object):
|
|||||||
try:
|
try:
|
||||||
sck, addr = task
|
sck, addr = task
|
||||||
me = threading.current_thread()
|
me = threading.current_thread()
|
||||||
me.name = "httpconn-{}-{}".format(
|
me.name = "httpconn-%s-%d" % (addr[0].split(".", 2)[-1][-6:], addr[1])
|
||||||
addr[0].split(".", 2)[-1][-6:], addr[1]
|
|
||||||
)
|
|
||||||
self.thr_client(sck, addr)
|
self.thr_client(sck, addr)
|
||||||
me.name = self.name + "-poolw"
|
me.name = self.name + "-poolw"
|
||||||
except Exception as ex:
|
except Exception as ex:
|
||||||
@@ -503,8 +543,8 @@ class HttpSrv(object):
|
|||||||
except:
|
except:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
v = base64.urlsafe_b64encode(spack(b">xxL", int(v)))
|
# spack gives 4 lsb, take 3 lsb, get 4 ch
|
||||||
self.cb_v = v.decode("ascii")[-4:]
|
self.cb_v = ub64enc(spack(b">L", int(v))[1:]).decode("ascii")
|
||||||
self.cb_ts = time.time()
|
self.cb_ts = time.time()
|
||||||
return self.cb_v
|
return self.cb_v
|
||||||
|
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ import re
|
|||||||
|
|
||||||
from .__init__ import PY2
|
from .__init__ import PY2
|
||||||
from .th_srv import HAVE_PIL, HAVE_PILF
|
from .th_srv import HAVE_PIL, HAVE_PILF
|
||||||
from .util import BytesIO
|
from .util import BytesIO, html_escape # type: ignore
|
||||||
|
|
||||||
|
|
||||||
class Ico(object):
|
class Ico(object):
|
||||||
@@ -22,19 +22,18 @@ class Ico(object):
|
|||||||
ext = bext.decode("utf-8")
|
ext = bext.decode("utf-8")
|
||||||
zb = hashlib.sha1(bext).digest()[2:4]
|
zb = hashlib.sha1(bext).digest()[2:4]
|
||||||
if PY2:
|
if PY2:
|
||||||
zb = [ord(x) for x in zb]
|
zb = [ord(x) for x in zb] # type: ignore
|
||||||
|
|
||||||
c1 = colorsys.hsv_to_rgb(zb[0] / 256.0, 1, 0.3)
|
c1 = colorsys.hsv_to_rgb(zb[0] / 256.0, 1, 0.3)
|
||||||
c2 = colorsys.hsv_to_rgb(zb[0] / 256.0, 0.8 if HAVE_PILF else 1, 1)
|
c2 = colorsys.hsv_to_rgb(zb[0] / 256.0, 0.8 if HAVE_PILF else 1, 1)
|
||||||
ci = [int(x * 255) for x in list(c1) + list(c2)]
|
ci = [int(x * 255) for x in list(c1) + list(c2)]
|
||||||
c = "".join(["{:02x}".format(x) for x in ci])
|
c = "".join(["%02x" % (x,) for x in ci])
|
||||||
|
|
||||||
w = 100
|
w = 100
|
||||||
h = 30
|
h = 30
|
||||||
if not self.args.th_no_crop and as_thumb:
|
if as_thumb:
|
||||||
sw, sh = self.args.th_size.split("x")
|
sw, sh = self.args.th_size.split("x")
|
||||||
h = int(100 / (float(sw) / float(sh)))
|
h = int(100.0 / (float(sw) / float(sh)))
|
||||||
w = 100
|
|
||||||
|
|
||||||
if chrome:
|
if chrome:
|
||||||
# cannot handle more than ~2000 unique SVGs
|
# cannot handle more than ~2000 unique SVGs
|
||||||
@@ -47,12 +46,12 @@ class Ico(object):
|
|||||||
# [.lt] are hard to see lowercase / unspaced
|
# [.lt] are hard to see lowercase / unspaced
|
||||||
ext2 = re.sub("(.)", "\\1 ", ext).upper()
|
ext2 = re.sub("(.)", "\\1 ", ext).upper()
|
||||||
|
|
||||||
h = int(128 * h / w)
|
h = int(128.0 * h / w)
|
||||||
w = 128
|
w = 128
|
||||||
img = Image.new("RGB", (w, h), "#" + c[:6])
|
img = Image.new("RGB", (w, h), "#" + c[:6])
|
||||||
pb = ImageDraw.Draw(img)
|
pb = ImageDraw.Draw(img)
|
||||||
_, _, tw, th = pb.textbbox((0, 0), ext2, font_size=16)
|
_, _, tw, th = pb.textbbox((0, 0), ext2, font_size=16)
|
||||||
xy = ((w - tw) // 2, (h - th) // 2)
|
xy = (int((w - tw) / 2), int((h - th) / 2))
|
||||||
pb.text(xy, ext2, fill="#" + c[6:], font_size=16)
|
pb.text(xy, ext2, fill="#" + c[6:], font_size=16)
|
||||||
|
|
||||||
img = img.resize((w * 2, h * 2), Image.NEAREST)
|
img = img.resize((w * 2, h * 2), Image.NEAREST)
|
||||||
@@ -68,14 +67,14 @@ class Ico(object):
|
|||||||
# svg: 3s, cache: 6s, this: 8s
|
# svg: 3s, cache: 6s, this: 8s
|
||||||
from PIL import Image, ImageDraw
|
from PIL import Image, ImageDraw
|
||||||
|
|
||||||
h = int(64 * h / w)
|
h = int(64.0 * h / w)
|
||||||
w = 64
|
w = 64
|
||||||
img = Image.new("RGB", (w, h), "#" + c[:6])
|
img = Image.new("RGB", (w, h), "#" + c[:6])
|
||||||
pb = ImageDraw.Draw(img)
|
pb = ImageDraw.Draw(img)
|
||||||
try:
|
try:
|
||||||
_, _, tw, th = pb.textbbox((0, 0), ext)
|
_, _, tw, th = pb.textbbox((0, 0), ext)
|
||||||
except:
|
except:
|
||||||
tw, th = pb.textsize(ext)
|
tw, th = pb.textsize(ext) # type: ignore
|
||||||
|
|
||||||
tw += len(ext)
|
tw += len(ext)
|
||||||
cw = tw // len(ext)
|
cw = tw // len(ext)
|
||||||
@@ -91,20 +90,6 @@ class Ico(object):
|
|||||||
img.save(buf, format="PNG", compress_level=1)
|
img.save(buf, format="PNG", compress_level=1)
|
||||||
return "image/png", buf.getvalue()
|
return "image/png", buf.getvalue()
|
||||||
|
|
||||||
elif False:
|
|
||||||
# 48s, too slow
|
|
||||||
import pyvips
|
|
||||||
|
|
||||||
h = int(192 * h / w)
|
|
||||||
w = 192
|
|
||||||
img = pyvips.Image.text(
|
|
||||||
ext, width=w, height=h, dpi=192, align=pyvips.Align.CENTRE
|
|
||||||
)
|
|
||||||
img = img.ifthenelse(ci[3:], ci[:3], blend=True)
|
|
||||||
# i = i.resize(3, kernel=pyvips.Kernel.NEAREST)
|
|
||||||
buf = img.write_to_buffer(".png[compression=1]")
|
|
||||||
return "image/png", buf
|
|
||||||
|
|
||||||
svg = """\
|
svg = """\
|
||||||
<?xml version="1.0" encoding="UTF-8"?>
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
<svg version="1.1" viewBox="0 0 100 {}" xmlns="http://www.w3.org/2000/svg"><g>
|
<svg version="1.1" viewBox="0 0 100 {}" xmlns="http://www.w3.org/2000/svg"><g>
|
||||||
@@ -113,6 +98,6 @@ class Ico(object):
|
|||||||
fill="#{}" font-family="monospace" font-size="14px" style="letter-spacing:.5px">{}</text>
|
fill="#{}" font-family="monospace" font-size="14px" style="letter-spacing:.5px">{}</text>
|
||||||
</g></svg>
|
</g></svg>
|
||||||
"""
|
"""
|
||||||
svg = svg.format(h, c[:6], c[6:], ext)
|
svg = svg.format(h, c[:6], c[6:], html_escape(ext, True))
|
||||||
|
|
||||||
return "image/svg+xml", svg.encode("utf-8")
|
return "image/svg+xml", svg.encode("utf-8")
|
||||||
|
|||||||
@@ -292,6 +292,22 @@ class MDNS(MCast):
|
|||||||
def run2(self) -> None:
|
def run2(self) -> None:
|
||||||
last_hop = time.time()
|
last_hop = time.time()
|
||||||
ihop = self.args.mc_hop
|
ihop = self.args.mc_hop
|
||||||
|
|
||||||
|
try:
|
||||||
|
if self.args.no_poll:
|
||||||
|
raise Exception()
|
||||||
|
fd2sck = {}
|
||||||
|
srvpoll = select.poll()
|
||||||
|
for sck in self.srv:
|
||||||
|
fd = sck.fileno()
|
||||||
|
fd2sck[fd] = sck
|
||||||
|
srvpoll.register(fd, select.POLLIN)
|
||||||
|
except Exception as ex:
|
||||||
|
srvpoll = None
|
||||||
|
if not self.args.no_poll:
|
||||||
|
t = "WARNING: failed to poll(), will use select() instead: %r"
|
||||||
|
self.log(t % (ex,), 3)
|
||||||
|
|
||||||
while self.running:
|
while self.running:
|
||||||
timeout = (
|
timeout = (
|
||||||
0.02 + random.random() * 0.07
|
0.02 + random.random() * 0.07
|
||||||
@@ -300,8 +316,13 @@ class MDNS(MCast):
|
|||||||
if self.unsolicited
|
if self.unsolicited
|
||||||
else (last_hop + ihop if ihop else 180)
|
else (last_hop + ihop if ihop else 180)
|
||||||
)
|
)
|
||||||
rdy = select.select(self.srv, [], [], timeout)
|
if srvpoll:
|
||||||
rx: list[socket.socket] = rdy[0] # type: ignore
|
pr = srvpoll.poll(timeout * 1000)
|
||||||
|
rx = [fd2sck[x[0]] for x in pr if x[1] & select.POLLIN]
|
||||||
|
else:
|
||||||
|
rdy = select.select(self.srv, [], [], timeout)
|
||||||
|
rx: list[socket.socket] = rdy[0] # type: ignore
|
||||||
|
|
||||||
self.rx4.cln()
|
self.rx4.cln()
|
||||||
self.rx6.cln()
|
self.rx6.cln()
|
||||||
buf = b""
|
buf = b""
|
||||||
@@ -340,7 +361,7 @@ class MDNS(MCast):
|
|||||||
except:
|
except:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
self.srv = {}
|
self.srv.clear()
|
||||||
|
|
||||||
def eat(self, buf: bytes, addr: tuple[str, int], sck: socket.socket) -> None:
|
def eat(self, buf: bytes, addr: tuple[str, int], sck: socket.socket) -> None:
|
||||||
cip = addr[0]
|
cip = addr[0]
|
||||||
|
|||||||
@@ -179,7 +179,7 @@ class Metrics(object):
|
|||||||
tnbytes = 0
|
tnbytes = 0
|
||||||
tnfiles = 0
|
tnfiles = 0
|
||||||
for vpath, vol in allvols:
|
for vpath, vol in allvols:
|
||||||
cur = idx.get_cur(vol.realpath)
|
cur = idx.get_cur(vol)
|
||||||
if not cur:
|
if not cur:
|
||||||
continue
|
continue
|
||||||
|
|
||||||
@@ -206,6 +206,9 @@ class Metrics(object):
|
|||||||
try:
|
try:
|
||||||
x = self.hsrv.broker.ask("up2k.get_unfinished")
|
x = self.hsrv.broker.ask("up2k.get_unfinished")
|
||||||
xs = x.get()
|
xs = x.get()
|
||||||
|
if not xs:
|
||||||
|
raise Exception("up2k mutex acquisition timed out")
|
||||||
|
|
||||||
xj = json.loads(xs)
|
xj = json.loads(xs)
|
||||||
for ptop, (nbytes, nfiles) in xj.items():
|
for ptop, (nbytes, nfiles) in xj.items():
|
||||||
tnbytes += nbytes
|
tnbytes += nbytes
|
||||||
|
|||||||
@@ -7,12 +7,15 @@ import os
|
|||||||
import shutil
|
import shutil
|
||||||
import subprocess as sp
|
import subprocess as sp
|
||||||
import sys
|
import sys
|
||||||
|
import tempfile
|
||||||
|
|
||||||
from .__init__ import ANYWIN, EXE, PY2, WINDOWS, E, unicode
|
from .__init__ import ANYWIN, EXE, PY2, WINDOWS, E, unicode
|
||||||
|
from .authsrv import VFS
|
||||||
from .bos import bos
|
from .bos import bos
|
||||||
from .util import (
|
from .util import (
|
||||||
FFMPEG_URL,
|
FFMPEG_URL,
|
||||||
REKOBO_LKEY,
|
REKOBO_LKEY,
|
||||||
|
VF_CAREFUL,
|
||||||
fsenc,
|
fsenc,
|
||||||
min_ex,
|
min_ex,
|
||||||
pybin,
|
pybin,
|
||||||
@@ -20,12 +23,24 @@ from .util import (
|
|||||||
runcmd,
|
runcmd,
|
||||||
sfsenc,
|
sfsenc,
|
||||||
uncyg,
|
uncyg,
|
||||||
|
wunlink,
|
||||||
)
|
)
|
||||||
|
|
||||||
if True: # pylint: disable=using-constant-test
|
if True: # pylint: disable=using-constant-test
|
||||||
from typing import Any, Union
|
from typing import Any, Optional, Union
|
||||||
|
|
||||||
from .util import RootLogger
|
from .util import NamedLogger, RootLogger
|
||||||
|
|
||||||
|
|
||||||
|
try:
|
||||||
|
if os.environ.get("PRTY_NO_MUTAGEN"):
|
||||||
|
raise Exception()
|
||||||
|
|
||||||
|
from mutagen import version # noqa: F401
|
||||||
|
|
||||||
|
HAVE_MUTAGEN = True
|
||||||
|
except:
|
||||||
|
HAVE_MUTAGEN = False
|
||||||
|
|
||||||
|
|
||||||
def have_ff(scmd: str) -> bool:
|
def have_ff(scmd: str) -> bool:
|
||||||
@@ -44,8 +59,8 @@ def have_ff(scmd: str) -> bool:
|
|||||||
return bool(shutil.which(scmd))
|
return bool(shutil.which(scmd))
|
||||||
|
|
||||||
|
|
||||||
HAVE_FFMPEG = have_ff("ffmpeg")
|
HAVE_FFMPEG = not os.environ.get("PRTY_NO_FFMPEG") and have_ff("ffmpeg")
|
||||||
HAVE_FFPROBE = have_ff("ffprobe")
|
HAVE_FFPROBE = not os.environ.get("PRTY_NO_FFPROBE") and have_ff("ffprobe")
|
||||||
|
|
||||||
|
|
||||||
class MParser(object):
|
class MParser(object):
|
||||||
@@ -107,6 +122,56 @@ class MParser(object):
|
|||||||
raise Exception()
|
raise Exception()
|
||||||
|
|
||||||
|
|
||||||
|
def au_unpk(
|
||||||
|
log: "NamedLogger", fmt_map: dict[str, str], abspath: str, vn: Optional[VFS] = None
|
||||||
|
) -> str:
|
||||||
|
ret = ""
|
||||||
|
try:
|
||||||
|
ext = abspath.split(".")[-1].lower()
|
||||||
|
au, pk = fmt_map[ext].split(".")
|
||||||
|
|
||||||
|
fd, ret = tempfile.mkstemp("." + au)
|
||||||
|
|
||||||
|
if pk == "gz":
|
||||||
|
import gzip
|
||||||
|
|
||||||
|
fi = gzip.GzipFile(abspath, mode="rb")
|
||||||
|
|
||||||
|
elif pk == "xz":
|
||||||
|
import lzma
|
||||||
|
|
||||||
|
fi = lzma.open(abspath, "rb")
|
||||||
|
|
||||||
|
elif pk == "zip":
|
||||||
|
import zipfile
|
||||||
|
|
||||||
|
zf = zipfile.ZipFile(abspath, "r")
|
||||||
|
zil = zf.infolist()
|
||||||
|
zil = [x for x in zil if x.filename.lower().split(".")[-1] == au]
|
||||||
|
fi = zf.open(zil[0])
|
||||||
|
|
||||||
|
else:
|
||||||
|
raise Exception("unknown compression %s" % (pk,))
|
||||||
|
|
||||||
|
with os.fdopen(fd, "wb") as fo:
|
||||||
|
while True:
|
||||||
|
buf = fi.read(32768)
|
||||||
|
if not buf:
|
||||||
|
break
|
||||||
|
|
||||||
|
fo.write(buf)
|
||||||
|
|
||||||
|
return ret
|
||||||
|
|
||||||
|
except Exception as ex:
|
||||||
|
if ret:
|
||||||
|
t = "failed to decompress audio file [%s]: %r"
|
||||||
|
log(t % (abspath, ex))
|
||||||
|
wunlink(log, ret, vn.flags if vn else VF_CAREFUL)
|
||||||
|
|
||||||
|
return abspath
|
||||||
|
|
||||||
|
|
||||||
def ffprobe(
|
def ffprobe(
|
||||||
abspath: str, timeout: int = 60
|
abspath: str, timeout: int = 60
|
||||||
) -> tuple[dict[str, tuple[int, Any]], dict[str, list[Any]]]:
|
) -> tuple[dict[str, tuple[int, Any]], dict[str, list[Any]]]:
|
||||||
@@ -118,7 +183,7 @@ def ffprobe(
|
|||||||
b"--",
|
b"--",
|
||||||
fsenc(abspath),
|
fsenc(abspath),
|
||||||
]
|
]
|
||||||
rc, so, se = runcmd(cmd, timeout=timeout, nice=True)
|
rc, so, se = runcmd(cmd, timeout=timeout, nice=True, oom=200)
|
||||||
retchk(rc, cmd, se)
|
retchk(rc, cmd, se)
|
||||||
return parse_ffprobe(so)
|
return parse_ffprobe(so)
|
||||||
|
|
||||||
@@ -240,7 +305,7 @@ def parse_ffprobe(txt: str) -> tuple[dict[str, tuple[int, Any]], dict[str, list[
|
|||||||
if "/" in fps:
|
if "/" in fps:
|
||||||
fa, fb = fps.split("/")
|
fa, fb = fps.split("/")
|
||||||
try:
|
try:
|
||||||
fps = int(fa) * 1.0 / int(fb)
|
fps = float(fa) / float(fb)
|
||||||
except:
|
except:
|
||||||
fps = 9001
|
fps = 9001
|
||||||
|
|
||||||
@@ -261,7 +326,8 @@ def parse_ffprobe(txt: str) -> tuple[dict[str, tuple[int, Any]], dict[str, list[
|
|||||||
if ".resw" in ret and ".resh" in ret:
|
if ".resw" in ret and ".resh" in ret:
|
||||||
ret["res"] = "{}x{}".format(ret[".resw"], ret[".resh"])
|
ret["res"] = "{}x{}".format(ret[".resw"], ret[".resh"])
|
||||||
|
|
||||||
zd = {k: (0, v) for k, v in ret.items()}
|
zero = int("0")
|
||||||
|
zd = {k: (zero, v) for k, v in ret.items()}
|
||||||
|
|
||||||
return zd, md
|
return zd, md
|
||||||
|
|
||||||
@@ -280,16 +346,14 @@ class MTag(object):
|
|||||||
or_ffprobe = " or FFprobe"
|
or_ffprobe = " or FFprobe"
|
||||||
|
|
||||||
if self.backend == "mutagen":
|
if self.backend == "mutagen":
|
||||||
self.get = self.get_mutagen
|
self._get = self.get_mutagen
|
||||||
try:
|
if not HAVE_MUTAGEN:
|
||||||
from mutagen import version # noqa: F401
|
|
||||||
except:
|
|
||||||
self.log("could not load Mutagen, trying FFprobe instead", c=3)
|
self.log("could not load Mutagen, trying FFprobe instead", c=3)
|
||||||
self.backend = "ffprobe"
|
self.backend = "ffprobe"
|
||||||
|
|
||||||
if self.backend == "ffprobe":
|
if self.backend == "ffprobe":
|
||||||
self.usable = self.can_ffprobe
|
self.usable = self.can_ffprobe
|
||||||
self.get = self.get_ffprobe
|
self._get = self.get_ffprobe
|
||||||
self.prefer_mt = True
|
self.prefer_mt = True
|
||||||
|
|
||||||
if not HAVE_FFPROBE:
|
if not HAVE_FFPROBE:
|
||||||
@@ -459,6 +523,17 @@ class MTag(object):
|
|||||||
|
|
||||||
return r1
|
return r1
|
||||||
|
|
||||||
|
def get(self, abspath: str) -> dict[str, Union[str, float]]:
|
||||||
|
ext = abspath.split(".")[-1].lower()
|
||||||
|
if ext not in self.args.au_unpk:
|
||||||
|
return self._get(abspath)
|
||||||
|
|
||||||
|
ap = au_unpk(self.log, self.args.au_unpk, abspath)
|
||||||
|
ret = self._get(ap)
|
||||||
|
if ap != abspath:
|
||||||
|
wunlink(self.log, ap, VF_CAREFUL)
|
||||||
|
return ret
|
||||||
|
|
||||||
def get_mutagen(self, abspath: str) -> dict[str, Union[str, float]]:
|
def get_mutagen(self, abspath: str) -> dict[str, Union[str, float]]:
|
||||||
ret: dict[str, tuple[int, Any]] = {}
|
ret: dict[str, tuple[int, Any]] = {}
|
||||||
|
|
||||||
@@ -512,7 +587,7 @@ class MTag(object):
|
|||||||
continue
|
continue
|
||||||
|
|
||||||
if k == ".aq":
|
if k == ".aq":
|
||||||
v /= 1000
|
v /= 1000 # type: ignore
|
||||||
|
|
||||||
if k == "ac" and v.startswith("mp4a.40."):
|
if k == "ac" and v.startswith("mp4a.40."):
|
||||||
v = "aac"
|
v = "aac"
|
||||||
@@ -550,19 +625,25 @@ class MTag(object):
|
|||||||
pypath = str(os.pathsep.join(zsl))
|
pypath = str(os.pathsep.join(zsl))
|
||||||
env["PYTHONPATH"] = pypath
|
env["PYTHONPATH"] = pypath
|
||||||
except:
|
except:
|
||||||
if not E.ox and not EXE:
|
raise # might be expected outside cpython
|
||||||
raise
|
|
||||||
|
ext = abspath.split(".")[-1].lower()
|
||||||
|
if ext in self.args.au_unpk:
|
||||||
|
ap = au_unpk(self.log, self.args.au_unpk, abspath)
|
||||||
|
else:
|
||||||
|
ap = abspath
|
||||||
|
|
||||||
ret: dict[str, Any] = {}
|
ret: dict[str, Any] = {}
|
||||||
for tagname, parser in sorted(parsers.items(), key=lambda x: (x[1].pri, x[0])):
|
for tagname, parser in sorted(parsers.items(), key=lambda x: (x[1].pri, x[0])):
|
||||||
try:
|
try:
|
||||||
cmd = [parser.bin, abspath]
|
cmd = [parser.bin, ap]
|
||||||
if parser.bin.endswith(".py"):
|
if parser.bin.endswith(".py"):
|
||||||
cmd = [pybin] + cmd
|
cmd = [pybin] + cmd
|
||||||
|
|
||||||
args = {
|
args = {
|
||||||
"env": env,
|
"env": env,
|
||||||
"nice": True,
|
"nice": True,
|
||||||
|
"oom": 300,
|
||||||
"timeout": parser.timeout,
|
"timeout": parser.timeout,
|
||||||
"kill": parser.kill,
|
"kill": parser.kill,
|
||||||
"capture": parser.capture,
|
"capture": parser.capture,
|
||||||
@@ -592,4 +673,7 @@ class MTag(object):
|
|||||||
t = "mtag error: tagname {}, parser {}, file {} => {}"
|
t = "mtag error: tagname {}, parser {}, file {} => {}"
|
||||||
self.log(t.format(tagname, parser.bin, abspath, min_ex()))
|
self.log(t.format(tagname, parser.bin, abspath, min_ex()))
|
||||||
|
|
||||||
|
if ap != abspath:
|
||||||
|
wunlink(self.log, ap, VF_CAREFUL)
|
||||||
|
|
||||||
return ret
|
return ret
|
||||||
|
|||||||
@@ -110,7 +110,7 @@ class MCast(object):
|
|||||||
)
|
)
|
||||||
|
|
||||||
ips = [x for x in ips if x not in ("::1", "127.0.0.1")]
|
ips = [x for x in ips if x not in ("::1", "127.0.0.1")]
|
||||||
ips = find_prefix(ips, netdevs)
|
ips = find_prefix(ips, list(netdevs))
|
||||||
|
|
||||||
on = self.on[:]
|
on = self.on[:]
|
||||||
off = self.off[:]
|
off = self.off[:]
|
||||||
@@ -206,6 +206,7 @@ class MCast(object):
|
|||||||
except:
|
except:
|
||||||
t = "announce failed on {} [{}]:\n{}"
|
t = "announce failed on {} [{}]:\n{}"
|
||||||
self.log(t.format(netdev, ip, min_ex()), 3)
|
self.log(t.format(netdev, ip, min_ex()), 3)
|
||||||
|
sck.close()
|
||||||
|
|
||||||
if self.args.zm_msub:
|
if self.args.zm_msub:
|
||||||
for s1 in self.srv.values():
|
for s1 in self.srv.values():
|
||||||
|
|||||||
@@ -4,11 +4,21 @@ from __future__ import print_function, unicode_literals
|
|||||||
import argparse
|
import argparse
|
||||||
import base64
|
import base64
|
||||||
import hashlib
|
import hashlib
|
||||||
|
import os
|
||||||
import sys
|
import sys
|
||||||
import threading
|
import threading
|
||||||
|
|
||||||
from .__init__ import unicode
|
from .__init__ import unicode
|
||||||
|
|
||||||
|
try:
|
||||||
|
if os.environ.get("PRTY_NO_ARGON2"):
|
||||||
|
raise Exception()
|
||||||
|
|
||||||
|
HAVE_ARGON2 = True
|
||||||
|
from argon2 import __version__ as argon2ver
|
||||||
|
except:
|
||||||
|
HAVE_ARGON2 = False
|
||||||
|
|
||||||
|
|
||||||
class PWHash(object):
|
class PWHash(object):
|
||||||
def __init__(self, args: argparse.Namespace):
|
def __init__(self, args: argparse.Namespace):
|
||||||
|
|||||||
@@ -127,7 +127,7 @@ class SMB(object):
|
|||||||
self.log("smb", msg, c)
|
self.log("smb", msg, c)
|
||||||
|
|
||||||
def start(self) -> None:
|
def start(self) -> None:
|
||||||
Daemon(self.srv.start)
|
Daemon(self.srv.start, "smbd")
|
||||||
|
|
||||||
def _auth_cb(self, *a, **ka):
|
def _auth_cb(self, *a, **ka):
|
||||||
debug("auth-result: %s %s", a, ka)
|
debug("auth-result: %s %s", a, ka)
|
||||||
@@ -187,6 +187,8 @@ class SMB(object):
|
|||||||
|
|
||||||
debug('%s("%s", %s) %s @%s\033[K\033[0m', caller, vpath, str(a), perms, uname)
|
debug('%s("%s", %s) %s @%s\033[K\033[0m', caller, vpath, str(a), perms, uname)
|
||||||
vfs, rem = self.asrv.vfs.get(vpath, uname, *perms)
|
vfs, rem = self.asrv.vfs.get(vpath, uname, *perms)
|
||||||
|
if not vfs.realpath:
|
||||||
|
raise Exception("unmapped vfs")
|
||||||
return vfs, vfs.canonical(rem)
|
return vfs, vfs.canonical(rem)
|
||||||
|
|
||||||
def _listdir(self, vpath: str, *a: Any, **ka: Any) -> list[str]:
|
def _listdir(self, vpath: str, *a: Any, **ka: Any) -> list[str]:
|
||||||
@@ -195,6 +197,8 @@ class SMB(object):
|
|||||||
uname = self._uname()
|
uname = self._uname()
|
||||||
# debug('listdir("%s", %s) @%s\033[K\033[0m', vpath, str(a), uname)
|
# debug('listdir("%s", %s) @%s\033[K\033[0m', vpath, str(a), uname)
|
||||||
vfs, rem = self.asrv.vfs.get(vpath, uname, False, False)
|
vfs, rem = self.asrv.vfs.get(vpath, uname, False, False)
|
||||||
|
if not vfs.realpath:
|
||||||
|
raise Exception("unmapped vfs")
|
||||||
_, vfs_ls, vfs_virt = vfs.ls(
|
_, vfs_ls, vfs_virt = vfs.ls(
|
||||||
rem, uname, not self.args.no_scandir, [[False, False]]
|
rem, uname, not self.args.no_scandir, [[False, False]]
|
||||||
)
|
)
|
||||||
@@ -240,7 +244,21 @@ class SMB(object):
|
|||||||
|
|
||||||
xbu = vfs.flags.get("xbu")
|
xbu = vfs.flags.get("xbu")
|
||||||
if xbu and not runhook(
|
if xbu and not runhook(
|
||||||
self.nlog, xbu, ap, vpath, "", "", 0, 0, "1.7.6.2", 0, ""
|
self.nlog,
|
||||||
|
None,
|
||||||
|
self.hub.up2k,
|
||||||
|
"xbu.smb",
|
||||||
|
xbu,
|
||||||
|
ap,
|
||||||
|
vpath,
|
||||||
|
"",
|
||||||
|
"",
|
||||||
|
"",
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
"1.7.6.2",
|
||||||
|
time.time(),
|
||||||
|
"",
|
||||||
):
|
):
|
||||||
yeet("blocked by xbu server config: " + vpath)
|
yeet("blocked by xbu server config: " + vpath)
|
||||||
|
|
||||||
@@ -297,7 +315,7 @@ class SMB(object):
|
|||||||
t = "blocked rename (no-move-acc %s): /%s @%s"
|
t = "blocked rename (no-move-acc %s): /%s @%s"
|
||||||
yeet(t % (vfs1.axs.umove, vp1, uname))
|
yeet(t % (vfs1.axs.umove, vp1, uname))
|
||||||
|
|
||||||
self.hub.up2k.handle_mv(uname, vp1, vp2)
|
self.hub.up2k.handle_mv(uname, "1.7.6.2", vp1, vp2)
|
||||||
try:
|
try:
|
||||||
bos.makedirs(ap2)
|
bos.makedirs(ap2)
|
||||||
except:
|
except:
|
||||||
@@ -340,7 +358,7 @@ class SMB(object):
|
|||||||
yeet("blocked delete (no-del-acc): " + vpath)
|
yeet("blocked delete (no-del-acc): " + vpath)
|
||||||
|
|
||||||
vpath = vpath.replace("\\", "/").lstrip("/")
|
vpath = vpath.replace("\\", "/").lstrip("/")
|
||||||
self.hub.up2k.handle_rm(uname, "1.7.6.2", [vpath], [], False)
|
self.hub.up2k.handle_rm(uname, "1.7.6.2", [vpath], [], False, False)
|
||||||
|
|
||||||
def _utime(self, vpath: str, times: tuple[float, float]) -> None:
|
def _utime(self, vpath: str, times: tuple[float, float]) -> None:
|
||||||
if not self.args.smbw:
|
if not self.args.smbw:
|
||||||
@@ -406,6 +424,7 @@ class SMB(object):
|
|||||||
|
|
||||||
smbserver.os.path.abspath = self._hook
|
smbserver.os.path.abspath = self._hook
|
||||||
smbserver.os.path.expanduser = self._hook
|
smbserver.os.path.expanduser = self._hook
|
||||||
|
smbserver.os.path.expandvars = self._hook
|
||||||
smbserver.os.path.getatime = self._hook
|
smbserver.os.path.getatime = self._hook
|
||||||
smbserver.os.path.getctime = self._hook
|
smbserver.os.path.getctime = self._hook
|
||||||
smbserver.os.path.getmtime = self._hook
|
smbserver.os.path.getmtime = self._hook
|
||||||
|
|||||||
@@ -5,11 +5,11 @@ import errno
|
|||||||
import re
|
import re
|
||||||
import select
|
import select
|
||||||
import socket
|
import socket
|
||||||
from email.utils import formatdate
|
import time
|
||||||
|
|
||||||
from .__init__ import TYPE_CHECKING
|
from .__init__ import TYPE_CHECKING
|
||||||
from .multicast import MC_Sck, MCast
|
from .multicast import MC_Sck, MCast
|
||||||
from .util import CachedSet, html_escape, min_ex
|
from .util import CachedSet, formatdate, html_escape, min_ex
|
||||||
|
|
||||||
if TYPE_CHECKING:
|
if TYPE_CHECKING:
|
||||||
from .broker_util import BrokerCli
|
from .broker_util import BrokerCli
|
||||||
@@ -141,9 +141,29 @@ class SSDPd(MCast):
|
|||||||
self.log("stopped", 2)
|
self.log("stopped", 2)
|
||||||
|
|
||||||
def run2(self) -> None:
|
def run2(self) -> None:
|
||||||
|
try:
|
||||||
|
if self.args.no_poll:
|
||||||
|
raise Exception()
|
||||||
|
fd2sck = {}
|
||||||
|
srvpoll = select.poll()
|
||||||
|
for sck in self.srv:
|
||||||
|
fd = sck.fileno()
|
||||||
|
fd2sck[fd] = sck
|
||||||
|
srvpoll.register(fd, select.POLLIN)
|
||||||
|
except Exception as ex:
|
||||||
|
srvpoll = None
|
||||||
|
if not self.args.no_poll:
|
||||||
|
t = "WARNING: failed to poll(), will use select() instead: %r"
|
||||||
|
self.log(t % (ex,), 3)
|
||||||
|
|
||||||
while self.running:
|
while self.running:
|
||||||
rdy = select.select(self.srv, [], [], self.args.z_chk or 180)
|
if srvpoll:
|
||||||
rx: list[socket.socket] = rdy[0] # type: ignore
|
pr = srvpoll.poll((self.args.z_chk or 180) * 1000)
|
||||||
|
rx = [fd2sck[x[0]] for x in pr if x[1] & select.POLLIN]
|
||||||
|
else:
|
||||||
|
rdy = select.select(self.srv, [], [], self.args.z_chk or 180)
|
||||||
|
rx: list[socket.socket] = rdy[0] # type: ignore
|
||||||
|
|
||||||
self.rxc.cln()
|
self.rxc.cln()
|
||||||
buf = b""
|
buf = b""
|
||||||
addr = ("0", 0)
|
addr = ("0", 0)
|
||||||
@@ -168,7 +188,7 @@ class SSDPd(MCast):
|
|||||||
except:
|
except:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
self.srv = {}
|
self.srv.clear()
|
||||||
|
|
||||||
def eat(self, buf: bytes, addr: tuple[str, int]) -> None:
|
def eat(self, buf: bytes, addr: tuple[str, int]) -> None:
|
||||||
cip = addr[0]
|
cip = addr[0]
|
||||||
@@ -209,7 +229,7 @@ CONFIGID.UPNP.ORG: 1
|
|||||||
|
|
||||||
"""
|
"""
|
||||||
v4 = srv.ip.replace("::ffff:", "")
|
v4 = srv.ip.replace("::ffff:", "")
|
||||||
zs = zs.format(formatdate(usegmt=True), v4, srv.hport, self.args.zsid)
|
zs = zs.format(formatdate(), v4, srv.hport, self.args.zsid)
|
||||||
zb = zs[1:].replace("\n", "\r\n").encode("utf-8", "replace")
|
zb = zs[1:].replace("\n", "\r\n").encode("utf-8", "replace")
|
||||||
srv.sck.sendto(zb, addr[:2])
|
srv.sck.sendto(zb, addr[:2])
|
||||||
|
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ import tarfile
|
|||||||
|
|
||||||
from queue import Queue
|
from queue import Queue
|
||||||
|
|
||||||
|
from .authsrv import AuthSrv
|
||||||
from .bos import bos
|
from .bos import bos
|
||||||
from .sutil import StreamArc, errdesc
|
from .sutil import StreamArc, errdesc
|
||||||
from .util import Daemon, fsenc, min_ex
|
from .util import Daemon, fsenc, min_ex
|
||||||
@@ -44,11 +45,12 @@ class StreamTar(StreamArc):
|
|||||||
def __init__(
|
def __init__(
|
||||||
self,
|
self,
|
||||||
log: "NamedLogger",
|
log: "NamedLogger",
|
||||||
|
asrv: AuthSrv,
|
||||||
fgen: Generator[dict[str, Any], None, None],
|
fgen: Generator[dict[str, Any], None, None],
|
||||||
cmp: str = "",
|
cmp: str = "",
|
||||||
**kwargs: Any
|
**kwargs: Any
|
||||||
):
|
):
|
||||||
super(StreamTar, self).__init__(log, fgen)
|
super(StreamTar, self).__init__(log, asrv, fgen)
|
||||||
|
|
||||||
self.ci = 0
|
self.ci = 0
|
||||||
self.co = 0
|
self.co = 0
|
||||||
@@ -65,21 +67,21 @@ class StreamTar(StreamArc):
|
|||||||
cmp = re.sub(r"[^a-z0-9]*pax[^a-z0-9]*", "", cmp)
|
cmp = re.sub(r"[^a-z0-9]*pax[^a-z0-9]*", "", cmp)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
cmp, lv = cmp.replace(":", ",").split(",")
|
cmp, zs = cmp.replace(":", ",").split(",")
|
||||||
lv = int(lv)
|
lv = int(zs)
|
||||||
except:
|
except:
|
||||||
lv = None
|
lv = -1
|
||||||
|
|
||||||
arg = {"name": None, "fileobj": self.qfile, "mode": "w", "format": fmt}
|
arg = {"name": None, "fileobj": self.qfile, "mode": "w", "format": fmt}
|
||||||
if cmp == "gz":
|
if cmp == "gz":
|
||||||
fun = tarfile.TarFile.gzopen
|
fun = tarfile.TarFile.gzopen
|
||||||
arg["compresslevel"] = lv if lv is not None else 3
|
arg["compresslevel"] = lv if lv >= 0 else 3
|
||||||
elif cmp == "bz2":
|
elif cmp == "bz2":
|
||||||
fun = tarfile.TarFile.bz2open
|
fun = tarfile.TarFile.bz2open
|
||||||
arg["compresslevel"] = lv if lv is not None else 2
|
arg["compresslevel"] = lv if lv >= 0 else 2
|
||||||
elif cmp == "xz":
|
elif cmp == "xz":
|
||||||
fun = tarfile.TarFile.xzopen
|
fun = tarfile.TarFile.xzopen
|
||||||
arg["preset"] = lv if lv is not None else 1
|
arg["preset"] = lv if lv >= 0 else 1
|
||||||
else:
|
else:
|
||||||
fun = tarfile.open
|
fun = tarfile.open
|
||||||
arg["mode"] = "w|"
|
arg["mode"] = "w|"
|
||||||
@@ -126,7 +128,7 @@ class StreamTar(StreamArc):
|
|||||||
inf.gid = 0
|
inf.gid = 0
|
||||||
|
|
||||||
self.ci += inf.size
|
self.ci += inf.size
|
||||||
with open(fsenc(src), "rb", 512 * 1024) as fo:
|
with open(fsenc(src), "rb", self.args.iobuf) as fo:
|
||||||
self.tar.addfile(inf, fo)
|
self.tar.addfile(inf, fo)
|
||||||
|
|
||||||
def _gen(self) -> None:
|
def _gen(self) -> None:
|
||||||
@@ -146,7 +148,7 @@ class StreamTar(StreamArc):
|
|||||||
errors.append((f["vp"], ex))
|
errors.append((f["vp"], ex))
|
||||||
|
|
||||||
if errors:
|
if errors:
|
||||||
self.errf, txt = errdesc(errors)
|
self.errf, txt = errdesc(self.asrv.vfs, errors)
|
||||||
self.log("\n".join(([repr(self.errf)] + txt[1:])))
|
self.log("\n".join(([repr(self.errf)] + txt[1:])))
|
||||||
self.ser(self.errf)
|
self.ser(self.errf)
|
||||||
|
|
||||||
|
|||||||
@@ -12,6 +12,12 @@ from .label import DNSBuffer, DNSLabel
|
|||||||
from .ranges import IP4, IP6, H, I, check_bytes
|
from .ranges import IP4, IP6, H, I, check_bytes
|
||||||
|
|
||||||
|
|
||||||
|
try:
|
||||||
|
range = xrange
|
||||||
|
except:
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
class DNSError(Exception):
|
class DNSError(Exception):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|||||||
@@ -11,7 +11,21 @@ import os
|
|||||||
|
|
||||||
from ._shared import IP, Adapter
|
from ._shared import IP, Adapter
|
||||||
|
|
||||||
if os.name == "nt":
|
|
||||||
|
def nope(include_unconfigured=False):
|
||||||
|
return []
|
||||||
|
|
||||||
|
|
||||||
|
try:
|
||||||
|
S390X = os.uname().machine == "s390x"
|
||||||
|
except:
|
||||||
|
S390X = False
|
||||||
|
|
||||||
|
|
||||||
|
if os.environ.get("PRTY_NO_IFADDR") or S390X:
|
||||||
|
# s390x deadlocks at libc.getifaddrs
|
||||||
|
get_adapters = nope
|
||||||
|
elif os.name == "nt":
|
||||||
from ._win32 import get_adapters
|
from ._win32 import get_adapters
|
||||||
elif os.name == "posix":
|
elif os.name == "posix":
|
||||||
from ._posix import get_adapters
|
from ._posix import get_adapters
|
||||||
|
|||||||
@@ -17,6 +17,7 @@ if not PY2:
|
|||||||
U: Callable[[str], str] = str
|
U: Callable[[str], str] = str
|
||||||
else:
|
else:
|
||||||
U = unicode # noqa: F821 # pylint: disable=undefined-variable,self-assigning-variable
|
U = unicode # noqa: F821 # pylint: disable=undefined-variable,self-assigning-variable
|
||||||
|
range = xrange # noqa: F821 # pylint: disable=undefined-variable,self-assigning-variable
|
||||||
|
|
||||||
|
|
||||||
class Adapter(object):
|
class Adapter(object):
|
||||||
@@ -61,7 +62,7 @@ class Adapter(object):
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
if True:
|
if True: # pylint: disable=using-constant-test
|
||||||
# Type of an IPv4 address (a string in "xxx.xxx.xxx.xxx" format)
|
# Type of an IPv4 address (a string in "xxx.xxx.xxx.xxx" format)
|
||||||
_IPv4Address = str
|
_IPv4Address = str
|
||||||
|
|
||||||
|
|||||||
@@ -16,6 +16,11 @@ if True: # pylint: disable=using-constant-test
|
|||||||
|
|
||||||
from typing import Callable, List, Optional, Tuple, Union
|
from typing import Callable, List, Optional, Tuple, Union
|
||||||
|
|
||||||
|
try:
|
||||||
|
range = xrange
|
||||||
|
except:
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
def num_char_count_bits(ver: int) -> int:
|
def num_char_count_bits(ver: int) -> int:
|
||||||
return 16 if (ver + 7) // 17 else 8
|
return 16 if (ver + 7) // 17 else 8
|
||||||
|
|||||||
@@ -6,9 +6,10 @@ import tempfile
|
|||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
|
|
||||||
from .__init__ import CORES
|
from .__init__ import CORES
|
||||||
|
from .authsrv import VFS, AuthSrv
|
||||||
from .bos import bos
|
from .bos import bos
|
||||||
from .th_cli import ThumbCli
|
from .th_cli import ThumbCli
|
||||||
from .util import UTC, vjoin
|
from .util import UTC, vjoin, vol_san
|
||||||
|
|
||||||
if True: # pylint: disable=using-constant-test
|
if True: # pylint: disable=using-constant-test
|
||||||
from typing import Any, Generator, Optional
|
from typing import Any, Generator, Optional
|
||||||
@@ -20,10 +21,13 @@ class StreamArc(object):
|
|||||||
def __init__(
|
def __init__(
|
||||||
self,
|
self,
|
||||||
log: "NamedLogger",
|
log: "NamedLogger",
|
||||||
|
asrv: AuthSrv,
|
||||||
fgen: Generator[dict[str, Any], None, None],
|
fgen: Generator[dict[str, Any], None, None],
|
||||||
**kwargs: Any
|
**kwargs: Any
|
||||||
):
|
):
|
||||||
self.log = log
|
self.log = log
|
||||||
|
self.asrv = asrv
|
||||||
|
self.args = asrv.args
|
||||||
self.fgen = fgen
|
self.fgen = fgen
|
||||||
self.stopped = False
|
self.stopped = False
|
||||||
|
|
||||||
@@ -78,7 +82,9 @@ def enthumb(
|
|||||||
) -> dict[str, Any]:
|
) -> dict[str, Any]:
|
||||||
rem = f["vp"]
|
rem = f["vp"]
|
||||||
ext = rem.rsplit(".", 1)[-1].lower()
|
ext = rem.rsplit(".", 1)[-1].lower()
|
||||||
if fmt == "opus" and ext in "aac|m4a|mp3|ogg|opus|wma".split("|"):
|
if (fmt == "mp3" and ext == "mp3") or (
|
||||||
|
fmt == "opus" and ext in "aac|m4a|mp3|ogg|opus|wma".split("|")
|
||||||
|
):
|
||||||
raise Exception()
|
raise Exception()
|
||||||
|
|
||||||
vp = vjoin(vtop, rem.split("/", 1)[1])
|
vp = vjoin(vtop, rem.split("/", 1)[1])
|
||||||
@@ -98,15 +104,20 @@ def enthumb(
|
|||||||
return f
|
return f
|
||||||
|
|
||||||
|
|
||||||
def errdesc(errors: list[tuple[str, str]]) -> tuple[dict[str, Any], list[str]]:
|
def errdesc(
|
||||||
|
vfs: VFS, errors: list[tuple[str, str]]
|
||||||
|
) -> tuple[dict[str, Any], list[str]]:
|
||||||
report = ["copyparty failed to add the following files to the archive:", ""]
|
report = ["copyparty failed to add the following files to the archive:", ""]
|
||||||
|
|
||||||
for fn, err in errors:
|
for fn, err in errors:
|
||||||
report.extend([" file: {}".format(fn), "error: {}".format(err), ""])
|
report.extend([" file: {}".format(fn), "error: {}".format(err), ""])
|
||||||
|
|
||||||
|
btxt = "\r\n".join(report).encode("utf-8", "replace")
|
||||||
|
btxt = vol_san(list(vfs.all_vols.values()), btxt)
|
||||||
|
|
||||||
with tempfile.NamedTemporaryFile(prefix="copyparty-", delete=False) as tf:
|
with tempfile.NamedTemporaryFile(prefix="copyparty-", delete=False) as tf:
|
||||||
tf_path = tf.name
|
tf_path = tf.name
|
||||||
tf.write("\r\n".join(report).encode("utf-8", "replace"))
|
tf.write(btxt)
|
||||||
|
|
||||||
dt = datetime.now(UTC).strftime("%Y-%m%d-%H%M%S")
|
dt = datetime.now(UTC).strftime("%Y-%m%d-%H%M%S")
|
||||||
|
|
||||||
|
|||||||
@@ -2,8 +2,6 @@
|
|||||||
from __future__ import print_function, unicode_literals
|
from __future__ import print_function, unicode_literals
|
||||||
|
|
||||||
import argparse
|
import argparse
|
||||||
import base64
|
|
||||||
import calendar
|
|
||||||
import errno
|
import errno
|
||||||
import gzip
|
import gzip
|
||||||
import logging
|
import logging
|
||||||
@@ -16,7 +14,7 @@ import string
|
|||||||
import sys
|
import sys
|
||||||
import threading
|
import threading
|
||||||
import time
|
import time
|
||||||
from datetime import datetime, timedelta
|
from datetime import datetime
|
||||||
|
|
||||||
# from inspect import currentframe
|
# from inspect import currentframe
|
||||||
# print(currentframe().f_lineno)
|
# print(currentframe().f_lineno)
|
||||||
@@ -28,18 +26,30 @@ if True: # pylint: disable=using-constant-test
|
|||||||
import typing
|
import typing
|
||||||
from typing import Any, Optional, Union
|
from typing import Any, Optional, Union
|
||||||
|
|
||||||
from .__init__ import ANYWIN, EXE, MACOS, TYPE_CHECKING, EnvParams, unicode
|
from .__init__ import ANYWIN, EXE, MACOS, PY2, TYPE_CHECKING, E, EnvParams, unicode
|
||||||
from .authsrv import BAD_CFG, AuthSrv
|
from .authsrv import BAD_CFG, AuthSrv
|
||||||
from .cert import ensure_cert
|
from .cert import ensure_cert
|
||||||
from .mtag import HAVE_FFMPEG, HAVE_FFPROBE
|
from .mtag import HAVE_FFMPEG, HAVE_FFPROBE, HAVE_MUTAGEN
|
||||||
|
from .pwhash import HAVE_ARGON2
|
||||||
from .tcpsrv import TcpSrv
|
from .tcpsrv import TcpSrv
|
||||||
from .th_srv import HAVE_PIL, HAVE_VIPS, HAVE_WEBP, ThumbSrv
|
from .th_srv import (
|
||||||
|
HAVE_AVIF,
|
||||||
|
HAVE_FFMPEG,
|
||||||
|
HAVE_FFPROBE,
|
||||||
|
HAVE_HEIF,
|
||||||
|
HAVE_PIL,
|
||||||
|
HAVE_VIPS,
|
||||||
|
HAVE_WEBP,
|
||||||
|
ThumbSrv,
|
||||||
|
)
|
||||||
from .up2k import Up2k
|
from .up2k import Up2k
|
||||||
from .util import (
|
from .util import (
|
||||||
DEF_EXP,
|
DEF_EXP,
|
||||||
DEF_MTE,
|
DEF_MTE,
|
||||||
DEF_MTH,
|
DEF_MTH,
|
||||||
FFMPEG_URL,
|
FFMPEG_URL,
|
||||||
|
HAVE_PSUTIL,
|
||||||
|
HAVE_SQLITE3,
|
||||||
UTC,
|
UTC,
|
||||||
VERSIONS,
|
VERSIONS,
|
||||||
Daemon,
|
Daemon,
|
||||||
@@ -49,12 +59,14 @@ from .util import (
|
|||||||
ODict,
|
ODict,
|
||||||
alltrace,
|
alltrace,
|
||||||
ansi_re,
|
ansi_re,
|
||||||
|
build_netmap,
|
||||||
min_ex,
|
min_ex,
|
||||||
mp,
|
mp,
|
||||||
odfusion,
|
odfusion,
|
||||||
pybin,
|
pybin,
|
||||||
start_log_thrs,
|
start_log_thrs,
|
||||||
start_stackmon,
|
start_stackmon,
|
||||||
|
ub64enc,
|
||||||
)
|
)
|
||||||
|
|
||||||
if TYPE_CHECKING:
|
if TYPE_CHECKING:
|
||||||
@@ -64,6 +76,9 @@ if TYPE_CHECKING:
|
|||||||
except:
|
except:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
if PY2:
|
||||||
|
range = xrange # type: ignore
|
||||||
|
|
||||||
|
|
||||||
class SvcHub(object):
|
class SvcHub(object):
|
||||||
"""
|
"""
|
||||||
@@ -88,20 +103,23 @@ class SvcHub(object):
|
|||||||
self.argv = argv
|
self.argv = argv
|
||||||
self.E: EnvParams = args.E
|
self.E: EnvParams = args.E
|
||||||
self.no_ansi = args.no_ansi
|
self.no_ansi = args.no_ansi
|
||||||
|
self.tz = UTC if args.log_utc else None
|
||||||
self.logf: Optional[typing.TextIO] = None
|
self.logf: Optional[typing.TextIO] = None
|
||||||
self.logf_base_fn = ""
|
self.logf_base_fn = ""
|
||||||
|
self.is_dut = False # running in unittest; always False
|
||||||
self.stop_req = False
|
self.stop_req = False
|
||||||
self.stopping = False
|
self.stopping = False
|
||||||
self.stopped = False
|
self.stopped = False
|
||||||
self.reload_req = False
|
self.reload_req = False
|
||||||
self.reloading = False
|
self.reloading = 0
|
||||||
self.stop_cond = threading.Condition()
|
self.stop_cond = threading.Condition()
|
||||||
self.nsigs = 3
|
self.nsigs = 3
|
||||||
self.retcode = 0
|
self.retcode = 0
|
||||||
self.httpsrv_up = 0
|
self.httpsrv_up = 0
|
||||||
|
|
||||||
self.log_mutex = threading.Lock()
|
self.log_mutex = threading.Lock()
|
||||||
self.next_day = 0
|
self.cday = 0
|
||||||
|
self.cmon = 0
|
||||||
self.tstack = 0.0
|
self.tstack = 0.0
|
||||||
|
|
||||||
self.iphash = HMaccas(os.path.join(self.E.cfg, "iphash"), 8)
|
self.iphash = HMaccas(os.path.join(self.E.cfg, "iphash"), 8)
|
||||||
@@ -133,12 +151,13 @@ class SvcHub(object):
|
|||||||
if not self._process_config():
|
if not self._process_config():
|
||||||
raise Exception(BAD_CFG)
|
raise Exception(BAD_CFG)
|
||||||
|
|
||||||
# for non-http clients (ftp)
|
# for non-http clients (ftp, tftp)
|
||||||
self.bans: dict[str, int] = {}
|
self.bans: dict[str, int] = {}
|
||||||
self.gpwd = Garda(self.args.ban_pw)
|
self.gpwd = Garda(self.args.ban_pw)
|
||||||
self.g404 = Garda(self.args.ban_404)
|
self.g404 = Garda(self.args.ban_404)
|
||||||
self.g403 = Garda(self.args.ban_403)
|
self.g403 = Garda(self.args.ban_403)
|
||||||
self.g422 = Garda(self.args.ban_422)
|
self.g422 = Garda(self.args.ban_422, False)
|
||||||
|
self.gmal = Garda(self.args.ban_422)
|
||||||
self.gurl = Garda(self.args.ban_url)
|
self.gurl = Garda(self.args.ban_url)
|
||||||
|
|
||||||
self.log_div = 10 ** (6 - args.log_tdec)
|
self.log_div = 10 ** (6 - args.log_tdec)
|
||||||
@@ -153,6 +172,8 @@ class SvcHub(object):
|
|||||||
lg.handlers = [lh]
|
lg.handlers = [lh]
|
||||||
lg.setLevel(logging.DEBUG)
|
lg.setLevel(logging.DEBUG)
|
||||||
|
|
||||||
|
self._check_env()
|
||||||
|
|
||||||
if args.stackmon:
|
if args.stackmon:
|
||||||
start_stackmon(args.stackmon, 0)
|
start_stackmon(args.stackmon, 0)
|
||||||
|
|
||||||
@@ -169,6 +190,43 @@ class SvcHub(object):
|
|||||||
self.log("root", t.format(args.j), c=3)
|
self.log("root", t.format(args.j), c=3)
|
||||||
args.no_fpool = True
|
args.no_fpool = True
|
||||||
|
|
||||||
|
for name, arg in (
|
||||||
|
("iobuf", "iobuf"),
|
||||||
|
("s-rd-sz", "s_rd_sz"),
|
||||||
|
("s-wr-sz", "s_wr_sz"),
|
||||||
|
):
|
||||||
|
zi = getattr(args, arg)
|
||||||
|
if zi < 32768:
|
||||||
|
t = "WARNING: expect very poor performance because you specified a very low value (%d) for --%s"
|
||||||
|
self.log("root", t % (zi, name), 3)
|
||||||
|
zi = 2
|
||||||
|
zi2 = 2 ** (zi - 1).bit_length()
|
||||||
|
if zi != zi2:
|
||||||
|
zi3 = 2 ** ((zi - 1).bit_length() - 1)
|
||||||
|
t = "WARNING: expect poor performance because --%s is not a power-of-two; consider using %d or %d instead of %d"
|
||||||
|
self.log("root", t % (name, zi2, zi3, zi), 3)
|
||||||
|
|
||||||
|
if args.s_rd_sz > args.iobuf:
|
||||||
|
t = "WARNING: --s-rd-sz (%d) is larger than --iobuf (%d); this may lead to reduced performance"
|
||||||
|
self.log("root", t % (args.s_rd_sz, args.iobuf), 3)
|
||||||
|
|
||||||
|
if args.chpw and args.idp_h_usr:
|
||||||
|
t = "ERROR: user-changeable passwords is incompatible with IdP/identity-providers; you must disable either --chpw or --idp-h-usr"
|
||||||
|
self.log("root", t, 1)
|
||||||
|
raise Exception(t)
|
||||||
|
|
||||||
|
noch = set()
|
||||||
|
for zs in args.chpw_no or []:
|
||||||
|
zsl = [x.strip() for x in zs.split(",")]
|
||||||
|
noch.update([x for x in zsl if x])
|
||||||
|
args.chpw_no = noch
|
||||||
|
|
||||||
|
if not self.args.no_ses:
|
||||||
|
self.setup_session_db()
|
||||||
|
|
||||||
|
if args.shr:
|
||||||
|
self.setup_share_db()
|
||||||
|
|
||||||
bri = "zy"[args.theme % 2 :][:1]
|
bri = "zy"[args.theme % 2 :][:1]
|
||||||
ch = "abcdefghijklmnopqrstuvwx"[int(args.theme / 2)]
|
ch = "abcdefghijklmnopqrstuvwx"[int(args.theme / 2)]
|
||||||
args.theme = "{0}{1} {0} {1}".format(ch, bri)
|
args.theme = "{0}{1} {0} {1}".format(ch, bri)
|
||||||
@@ -208,6 +266,8 @@ class SvcHub(object):
|
|||||||
|
|
||||||
self.up2k = Up2k(self)
|
self.up2k = Up2k(self)
|
||||||
|
|
||||||
|
self._feature_test()
|
||||||
|
|
||||||
decs = {k: 1 for k in self.args.th_dec.split(",")}
|
decs = {k: 1 for k in self.args.th_dec.split(",")}
|
||||||
if not HAVE_VIPS:
|
if not HAVE_VIPS:
|
||||||
decs.pop("vips", None)
|
decs.pop("vips", None)
|
||||||
@@ -216,6 +276,10 @@ class SvcHub(object):
|
|||||||
if not HAVE_FFMPEG or not HAVE_FFPROBE:
|
if not HAVE_FFMPEG or not HAVE_FFPROBE:
|
||||||
decs.pop("ff", None)
|
decs.pop("ff", None)
|
||||||
|
|
||||||
|
# compressed formats; "s3z=s3m.zip, s3gz=s3m.gz, ..."
|
||||||
|
zlss = [x.strip().lower().split("=", 1) for x in args.au_unpk.split(",")]
|
||||||
|
args.au_unpk = {x[0]: x[1] for x in zlss}
|
||||||
|
|
||||||
self.args.th_dec = list(decs.keys())
|
self.args.th_dec = list(decs.keys())
|
||||||
self.thumbsrv = None
|
self.thumbsrv = None
|
||||||
want_ff = False
|
want_ff = False
|
||||||
@@ -252,6 +316,13 @@ class SvcHub(object):
|
|||||||
if want_ff and ANYWIN:
|
if want_ff and ANYWIN:
|
||||||
self.log("thumb", "download FFmpeg to fix it:\033[0m " + FFMPEG_URL, 3)
|
self.log("thumb", "download FFmpeg to fix it:\033[0m " + FFMPEG_URL, 3)
|
||||||
|
|
||||||
|
if not args.no_acode:
|
||||||
|
if not re.match("^(0|[qv][0-9]|[0-9]{2,3}k)$", args.q_mp3.lower()):
|
||||||
|
t = "invalid mp3 transcoding quality [%s] specified; only supports [0] to disable, a CBR value such as [192k], or a CQ/CRF value such as [v2]"
|
||||||
|
raise Exception(t % (args.q_mp3,))
|
||||||
|
else:
|
||||||
|
args.au_unpk = {}
|
||||||
|
|
||||||
args.th_poke = min(args.th_poke, args.th_maxage, args.ac_maxage)
|
args.th_poke = min(args.th_poke, args.th_maxage, args.ac_maxage)
|
||||||
|
|
||||||
zms = ""
|
zms = ""
|
||||||
@@ -264,9 +335,16 @@ class SvcHub(object):
|
|||||||
from .ftpd import Ftpd
|
from .ftpd import Ftpd
|
||||||
|
|
||||||
self.ftpd: Optional[Ftpd] = None
|
self.ftpd: Optional[Ftpd] = None
|
||||||
Daemon(self.start_ftpd, "start_ftpd")
|
|
||||||
zms += "f" if args.ftp else "F"
|
zms += "f" if args.ftp else "F"
|
||||||
|
|
||||||
|
if args.tftp:
|
||||||
|
from .tftpd import Tftpd
|
||||||
|
|
||||||
|
self.tftpd: Optional[Tftpd] = None
|
||||||
|
|
||||||
|
if args.ftp or args.ftps or args.tftp:
|
||||||
|
Daemon(self.start_ftpd, "start_tftpd")
|
||||||
|
|
||||||
if args.smb:
|
if args.smb:
|
||||||
# impacket.dcerpc is noisy about listen timeouts
|
# impacket.dcerpc is noisy about listen timeouts
|
||||||
sto = socket.getdefaulttimeout()
|
sto = socket.getdefaulttimeout()
|
||||||
@@ -294,12 +372,159 @@ class SvcHub(object):
|
|||||||
|
|
||||||
self.broker = Broker(self)
|
self.broker = Broker(self)
|
||||||
|
|
||||||
def start_ftpd(self) -> None:
|
def setup_session_db(self) -> None:
|
||||||
time.sleep(30)
|
if not HAVE_SQLITE3:
|
||||||
if self.ftpd:
|
self.args.no_ses = True
|
||||||
|
t = "WARNING: sqlite3 not available; disabling sessions, will use plaintext passwords in cookies"
|
||||||
|
self.log("root", t, 3)
|
||||||
return
|
return
|
||||||
|
|
||||||
self.restart_ftpd()
|
import sqlite3
|
||||||
|
|
||||||
|
create = True
|
||||||
|
db_path = self.args.ses_db
|
||||||
|
self.log("root", "opening sessions-db %s" % (db_path,))
|
||||||
|
for n in range(2):
|
||||||
|
try:
|
||||||
|
db = sqlite3.connect(db_path)
|
||||||
|
cur = db.cursor()
|
||||||
|
try:
|
||||||
|
cur.execute("select count(*) from us").fetchone()
|
||||||
|
create = False
|
||||||
|
break
|
||||||
|
except:
|
||||||
|
pass
|
||||||
|
except Exception as ex:
|
||||||
|
if n:
|
||||||
|
raise
|
||||||
|
t = "sessions-db corrupt; deleting and recreating: %r"
|
||||||
|
self.log("root", t % (ex,), 3)
|
||||||
|
try:
|
||||||
|
cur.close() # type: ignore
|
||||||
|
except:
|
||||||
|
pass
|
||||||
|
try:
|
||||||
|
db.close() # type: ignore
|
||||||
|
except:
|
||||||
|
pass
|
||||||
|
os.unlink(db_path)
|
||||||
|
|
||||||
|
sch = [
|
||||||
|
r"create table kv (k text, v int)",
|
||||||
|
r"create table us (un text, si text, t0 int)",
|
||||||
|
# username, session-id, creation-time
|
||||||
|
r"create index us_un on us(un)",
|
||||||
|
r"create index us_si on us(si)",
|
||||||
|
r"create index us_t0 on us(t0)",
|
||||||
|
r"insert into kv values ('sver', 1)",
|
||||||
|
]
|
||||||
|
|
||||||
|
assert db # type: ignore # !rm
|
||||||
|
assert cur # type: ignore # !rm
|
||||||
|
if create:
|
||||||
|
for cmd in sch:
|
||||||
|
cur.execute(cmd)
|
||||||
|
self.log("root", "created new sessions-db")
|
||||||
|
db.commit()
|
||||||
|
|
||||||
|
cur.close()
|
||||||
|
db.close()
|
||||||
|
|
||||||
|
def setup_share_db(self) -> None:
|
||||||
|
al = self.args
|
||||||
|
if not HAVE_SQLITE3:
|
||||||
|
self.log("root", "sqlite3 not available; disabling --shr", 1)
|
||||||
|
al.shr = ""
|
||||||
|
return
|
||||||
|
|
||||||
|
import sqlite3
|
||||||
|
|
||||||
|
al.shr = al.shr.strip("/")
|
||||||
|
if "/" in al.shr or not al.shr:
|
||||||
|
t = "config error: --shr must be the name of a virtual toplevel directory to put shares inside"
|
||||||
|
self.log("root", t, 1)
|
||||||
|
raise Exception(t)
|
||||||
|
|
||||||
|
al.shr = "/%s/" % (al.shr,)
|
||||||
|
|
||||||
|
create = True
|
||||||
|
modified = False
|
||||||
|
db_path = self.args.shr_db
|
||||||
|
self.log("root", "opening shares-db %s" % (db_path,))
|
||||||
|
for n in range(2):
|
||||||
|
try:
|
||||||
|
db = sqlite3.connect(db_path)
|
||||||
|
cur = db.cursor()
|
||||||
|
try:
|
||||||
|
cur.execute("select count(*) from sh").fetchone()
|
||||||
|
create = False
|
||||||
|
break
|
||||||
|
except:
|
||||||
|
pass
|
||||||
|
except Exception as ex:
|
||||||
|
if n:
|
||||||
|
raise
|
||||||
|
t = "shares-db corrupt; deleting and recreating: %r"
|
||||||
|
self.log("root", t % (ex,), 3)
|
||||||
|
try:
|
||||||
|
cur.close() # type: ignore
|
||||||
|
except:
|
||||||
|
pass
|
||||||
|
try:
|
||||||
|
db.close() # type: ignore
|
||||||
|
except:
|
||||||
|
pass
|
||||||
|
os.unlink(db_path)
|
||||||
|
|
||||||
|
sch1 = [
|
||||||
|
r"create table kv (k text, v int)",
|
||||||
|
r"create table sh (k text, pw text, vp text, pr text, st int, un text, t0 int, t1 int)",
|
||||||
|
# sharekey, password, src, perms, numFiles, owner, created, expires
|
||||||
|
]
|
||||||
|
sch2 = [
|
||||||
|
r"create table sf (k text, vp text)",
|
||||||
|
r"create index sf_k on sf(k)",
|
||||||
|
r"create index sh_k on sh(k)",
|
||||||
|
r"create index sh_t1 on sh(t1)",
|
||||||
|
]
|
||||||
|
|
||||||
|
assert db # type: ignore # !rm
|
||||||
|
assert cur # type: ignore # !rm
|
||||||
|
if create:
|
||||||
|
dver = 2
|
||||||
|
modified = True
|
||||||
|
for cmd in sch1 + sch2:
|
||||||
|
cur.execute(cmd)
|
||||||
|
self.log("root", "created new shares-db")
|
||||||
|
else:
|
||||||
|
(dver,) = cur.execute("select v from kv where k = 'sver'").fetchall()[0]
|
||||||
|
|
||||||
|
if dver == 1:
|
||||||
|
modified = True
|
||||||
|
for cmd in sch2:
|
||||||
|
cur.execute(cmd)
|
||||||
|
cur.execute("update sh set st = 0")
|
||||||
|
self.log("root", "shares-db schema upgrade ok")
|
||||||
|
|
||||||
|
if modified:
|
||||||
|
for cmd in [
|
||||||
|
r"delete from kv where k = 'sver'",
|
||||||
|
r"insert into kv values ('sver', %d)" % (2,),
|
||||||
|
]:
|
||||||
|
cur.execute(cmd)
|
||||||
|
db.commit()
|
||||||
|
|
||||||
|
cur.close()
|
||||||
|
db.close()
|
||||||
|
|
||||||
|
def start_ftpd(self) -> None:
|
||||||
|
time.sleep(30)
|
||||||
|
|
||||||
|
if hasattr(self, "ftpd") and not self.ftpd:
|
||||||
|
self.restart_ftpd()
|
||||||
|
|
||||||
|
if hasattr(self, "tftpd") and not self.tftpd:
|
||||||
|
self.restart_tftpd()
|
||||||
|
|
||||||
def restart_ftpd(self) -> None:
|
def restart_ftpd(self) -> None:
|
||||||
if not hasattr(self, "ftpd"):
|
if not hasattr(self, "ftpd"):
|
||||||
@@ -316,6 +541,17 @@ class SvcHub(object):
|
|||||||
self.ftpd = Ftpd(self)
|
self.ftpd = Ftpd(self)
|
||||||
self.log("root", "started FTPd")
|
self.log("root", "started FTPd")
|
||||||
|
|
||||||
|
def restart_tftpd(self) -> None:
|
||||||
|
if not hasattr(self, "tftpd"):
|
||||||
|
return
|
||||||
|
|
||||||
|
from .tftpd import Tftpd
|
||||||
|
|
||||||
|
if self.tftpd:
|
||||||
|
return # todo
|
||||||
|
|
||||||
|
self.tftpd = Tftpd(self)
|
||||||
|
|
||||||
def thr_httpsrv_up(self) -> None:
|
def thr_httpsrv_up(self) -> None:
|
||||||
time.sleep(1 if self.args.ign_ebind_all else 5)
|
time.sleep(1 if self.args.ign_ebind_all else 5)
|
||||||
expected = self.broker.num_workers * self.tcpsrv.nsrv
|
expected = self.broker.num_workers * self.tcpsrv.nsrv
|
||||||
@@ -340,7 +576,7 @@ class SvcHub(object):
|
|||||||
self.sigterm()
|
self.sigterm()
|
||||||
|
|
||||||
def sigterm(self) -> None:
|
def sigterm(self) -> None:
|
||||||
os.kill(os.getpid(), signal.SIGTERM)
|
self.signal_handler(signal.SIGTERM, None)
|
||||||
|
|
||||||
def cb_httpsrv_up(self) -> None:
|
def cb_httpsrv_up(self) -> None:
|
||||||
self.httpsrv_up += 1
|
self.httpsrv_up += 1
|
||||||
@@ -365,6 +601,75 @@ class SvcHub(object):
|
|||||||
|
|
||||||
Daemon(self.sd_notify, "sd-notify")
|
Daemon(self.sd_notify, "sd-notify")
|
||||||
|
|
||||||
|
def _feature_test(self) -> None:
|
||||||
|
fok = []
|
||||||
|
fng = []
|
||||||
|
t_ff = "transcode audio, create spectrograms, video thumbnails"
|
||||||
|
to_check = [
|
||||||
|
(HAVE_SQLITE3, "sqlite", "sessions and file/media indexing"),
|
||||||
|
(HAVE_PIL, "pillow", "image thumbnails (plenty fast)"),
|
||||||
|
(HAVE_VIPS, "vips", "image thumbnails (faster, eats more ram)"),
|
||||||
|
(HAVE_WEBP, "pillow-webp", "create thumbnails as webp files"),
|
||||||
|
(HAVE_FFMPEG, "ffmpeg", t_ff + ", good-but-slow image thumbnails"),
|
||||||
|
(HAVE_FFPROBE, "ffprobe", t_ff + ", read audio/media tags"),
|
||||||
|
(HAVE_MUTAGEN, "mutagen", "read audio tags (ffprobe is better but slower)"),
|
||||||
|
(HAVE_ARGON2, "argon2", "secure password hashing (advanced users only)"),
|
||||||
|
(HAVE_HEIF, "pillow-heif", "read .heif images with pillow (rarely useful)"),
|
||||||
|
(HAVE_AVIF, "pillow-avif", "read .avif images with pillow (rarely useful)"),
|
||||||
|
]
|
||||||
|
if ANYWIN:
|
||||||
|
to_check += [
|
||||||
|
(HAVE_PSUTIL, "psutil", "improved plugin cleanup (rarely useful)")
|
||||||
|
]
|
||||||
|
|
||||||
|
verbose = self.args.deps
|
||||||
|
if verbose:
|
||||||
|
self.log("dependencies", "")
|
||||||
|
|
||||||
|
for have, feat, what in to_check:
|
||||||
|
lst = fok if have else fng
|
||||||
|
lst.append((feat, what))
|
||||||
|
if verbose:
|
||||||
|
zi = 2 if have else 5
|
||||||
|
sgot = "found" if have else "missing"
|
||||||
|
t = "%7s: %s \033[36m(%s)"
|
||||||
|
self.log("dependencies", t % (sgot, feat, what), zi)
|
||||||
|
|
||||||
|
if verbose:
|
||||||
|
self.log("dependencies", "")
|
||||||
|
return
|
||||||
|
|
||||||
|
sok = ", ".join(x[0] for x in fok)
|
||||||
|
sng = ", ".join(x[0] for x in fng)
|
||||||
|
|
||||||
|
t = ""
|
||||||
|
if sok:
|
||||||
|
t += "OK: \033[32m" + sok
|
||||||
|
if sng:
|
||||||
|
if t:
|
||||||
|
t += ", "
|
||||||
|
t += "\033[0mNG: \033[35m" + sng
|
||||||
|
|
||||||
|
t += "\033[0m, see --deps"
|
||||||
|
self.log("dependencies", t, 6)
|
||||||
|
|
||||||
|
def _check_env(self) -> None:
|
||||||
|
try:
|
||||||
|
files = os.listdir(E.cfg)
|
||||||
|
except:
|
||||||
|
files = []
|
||||||
|
|
||||||
|
hits = [x for x in files if x.lower().endswith(".conf")]
|
||||||
|
if hits:
|
||||||
|
t = "WARNING: found config files in [%s]: %s\n config files are not expected here, and will NOT be loaded (unless your setup is intentionally hella funky)"
|
||||||
|
self.log("root", t % (E.cfg, ", ".join(hits)), 3)
|
||||||
|
|
||||||
|
if self.args.no_bauth:
|
||||||
|
t = "WARNING: --no-bauth disables support for the Android app; you may want to use --bauth-last instead"
|
||||||
|
self.log("root", t, 3)
|
||||||
|
if self.args.bauth_last:
|
||||||
|
self.log("root", "WARNING: ignoring --bauth-last due to --no-bauth", 3)
|
||||||
|
|
||||||
def _process_config(self) -> bool:
|
def _process_config(self) -> bool:
|
||||||
al = self.args
|
al = self.args
|
||||||
|
|
||||||
@@ -404,20 +709,27 @@ class SvcHub(object):
|
|||||||
if al.rsp_jtr:
|
if al.rsp_jtr:
|
||||||
al.rsp_slp = 0.000001
|
al.rsp_slp = 0.000001
|
||||||
|
|
||||||
al.th_covers = set(al.th_covers.split(","))
|
zsl = al.th_covers.split(",")
|
||||||
|
zsl = [x.strip() for x in zsl]
|
||||||
|
zsl = [x for x in zsl if x]
|
||||||
|
al.th_covers = zsl
|
||||||
|
al.th_coversd = zsl + ["." + x for x in zsl]
|
||||||
|
al.th_covers_set = set(al.th_covers)
|
||||||
|
al.th_coversd_set = set(al.th_coversd)
|
||||||
|
|
||||||
for k in "c".split(" "):
|
for k in "c".split(" "):
|
||||||
vl = getattr(al, k)
|
vl = getattr(al, k)
|
||||||
if not vl:
|
if not vl:
|
||||||
continue
|
continue
|
||||||
|
|
||||||
vl = [os.path.expanduser(x) if x.startswith("~") else x for x in vl]
|
vl = [os.path.expandvars(os.path.expanduser(x)) for x in vl]
|
||||||
setattr(al, k, vl)
|
setattr(al, k, vl)
|
||||||
|
|
||||||
for k in "lo hist ssl_log".split(" "):
|
for k in "lo hist ssl_log".split(" "):
|
||||||
vs = getattr(al, k)
|
vs = getattr(al, k)
|
||||||
if vs and vs.startswith("~"):
|
if vs:
|
||||||
setattr(al, k, os.path.expanduser(vs))
|
vs = os.path.expandvars(os.path.expanduser(vs))
|
||||||
|
setattr(al, k, vs)
|
||||||
|
|
||||||
for k in "sus_urls nonsus_urls".split(" "):
|
for k in "sus_urls nonsus_urls".split(" "):
|
||||||
vs = getattr(al, k)
|
vs = getattr(al, k)
|
||||||
@@ -426,16 +738,25 @@ class SvcHub(object):
|
|||||||
else:
|
else:
|
||||||
setattr(al, k, re.compile(vs))
|
setattr(al, k, re.compile(vs))
|
||||||
|
|
||||||
|
for k in "tftp_lsf".split(" "):
|
||||||
|
vs = getattr(al, k)
|
||||||
|
if not vs or vs == "no":
|
||||||
|
setattr(al, k, None)
|
||||||
|
else:
|
||||||
|
setattr(al, k, re.compile("^" + vs + "$"))
|
||||||
|
|
||||||
if not al.sus_urls:
|
if not al.sus_urls:
|
||||||
al.ban_url = "no"
|
al.ban_url = "no"
|
||||||
elif al.ban_url == "no":
|
elif al.ban_url == "no":
|
||||||
al.sus_urls = None
|
al.sus_urls = None
|
||||||
|
|
||||||
if al.xff_src in ("any", "0", ""):
|
al.xff_hdr = al.xff_hdr.lower()
|
||||||
al.xff_re = None
|
al.idp_h_usr = al.idp_h_usr.lower()
|
||||||
else:
|
al.idp_h_grp = al.idp_h_grp.lower()
|
||||||
zs = al.xff_src.replace(" ", "").replace(".", "\\.").replace(",", "|")
|
al.idp_h_key = al.idp_h_key.lower()
|
||||||
al.xff_re = re.compile("^(?:" + zs + ")")
|
|
||||||
|
al.ftp_ipa_nm = build_netmap(al.ftp_ipa or al.ipa)
|
||||||
|
al.tftp_ipa_nm = build_netmap(al.tftp_ipa or al.ipa)
|
||||||
|
|
||||||
mte = ODict.fromkeys(DEF_MTE.split(","), True)
|
mte = ODict.fromkeys(DEF_MTE.split(","), True)
|
||||||
al.mte = odfusion(mte, al.mte)
|
al.mte = odfusion(mte, al.mte)
|
||||||
@@ -447,19 +768,56 @@ class SvcHub(object):
|
|||||||
al.exp_md = odfusion(exp, al.exp_md.replace(" ", ","))
|
al.exp_md = odfusion(exp, al.exp_md.replace(" ", ","))
|
||||||
al.exp_lg = odfusion(exp, al.exp_lg.replace(" ", ","))
|
al.exp_lg = odfusion(exp, al.exp_lg.replace(" ", ","))
|
||||||
|
|
||||||
for k in ["no_hash", "no_idx"]:
|
for k in ["no_hash", "no_idx", "og_ua"]:
|
||||||
ptn = getattr(self.args, k)
|
ptn = getattr(self.args, k)
|
||||||
if ptn:
|
if ptn:
|
||||||
setattr(self.args, k, re.compile(ptn))
|
setattr(self.args, k, re.compile(ptn))
|
||||||
|
|
||||||
|
for k in ["idp_gsep"]:
|
||||||
|
ptn = getattr(self.args, k)
|
||||||
|
if "]" in ptn:
|
||||||
|
ptn = "]" + ptn.replace("]", "")
|
||||||
|
if "[" in ptn:
|
||||||
|
ptn = ptn.replace("[", "") + "["
|
||||||
|
if "-" in ptn:
|
||||||
|
ptn = ptn.replace("-", "") + "-"
|
||||||
|
|
||||||
|
ptn = ptn.replace("\\", "\\\\").replace("^", "\\^")
|
||||||
|
setattr(self.args, k, re.compile("[%s]" % (ptn,)))
|
||||||
|
|
||||||
|
try:
|
||||||
|
zf1, zf2 = self.args.rm_retry.split("/")
|
||||||
|
self.args.rm_re_t = float(zf1)
|
||||||
|
self.args.rm_re_r = float(zf2)
|
||||||
|
except:
|
||||||
|
raise Exception("invalid --rm-retry [%s]" % (self.args.rm_retry,))
|
||||||
|
|
||||||
|
try:
|
||||||
|
zf1, zf2 = self.args.mv_retry.split("/")
|
||||||
|
self.args.mv_re_t = float(zf1)
|
||||||
|
self.args.mv_re_r = float(zf2)
|
||||||
|
except:
|
||||||
|
raise Exception("invalid --mv-retry [%s]" % (self.args.mv_retry,))
|
||||||
|
|
||||||
|
al.tcolor = al.tcolor.lstrip("#")
|
||||||
|
if len(al.tcolor) == 3: # fc5 => ffcc55
|
||||||
|
al.tcolor = "".join([x * 2 for x in al.tcolor])
|
||||||
|
|
||||||
return True
|
return True
|
||||||
|
|
||||||
|
def _ipa2re(self, txt) -> Optional[re.Pattern]:
|
||||||
|
if txt in ("any", "0", ""):
|
||||||
|
return None
|
||||||
|
|
||||||
|
zs = txt.replace(" ", "").replace(".", "\\.").replace(",", "|")
|
||||||
|
return re.compile("^(?:" + zs + ")")
|
||||||
|
|
||||||
def _setlimits(self) -> None:
|
def _setlimits(self) -> None:
|
||||||
try:
|
try:
|
||||||
import resource
|
import resource
|
||||||
|
|
||||||
soft, hard = [
|
soft, hard = [
|
||||||
x if x > 0 else 1024 * 1024
|
int(x) if x > 0 else 1024 * 1024
|
||||||
for x in list(resource.getrlimit(resource.RLIMIT_NOFILE))
|
for x in list(resource.getrlimit(resource.RLIMIT_NOFILE))
|
||||||
]
|
]
|
||||||
except:
|
except:
|
||||||
@@ -495,7 +853,7 @@ class SvcHub(object):
|
|||||||
self.args.nc = min(self.args.nc, soft // 2)
|
self.args.nc = min(self.args.nc, soft // 2)
|
||||||
|
|
||||||
def _logname(self) -> str:
|
def _logname(self) -> str:
|
||||||
dt = datetime.now(UTC)
|
dt = datetime.now(self.tz)
|
||||||
fn = str(self.args.lo)
|
fn = str(self.args.lo)
|
||||||
for fs in "YmdHMS":
|
for fs in "YmdHMS":
|
||||||
fs = "%" + fs
|
fs = "%" + fs
|
||||||
@@ -516,12 +874,17 @@ class SvcHub(object):
|
|||||||
sel_fn = "{}.{}".format(fn, ctr)
|
sel_fn = "{}.{}".format(fn, ctr)
|
||||||
|
|
||||||
fn = sel_fn
|
fn = sel_fn
|
||||||
|
try:
|
||||||
|
os.makedirs(os.path.dirname(fn))
|
||||||
|
except:
|
||||||
|
pass
|
||||||
|
|
||||||
try:
|
try:
|
||||||
if do_xz:
|
if do_xz:
|
||||||
import lzma
|
import lzma
|
||||||
|
|
||||||
lh = lzma.open(fn, "wt", encoding="utf-8", errors="replace", preset=0)
|
lh = lzma.open(fn, "wt", encoding="utf-8", errors="replace", preset=0)
|
||||||
|
self.args.no_logflush = True
|
||||||
else:
|
else:
|
||||||
lh = open(fn, "wt", encoding="utf-8", errors="replace")
|
lh = open(fn, "wt", encoding="utf-8", errors="replace")
|
||||||
except:
|
except:
|
||||||
@@ -608,21 +971,45 @@ class SvcHub(object):
|
|||||||
self.log("root", "ssdp startup failed;\n" + min_ex(), 3)
|
self.log("root", "ssdp startup failed;\n" + min_ex(), 3)
|
||||||
|
|
||||||
def reload(self) -> str:
|
def reload(self) -> str:
|
||||||
if self.reloading:
|
with self.up2k.mutex:
|
||||||
return "cannot reload; already in progress"
|
if self.reloading:
|
||||||
|
return "cannot reload; already in progress"
|
||||||
|
self.reloading = 1
|
||||||
|
|
||||||
self.reloading = True
|
|
||||||
Daemon(self._reload, "reloading")
|
Daemon(self._reload, "reloading")
|
||||||
return "reload initiated"
|
return "reload initiated"
|
||||||
|
|
||||||
def _reload(self) -> None:
|
def _reload(self, rescan_all_vols: bool = True, up2k: bool = True) -> None:
|
||||||
self.log("root", "reload scheduled")
|
|
||||||
with self.up2k.mutex:
|
with self.up2k.mutex:
|
||||||
self.asrv.reload()
|
if self.reloading != 1:
|
||||||
self.up2k.reload()
|
return
|
||||||
|
self.reloading = 2
|
||||||
|
self.log("root", "reloading config")
|
||||||
|
self.asrv.reload(9 if up2k else 4)
|
||||||
|
if up2k:
|
||||||
|
self.up2k.reload(rescan_all_vols)
|
||||||
|
else:
|
||||||
|
self.log("root", "reload done")
|
||||||
self.broker.reload()
|
self.broker.reload()
|
||||||
|
self.reloading = 0
|
||||||
|
|
||||||
self.reloading = False
|
def _reload_blocking(self, rescan_all_vols: bool = True, up2k: bool = True) -> None:
|
||||||
|
while True:
|
||||||
|
with self.up2k.mutex:
|
||||||
|
if self.reloading < 2:
|
||||||
|
self.reloading = 1
|
||||||
|
break
|
||||||
|
time.sleep(0.05)
|
||||||
|
|
||||||
|
# try to handle multiple pending IdP reloads at once:
|
||||||
|
time.sleep(0.2)
|
||||||
|
|
||||||
|
self._reload(rescan_all_vols=rescan_all_vols, up2k=up2k)
|
||||||
|
|
||||||
|
def _reload_sessions(self) -> None:
|
||||||
|
with self.asrv.mutex:
|
||||||
|
self.asrv.load_sessions(True)
|
||||||
|
self.broker.reload_sessions()
|
||||||
|
|
||||||
def stop_thr(self) -> None:
|
def stop_thr(self) -> None:
|
||||||
while not self.stop_req:
|
while not self.stop_req:
|
||||||
@@ -744,50 +1131,52 @@ class SvcHub(object):
|
|||||||
return
|
return
|
||||||
|
|
||||||
with self.log_mutex:
|
with self.log_mutex:
|
||||||
zd = datetime.now(UTC)
|
dt = datetime.now(self.tz)
|
||||||
ts = self.log_dfmt % (
|
ts = self.log_dfmt % (
|
||||||
zd.year,
|
dt.year,
|
||||||
zd.month * 100 + zd.day,
|
dt.month * 100 + dt.day,
|
||||||
(zd.hour * 100 + zd.minute) * 100 + zd.second,
|
(dt.hour * 100 + dt.minute) * 100 + dt.second,
|
||||||
zd.microsecond // self.log_div,
|
dt.microsecond // self.log_div,
|
||||||
)
|
)
|
||||||
self.logf.write("@%s [%s\033[0m] %s\n" % (ts, src, msg))
|
|
||||||
|
|
||||||
now = time.time()
|
if c and not self.args.no_ansi:
|
||||||
if now >= self.next_day:
|
if isinstance(c, int):
|
||||||
self._set_next_day()
|
msg = "\033[3%sm%s\033[0m" % (c, msg)
|
||||||
|
elif "\033" not in c:
|
||||||
|
msg = "\033[%sm%s\033[0m" % (c, msg)
|
||||||
|
else:
|
||||||
|
msg = "%s%s\033[0m" % (c, msg)
|
||||||
|
|
||||||
def _set_next_day(self) -> None:
|
if "\033" in src:
|
||||||
if self.next_day and self.logf and self.logf_base_fn != self._logname():
|
src += "\033[0m"
|
||||||
|
|
||||||
|
if "\033" in msg:
|
||||||
|
msg += "\033[0m"
|
||||||
|
|
||||||
|
self.logf.write("@%s [%-21s] %s\n" % (ts, src, msg))
|
||||||
|
if not self.args.no_logflush:
|
||||||
|
self.logf.flush()
|
||||||
|
|
||||||
|
if dt.day != self.cday or dt.month != self.cmon:
|
||||||
|
self._set_next_day(dt)
|
||||||
|
|
||||||
|
def _set_next_day(self, dt: datetime) -> None:
|
||||||
|
if self.cday and self.logf and self.logf_base_fn != self._logname():
|
||||||
self.logf.close()
|
self.logf.close()
|
||||||
self._setup_logfile("")
|
self._setup_logfile("")
|
||||||
|
|
||||||
dt = datetime.now(UTC)
|
self.cday = dt.day
|
||||||
|
self.cmon = dt.month
|
||||||
# unix timestamp of next 00:00:00 (leap-seconds safe)
|
|
||||||
day_now = dt.day
|
|
||||||
while dt.day == day_now:
|
|
||||||
dt += timedelta(hours=12)
|
|
||||||
|
|
||||||
dt = dt.replace(hour=0, minute=0, second=0)
|
|
||||||
try:
|
|
||||||
tt = dt.utctimetuple()
|
|
||||||
except:
|
|
||||||
# still makes me hella uncomfortable
|
|
||||||
tt = dt.timetuple()
|
|
||||||
|
|
||||||
self.next_day = calendar.timegm(tt)
|
|
||||||
|
|
||||||
def _log_enabled(self, src: str, msg: str, c: Union[int, str] = 0) -> None:
|
def _log_enabled(self, src: str, msg: str, c: Union[int, str] = 0) -> None:
|
||||||
"""handles logging from all components"""
|
"""handles logging from all components"""
|
||||||
with self.log_mutex:
|
with self.log_mutex:
|
||||||
now = time.time()
|
dt = datetime.now(self.tz)
|
||||||
if now >= self.next_day:
|
if dt.day != self.cday or dt.month != self.cmon:
|
||||||
dt = datetime.fromtimestamp(now, UTC)
|
|
||||||
zs = "{}\n" if self.no_ansi else "\033[36m{}\033[0m\n"
|
zs = "{}\n" if self.no_ansi else "\033[36m{}\033[0m\n"
|
||||||
zs = zs.format(dt.strftime("%Y-%m-%d"))
|
zs = zs.format(dt.strftime("%Y-%m-%d"))
|
||||||
print(zs, end="")
|
print(zs, end="")
|
||||||
self._set_next_day()
|
self._set_next_day(dt)
|
||||||
if self.logf:
|
if self.logf:
|
||||||
self.logf.write(zs)
|
self.logf.write(zs)
|
||||||
|
|
||||||
@@ -806,12 +1195,11 @@ class SvcHub(object):
|
|||||||
else:
|
else:
|
||||||
msg = "%s%s\033[0m" % (c, msg)
|
msg = "%s%s\033[0m" % (c, msg)
|
||||||
|
|
||||||
zd = datetime.fromtimestamp(now, UTC)
|
|
||||||
ts = self.log_efmt % (
|
ts = self.log_efmt % (
|
||||||
zd.hour,
|
dt.hour,
|
||||||
zd.minute,
|
dt.minute,
|
||||||
zd.second,
|
dt.second,
|
||||||
zd.microsecond // self.log_div,
|
dt.microsecond // self.log_div,
|
||||||
)
|
)
|
||||||
msg = fmt % (ts, src, msg)
|
msg = fmt % (ts, src, msg)
|
||||||
try:
|
try:
|
||||||
@@ -827,6 +1215,8 @@ class SvcHub(object):
|
|||||||
|
|
||||||
if self.logf:
|
if self.logf:
|
||||||
self.logf.write(msg)
|
self.logf.write(msg)
|
||||||
|
if not self.args.no_logflush:
|
||||||
|
self.logf.flush()
|
||||||
|
|
||||||
def pr(self, *a: Any, **ka: Any) -> None:
|
def pr(self, *a: Any, **ka: Any) -> None:
|
||||||
try:
|
try:
|
||||||
@@ -907,5 +1297,5 @@ class SvcHub(object):
|
|||||||
zs = "{}\n{}".format(VERSIONS, alltrace())
|
zs = "{}\n{}".format(VERSIONS, alltrace())
|
||||||
zb = zs.encode("utf-8", "replace")
|
zb = zs.encode("utf-8", "replace")
|
||||||
zb = gzip.compress(zb)
|
zb = gzip.compress(zb)
|
||||||
zs = base64.b64encode(zb).decode("ascii")
|
zs = ub64enc(zb).decode("ascii")
|
||||||
self.log("stacks", zs)
|
self.log("stacks", zs)
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ import stat
|
|||||||
import time
|
import time
|
||||||
import zlib
|
import zlib
|
||||||
|
|
||||||
|
from .authsrv import AuthSrv
|
||||||
from .bos import bos
|
from .bos import bos
|
||||||
from .sutil import StreamArc, errdesc
|
from .sutil import StreamArc, errdesc
|
||||||
from .util import min_ex, sanitize_fn, spack, sunpack, yieldfile
|
from .util import min_ex, sanitize_fn, spack, sunpack, yieldfile
|
||||||
@@ -36,9 +37,7 @@ def dostime2unix(buf: bytes) -> int:
|
|||||||
|
|
||||||
|
|
||||||
def unixtime2dos(ts: int) -> bytes:
|
def unixtime2dos(ts: int) -> bytes:
|
||||||
tt = time.gmtime(ts + 1)
|
dy, dm, dd, th, tm, ts, _, _, _ = time.gmtime(ts + 1)
|
||||||
dy, dm, dd, th, tm, ts = list(tt)[:6]
|
|
||||||
|
|
||||||
bd = ((dy - 1980) << 9) + (dm << 5) + dd
|
bd = ((dy - 1980) << 9) + (dm << 5) + dd
|
||||||
bt = (th << 11) + (tm << 5) + ts // 2
|
bt = (th << 11) + (tm << 5) + ts // 2
|
||||||
try:
|
try:
|
||||||
@@ -218,12 +217,13 @@ class StreamZip(StreamArc):
|
|||||||
def __init__(
|
def __init__(
|
||||||
self,
|
self,
|
||||||
log: "NamedLogger",
|
log: "NamedLogger",
|
||||||
|
asrv: AuthSrv,
|
||||||
fgen: Generator[dict[str, Any], None, None],
|
fgen: Generator[dict[str, Any], None, None],
|
||||||
utf8: bool = False,
|
utf8: bool = False,
|
||||||
pre_crc: bool = False,
|
pre_crc: bool = False,
|
||||||
**kwargs: Any
|
**kwargs: Any
|
||||||
) -> None:
|
) -> None:
|
||||||
super(StreamZip, self).__init__(log, fgen)
|
super(StreamZip, self).__init__(log, asrv, fgen)
|
||||||
|
|
||||||
self.utf8 = utf8
|
self.utf8 = utf8
|
||||||
self.pre_crc = pre_crc
|
self.pre_crc = pre_crc
|
||||||
@@ -248,7 +248,7 @@ class StreamZip(StreamArc):
|
|||||||
|
|
||||||
crc = 0
|
crc = 0
|
||||||
if self.pre_crc:
|
if self.pre_crc:
|
||||||
for buf in yieldfile(src):
|
for buf in yieldfile(src, self.args.iobuf):
|
||||||
crc = zlib.crc32(buf, crc)
|
crc = zlib.crc32(buf, crc)
|
||||||
|
|
||||||
crc &= 0xFFFFFFFF
|
crc &= 0xFFFFFFFF
|
||||||
@@ -257,7 +257,7 @@ class StreamZip(StreamArc):
|
|||||||
buf = gen_hdr(None, name, sz, ts, self.utf8, crc, self.pre_crc)
|
buf = gen_hdr(None, name, sz, ts, self.utf8, crc, self.pre_crc)
|
||||||
yield self._ct(buf)
|
yield self._ct(buf)
|
||||||
|
|
||||||
for buf in yieldfile(src):
|
for buf in yieldfile(src, self.args.iobuf):
|
||||||
if not self.pre_crc:
|
if not self.pre_crc:
|
||||||
crc = zlib.crc32(buf, crc)
|
crc = zlib.crc32(buf, crc)
|
||||||
|
|
||||||
@@ -300,7 +300,7 @@ class StreamZip(StreamArc):
|
|||||||
mbuf = b""
|
mbuf = b""
|
||||||
|
|
||||||
if errors:
|
if errors:
|
||||||
errf, txt = errdesc(errors)
|
errf, txt = errdesc(self.asrv.vfs, errors)
|
||||||
self.log("\n".join(([repr(errf)] + txt[1:])))
|
self.log("\n".join(([repr(errf)] + txt[1:])))
|
||||||
for x in self.ser(errf):
|
for x in self.ser(errf):
|
||||||
yield x
|
yield x
|
||||||
|
|||||||
@@ -15,19 +15,25 @@ from .util import (
|
|||||||
E_ADDR_IN_USE,
|
E_ADDR_IN_USE,
|
||||||
E_ADDR_NOT_AVAIL,
|
E_ADDR_NOT_AVAIL,
|
||||||
E_UNREACH,
|
E_UNREACH,
|
||||||
|
HAVE_IPV6,
|
||||||
IP6ALL,
|
IP6ALL,
|
||||||
|
VF_CAREFUL,
|
||||||
Netdev,
|
Netdev,
|
||||||
|
atomic_move,
|
||||||
min_ex,
|
min_ex,
|
||||||
sunpack,
|
sunpack,
|
||||||
termsize,
|
termsize,
|
||||||
)
|
)
|
||||||
|
|
||||||
if True:
|
if True:
|
||||||
from typing import Generator
|
from typing import Generator, Union
|
||||||
|
|
||||||
if TYPE_CHECKING:
|
if TYPE_CHECKING:
|
||||||
from .svchub import SvcHub
|
from .svchub import SvcHub
|
||||||
|
|
||||||
|
if not hasattr(socket, "AF_UNIX"):
|
||||||
|
setattr(socket, "AF_UNIX", -9001)
|
||||||
|
|
||||||
if not hasattr(socket, "IPPROTO_IPV6"):
|
if not hasattr(socket, "IPPROTO_IPV6"):
|
||||||
setattr(socket, "IPPROTO_IPV6", 41)
|
setattr(socket, "IPPROTO_IPV6", 41)
|
||||||
|
|
||||||
@@ -111,8 +117,10 @@ class TcpSrv(object):
|
|||||||
|
|
||||||
eps = {
|
eps = {
|
||||||
"127.0.0.1": Netdev("127.0.0.1", 0, "", "local only"),
|
"127.0.0.1": Netdev("127.0.0.1", 0, "", "local only"),
|
||||||
"::1": Netdev("::1", 0, "", "local only"),
|
|
||||||
}
|
}
|
||||||
|
if HAVE_IPV6:
|
||||||
|
eps["::1"] = Netdev("::1", 0, "", "local only")
|
||||||
|
|
||||||
nonlocals = [x for x in self.args.i if x not in [k.split("/")[0] for k in eps]]
|
nonlocals = [x for x in self.args.i if x not in [k.split("/")[0] for k in eps]]
|
||||||
if nonlocals:
|
if nonlocals:
|
||||||
try:
|
try:
|
||||||
@@ -214,14 +222,41 @@ class TcpSrv(object):
|
|||||||
if self.args.qr or self.args.qrs:
|
if self.args.qr or self.args.qrs:
|
||||||
self.qr = self._qr(qr1, qr2)
|
self.qr = self._qr(qr1, qr2)
|
||||||
|
|
||||||
|
def nlog(self, msg: str, c: Union[int, str] = 0) -> None:
|
||||||
|
self.log("tcpsrv", msg, c)
|
||||||
|
|
||||||
def _listen(self, ip: str, port: int) -> None:
|
def _listen(self, ip: str, port: int) -> None:
|
||||||
ipv = socket.AF_INET6 if ":" in ip else socket.AF_INET
|
uds_perm = uds_gid = -1
|
||||||
|
if "unix:" in ip:
|
||||||
|
tcp = False
|
||||||
|
ipv = socket.AF_UNIX
|
||||||
|
uds = ip.split(":")
|
||||||
|
ip = uds[-1]
|
||||||
|
if len(uds) > 2:
|
||||||
|
uds_perm = int(uds[1], 8)
|
||||||
|
if len(uds) > 3:
|
||||||
|
try:
|
||||||
|
uds_gid = int(uds[2])
|
||||||
|
except:
|
||||||
|
import grp
|
||||||
|
|
||||||
|
uds_gid = grp.getgrnam(uds[2]).gr_gid
|
||||||
|
|
||||||
|
elif ":" in ip:
|
||||||
|
tcp = True
|
||||||
|
ipv = socket.AF_INET6
|
||||||
|
else:
|
||||||
|
tcp = True
|
||||||
|
ipv = socket.AF_INET
|
||||||
|
|
||||||
srv = socket.socket(ipv, socket.SOCK_STREAM)
|
srv = socket.socket(ipv, socket.SOCK_STREAM)
|
||||||
|
|
||||||
if not ANYWIN or self.args.reuseaddr:
|
if not ANYWIN or self.args.reuseaddr:
|
||||||
srv.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
|
srv.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
|
||||||
|
|
||||||
srv.setsockopt(socket.IPPROTO_TCP, socket.TCP_NODELAY, 1)
|
if tcp:
|
||||||
|
srv.setsockopt(socket.IPPROTO_TCP, socket.TCP_NODELAY, 1)
|
||||||
|
|
||||||
srv.settimeout(None) # < does not inherit, ^ opts above do
|
srv.settimeout(None) # < does not inherit, ^ opts above do
|
||||||
|
|
||||||
try:
|
try:
|
||||||
@@ -233,20 +268,53 @@ class TcpSrv(object):
|
|||||||
srv.setsockopt(socket.SOL_IP, socket.IP_FREEBIND, 1)
|
srv.setsockopt(socket.SOL_IP, socket.IP_FREEBIND, 1)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
srv.bind((ip, port))
|
if tcp:
|
||||||
sport = srv.getsockname()[1]
|
srv.bind((ip, port))
|
||||||
|
else:
|
||||||
|
if ANYWIN or self.args.rm_sck:
|
||||||
|
if os.path.exists(ip):
|
||||||
|
os.unlink(ip)
|
||||||
|
srv.bind(ip)
|
||||||
|
else:
|
||||||
|
tf = "%s.%d" % (ip, os.getpid())
|
||||||
|
if os.path.exists(tf):
|
||||||
|
os.unlink(tf)
|
||||||
|
srv.bind(tf)
|
||||||
|
if uds_gid != -1:
|
||||||
|
os.chown(tf, -1, uds_gid)
|
||||||
|
if uds_perm != -1:
|
||||||
|
os.chmod(tf, uds_perm)
|
||||||
|
atomic_move(self.nlog, tf, ip, VF_CAREFUL)
|
||||||
|
|
||||||
|
sport = srv.getsockname()[1] if tcp else port
|
||||||
if port != sport:
|
if port != sport:
|
||||||
# linux 6.0.16 lets you bind a port which is in use
|
# linux 6.0.16 lets you bind a port which is in use
|
||||||
# except it just gives you a random port instead
|
# except it just gives you a random port instead
|
||||||
raise OSError(E_ADDR_IN_USE[0], "")
|
raise OSError(E_ADDR_IN_USE[0], "")
|
||||||
self.srv.append(srv)
|
self.srv.append(srv)
|
||||||
except (OSError, socket.error) as ex:
|
except (OSError, socket.error) as ex:
|
||||||
|
try:
|
||||||
|
srv.close()
|
||||||
|
except:
|
||||||
|
pass
|
||||||
|
|
||||||
|
e = ""
|
||||||
if ex.errno in E_ADDR_IN_USE:
|
if ex.errno in E_ADDR_IN_USE:
|
||||||
e = "\033[1;31mport {} is busy on interface {}\033[0m".format(port, ip)
|
e = "\033[1;31mport {} is busy on interface {}\033[0m".format(port, ip)
|
||||||
|
if not tcp:
|
||||||
|
e = "\033[1;31munix-socket {} is busy\033[0m".format(ip)
|
||||||
elif ex.errno in E_ADDR_NOT_AVAIL:
|
elif ex.errno in E_ADDR_NOT_AVAIL:
|
||||||
e = "\033[1;31minterface {} does not exist\033[0m".format(ip)
|
e = "\033[1;31minterface {} does not exist\033[0m".format(ip)
|
||||||
else:
|
|
||||||
|
if not e:
|
||||||
|
if not tcp:
|
||||||
|
t = "\n\n\n NOTE: this crash may be due to a unix-socket bug; try --rm-sck\n"
|
||||||
|
self.log("tcpsrv", t, 2)
|
||||||
raise
|
raise
|
||||||
|
|
||||||
|
if not tcp and not self.args.rm_sck:
|
||||||
|
e += "; maybe this is a bug? try --rm-sck"
|
||||||
|
|
||||||
raise Exception(e)
|
raise Exception(e)
|
||||||
|
|
||||||
def run(self) -> None:
|
def run(self) -> None:
|
||||||
@@ -254,7 +322,14 @@ class TcpSrv(object):
|
|||||||
bound: list[tuple[str, int]] = []
|
bound: list[tuple[str, int]] = []
|
||||||
srvs: list[socket.socket] = []
|
srvs: list[socket.socket] = []
|
||||||
for srv in self.srv:
|
for srv in self.srv:
|
||||||
ip, port = srv.getsockname()[:2]
|
if srv.family == socket.AF_UNIX:
|
||||||
|
tcp = False
|
||||||
|
ip = re.sub(r"\.[0-9]+$", "", srv.getsockname())
|
||||||
|
port = 0
|
||||||
|
else:
|
||||||
|
tcp = True
|
||||||
|
ip, port = srv.getsockname()[:2]
|
||||||
|
|
||||||
if ip == IP6ALL:
|
if ip == IP6ALL:
|
||||||
ip = "::" # jython
|
ip = "::" # jython
|
||||||
|
|
||||||
@@ -286,8 +361,12 @@ class TcpSrv(object):
|
|||||||
bound.append((ip, port))
|
bound.append((ip, port))
|
||||||
srvs.append(srv)
|
srvs.append(srv)
|
||||||
fno = srv.fileno()
|
fno = srv.fileno()
|
||||||
hip = "[{}]".format(ip) if ":" in ip else ip
|
if tcp:
|
||||||
msg = "listening @ {}:{} f{} p{}".format(hip, port, fno, os.getpid())
|
hip = "[{}]".format(ip) if ":" in ip else ip
|
||||||
|
msg = "listening @ {}:{} f{} p{}".format(hip, port, fno, os.getpid())
|
||||||
|
else:
|
||||||
|
msg = "listening @ {} f{} p{}".format(ip, fno, os.getpid())
|
||||||
|
|
||||||
self.log("tcpsrv", msg)
|
self.log("tcpsrv", msg)
|
||||||
if self.args.q:
|
if self.args.q:
|
||||||
print(msg)
|
print(msg)
|
||||||
@@ -304,6 +383,7 @@ class TcpSrv(object):
|
|||||||
self.hub.start_zeroconf()
|
self.hub.start_zeroconf()
|
||||||
gencert(self.log, self.args, self.netdevs)
|
gencert(self.log, self.args, self.netdevs)
|
||||||
self.hub.restart_ftpd()
|
self.hub.restart_ftpd()
|
||||||
|
self.hub.restart_tftpd()
|
||||||
|
|
||||||
def shutdown(self) -> None:
|
def shutdown(self) -> None:
|
||||||
self.stopping = True
|
self.stopping = True
|
||||||
@@ -339,6 +419,8 @@ class TcpSrv(object):
|
|||||||
def detect_interfaces(self, listen_ips: list[str]) -> dict[str, Netdev]:
|
def detect_interfaces(self, listen_ips: list[str]) -> dict[str, Netdev]:
|
||||||
from .stolen.ifaddr import get_adapters
|
from .stolen.ifaddr import get_adapters
|
||||||
|
|
||||||
|
listen_ips = [x for x in listen_ips if "unix:" not in x]
|
||||||
|
|
||||||
nics = get_adapters(True)
|
nics = get_adapters(True)
|
||||||
eps: dict[str, Netdev] = {}
|
eps: dict[str, Netdev] = {}
|
||||||
for nic in nics:
|
for nic in nics:
|
||||||
@@ -457,6 +539,12 @@ class TcpSrv(object):
|
|||||||
sys.stderr.flush()
|
sys.stderr.flush()
|
||||||
|
|
||||||
def _qr(self, t1: dict[str, list[int]], t2: dict[str, list[int]]) -> str:
|
def _qr(self, t1: dict[str, list[int]], t2: dict[str, list[int]]) -> str:
|
||||||
|
t2c = {zs: zli for zs, zli in t2.items() if zs in ("127.0.0.1", "::1")}
|
||||||
|
t2b = {zs: zli for zs, zli in t2.items() if ":" in zs and zs not in t2c}
|
||||||
|
t2 = {zs: zli for zs, zli in t2.items() if zs not in t2b and zs not in t2c}
|
||||||
|
t2.update(t2b) # first ipv4, then ipv6...
|
||||||
|
t2.update(t2c) # ...and finally localhost
|
||||||
|
|
||||||
ip = None
|
ip = None
|
||||||
ips = list(t1) + list(t2)
|
ips = list(t1) + list(t2)
|
||||||
qri = self.args.qri
|
qri = self.args.qri
|
||||||
|
|||||||
455
copyparty/tftpd.py
Normal file
455
copyparty/tftpd.py
Normal file
@@ -0,0 +1,455 @@
|
|||||||
|
# coding: utf-8
|
||||||
|
from __future__ import print_function, unicode_literals
|
||||||
|
|
||||||
|
try:
|
||||||
|
from types import SimpleNamespace
|
||||||
|
except:
|
||||||
|
|
||||||
|
class SimpleNamespace(object):
|
||||||
|
def __init__(self, **attr):
|
||||||
|
self.__dict__.update(attr)
|
||||||
|
|
||||||
|
|
||||||
|
import logging
|
||||||
|
import os
|
||||||
|
import re
|
||||||
|
import socket
|
||||||
|
import stat
|
||||||
|
import threading
|
||||||
|
import time
|
||||||
|
from datetime import datetime
|
||||||
|
|
||||||
|
try:
|
||||||
|
import inspect
|
||||||
|
except:
|
||||||
|
pass
|
||||||
|
|
||||||
|
from partftpy import (
|
||||||
|
TftpContexts,
|
||||||
|
TftpPacketFactory,
|
||||||
|
TftpPacketTypes,
|
||||||
|
TftpServer,
|
||||||
|
TftpStates,
|
||||||
|
)
|
||||||
|
from partftpy.TftpShared import TftpException
|
||||||
|
|
||||||
|
from .__init__ import EXE, PY2, TYPE_CHECKING
|
||||||
|
from .authsrv import VFS
|
||||||
|
from .bos import bos
|
||||||
|
from .util import UTC, BytesIO, Daemon, ODict, exclude_dotfiles, min_ex, runhook, undot
|
||||||
|
|
||||||
|
if True: # pylint: disable=using-constant-test
|
||||||
|
from typing import Any, Union
|
||||||
|
|
||||||
|
if TYPE_CHECKING:
|
||||||
|
from .svchub import SvcHub
|
||||||
|
|
||||||
|
if PY2:
|
||||||
|
range = xrange # type: ignore
|
||||||
|
|
||||||
|
|
||||||
|
lg = logging.getLogger("tftp")
|
||||||
|
debug, info, warning, error = (lg.debug, lg.info, lg.warning, lg.error)
|
||||||
|
|
||||||
|
|
||||||
|
def noop(*a, **ka) -> None:
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
def _serverInitial(self, pkt: Any, raddress: str, rport: int) -> bool:
|
||||||
|
info("connection from %s:%s", raddress, rport)
|
||||||
|
ret = _sinitial[0](self, pkt, raddress, rport)
|
||||||
|
nm = _hub[0].args.tftp_ipa_nm
|
||||||
|
if nm and not nm.map(raddress):
|
||||||
|
yeet("client rejected (--tftp-ipa): %s" % (raddress,))
|
||||||
|
return ret
|
||||||
|
|
||||||
|
|
||||||
|
# patch ipa-check into partftpd (part 1/2)
|
||||||
|
_hub: list["SvcHub"] = []
|
||||||
|
_sinitial: list[Any] = []
|
||||||
|
|
||||||
|
|
||||||
|
class Tftpd(object):
|
||||||
|
def __init__(self, hub: "SvcHub") -> None:
|
||||||
|
self.hub = hub
|
||||||
|
self.args = hub.args
|
||||||
|
self.asrv = hub.asrv
|
||||||
|
self.log = hub.log
|
||||||
|
self.mutex = threading.Lock()
|
||||||
|
|
||||||
|
_hub[:] = []
|
||||||
|
_hub.append(hub)
|
||||||
|
|
||||||
|
lg.setLevel(logging.DEBUG if self.args.tftpv else logging.INFO)
|
||||||
|
for x in ["partftpy", "partftpy.TftpStates", "partftpy.TftpServer"]:
|
||||||
|
lgr = logging.getLogger(x)
|
||||||
|
lgr.setLevel(logging.DEBUG if self.args.tftpv else logging.INFO)
|
||||||
|
|
||||||
|
if not self.args.tftpv and not self.args.tftpvv:
|
||||||
|
# contexts -> states -> packettypes -> shared
|
||||||
|
# contexts -> packetfactory
|
||||||
|
# packetfactory -> packettypes
|
||||||
|
Cs = [
|
||||||
|
TftpPacketTypes,
|
||||||
|
TftpPacketFactory,
|
||||||
|
TftpStates,
|
||||||
|
TftpContexts,
|
||||||
|
TftpServer,
|
||||||
|
]
|
||||||
|
cbak = []
|
||||||
|
if not self.args.tftp_no_fast and not EXE and not PY2:
|
||||||
|
try:
|
||||||
|
ptn = re.compile(r"(^\s*)log\.debug\(.*\)$")
|
||||||
|
for C in Cs:
|
||||||
|
cbak.append(C.__dict__)
|
||||||
|
src1 = inspect.getsource(C).split("\n")
|
||||||
|
src2 = "\n".join([ptn.sub("\\1pass", ln) for ln in src1])
|
||||||
|
cfn = C.__spec__.origin
|
||||||
|
exec (compile(src2, filename=cfn, mode="exec"), C.__dict__)
|
||||||
|
except Exception:
|
||||||
|
t = "failed to optimize tftp code; run with --tftp-no-fast if there are issues:\n"
|
||||||
|
self.log("tftp", t + min_ex(), 3)
|
||||||
|
for n, zd in enumerate(cbak):
|
||||||
|
Cs[n].__dict__ = zd
|
||||||
|
|
||||||
|
for C in Cs:
|
||||||
|
C.log.debug = noop
|
||||||
|
|
||||||
|
# patch ipa-check into partftpd (part 2/2)
|
||||||
|
_sinitial[:] = []
|
||||||
|
_sinitial.append(TftpStates.TftpServerState.serverInitial)
|
||||||
|
TftpStates.TftpServerState.serverInitial = _serverInitial
|
||||||
|
|
||||||
|
# patch vfs into partftpy
|
||||||
|
TftpContexts.open = self._open
|
||||||
|
TftpStates.open = self._open
|
||||||
|
|
||||||
|
fos = SimpleNamespace()
|
||||||
|
for k in os.__dict__:
|
||||||
|
try:
|
||||||
|
setattr(fos, k, getattr(os, k))
|
||||||
|
except:
|
||||||
|
pass
|
||||||
|
fos.access = self._access
|
||||||
|
fos.mkdir = self._mkdir
|
||||||
|
fos.unlink = self._unlink
|
||||||
|
fos.sep = "/"
|
||||||
|
TftpContexts.os = fos
|
||||||
|
TftpServer.os = fos
|
||||||
|
TftpStates.os = fos
|
||||||
|
|
||||||
|
fop = SimpleNamespace()
|
||||||
|
for k in os.path.__dict__:
|
||||||
|
try:
|
||||||
|
setattr(fop, k, getattr(os.path, k))
|
||||||
|
except:
|
||||||
|
pass
|
||||||
|
fop.abspath = self._p_abspath
|
||||||
|
fop.exists = self._p_exists
|
||||||
|
fop.isdir = self._p_isdir
|
||||||
|
fop.normpath = self._p_normpath
|
||||||
|
fos.path = fop
|
||||||
|
|
||||||
|
self._disarm(fos)
|
||||||
|
|
||||||
|
self.port = int(self.args.tftp)
|
||||||
|
self.srv = []
|
||||||
|
self.ips = []
|
||||||
|
|
||||||
|
ports = []
|
||||||
|
if self.args.tftp_pr:
|
||||||
|
p1, p2 = [int(x) for x in self.args.tftp_pr.split("-")]
|
||||||
|
ports = list(range(p1, p2 + 1))
|
||||||
|
|
||||||
|
ips = self.args.i
|
||||||
|
if "::" in ips:
|
||||||
|
ips.append("0.0.0.0")
|
||||||
|
|
||||||
|
ips = [x for x in ips if "unix:" not in x]
|
||||||
|
|
||||||
|
if self.args.tftp4:
|
||||||
|
ips = [x for x in ips if ":" not in x]
|
||||||
|
|
||||||
|
if not ips:
|
||||||
|
t = "cannot start tftp-server; no compatible IPs in -i"
|
||||||
|
self.nlog(t, 1)
|
||||||
|
return
|
||||||
|
|
||||||
|
ips = list(ODict.fromkeys(ips)) # dedup
|
||||||
|
|
||||||
|
for ip in ips:
|
||||||
|
name = "tftp_%s" % (ip,)
|
||||||
|
Daemon(self._start, name, [ip, ports])
|
||||||
|
time.sleep(0.2) # give dualstack a chance
|
||||||
|
|
||||||
|
def nlog(self, msg: str, c: Union[int, str] = 0) -> None:
|
||||||
|
self.log("tftp", msg, c)
|
||||||
|
|
||||||
|
def _start(self, ip, ports):
|
||||||
|
fam = socket.AF_INET6 if ":" in ip else socket.AF_INET
|
||||||
|
have_been_alive = False
|
||||||
|
while True:
|
||||||
|
srv = TftpServer.TftpServer("/", self._ls)
|
||||||
|
with self.mutex:
|
||||||
|
self.srv.append(srv)
|
||||||
|
self.ips.append(ip)
|
||||||
|
|
||||||
|
try:
|
||||||
|
# this is the listen loop; it should block forever
|
||||||
|
srv.listen(ip, self.port, af_family=fam, ports=ports)
|
||||||
|
except:
|
||||||
|
with self.mutex:
|
||||||
|
self.srv.remove(srv)
|
||||||
|
self.ips.remove(ip)
|
||||||
|
|
||||||
|
try:
|
||||||
|
srv.sock.close()
|
||||||
|
except:
|
||||||
|
pass
|
||||||
|
|
||||||
|
try:
|
||||||
|
bound = bool(srv.listenport)
|
||||||
|
except:
|
||||||
|
bound = False
|
||||||
|
|
||||||
|
if bound:
|
||||||
|
# this instance has managed to bind at least once
|
||||||
|
have_been_alive = True
|
||||||
|
|
||||||
|
if have_been_alive:
|
||||||
|
t = "tftp server [%s]:%d crashed; restarting in 3 sec:\n%s"
|
||||||
|
error(t, ip, self.port, min_ex())
|
||||||
|
time.sleep(3)
|
||||||
|
continue
|
||||||
|
|
||||||
|
# server failed to start; could be due to dualstack (ipv6 managed to bind and this is ipv4)
|
||||||
|
if ip != "0.0.0.0" or "::" not in self.ips:
|
||||||
|
# nope, it's fatal
|
||||||
|
t = "tftp server [%s]:%d failed to start:\n%s"
|
||||||
|
error(t, ip, self.port, min_ex())
|
||||||
|
|
||||||
|
# yep; ignore
|
||||||
|
# (TODO: move the "listening @ ..." infolog in partftpy to
|
||||||
|
# after the bind attempt so it doesn't print twice)
|
||||||
|
return
|
||||||
|
|
||||||
|
info("tftp server [%s]:%d terminated", ip, self.port)
|
||||||
|
break
|
||||||
|
|
||||||
|
def stop(self):
|
||||||
|
with self.mutex:
|
||||||
|
srvs = self.srv[:]
|
||||||
|
|
||||||
|
for srv in srvs:
|
||||||
|
srv.stop()
|
||||||
|
|
||||||
|
def _v2a(self, caller: str, vpath: str, perms: list, *a: Any) -> tuple[VFS, str]:
|
||||||
|
vpath = vpath.replace("\\", "/").lstrip("/")
|
||||||
|
if not perms:
|
||||||
|
perms = [True, True]
|
||||||
|
|
||||||
|
debug('%s("%s", %s) %s\033[K\033[0m', caller, vpath, str(a), perms)
|
||||||
|
vfs, rem = self.asrv.vfs.get(vpath, "*", *perms)
|
||||||
|
if not vfs.realpath:
|
||||||
|
raise Exception("unmapped vfs")
|
||||||
|
return vfs, vfs.canonical(rem)
|
||||||
|
|
||||||
|
def _ls(self, vpath: str, raddress: str, rport: int, force=False) -> Any:
|
||||||
|
# generate file listing if vpath is dir.txt and return as file object
|
||||||
|
if not force:
|
||||||
|
vpath, fn = os.path.split(vpath.replace("\\", "/"))
|
||||||
|
ptn = self.args.tftp_lsf
|
||||||
|
if not ptn or not ptn.match(fn.lower()):
|
||||||
|
return None
|
||||||
|
|
||||||
|
vn, rem = self.asrv.vfs.get(vpath, "*", True, False)
|
||||||
|
fsroot, vfs_ls, vfs_virt = vn.ls(
|
||||||
|
rem,
|
||||||
|
"*",
|
||||||
|
not self.args.no_scandir,
|
||||||
|
[[True, False]],
|
||||||
|
)
|
||||||
|
dnames = set([x[0] for x in vfs_ls if stat.S_ISDIR(x[1].st_mode)])
|
||||||
|
dirs1 = [(v.st_mtime, v.st_size, k + "/") for k, v in vfs_ls if k in dnames]
|
||||||
|
fils1 = [(v.st_mtime, v.st_size, k) for k, v in vfs_ls if k not in dnames]
|
||||||
|
real1 = dirs1 + fils1
|
||||||
|
realt = [(datetime.fromtimestamp(mt, UTC), sz, fn) for mt, sz, fn in real1]
|
||||||
|
reals = [
|
||||||
|
(
|
||||||
|
"%04d-%02d-%02d %02d:%02d:%02d"
|
||||||
|
% (
|
||||||
|
zd.year,
|
||||||
|
zd.month,
|
||||||
|
zd.day,
|
||||||
|
zd.hour,
|
||||||
|
zd.minute,
|
||||||
|
zd.second,
|
||||||
|
),
|
||||||
|
sz,
|
||||||
|
fn,
|
||||||
|
)
|
||||||
|
for zd, sz, fn in realt
|
||||||
|
]
|
||||||
|
virs = [("????-??-?? ??:??:??", 0, k + "/") for k in vfs_virt.keys()]
|
||||||
|
ls = virs + reals
|
||||||
|
|
||||||
|
if "*" not in vn.axs.udot:
|
||||||
|
names = set(exclude_dotfiles([x[2] for x in ls]))
|
||||||
|
ls = [x for x in ls if x[2] in names]
|
||||||
|
|
||||||
|
try:
|
||||||
|
biggest = max([x[1] for x in ls])
|
||||||
|
except:
|
||||||
|
biggest = 0
|
||||||
|
|
||||||
|
perms = []
|
||||||
|
if "*" in vn.axs.uread:
|
||||||
|
perms.append("read")
|
||||||
|
if "*" in vn.axs.udot:
|
||||||
|
perms.append("hidden")
|
||||||
|
if "*" in vn.axs.uwrite:
|
||||||
|
if "*" in vn.axs.udel:
|
||||||
|
perms.append("overwrite")
|
||||||
|
else:
|
||||||
|
perms.append("write")
|
||||||
|
|
||||||
|
fmt = "{{}} {{:{},}} {{}}"
|
||||||
|
fmt = fmt.format(len("{:,}".format(biggest)))
|
||||||
|
retl = ["# permissions: %s" % (", ".join(perms),)]
|
||||||
|
retl += [fmt.format(*x) for x in ls]
|
||||||
|
ret = "\n".join(retl).encode("utf-8", "replace")
|
||||||
|
return BytesIO(ret + b"\n")
|
||||||
|
|
||||||
|
def _open(self, vpath: str, mode: str, *a: Any, **ka: Any) -> Any:
|
||||||
|
rd = wr = False
|
||||||
|
if mode == "rb":
|
||||||
|
rd = True
|
||||||
|
elif mode == "wb":
|
||||||
|
wr = True
|
||||||
|
else:
|
||||||
|
raise Exception("bad mode %s" % (mode,))
|
||||||
|
|
||||||
|
vfs, ap = self._v2a("open", vpath, [rd, wr])
|
||||||
|
if wr:
|
||||||
|
if "*" not in vfs.axs.uwrite:
|
||||||
|
yeet("blocked write; folder not world-writable: /%s" % (vpath,))
|
||||||
|
|
||||||
|
if bos.path.exists(ap) and "*" not in vfs.axs.udel:
|
||||||
|
yeet("blocked write; folder not world-deletable: /%s" % (vpath,))
|
||||||
|
|
||||||
|
xbu = vfs.flags.get("xbu")
|
||||||
|
if xbu and not runhook(
|
||||||
|
self.nlog,
|
||||||
|
None,
|
||||||
|
self.hub.up2k,
|
||||||
|
"xbu.tftpd",
|
||||||
|
xbu,
|
||||||
|
ap,
|
||||||
|
vpath,
|
||||||
|
"",
|
||||||
|
"",
|
||||||
|
"",
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
"8.3.8.7",
|
||||||
|
time.time(),
|
||||||
|
"",
|
||||||
|
):
|
||||||
|
yeet("blocked by xbu server config: " + vpath)
|
||||||
|
|
||||||
|
if not self.args.tftp_nols and bos.path.isdir(ap):
|
||||||
|
return self._ls(vpath, "", 0, True)
|
||||||
|
|
||||||
|
if not a:
|
||||||
|
a = (self.args.iobuf,)
|
||||||
|
|
||||||
|
return open(ap, mode, *a, **ka)
|
||||||
|
|
||||||
|
def _mkdir(self, vpath: str, *a) -> None:
|
||||||
|
vfs, ap = self._v2a("mkdir", vpath, [])
|
||||||
|
if "*" not in vfs.axs.uwrite:
|
||||||
|
yeet("blocked mkdir; folder not world-writable: /%s" % (vpath,))
|
||||||
|
|
||||||
|
return bos.mkdir(ap)
|
||||||
|
|
||||||
|
def _unlink(self, vpath: str) -> None:
|
||||||
|
# return bos.unlink(self._v2a("stat", vpath, *a)[1])
|
||||||
|
vfs, ap = self._v2a("delete", vpath, [True, False, False, True])
|
||||||
|
|
||||||
|
try:
|
||||||
|
inf = bos.stat(ap)
|
||||||
|
except:
|
||||||
|
return
|
||||||
|
|
||||||
|
if not stat.S_ISREG(inf.st_mode) or inf.st_size:
|
||||||
|
yeet("attempted delete of non-empty file")
|
||||||
|
|
||||||
|
vpath = vpath.replace("\\", "/").lstrip("/")
|
||||||
|
self.hub.up2k.handle_rm("*", "8.3.8.7", [vpath], [], False, False)
|
||||||
|
|
||||||
|
def _access(self, *a: Any) -> bool:
|
||||||
|
return True
|
||||||
|
|
||||||
|
def _p_abspath(self, vpath: str) -> str:
|
||||||
|
return "/" + undot(vpath)
|
||||||
|
|
||||||
|
def _p_normpath(self, *a: Any) -> str:
|
||||||
|
return ""
|
||||||
|
|
||||||
|
def _p_exists(self, vpath: str) -> bool:
|
||||||
|
try:
|
||||||
|
ap = self._v2a("p.exists", vpath, [False, False])[1]
|
||||||
|
bos.stat(ap)
|
||||||
|
return True
|
||||||
|
except:
|
||||||
|
return vpath == "/"
|
||||||
|
|
||||||
|
def _p_isdir(self, vpath: str) -> bool:
|
||||||
|
try:
|
||||||
|
st = bos.stat(self._v2a("p.isdir", vpath, [False, False])[1])
|
||||||
|
ret = stat.S_ISDIR(st.st_mode)
|
||||||
|
return ret
|
||||||
|
except:
|
||||||
|
return vpath == "/"
|
||||||
|
|
||||||
|
def _hook(self, *a: Any, **ka: Any) -> None:
|
||||||
|
src = inspect.currentframe().f_back.f_code.co_name
|
||||||
|
error("\033[31m%s:hook(%s)\033[0m", src, a)
|
||||||
|
raise Exception("nope")
|
||||||
|
|
||||||
|
def _disarm(self, fos: SimpleNamespace) -> None:
|
||||||
|
fos.chmod = self._hook
|
||||||
|
fos.chown = self._hook
|
||||||
|
fos.close = self._hook
|
||||||
|
fos.ftruncate = self._hook
|
||||||
|
fos.lchown = self._hook
|
||||||
|
fos.link = self._hook
|
||||||
|
fos.listdir = self._hook
|
||||||
|
fos.lstat = self._hook
|
||||||
|
fos.open = self._hook
|
||||||
|
fos.remove = self._hook
|
||||||
|
fos.rename = self._hook
|
||||||
|
fos.replace = self._hook
|
||||||
|
fos.scandir = self._hook
|
||||||
|
fos.stat = self._hook
|
||||||
|
fos.symlink = self._hook
|
||||||
|
fos.truncate = self._hook
|
||||||
|
fos.utime = self._hook
|
||||||
|
fos.walk = self._hook
|
||||||
|
|
||||||
|
fos.path.expanduser = self._hook
|
||||||
|
fos.path.expandvars = self._hook
|
||||||
|
fos.path.getatime = self._hook
|
||||||
|
fos.path.getctime = self._hook
|
||||||
|
fos.path.getmtime = self._hook
|
||||||
|
fos.path.getsize = self._hook
|
||||||
|
fos.path.isabs = self._hook
|
||||||
|
fos.path.isfile = self._hook
|
||||||
|
fos.path.islink = self._hook
|
||||||
|
fos.path.realpath = self._hook
|
||||||
|
|
||||||
|
|
||||||
|
def yeet(msg: str) -> None:
|
||||||
|
warning(msg)
|
||||||
|
raise TftpException(msg)
|
||||||
@@ -57,9 +57,10 @@ class ThumbCli(object):
|
|||||||
if is_vid and "dvthumb" in dbv.flags:
|
if is_vid and "dvthumb" in dbv.flags:
|
||||||
return None
|
return None
|
||||||
|
|
||||||
want_opus = fmt in ("opus", "caf")
|
want_opus = fmt in ("opus", "caf", "mp3")
|
||||||
is_au = ext in self.fmt_ffa
|
is_au = ext in self.fmt_ffa
|
||||||
if is_au:
|
is_vau = want_opus and ext in self.fmt_ffv
|
||||||
|
if is_au or is_vau:
|
||||||
if want_opus:
|
if want_opus:
|
||||||
if self.args.no_acode:
|
if self.args.no_acode:
|
||||||
return None
|
return None
|
||||||
@@ -78,16 +79,39 @@ class ThumbCli(object):
|
|||||||
if rem.startswith(".hist/th/") and rem.split(".")[-1] in ["webp", "jpg", "png"]:
|
if rem.startswith(".hist/th/") and rem.split(".")[-1] in ["webp", "jpg", "png"]:
|
||||||
return os.path.join(ptop, rem)
|
return os.path.join(ptop, rem)
|
||||||
|
|
||||||
if fmt == "j" and self.args.th_no_jpg:
|
if fmt[:1] in "jw":
|
||||||
fmt = "w"
|
sfmt = fmt[:1]
|
||||||
|
|
||||||
if fmt == "w":
|
if sfmt == "j" and self.args.th_no_jpg:
|
||||||
if (
|
sfmt = "w"
|
||||||
self.args.th_no_webp
|
|
||||||
or (is_img and not self.can_webp)
|
if sfmt == "w":
|
||||||
or (self.args.th_ff_jpg and (not is_img or preferred == "ff"))
|
if (
|
||||||
):
|
self.args.th_no_webp
|
||||||
fmt = "j"
|
or (is_img and not self.can_webp)
|
||||||
|
or (self.args.th_ff_jpg and (not is_img or preferred == "ff"))
|
||||||
|
):
|
||||||
|
sfmt = "j"
|
||||||
|
|
||||||
|
vf_crop = dbv.flags["crop"]
|
||||||
|
vf_th3x = dbv.flags["th3x"]
|
||||||
|
|
||||||
|
if "f" in vf_crop:
|
||||||
|
sfmt += "f" if "n" in vf_crop else ""
|
||||||
|
else:
|
||||||
|
sfmt += "f" if "f" in fmt else ""
|
||||||
|
|
||||||
|
if "f" in vf_th3x:
|
||||||
|
sfmt += "3" if "y" in vf_th3x else ""
|
||||||
|
else:
|
||||||
|
sfmt += "3" if "3" in fmt else ""
|
||||||
|
|
||||||
|
fmt = sfmt
|
||||||
|
|
||||||
|
elif fmt[:1] == "p" and not is_au and not is_vid:
|
||||||
|
t = "cannot thumbnail [%s]: png only allowed for waveforms"
|
||||||
|
self.log(t % (rem), 6)
|
||||||
|
return None
|
||||||
|
|
||||||
histpath = self.asrv.vfs.histtab.get(ptop)
|
histpath = self.asrv.vfs.histtab.get(ptop)
|
||||||
if not histpath:
|
if not histpath:
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
# coding: utf-8
|
# coding: utf-8
|
||||||
from __future__ import print_function, unicode_literals
|
from __future__ import print_function, unicode_literals
|
||||||
|
|
||||||
import base64
|
|
||||||
import hashlib
|
import hashlib
|
||||||
import logging
|
import logging
|
||||||
import os
|
import os
|
||||||
@@ -12,13 +11,13 @@ import time
|
|||||||
|
|
||||||
from queue import Queue
|
from queue import Queue
|
||||||
|
|
||||||
from .__init__ import ANYWIN, TYPE_CHECKING
|
from .__init__ import ANYWIN, PY2, TYPE_CHECKING
|
||||||
from .authsrv import VFS
|
from .authsrv import VFS
|
||||||
from .bos import bos
|
from .bos import bos
|
||||||
from .mtag import HAVE_FFMPEG, HAVE_FFPROBE, ffprobe
|
from .mtag import HAVE_FFMPEG, HAVE_FFPROBE, au_unpk, ffprobe
|
||||||
|
from .util import BytesIO # type: ignore
|
||||||
from .util import (
|
from .util import (
|
||||||
FFMPEG_URL,
|
FFMPEG_URL,
|
||||||
BytesIO,
|
|
||||||
Cooldown,
|
Cooldown,
|
||||||
Daemon,
|
Daemon,
|
||||||
Pebkac,
|
Pebkac,
|
||||||
@@ -27,7 +26,10 @@ from .util import (
|
|||||||
min_ex,
|
min_ex,
|
||||||
runcmd,
|
runcmd,
|
||||||
statdir,
|
statdir,
|
||||||
|
ub64enc,
|
||||||
vsplit,
|
vsplit,
|
||||||
|
wrename,
|
||||||
|
wunlink,
|
||||||
)
|
)
|
||||||
|
|
||||||
if True: # pylint: disable=using-constant-test
|
if True: # pylint: disable=using-constant-test
|
||||||
@@ -36,6 +38,9 @@ if True: # pylint: disable=using-constant-test
|
|||||||
if TYPE_CHECKING:
|
if TYPE_CHECKING:
|
||||||
from .svchub import SvcHub
|
from .svchub import SvcHub
|
||||||
|
|
||||||
|
if PY2:
|
||||||
|
range = xrange # type: ignore
|
||||||
|
|
||||||
HAVE_PIL = False
|
HAVE_PIL = False
|
||||||
HAVE_PILF = False
|
HAVE_PILF = False
|
||||||
HAVE_HEIF = False
|
HAVE_HEIF = False
|
||||||
@@ -43,22 +48,34 @@ HAVE_AVIF = False
|
|||||||
HAVE_WEBP = False
|
HAVE_WEBP = False
|
||||||
|
|
||||||
try:
|
try:
|
||||||
|
if os.environ.get("PRTY_NO_PIL"):
|
||||||
|
raise Exception()
|
||||||
|
|
||||||
from PIL import ExifTags, Image, ImageFont, ImageOps
|
from PIL import ExifTags, Image, ImageFont, ImageOps
|
||||||
|
|
||||||
HAVE_PIL = True
|
HAVE_PIL = True
|
||||||
try:
|
try:
|
||||||
|
if os.environ.get("PRTY_NO_PILF"):
|
||||||
|
raise Exception()
|
||||||
|
|
||||||
ImageFont.load_default(size=16)
|
ImageFont.load_default(size=16)
|
||||||
HAVE_PILF = True
|
HAVE_PILF = True
|
||||||
except:
|
except:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
try:
|
try:
|
||||||
|
if os.environ.get("PRTY_NO_PIL_WEBP"):
|
||||||
|
raise Exception()
|
||||||
|
|
||||||
Image.new("RGB", (2, 2)).save(BytesIO(), format="webp")
|
Image.new("RGB", (2, 2)).save(BytesIO(), format="webp")
|
||||||
HAVE_WEBP = True
|
HAVE_WEBP = True
|
||||||
except:
|
except:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
try:
|
try:
|
||||||
|
if os.environ.get("PRTY_NO_PIL_HEIF"):
|
||||||
|
raise Exception()
|
||||||
|
|
||||||
from pyheif_pillow_opener import register_heif_opener
|
from pyheif_pillow_opener import register_heif_opener
|
||||||
|
|
||||||
register_heif_opener()
|
register_heif_opener()
|
||||||
@@ -67,6 +84,9 @@ try:
|
|||||||
pass
|
pass
|
||||||
|
|
||||||
try:
|
try:
|
||||||
|
if os.environ.get("PRTY_NO_PIL_AVIF"):
|
||||||
|
raise Exception()
|
||||||
|
|
||||||
import pillow_avif # noqa: F401 # pylint: disable=unused-import
|
import pillow_avif # noqa: F401 # pylint: disable=unused-import
|
||||||
|
|
||||||
HAVE_AVIF = True
|
HAVE_AVIF = True
|
||||||
@@ -78,6 +98,9 @@ except:
|
|||||||
pass
|
pass
|
||||||
|
|
||||||
try:
|
try:
|
||||||
|
if os.environ.get("PRTY_NO_VIPS"):
|
||||||
|
raise Exception()
|
||||||
|
|
||||||
HAVE_VIPS = True
|
HAVE_VIPS = True
|
||||||
import pyvips
|
import pyvips
|
||||||
|
|
||||||
@@ -86,6 +109,9 @@ except:
|
|||||||
HAVE_VIPS = False
|
HAVE_VIPS = False
|
||||||
|
|
||||||
|
|
||||||
|
th_dir_cache = {}
|
||||||
|
|
||||||
|
|
||||||
def thumb_path(histpath: str, rem: str, mtime: float, fmt: str, ffa: set[str]) -> str:
|
def thumb_path(histpath: str, rem: str, mtime: float, fmt: str, ffa: set[str]) -> str:
|
||||||
# base16 = 16 = 256
|
# base16 = 16 = 256
|
||||||
# b64-lc = 38 = 1444
|
# b64-lc = 38 = 1444
|
||||||
@@ -96,26 +122,32 @@ def thumb_path(histpath: str, rem: str, mtime: float, fmt: str, ffa: set[str]) -
|
|||||||
|
|
||||||
# spectrograms are never cropped; strip fullsize flag
|
# spectrograms are never cropped; strip fullsize flag
|
||||||
ext = rem.split(".")[-1].lower()
|
ext = rem.split(".")[-1].lower()
|
||||||
if ext in ffa and fmt in ("wf", "jf"):
|
if ext in ffa and fmt[:2] in ("wf", "jf"):
|
||||||
fmt = fmt[:1]
|
fmt = fmt.replace("f", "")
|
||||||
|
|
||||||
rd += "\n" + fmt
|
dcache = th_dir_cache
|
||||||
h = hashlib.sha512(afsenc(rd)).digest()
|
rd_key = rd + "\n" + fmt
|
||||||
b64 = base64.urlsafe_b64encode(h).decode("ascii")[:24]
|
rd = dcache.get(rd_key)
|
||||||
rd = "{}/{}/".format(b64[:2], b64[2:4]).lower() + b64
|
if not rd:
|
||||||
|
h = hashlib.sha512(afsenc(rd_key)).digest()
|
||||||
|
b64 = ub64enc(h).decode("ascii")[:24]
|
||||||
|
rd = ("%s/%s/" % (b64[:2], b64[2:4])).lower() + b64
|
||||||
|
if len(dcache) > 9001:
|
||||||
|
dcache.clear()
|
||||||
|
dcache[rd_key] = rd
|
||||||
|
|
||||||
# could keep original filenames but this is safer re pathlen
|
# could keep original filenames but this is safer re pathlen
|
||||||
h = hashlib.sha512(afsenc(fn)).digest()
|
h = hashlib.sha512(afsenc(fn)).digest()
|
||||||
fn = base64.urlsafe_b64encode(h).decode("ascii")[:24]
|
fn = ub64enc(h).decode("ascii")[:24]
|
||||||
|
|
||||||
if fmt in ("opus", "caf"):
|
if fmt in ("opus", "caf", "mp3"):
|
||||||
cat = "ac"
|
cat = "ac"
|
||||||
else:
|
else:
|
||||||
fc = fmt[:1]
|
fc = fmt[:1]
|
||||||
fmt = "webp" if fc == "w" else "png" if fc == "p" else "jpg"
|
fmt = "webp" if fc == "w" else "png" if fc == "p" else "jpg"
|
||||||
cat = "th"
|
cat = "th"
|
||||||
|
|
||||||
return "{}/{}/{}/{}.{:x}.{}".format(histpath, cat, rd, fn, int(mtime), fmt)
|
return "%s/%s/%s/%s.%x.%s" % (histpath, cat, rd, fn, int(mtime), fmt)
|
||||||
|
|
||||||
|
|
||||||
class ThumbSrv(object):
|
class ThumbSrv(object):
|
||||||
@@ -129,6 +161,8 @@ class ThumbSrv(object):
|
|||||||
|
|
||||||
self.mutex = threading.Lock()
|
self.mutex = threading.Lock()
|
||||||
self.busy: dict[str, list[threading.Condition]] = {}
|
self.busy: dict[str, list[threading.Condition]] = {}
|
||||||
|
self.ram: dict[str, float] = {}
|
||||||
|
self.memcond = threading.Condition(self.mutex)
|
||||||
self.stopping = False
|
self.stopping = False
|
||||||
self.nthr = max(1, self.args.th_mt)
|
self.nthr = max(1, self.args.th_mt)
|
||||||
|
|
||||||
@@ -197,9 +231,10 @@ class ThumbSrv(object):
|
|||||||
with self.mutex:
|
with self.mutex:
|
||||||
return not self.nthr
|
return not self.nthr
|
||||||
|
|
||||||
def getres(self, vn: VFS) -> tuple[int, int]:
|
def getres(self, vn: VFS, fmt: str) -> tuple[int, int]:
|
||||||
|
mul = 3 if "3" in fmt else 1
|
||||||
w, h = vn.flags["thsize"].split("x")
|
w, h = vn.flags["thsize"].split("x")
|
||||||
return int(w), int(h)
|
return int(w) * mul, int(h) * mul
|
||||||
|
|
||||||
def get(self, ptop: str, rem: str, mtime: float, fmt: str) -> Optional[str]:
|
def get(self, ptop: str, rem: str, mtime: float, fmt: str) -> Optional[str]:
|
||||||
histpath = self.asrv.vfs.histtab.get(ptop)
|
histpath = self.asrv.vfs.histtab.get(ptop)
|
||||||
@@ -214,7 +249,7 @@ class ThumbSrv(object):
|
|||||||
with self.mutex:
|
with self.mutex:
|
||||||
try:
|
try:
|
||||||
self.busy[tpath].append(cond)
|
self.busy[tpath].append(cond)
|
||||||
self.log("wait {}".format(tpath))
|
self.log("joined waiting room for %s" % (tpath,))
|
||||||
except:
|
except:
|
||||||
thdir = os.path.dirname(tpath)
|
thdir = os.path.dirname(tpath)
|
||||||
bos.makedirs(os.path.join(thdir, "w"))
|
bos.makedirs(os.path.join(thdir, "w"))
|
||||||
@@ -265,6 +300,23 @@ class ThumbSrv(object):
|
|||||||
"ffa": self.fmt_ffa,
|
"ffa": self.fmt_ffa,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
def wait4ram(self, need: float, ttpath: str) -> None:
|
||||||
|
ram = self.args.th_ram_max
|
||||||
|
if need > ram * 0.99:
|
||||||
|
t = "file too big; need %.2f GiB RAM, but --th-ram-max is only %.1f"
|
||||||
|
raise Exception(t % (need, ram))
|
||||||
|
|
||||||
|
while True:
|
||||||
|
with self.mutex:
|
||||||
|
used = sum([v for k, v in self.ram.items() if k != ttpath]) + need
|
||||||
|
if used < ram:
|
||||||
|
# self.log("XXX self.ram: %s" % (self.ram,), 5)
|
||||||
|
self.ram[ttpath] = need
|
||||||
|
return
|
||||||
|
with self.memcond:
|
||||||
|
# self.log("at RAM limit; used %.2f GiB, need %.2f more" % (used-need, need), 1)
|
||||||
|
self.memcond.wait(3)
|
||||||
|
|
||||||
def worker(self) -> None:
|
def worker(self) -> None:
|
||||||
while not self.stopping:
|
while not self.stopping:
|
||||||
task = self.q.get()
|
task = self.q.get()
|
||||||
@@ -275,36 +327,52 @@ class ThumbSrv(object):
|
|||||||
ext = abspath.split(".")[-1].lower()
|
ext = abspath.split(".")[-1].lower()
|
||||||
png_ok = False
|
png_ok = False
|
||||||
funs = []
|
funs = []
|
||||||
|
|
||||||
|
if ext in self.args.au_unpk:
|
||||||
|
ap_unpk = au_unpk(self.log, self.args.au_unpk, abspath, vn)
|
||||||
|
else:
|
||||||
|
ap_unpk = abspath
|
||||||
|
|
||||||
if not bos.path.exists(tpath):
|
if not bos.path.exists(tpath):
|
||||||
|
want_mp3 = tpath.endswith(".mp3")
|
||||||
|
want_opus = tpath.endswith(".opus") or tpath.endswith(".caf")
|
||||||
|
want_png = tpath.endswith(".png")
|
||||||
|
want_au = want_mp3 or want_opus
|
||||||
for lib in self.args.th_dec:
|
for lib in self.args.th_dec:
|
||||||
|
can_au = lib == "ff" and (
|
||||||
|
ext in self.fmt_ffa or ext in self.fmt_ffv
|
||||||
|
)
|
||||||
|
|
||||||
if lib == "pil" and ext in self.fmt_pil:
|
if lib == "pil" and ext in self.fmt_pil:
|
||||||
funs.append(self.conv_pil)
|
funs.append(self.conv_pil)
|
||||||
elif lib == "vips" and ext in self.fmt_vips:
|
elif lib == "vips" and ext in self.fmt_vips:
|
||||||
funs.append(self.conv_vips)
|
funs.append(self.conv_vips)
|
||||||
elif lib == "ff" and ext in self.fmt_ffi or ext in self.fmt_ffv:
|
elif can_au and (want_png or want_au):
|
||||||
funs.append(self.conv_ffmpeg)
|
if want_opus:
|
||||||
elif lib == "ff" and ext in self.fmt_ffa:
|
|
||||||
if tpath.endswith(".opus") or tpath.endswith(".caf"):
|
|
||||||
funs.append(self.conv_opus)
|
funs.append(self.conv_opus)
|
||||||
elif tpath.endswith(".png"):
|
elif want_mp3:
|
||||||
|
funs.append(self.conv_mp3)
|
||||||
|
elif want_png:
|
||||||
funs.append(self.conv_waves)
|
funs.append(self.conv_waves)
|
||||||
png_ok = True
|
png_ok = True
|
||||||
else:
|
elif lib == "ff" and (ext in self.fmt_ffi or ext in self.fmt_ffv):
|
||||||
funs.append(self.conv_spec)
|
funs.append(self.conv_ffmpeg)
|
||||||
|
elif lib == "ff" and ext in self.fmt_ffa and not want_au:
|
||||||
if not png_ok and tpath.endswith(".png"):
|
funs.append(self.conv_spec)
|
||||||
raise Pebkac(400, "png only allowed for waveforms")
|
|
||||||
|
|
||||||
tdir, tfn = os.path.split(tpath)
|
tdir, tfn = os.path.split(tpath)
|
||||||
ttpath = os.path.join(tdir, "w", tfn)
|
ttpath = os.path.join(tdir, "w", tfn)
|
||||||
try:
|
try:
|
||||||
bos.unlink(ttpath)
|
wunlink(self.log, ttpath, vn.flags)
|
||||||
except:
|
except:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
for fun in funs:
|
for fun in funs:
|
||||||
try:
|
try:
|
||||||
fun(abspath, ttpath, fmt, vn)
|
if not png_ok and tpath.endswith(".png"):
|
||||||
|
raise Exception("png only allowed for waveforms")
|
||||||
|
|
||||||
|
fun(ap_unpk, ttpath, fmt, vn)
|
||||||
break
|
break
|
||||||
except Exception as ex:
|
except Exception as ex:
|
||||||
msg = "{} could not create thumbnail of {}\n{}"
|
msg = "{} could not create thumbnail of {}\n{}"
|
||||||
@@ -318,29 +386,36 @@ class ThumbSrv(object):
|
|||||||
else:
|
else:
|
||||||
# ffmpeg may spawn empty files on windows
|
# ffmpeg may spawn empty files on windows
|
||||||
try:
|
try:
|
||||||
os.unlink(ttpath)
|
wunlink(self.log, ttpath, vn.flags)
|
||||||
except:
|
except:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
if abspath != ap_unpk:
|
||||||
|
wunlink(self.log, ap_unpk, vn.flags)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
bos.rename(ttpath, tpath)
|
wrename(self.log, ttpath, tpath, vn.flags)
|
||||||
except:
|
except:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
with self.mutex:
|
with self.mutex:
|
||||||
subs = self.busy[tpath]
|
subs = self.busy[tpath]
|
||||||
del self.busy[tpath]
|
del self.busy[tpath]
|
||||||
|
self.ram.pop(ttpath, None)
|
||||||
|
|
||||||
for x in subs:
|
for x in subs:
|
||||||
with x:
|
with x:
|
||||||
x.notify_all()
|
x.notify_all()
|
||||||
|
|
||||||
|
with self.memcond:
|
||||||
|
self.memcond.notify_all()
|
||||||
|
|
||||||
with self.mutex:
|
with self.mutex:
|
||||||
self.nthr -= 1
|
self.nthr -= 1
|
||||||
|
|
||||||
def fancy_pillow(self, im: "Image.Image", fmt: str, vn: VFS) -> "Image.Image":
|
def fancy_pillow(self, im: "Image.Image", fmt: str, vn: VFS) -> "Image.Image":
|
||||||
# exif_transpose is expensive (loads full image + unconditional copy)
|
# exif_transpose is expensive (loads full image + unconditional copy)
|
||||||
res = self.getres(vn)
|
res = self.getres(vn, fmt)
|
||||||
r = max(*res) * 2
|
r = max(*res) * 2
|
||||||
im.thumbnail((r, r), resample=Image.LANCZOS)
|
im.thumbnail((r, r), resample=Image.LANCZOS)
|
||||||
try:
|
try:
|
||||||
@@ -355,7 +430,7 @@ class ThumbSrv(object):
|
|||||||
if rot in rots:
|
if rot in rots:
|
||||||
im = im.transpose(rots[rot])
|
im = im.transpose(rots[rot])
|
||||||
|
|
||||||
if fmt.endswith("f"):
|
if "f" in fmt:
|
||||||
im.thumbnail(res, resample=Image.LANCZOS)
|
im.thumbnail(res, resample=Image.LANCZOS)
|
||||||
else:
|
else:
|
||||||
iw, ih = im.size
|
iw, ih = im.size
|
||||||
@@ -366,12 +441,13 @@ class ThumbSrv(object):
|
|||||||
return im
|
return im
|
||||||
|
|
||||||
def conv_pil(self, abspath: str, tpath: str, fmt: str, vn: VFS) -> None:
|
def conv_pil(self, abspath: str, tpath: str, fmt: str, vn: VFS) -> None:
|
||||||
|
self.wait4ram(0.2, tpath)
|
||||||
with Image.open(fsenc(abspath)) as im:
|
with Image.open(fsenc(abspath)) as im:
|
||||||
try:
|
try:
|
||||||
im = self.fancy_pillow(im, fmt, vn)
|
im = self.fancy_pillow(im, fmt, vn)
|
||||||
except Exception as ex:
|
except Exception as ex:
|
||||||
self.log("fancy_pillow {}".format(ex), "90")
|
self.log("fancy_pillow {}".format(ex), "90")
|
||||||
im.thumbnail(self.getres(vn))
|
im.thumbnail(self.getres(vn, fmt))
|
||||||
|
|
||||||
fmts = ["RGB", "L"]
|
fmts = ["RGB", "L"]
|
||||||
args = {"quality": 40}
|
args = {"quality": 40}
|
||||||
@@ -382,7 +458,7 @@ class ThumbSrv(object):
|
|||||||
# method 0 = pillow-default, fast
|
# method 0 = pillow-default, fast
|
||||||
# method 4 = ffmpeg-default
|
# method 4 = ffmpeg-default
|
||||||
# method 6 = max, slow
|
# method 6 = max, slow
|
||||||
fmts += ["RGBA", "LA"]
|
fmts.extend(("RGBA", "LA"))
|
||||||
args["method"] = 6
|
args["method"] = 6
|
||||||
else:
|
else:
|
||||||
# default q = 75
|
# default q = 75
|
||||||
@@ -395,11 +471,12 @@ class ThumbSrv(object):
|
|||||||
im.save(tpath, **args)
|
im.save(tpath, **args)
|
||||||
|
|
||||||
def conv_vips(self, abspath: str, tpath: str, fmt: str, vn: VFS) -> None:
|
def conv_vips(self, abspath: str, tpath: str, fmt: str, vn: VFS) -> None:
|
||||||
|
self.wait4ram(0.2, tpath)
|
||||||
crops = ["centre", "none"]
|
crops = ["centre", "none"]
|
||||||
if fmt.endswith("f"):
|
if "f" in fmt:
|
||||||
crops = ["none"]
|
crops = ["none"]
|
||||||
|
|
||||||
w, h = self.getres(vn)
|
w, h = self.getres(vn, fmt)
|
||||||
kw = {"height": h, "size": "down", "intent": "relative"}
|
kw = {"height": h, "size": "down", "intent": "relative"}
|
||||||
|
|
||||||
for c in crops:
|
for c in crops:
|
||||||
@@ -411,9 +488,11 @@ class ThumbSrv(object):
|
|||||||
if c == crops[-1]:
|
if c == crops[-1]:
|
||||||
raise
|
raise
|
||||||
|
|
||||||
|
assert img # type: ignore # !rm
|
||||||
img.write_to_file(tpath, Q=40)
|
img.write_to_file(tpath, Q=40)
|
||||||
|
|
||||||
def conv_ffmpeg(self, abspath: str, tpath: str, fmt: str, vn: VFS) -> None:
|
def conv_ffmpeg(self, abspath: str, tpath: str, fmt: str, vn: VFS) -> None:
|
||||||
|
self.wait4ram(0.2, tpath)
|
||||||
ret, _ = ffprobe(abspath, int(vn.flags["convt"] / 2))
|
ret, _ = ffprobe(abspath, int(vn.flags["convt"] / 2))
|
||||||
if not ret:
|
if not ret:
|
||||||
return
|
return
|
||||||
@@ -426,12 +505,12 @@ class ThumbSrv(object):
|
|||||||
seek = [b"-ss", "{:.0f}".format(dur / 3).encode("utf-8")]
|
seek = [b"-ss", "{:.0f}".format(dur / 3).encode("utf-8")]
|
||||||
|
|
||||||
scale = "scale={0}:{1}:force_original_aspect_ratio="
|
scale = "scale={0}:{1}:force_original_aspect_ratio="
|
||||||
if fmt.endswith("f"):
|
if "f" in fmt:
|
||||||
scale += "decrease,setsar=1:1"
|
scale += "decrease,setsar=1:1"
|
||||||
else:
|
else:
|
||||||
scale += "increase,crop={0}:{1},setsar=1:1"
|
scale += "increase,crop={0}:{1},setsar=1:1"
|
||||||
|
|
||||||
res = self.getres(vn)
|
res = self.getres(vn, fmt)
|
||||||
bscale = scale.format(*list(res)).encode("utf-8")
|
bscale = scale.format(*list(res)).encode("utf-8")
|
||||||
# fmt: off
|
# fmt: off
|
||||||
cmd = [
|
cmd = [
|
||||||
@@ -466,9 +545,9 @@ class ThumbSrv(object):
|
|||||||
cmd += [fsenc(tpath)]
|
cmd += [fsenc(tpath)]
|
||||||
self._run_ff(cmd, vn)
|
self._run_ff(cmd, vn)
|
||||||
|
|
||||||
def _run_ff(self, cmd: list[bytes], vn: VFS) -> None:
|
def _run_ff(self, cmd: list[bytes], vn: VFS, oom: int = 400) -> None:
|
||||||
# self.log((b" ".join(cmd)).decode("utf-8"))
|
# self.log((b" ".join(cmd)).decode("utf-8"))
|
||||||
ret, _, serr = runcmd(cmd, timeout=vn.flags["convt"], nice=True)
|
ret, _, serr = runcmd(cmd, timeout=vn.flags["convt"], nice=True, oom=oom)
|
||||||
if not ret:
|
if not ret:
|
||||||
return
|
return
|
||||||
|
|
||||||
@@ -516,8 +595,21 @@ class ThumbSrv(object):
|
|||||||
if "ac" not in ret:
|
if "ac" not in ret:
|
||||||
raise Exception("not audio")
|
raise Exception("not audio")
|
||||||
|
|
||||||
flt = (
|
# jt_versi.xm: 405M/839s
|
||||||
b"[0:a:0]"
|
dur = ret[".dur"][1] if ".dur" in ret else 300
|
||||||
|
need = 0.2 + dur / 3000
|
||||||
|
speedup = b""
|
||||||
|
if need > self.args.th_ram_max * 0.7:
|
||||||
|
self.log("waves too big (need %.2f GiB); trying to optimize" % (need,))
|
||||||
|
need = 0.2 + dur / 4200 # only helps about this much...
|
||||||
|
speedup = b"aresample=8000,"
|
||||||
|
if need > self.args.th_ram_max * 0.96:
|
||||||
|
raise Exception("file too big; cannot waves")
|
||||||
|
|
||||||
|
self.wait4ram(need, tpath)
|
||||||
|
|
||||||
|
flt = b"[0:a:0]" + speedup
|
||||||
|
flt += (
|
||||||
b"compand=.3|.3:1|1:-90/-60|-60/-40|-40/-30|-20/-20:6:0:-90:0.2"
|
b"compand=.3|.3:1|1:-90/-60|-60/-40|-40/-30|-20/-20:6:0:-90:0.2"
|
||||||
b",volume=2"
|
b",volume=2"
|
||||||
b",showwavespic=s=2048x64:colors=white"
|
b",showwavespic=s=2048x64:colors=white"
|
||||||
@@ -539,12 +631,44 @@ class ThumbSrv(object):
|
|||||||
cmd += [fsenc(tpath)]
|
cmd += [fsenc(tpath)]
|
||||||
self._run_ff(cmd, vn)
|
self._run_ff(cmd, vn)
|
||||||
|
|
||||||
|
if "pngquant" in vn.flags:
|
||||||
|
wtpath = tpath + ".png"
|
||||||
|
cmd = [
|
||||||
|
b"pngquant",
|
||||||
|
b"--strip",
|
||||||
|
b"--nofs",
|
||||||
|
b"--output",
|
||||||
|
fsenc(wtpath),
|
||||||
|
fsenc(tpath),
|
||||||
|
]
|
||||||
|
ret = runcmd(cmd, timeout=vn.flags["convt"], nice=True, oom=400)[0]
|
||||||
|
if ret:
|
||||||
|
try:
|
||||||
|
wunlink(self.log, wtpath, vn.flags)
|
||||||
|
except:
|
||||||
|
pass
|
||||||
|
else:
|
||||||
|
wrename(self.log, wtpath, tpath, vn.flags)
|
||||||
|
|
||||||
def conv_spec(self, abspath: str, tpath: str, fmt: str, vn: VFS) -> None:
|
def conv_spec(self, abspath: str, tpath: str, fmt: str, vn: VFS) -> None:
|
||||||
ret, _ = ffprobe(abspath, int(vn.flags["convt"] / 2))
|
ret, _ = ffprobe(abspath, int(vn.flags["convt"] / 2))
|
||||||
if "ac" not in ret:
|
if "ac" not in ret:
|
||||||
raise Exception("not audio")
|
raise Exception("not audio")
|
||||||
|
|
||||||
fc = "[0:a:0]aresample=48000{},showspectrumpic=s=640x512,crop=780:544:70:50[o]"
|
# https://trac.ffmpeg.org/ticket/10797
|
||||||
|
# expect 1 GiB every 600 seconds when duration is tricky;
|
||||||
|
# simple filetypes are generally safer so let's special-case those
|
||||||
|
safe = ("flac", "wav", "aif", "aiff", "opus")
|
||||||
|
coeff = 1800 if abspath.split(".")[-1].lower() in safe else 600
|
||||||
|
dur = ret[".dur"][1] if ".dur" in ret else 300
|
||||||
|
need = 0.2 + dur / coeff
|
||||||
|
self.wait4ram(need, tpath)
|
||||||
|
|
||||||
|
fc = "[0:a:0]aresample=48000{},showspectrumpic=s="
|
||||||
|
if "3" in fmt:
|
||||||
|
fc += "1280x1024,crop=1420:1056:70:48[o]"
|
||||||
|
else:
|
||||||
|
fc += "640x512,crop=780:544:70:48[o]"
|
||||||
|
|
||||||
if self.args.th_ff_swr:
|
if self.args.th_ff_swr:
|
||||||
fco = ":filter_size=128:cutoff=0.877"
|
fco = ":filter_size=128:cutoff=0.877"
|
||||||
@@ -582,30 +706,71 @@ class ThumbSrv(object):
|
|||||||
cmd += [fsenc(tpath)]
|
cmd += [fsenc(tpath)]
|
||||||
self._run_ff(cmd, vn)
|
self._run_ff(cmd, vn)
|
||||||
|
|
||||||
def conv_opus(self, abspath: str, tpath: str, fmt: str, vn: VFS) -> None:
|
def conv_mp3(self, abspath: str, tpath: str, fmt: str, vn: VFS) -> None:
|
||||||
if self.args.no_acode:
|
quality = self.args.q_mp3.lower()
|
||||||
|
if self.args.no_acode or not quality:
|
||||||
raise Exception("disabled in server config")
|
raise Exception("disabled in server config")
|
||||||
|
|
||||||
ret, _ = ffprobe(abspath, int(vn.flags["convt"] / 2))
|
self.wait4ram(0.2, tpath)
|
||||||
if "ac" not in ret:
|
tags, rawtags = ffprobe(abspath, int(vn.flags["convt"] / 2))
|
||||||
|
if "ac" not in tags:
|
||||||
|
raise Exception("not audio")
|
||||||
|
|
||||||
|
if quality.endswith("k"):
|
||||||
|
qk = b"-b:a"
|
||||||
|
qv = quality.encode("ascii")
|
||||||
|
else:
|
||||||
|
qk = b"-q:a"
|
||||||
|
qv = quality[1:].encode("ascii")
|
||||||
|
|
||||||
|
# extremely conservative choices for output format
|
||||||
|
# (always 2ch 44k1) because if a device is old enough
|
||||||
|
# to not support opus then it's probably also super picky
|
||||||
|
|
||||||
|
# fmt: off
|
||||||
|
cmd = [
|
||||||
|
b"ffmpeg",
|
||||||
|
b"-nostdin",
|
||||||
|
b"-v", b"error",
|
||||||
|
b"-hide_banner",
|
||||||
|
b"-i", fsenc(abspath),
|
||||||
|
] + self.big_tags(rawtags) + [
|
||||||
|
b"-map", b"0:a:0",
|
||||||
|
b"-ar", b"44100",
|
||||||
|
b"-ac", b"2",
|
||||||
|
b"-c:a", b"libmp3lame",
|
||||||
|
qk, qv,
|
||||||
|
fsenc(tpath)
|
||||||
|
]
|
||||||
|
# fmt: on
|
||||||
|
self._run_ff(cmd, vn, oom=300)
|
||||||
|
|
||||||
|
def conv_opus(self, abspath: str, tpath: str, fmt: str, vn: VFS) -> None:
|
||||||
|
if self.args.no_acode or not self.args.q_opus:
|
||||||
|
raise Exception("disabled in server config")
|
||||||
|
|
||||||
|
self.wait4ram(0.2, tpath)
|
||||||
|
tags, rawtags = ffprobe(abspath, int(vn.flags["convt"] / 2))
|
||||||
|
if "ac" not in tags:
|
||||||
raise Exception("not audio")
|
raise Exception("not audio")
|
||||||
|
|
||||||
try:
|
try:
|
||||||
dur = ret[".dur"][1]
|
dur = tags[".dur"][1]
|
||||||
except:
|
except:
|
||||||
dur = 0
|
dur = 0
|
||||||
|
|
||||||
src_opus = abspath.lower().endswith(".opus") or ret["ac"][1] == "opus"
|
src_opus = abspath.lower().endswith(".opus") or tags["ac"][1] == "opus"
|
||||||
want_caf = tpath.endswith(".caf")
|
want_caf = tpath.endswith(".caf")
|
||||||
tmp_opus = tpath
|
tmp_opus = tpath
|
||||||
if want_caf:
|
if want_caf:
|
||||||
tmp_opus = tpath + ".opus"
|
tmp_opus = tpath + ".opus"
|
||||||
try:
|
try:
|
||||||
bos.unlink(tmp_opus)
|
wunlink(self.log, tmp_opus, vn.flags)
|
||||||
except:
|
except:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
caf_src = abspath if src_opus else tmp_opus
|
caf_src = abspath if src_opus else tmp_opus
|
||||||
|
bq = ("%dk" % (self.args.q_opus,)).encode("ascii")
|
||||||
|
|
||||||
if not want_caf or not src_opus:
|
if not want_caf or not src_opus:
|
||||||
# fmt: off
|
# fmt: off
|
||||||
@@ -615,14 +780,14 @@ class ThumbSrv(object):
|
|||||||
b"-v", b"error",
|
b"-v", b"error",
|
||||||
b"-hide_banner",
|
b"-hide_banner",
|
||||||
b"-i", fsenc(abspath),
|
b"-i", fsenc(abspath),
|
||||||
b"-map_metadata", b"-1",
|
] + self.big_tags(rawtags) + [
|
||||||
b"-map", b"0:a:0",
|
b"-map", b"0:a:0",
|
||||||
b"-c:a", b"libopus",
|
b"-c:a", b"libopus",
|
||||||
b"-b:a", b"128k",
|
b"-b:a", bq,
|
||||||
fsenc(tmp_opus)
|
fsenc(tmp_opus)
|
||||||
]
|
]
|
||||||
# fmt: on
|
# fmt: on
|
||||||
self._run_ff(cmd, vn)
|
self._run_ff(cmd, vn, oom=300)
|
||||||
|
|
||||||
# iOS fails to play some "insufficiently complex" files
|
# iOS fails to play some "insufficiently complex" files
|
||||||
# (average file shorter than 8 seconds), so of course we
|
# (average file shorter than 8 seconds), so of course we
|
||||||
@@ -641,12 +806,12 @@ class ThumbSrv(object):
|
|||||||
b"-map_metadata", b"-1",
|
b"-map_metadata", b"-1",
|
||||||
b"-ac", b"2",
|
b"-ac", b"2",
|
||||||
b"-c:a", b"libopus",
|
b"-c:a", b"libopus",
|
||||||
b"-b:a", b"128k",
|
b"-b:a", bq,
|
||||||
b"-f", b"caf",
|
b"-f", b"caf",
|
||||||
fsenc(tpath)
|
fsenc(tpath)
|
||||||
]
|
]
|
||||||
# fmt: on
|
# fmt: on
|
||||||
self._run_ff(cmd, vn)
|
self._run_ff(cmd, vn, oom=300)
|
||||||
|
|
||||||
elif want_caf:
|
elif want_caf:
|
||||||
# simple remux should be safe
|
# simple remux should be safe
|
||||||
@@ -664,14 +829,24 @@ class ThumbSrv(object):
|
|||||||
fsenc(tpath)
|
fsenc(tpath)
|
||||||
]
|
]
|
||||||
# fmt: on
|
# fmt: on
|
||||||
self._run_ff(cmd, vn)
|
self._run_ff(cmd, vn, oom=300)
|
||||||
|
|
||||||
if tmp_opus != tpath:
|
if tmp_opus != tpath:
|
||||||
try:
|
try:
|
||||||
bos.unlink(tmp_opus)
|
wunlink(self.log, tmp_opus, vn.flags)
|
||||||
except:
|
except:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
def big_tags(self, raw_tags: dict[str, list[str]]) -> list[bytes]:
|
||||||
|
ret = []
|
||||||
|
for k, vs in raw_tags.items():
|
||||||
|
for v in vs:
|
||||||
|
if len(str(v)) >= 1024:
|
||||||
|
bv = k.encode("utf-8", "replace")
|
||||||
|
ret += [b"-metadata", bv + b"="]
|
||||||
|
break
|
||||||
|
return ret
|
||||||
|
|
||||||
def poke(self, tdir: str) -> None:
|
def poke(self, tdir: str) -> None:
|
||||||
if not self.poke_cd.poke(tdir):
|
if not self.poke_cd.poke(tdir):
|
||||||
return
|
return
|
||||||
@@ -695,7 +870,10 @@ class ThumbSrv(object):
|
|||||||
else:
|
else:
|
||||||
self.log("\033[Jcln {} ({})/\033[A".format(histpath, vol))
|
self.log("\033[Jcln {} ({})/\033[A".format(histpath, vol))
|
||||||
|
|
||||||
ndirs += self.clean(histpath)
|
try:
|
||||||
|
ndirs += self.clean(histpath)
|
||||||
|
except Exception as ex:
|
||||||
|
self.log("\033[Jcln err in %s: %r" % (histpath, ex), 3)
|
||||||
|
|
||||||
self.log("\033[Jcln ok; rm {} dirs".format(ndirs))
|
self.log("\033[Jcln ok; rm {} dirs".format(ndirs))
|
||||||
|
|
||||||
@@ -712,7 +890,7 @@ class ThumbSrv(object):
|
|||||||
|
|
||||||
def _clean(self, cat: str, thumbpath: str) -> int:
|
def _clean(self, cat: str, thumbpath: str) -> int:
|
||||||
# self.log("cln {}".format(thumbpath))
|
# self.log("cln {}".format(thumbpath))
|
||||||
exts = ["jpg", "webp", "png"] if cat == "th" else ["opus", "caf"]
|
exts = ["jpg", "webp", "png"] if cat == "th" else ["opus", "caf", "mp3"]
|
||||||
maxage = getattr(self.args, cat + "_maxage")
|
maxage = getattr(self.args, cat + "_maxage")
|
||||||
now = time.time()
|
now = time.time()
|
||||||
prev_b64 = None
|
prev_b64 = None
|
||||||
|
|||||||
@@ -8,8 +8,8 @@ import threading
|
|||||||
import time
|
import time
|
||||||
from operator import itemgetter
|
from operator import itemgetter
|
||||||
|
|
||||||
from .__init__ import ANYWIN, TYPE_CHECKING, unicode
|
from .__init__ import ANYWIN, PY2, TYPE_CHECKING, unicode
|
||||||
from .authsrv import LEELOO_DALLAS
|
from .authsrv import LEELOO_DALLAS, VFS
|
||||||
from .bos import bos
|
from .bos import bos
|
||||||
from .up2k import up2k_wark_from_hashlist
|
from .up2k import up2k_wark_from_hashlist
|
||||||
from .util import (
|
from .util import (
|
||||||
@@ -38,6 +38,9 @@ if True: # pylint: disable=using-constant-test
|
|||||||
if TYPE_CHECKING:
|
if TYPE_CHECKING:
|
||||||
from .httpsrv import HttpSrv
|
from .httpsrv import HttpSrv
|
||||||
|
|
||||||
|
if PY2:
|
||||||
|
range = xrange # type: ignore
|
||||||
|
|
||||||
|
|
||||||
class U2idx(object):
|
class U2idx(object):
|
||||||
def __init__(self, hsrv: "HttpSrv") -> None:
|
def __init__(self, hsrv: "HttpSrv") -> None:
|
||||||
@@ -56,14 +59,27 @@ class U2idx(object):
|
|||||||
self.mem_cur = sqlite3.connect(":memory:", check_same_thread=False).cursor()
|
self.mem_cur = sqlite3.connect(":memory:", check_same_thread=False).cursor()
|
||||||
self.mem_cur.execute(r"create table a (b text)")
|
self.mem_cur.execute(r"create table a (b text)")
|
||||||
|
|
||||||
|
self.sh_cur: Optional["sqlite3.Cursor"] = None
|
||||||
|
|
||||||
self.p_end = 0.0
|
self.p_end = 0.0
|
||||||
self.p_dur = 0.0
|
self.p_dur = 0.0
|
||||||
|
|
||||||
def log(self, msg: str, c: Union[int, str] = 0) -> None:
|
def log(self, msg: str, c: Union[int, str] = 0) -> None:
|
||||||
self.log_func("u2idx", msg, c)
|
self.log_func("u2idx", msg, c)
|
||||||
|
|
||||||
|
def shutdown(self) -> None:
|
||||||
|
for cur in self.cur.values():
|
||||||
|
db = cur.connection
|
||||||
|
try:
|
||||||
|
db.interrupt()
|
||||||
|
except:
|
||||||
|
pass
|
||||||
|
|
||||||
|
cur.close()
|
||||||
|
db.close()
|
||||||
|
|
||||||
def fsearch(
|
def fsearch(
|
||||||
self, vols: list[tuple[str, str, dict[str, Any]]], body: dict[str, Any]
|
self, uname: str, vols: list[VFS], body: dict[str, Any]
|
||||||
) -> list[dict[str, Any]]:
|
) -> list[dict[str, Any]]:
|
||||||
"""search by up2k hashlist"""
|
"""search by up2k hashlist"""
|
||||||
if not HAVE_SQLITE3:
|
if not HAVE_SQLITE3:
|
||||||
@@ -77,18 +93,36 @@ class U2idx(object):
|
|||||||
uv: list[Union[str, int]] = [wark[:16], wark]
|
uv: list[Union[str, int]] = [wark[:16], wark]
|
||||||
|
|
||||||
try:
|
try:
|
||||||
return self.run_query(vols, uq, uv, True, False, 99999)[0]
|
return self.run_query(uname, vols, uq, uv, False, 99999)[0]
|
||||||
except:
|
except:
|
||||||
raise Pebkac(500, min_ex())
|
raise Pebkac(500, min_ex())
|
||||||
|
|
||||||
def get_cur(self, ptop: str) -> Optional["sqlite3.Cursor"]:
|
def get_shr(self) -> Optional["sqlite3.Cursor"]:
|
||||||
if not HAVE_SQLITE3:
|
if self.sh_cur:
|
||||||
|
return self.sh_cur
|
||||||
|
|
||||||
|
if not HAVE_SQLITE3 or not self.args.shr:
|
||||||
return None
|
return None
|
||||||
|
|
||||||
cur = self.cur.get(ptop)
|
assert sqlite3 # type: ignore # !rm
|
||||||
|
|
||||||
|
db = sqlite3.connect(self.args.shr_db, timeout=2, check_same_thread=False)
|
||||||
|
cur = db.cursor()
|
||||||
|
cur.execute('pragma table_info("sh")').fetchall()
|
||||||
|
self.sh_cur = cur
|
||||||
|
return cur
|
||||||
|
|
||||||
|
def get_cur(self, vn: VFS) -> Optional["sqlite3.Cursor"]:
|
||||||
|
cur = self.cur.get(vn.realpath)
|
||||||
if cur:
|
if cur:
|
||||||
return cur
|
return cur
|
||||||
|
|
||||||
|
if not HAVE_SQLITE3 or "e2d" not in vn.flags:
|
||||||
|
return None
|
||||||
|
|
||||||
|
assert sqlite3 # type: ignore # !rm
|
||||||
|
|
||||||
|
ptop = vn.realpath
|
||||||
histpath = self.asrv.vfs.histtab.get(ptop)
|
histpath = self.asrv.vfs.histtab.get(ptop)
|
||||||
if not histpath:
|
if not histpath:
|
||||||
self.log("no histpath for [{}]".format(ptop))
|
self.log("no histpath for [{}]".format(ptop))
|
||||||
@@ -103,7 +137,7 @@ class U2idx(object):
|
|||||||
uri = ""
|
uri = ""
|
||||||
try:
|
try:
|
||||||
uri = "{}?mode=ro&nolock=1".format(Path(db_path).as_uri())
|
uri = "{}?mode=ro&nolock=1".format(Path(db_path).as_uri())
|
||||||
db = sqlite3.connect(uri, 2, uri=True, check_same_thread=False)
|
db = sqlite3.connect(uri, timeout=2, uri=True, check_same_thread=False)
|
||||||
cur = db.cursor()
|
cur = db.cursor()
|
||||||
cur.execute('pragma table_info("up")').fetchone()
|
cur.execute('pragma table_info("up")').fetchone()
|
||||||
self.log("ro: {}".format(db_path))
|
self.log("ro: {}".format(db_path))
|
||||||
@@ -115,14 +149,14 @@ class U2idx(object):
|
|||||||
if not cur:
|
if not cur:
|
||||||
# on windows, this steals the write-lock from up2k.deferred_init --
|
# 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
|
# seen on win 10.0.17763.2686, py 3.10.4, sqlite 3.37.2
|
||||||
cur = sqlite3.connect(db_path, 2, check_same_thread=False).cursor()
|
cur = sqlite3.connect(db_path, timeout=2, check_same_thread=False).cursor()
|
||||||
self.log("opened {}".format(db_path))
|
self.log("opened {}".format(db_path))
|
||||||
|
|
||||||
self.cur[ptop] = cur
|
self.cur[ptop] = cur
|
||||||
return cur
|
return cur
|
||||||
|
|
||||||
def search(
|
def search(
|
||||||
self, vols: list[tuple[str, str, dict[str, Any]]], uq: str, lim: int
|
self, uname: str, vols: list[VFS], uq: str, lim: int
|
||||||
) -> tuple[list[dict[str, Any]], list[str], bool]:
|
) -> tuple[list[dict[str, Any]], list[str], bool]:
|
||||||
"""search by query params"""
|
"""search by query params"""
|
||||||
if not HAVE_SQLITE3:
|
if not HAVE_SQLITE3:
|
||||||
@@ -131,7 +165,6 @@ class U2idx(object):
|
|||||||
q = ""
|
q = ""
|
||||||
v: Union[str, int] = ""
|
v: Union[str, int] = ""
|
||||||
va: list[Union[str, int]] = []
|
va: list[Union[str, int]] = []
|
||||||
have_up = False # query has up.* operands
|
|
||||||
have_mt = False
|
have_mt = False
|
||||||
is_key = True
|
is_key = True
|
||||||
is_size = False
|
is_size = False
|
||||||
@@ -176,26 +209,21 @@ class U2idx(object):
|
|||||||
if v == "size":
|
if v == "size":
|
||||||
v = "up.sz"
|
v = "up.sz"
|
||||||
is_size = True
|
is_size = True
|
||||||
have_up = True
|
|
||||||
|
|
||||||
elif v == "date":
|
elif v == "date":
|
||||||
v = "up.mt"
|
v = "up.mt"
|
||||||
is_date = True
|
is_date = True
|
||||||
have_up = True
|
|
||||||
|
|
||||||
elif v == "up_at":
|
elif v == "up_at":
|
||||||
v = "up.at"
|
v = "up.at"
|
||||||
is_date = True
|
is_date = True
|
||||||
have_up = True
|
|
||||||
|
|
||||||
elif v == "path":
|
elif v == "path":
|
||||||
v = "trim(?||up.rd,'/')"
|
v = "trim(?||up.rd,'/')"
|
||||||
va.append("\nrd")
|
va.append("\nrd")
|
||||||
have_up = True
|
|
||||||
|
|
||||||
elif v == "name":
|
elif v == "name":
|
||||||
v = "up.fn"
|
v = "up.fn"
|
||||||
have_up = True
|
|
||||||
|
|
||||||
elif v == "tags" or ptn_mt.match(v):
|
elif v == "tags" or ptn_mt.match(v):
|
||||||
have_mt = True
|
have_mt = True
|
||||||
@@ -271,22 +299,22 @@ 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, lim)
|
return self.run_query(uname, vols, q, va, have_mt, lim)
|
||||||
except Exception as ex:
|
except Exception as ex:
|
||||||
raise Pebkac(500, repr(ex))
|
raise Pebkac(500, repr(ex))
|
||||||
|
|
||||||
def run_query(
|
def run_query(
|
||||||
self,
|
self,
|
||||||
vols: list[tuple[str, str, dict[str, Any]]],
|
uname: str,
|
||||||
|
vols: list[VFS],
|
||||||
uq: str,
|
uq: str,
|
||||||
uv: list[Union[str, int]],
|
uv: list[Union[str, int]],
|
||||||
have_up: bool,
|
|
||||||
have_mt: bool,
|
have_mt: bool,
|
||||||
lim: int,
|
lim: int,
|
||||||
) -> tuple[list[dict[str, Any]], list[str], bool]:
|
) -> tuple[list[dict[str, Any]], list[str], bool]:
|
||||||
if self.args.srch_dbg:
|
if self.args.srch_dbg:
|
||||||
t = "searching across all %s volumes in which the user has 'r' (full read access):\n %s"
|
t = "searching across all %s volumes in which the user has 'r' (full read access):\n %s"
|
||||||
zs = "\n ".join(["/%s = %s" % (x[0], x[1]) for x in vols])
|
zs = "\n ".join(["/%s = %s" % (x.vpath, x.realpath) for x in vols])
|
||||||
self.log(t % (len(vols), zs), 5)
|
self.log(t % (len(vols), zs), 5)
|
||||||
|
|
||||||
done_flag: list[bool] = []
|
done_flag: list[bool] = []
|
||||||
@@ -315,11 +343,15 @@ class U2idx(object):
|
|||||||
clamped = False
|
clamped = False
|
||||||
|
|
||||||
taglist = {}
|
taglist = {}
|
||||||
for (vtop, ptop, flags) in vols:
|
for vol in vols:
|
||||||
if lim < 0:
|
if lim < 0:
|
||||||
break
|
break
|
||||||
|
|
||||||
cur = self.get_cur(ptop)
|
vtop = vol.vpath
|
||||||
|
ptop = vol.realpath
|
||||||
|
flags = vol.flags
|
||||||
|
|
||||||
|
cur = self.get_cur(vol)
|
||||||
if not cur:
|
if not cur:
|
||||||
continue
|
continue
|
||||||
|
|
||||||
@@ -343,7 +375,7 @@ class U2idx(object):
|
|||||||
|
|
||||||
sret = []
|
sret = []
|
||||||
fk = flags.get("fk")
|
fk = flags.get("fk")
|
||||||
dots = flags.get("dotsrch")
|
dots = flags.get("dotsrch") and uname in vol.axs.udot
|
||||||
fk_alg = 2 if "fka" in flags else 1
|
fk_alg = 2 if "fka" in flags else 1
|
||||||
c = cur.execute(uq, tuple(vuv))
|
c = cur.execute(uq, tuple(vuv))
|
||||||
for hit in c:
|
for hit in c:
|
||||||
@@ -435,5 +467,5 @@ class U2idx(object):
|
|||||||
return
|
return
|
||||||
|
|
||||||
if identifier == self.active_id:
|
if identifier == self.active_id:
|
||||||
assert self.active_cur
|
assert self.active_cur # !rm
|
||||||
self.active_cur.connection.interrupt()
|
self.active_cur.connection.interrupt()
|
||||||
|
|||||||
1997
copyparty/up2k.py
1997
copyparty/up2k.py
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -17,8 +17,10 @@ window.baguetteBox = (function () {
|
|||||||
titleTag: false,
|
titleTag: false,
|
||||||
async: false,
|
async: false,
|
||||||
preload: 2,
|
preload: 2,
|
||||||
|
refocus: true,
|
||||||
afterShow: null,
|
afterShow: null,
|
||||||
afterHide: null,
|
afterHide: null,
|
||||||
|
duringHide: null,
|
||||||
onChange: null,
|
onChange: null,
|
||||||
},
|
},
|
||||||
overlay, slider, btnPrev, btnNext, btnHelp, btnAnim, btnRotL, btnRotR, btnSel, btnFull, btnVmode, btnClose,
|
overlay, slider, btnPrev, btnNext, btnHelp, btnAnim, btnRotL, btnRotR, btnSel, btnFull, btnVmode, btnClose,
|
||||||
@@ -27,6 +29,8 @@ window.baguetteBox = (function () {
|
|||||||
isOverlayVisible = false,
|
isOverlayVisible = false,
|
||||||
touch = {}, // start-pos
|
touch = {}, // start-pos
|
||||||
touchFlag = false, // busy
|
touchFlag = false, // busy
|
||||||
|
scrollCSS = ['', ''],
|
||||||
|
scrollTimer = 0,
|
||||||
re_i = /^[^?]+\.(a?png|avif|bmp|gif|heif|jpe?g|jfif|svg|webp)(\?|$)/i,
|
re_i = /^[^?]+\.(a?png|avif|bmp|gif|heif|jpe?g|jfif|svg|webp)(\?|$)/i,
|
||||||
re_v = /^[^?]+\.(webm|mkv|mp4)(\?|$)/i,
|
re_v = /^[^?]+\.(webm|mkv|mp4)(\?|$)/i,
|
||||||
anims = ['slideIn', 'fadeIn', 'none'],
|
anims = ['slideIn', 'fadeIn', 'none'],
|
||||||
@@ -89,6 +93,30 @@ window.baguetteBox = (function () {
|
|||||||
touchendHandler();
|
touchendHandler();
|
||||||
};
|
};
|
||||||
|
|
||||||
|
var overlayWheelHandler = function (e) {
|
||||||
|
if (!options.noScrollbars || anymod(e))
|
||||||
|
return;
|
||||||
|
|
||||||
|
ev(e);
|
||||||
|
|
||||||
|
var x = e.deltaX,
|
||||||
|
y = e.deltaY,
|
||||||
|
d = Math.abs(x) > Math.abs(y) ? x : y;
|
||||||
|
|
||||||
|
if (e.deltaMode)
|
||||||
|
d *= 10;
|
||||||
|
|
||||||
|
if (Date.now() - scrollTimer < (Math.abs(d) > 20 ? 100 : 300))
|
||||||
|
return;
|
||||||
|
|
||||||
|
scrollTimer = Date.now();
|
||||||
|
|
||||||
|
if (d > 0)
|
||||||
|
showNextImage();
|
||||||
|
else
|
||||||
|
showPreviousImage();
|
||||||
|
};
|
||||||
|
|
||||||
var trapFocusInsideOverlay = function (e) {
|
var trapFocusInsideOverlay = function (e) {
|
||||||
if (overlay.style.display === 'block' && (overlay.contains && !overlay.contains(e.target))) {
|
if (overlay.style.display === 'block' && (overlay.contains && !overlay.contains(e.target))) {
|
||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
@@ -144,7 +172,7 @@ window.baguetteBox = (function () {
|
|||||||
selectorData.galleries.push(gallery);
|
selectorData.galleries.push(gallery);
|
||||||
});
|
});
|
||||||
|
|
||||||
return selectorData.galleries;
|
return [selectorData.galleries, options];
|
||||||
}
|
}
|
||||||
|
|
||||||
function clearCachedData() {
|
function clearCachedData() {
|
||||||
@@ -255,19 +283,19 @@ window.baguetteBox = (function () {
|
|||||||
if (anymod(e, true))
|
if (anymod(e, true))
|
||||||
return;
|
return;
|
||||||
|
|
||||||
var k = e.code + '', v = vid(), pos = -1;
|
var k = (e.code || e.key) + '', v = vid(), pos = -1;
|
||||||
|
|
||||||
if (k == "BracketLeft")
|
if (k == "BracketLeft")
|
||||||
setloop(1);
|
setloop(1);
|
||||||
else if (k == "BracketRight")
|
else if (k == "BracketRight")
|
||||||
setloop(2);
|
setloop(2);
|
||||||
else if (e.shiftKey && k != 'KeyR')
|
else if (e.shiftKey && k != "KeyR" && k != "R")
|
||||||
return;
|
return;
|
||||||
else if (k == "ArrowLeft" || k == "KeyJ")
|
else if (k == "ArrowLeft" || k == "KeyJ" || k == "Left" || k == "j")
|
||||||
showPreviousImage();
|
showPreviousImage();
|
||||||
else if (k == "ArrowRight" || k == "KeyL")
|
else if (k == "ArrowRight" || k == "KeyL" || k == "Right" || k == "l")
|
||||||
showNextImage();
|
showNextImage();
|
||||||
else if (k == "Escape")
|
else if (k == "Escape" || k == "Esc")
|
||||||
hideOverlay();
|
hideOverlay();
|
||||||
else if (k == "Home")
|
else if (k == "Home")
|
||||||
showFirstImage(e);
|
showFirstImage(e);
|
||||||
@@ -295,9 +323,9 @@ window.baguetteBox = (function () {
|
|||||||
}
|
}
|
||||||
else if (k == "KeyF")
|
else if (k == "KeyF")
|
||||||
tglfull();
|
tglfull();
|
||||||
else if (k == "KeyS")
|
else if (k == "KeyS" || k == "s")
|
||||||
tglsel();
|
tglsel();
|
||||||
else if (k == "KeyR")
|
else if (k == "KeyR" || k == "r" || k == "R")
|
||||||
rotn(e.shiftKey ? -1 : 1);
|
rotn(e.shiftKey ? -1 : 1);
|
||||||
else if (k == "KeyY")
|
else if (k == "KeyY")
|
||||||
dlpic();
|
dlpic();
|
||||||
@@ -392,8 +420,7 @@ window.baguetteBox = (function () {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function dlpic() {
|
function dlpic() {
|
||||||
var url = findfile()[3].href;
|
var url = addq(findfile()[3].href, 'cache');
|
||||||
url += (url.indexOf('?') < 0 ? '?' : '&') + 'cache';
|
|
||||||
dl_file(url);
|
dl_file(url);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -450,6 +477,7 @@ window.baguetteBox = (function () {
|
|||||||
bind(document, 'keyup', keyUpHandler);
|
bind(document, 'keyup', keyUpHandler);
|
||||||
bind(document, 'fullscreenchange', onFSC);
|
bind(document, 'fullscreenchange', onFSC);
|
||||||
bind(overlay, 'click', overlayClickHandler);
|
bind(overlay, 'click', overlayClickHandler);
|
||||||
|
bind(overlay, 'wheel', overlayWheelHandler);
|
||||||
bind(btnPrev, 'click', showPreviousImage);
|
bind(btnPrev, 'click', showPreviousImage);
|
||||||
bind(btnNext, 'click', showNextImage);
|
bind(btnNext, 'click', showNextImage);
|
||||||
bind(btnClose, 'click', hideOverlay);
|
bind(btnClose, 'click', hideOverlay);
|
||||||
@@ -472,6 +500,7 @@ window.baguetteBox = (function () {
|
|||||||
unbind(document, 'keyup', keyUpHandler);
|
unbind(document, 'keyup', keyUpHandler);
|
||||||
unbind(document, 'fullscreenchange', onFSC);
|
unbind(document, 'fullscreenchange', onFSC);
|
||||||
unbind(overlay, 'click', overlayClickHandler);
|
unbind(overlay, 'click', overlayClickHandler);
|
||||||
|
unbind(overlay, 'wheel', overlayWheelHandler);
|
||||||
unbind(btnPrev, 'click', showPreviousImage);
|
unbind(btnPrev, 'click', showPreviousImage);
|
||||||
unbind(btnNext, 'click', showNextImage);
|
unbind(btnNext, 'click', showNextImage);
|
||||||
unbind(btnClose, 'click', hideOverlay);
|
unbind(btnClose, 'click', hideOverlay);
|
||||||
@@ -539,6 +568,12 @@ window.baguetteBox = (function () {
|
|||||||
|
|
||||||
function showOverlay(chosenImageIndex) {
|
function showOverlay(chosenImageIndex) {
|
||||||
if (options.noScrollbars) {
|
if (options.noScrollbars) {
|
||||||
|
var a = document.documentElement.style.overflowY,
|
||||||
|
b = document.body.style.overflowY;
|
||||||
|
|
||||||
|
if (a != 'hidden' || b != 'scroll')
|
||||||
|
scrollCSS = [a, b];
|
||||||
|
|
||||||
document.documentElement.style.overflowY = 'hidden';
|
document.documentElement.style.overflowY = 'hidden';
|
||||||
document.body.style.overflowY = 'scroll';
|
document.body.style.overflowY = 'scroll';
|
||||||
}
|
}
|
||||||
@@ -582,24 +617,30 @@ window.baguetteBox = (function () {
|
|||||||
isOverlayVisible = true;
|
isOverlayVisible = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
function hideOverlay(e) {
|
function hideOverlay(e, dtor) {
|
||||||
ev(e);
|
ev(e);
|
||||||
playvid(false);
|
playvid(false);
|
||||||
removeFromCache('#files');
|
removeFromCache('#files');
|
||||||
if (options.noScrollbars) {
|
if (options.noScrollbars) {
|
||||||
document.documentElement.style.overflowY = 'auto';
|
document.documentElement.style.overflowY = scrollCSS[0];
|
||||||
document.body.style.overflowY = 'auto';
|
document.body.style.overflowY = scrollCSS[1];
|
||||||
}
|
}
|
||||||
if (overlay.style.display === 'none')
|
|
||||||
|
try {
|
||||||
|
if (document.fullscreenElement)
|
||||||
|
document.exitFullscreen();
|
||||||
|
}
|
||||||
|
catch (ex) { }
|
||||||
|
isFullscreen = false;
|
||||||
|
|
||||||
|
if (dtor || overlay.style.display === 'none')
|
||||||
return;
|
return;
|
||||||
|
|
||||||
|
if (options.duringHide)
|
||||||
|
options.duringHide();
|
||||||
|
|
||||||
sethash('');
|
sethash('');
|
||||||
unbindEvents();
|
unbindEvents();
|
||||||
try {
|
|
||||||
document.exitFullscreen();
|
|
||||||
isFullscreen = false;
|
|
||||||
}
|
|
||||||
catch (ex) { }
|
|
||||||
|
|
||||||
// Fade out and hide the overlay
|
// Fade out and hide the overlay
|
||||||
overlay.className = '';
|
overlay.className = '';
|
||||||
@@ -613,9 +654,45 @@ window.baguetteBox = (function () {
|
|||||||
if (options.afterHide)
|
if (options.afterHide)
|
||||||
options.afterHide();
|
options.afterHide();
|
||||||
|
|
||||||
documentLastFocus && documentLastFocus.focus();
|
options.refocus && documentLastFocus && documentLastFocus.focus();
|
||||||
isOverlayVisible = false;
|
isOverlayVisible = false;
|
||||||
}, 500);
|
unvid();
|
||||||
|
unfig();
|
||||||
|
}, 250);
|
||||||
|
}
|
||||||
|
|
||||||
|
function unvid(keep) {
|
||||||
|
var vids = QSA('#bbox-overlay video');
|
||||||
|
for (var a = vids.length - 1; a >= 0; a--) {
|
||||||
|
var v = vids[a];
|
||||||
|
if (v == keep)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
v.src = '';
|
||||||
|
v.load();
|
||||||
|
|
||||||
|
var p = v.parentNode;
|
||||||
|
p.removeChild(v);
|
||||||
|
p.parentNode.removeChild(p);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function unfig(keep) {
|
||||||
|
var figs = QSA('#bbox-overlay figure'),
|
||||||
|
npre = options.preload || 0,
|
||||||
|
k = [];
|
||||||
|
|
||||||
|
if (keep === undefined)
|
||||||
|
keep = -9;
|
||||||
|
|
||||||
|
for (var a = keep - npre; a <= keep + npre; a++)
|
||||||
|
k.push('bbox-figure-' + a);
|
||||||
|
|
||||||
|
for (var a = figs.length - 1; a >= 0; a--) {
|
||||||
|
var f = figs[a];
|
||||||
|
if (!has(k, f.getAttribute('id')))
|
||||||
|
f.parentNode.removeChild(f);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function loadImage(index, callback) {
|
function loadImage(index, callback) {
|
||||||
@@ -641,7 +718,7 @@ window.baguetteBox = (function () {
|
|||||||
options.captions.call(currentGallery, imageElement) :
|
options.captions.call(currentGallery, imageElement) :
|
||||||
imageElement.getAttribute('data-caption') || imageElement.title;
|
imageElement.getAttribute('data-caption') || imageElement.title;
|
||||||
|
|
||||||
imageSrc += imageSrc.indexOf('?') < 0 ? '?cache' : '&cache';
|
imageSrc = addq(imageSrc, 'cache');
|
||||||
|
|
||||||
if (is_vid && index != currentIndex)
|
if (is_vid && index != currentIndex)
|
||||||
return; // no preload
|
return; // no preload
|
||||||
@@ -670,8 +747,11 @@ window.baguetteBox = (function () {
|
|||||||
});
|
});
|
||||||
image.setAttribute('src', imageSrc);
|
image.setAttribute('src', imageSrc);
|
||||||
if (is_vid) {
|
if (is_vid) {
|
||||||
|
image.volume = clamp(fcfg_get('vol', dvol / 100), 0, 1);
|
||||||
image.setAttribute('controls', 'controls');
|
image.setAttribute('controls', 'controls');
|
||||||
image.onended = vidEnd;
|
image.onended = vidEnd;
|
||||||
|
image.onplay = function () { show_buttons(1); };
|
||||||
|
image.onpause = function () { show_buttons(); };
|
||||||
}
|
}
|
||||||
image.alt = thumbnailElement ? thumbnailElement.alt || '' : '';
|
image.alt = thumbnailElement ? thumbnailElement.alt || '' : '';
|
||||||
if (options.titleTag && imageCaption)
|
if (options.titleTag && imageCaption)
|
||||||
@@ -679,6 +759,9 @@ window.baguetteBox = (function () {
|
|||||||
|
|
||||||
figure.appendChild(image);
|
figure.appendChild(image);
|
||||||
|
|
||||||
|
if (is_vid && window.afilt)
|
||||||
|
afilt.apply(undefined, image);
|
||||||
|
|
||||||
if (options.async && callback)
|
if (options.async && callback)
|
||||||
callback();
|
callback();
|
||||||
}
|
}
|
||||||
@@ -708,6 +791,7 @@ window.baguetteBox = (function () {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function show(index, gallery) {
|
function show(index, gallery) {
|
||||||
|
gallery = gallery || currentGallery;
|
||||||
if (!isOverlayVisible && index >= 0 && index < gallery.length) {
|
if (!isOverlayVisible && index >= 0 && index < gallery.length) {
|
||||||
prepareOverlay(gallery, options);
|
prepareOverlay(gallery, options);
|
||||||
showOverlay(index);
|
showOverlay(index);
|
||||||
@@ -720,12 +804,10 @@ window.baguetteBox = (function () {
|
|||||||
if (index >= imagesElements.length)
|
if (index >= imagesElements.length)
|
||||||
return bounceAnimation('right');
|
return bounceAnimation('right');
|
||||||
|
|
||||||
var v = vid();
|
try {
|
||||||
if (v) {
|
vid().pause();
|
||||||
v.src = '';
|
|
||||||
v.load();
|
|
||||||
v.parentNode.removeChild(v);
|
|
||||||
}
|
}
|
||||||
|
catch (ex) { }
|
||||||
|
|
||||||
currentIndex = index;
|
currentIndex = index;
|
||||||
loadImage(currentIndex, function () {
|
loadImage(currentIndex, function () {
|
||||||
@@ -734,6 +816,15 @@ window.baguetteBox = (function () {
|
|||||||
});
|
});
|
||||||
updateOffset();
|
updateOffset();
|
||||||
|
|
||||||
|
if (options.animation == 'none')
|
||||||
|
unvid(vid());
|
||||||
|
else
|
||||||
|
setTimeout(function () {
|
||||||
|
unvid(vid());
|
||||||
|
}, 100);
|
||||||
|
|
||||||
|
unfig(index);
|
||||||
|
|
||||||
if (options.onChange)
|
if (options.onChange)
|
||||||
options.onChange(currentIndex, imagesElements.length);
|
options.onChange(currentIndex, imagesElements.length);
|
||||||
|
|
||||||
@@ -906,6 +997,12 @@ window.baguetteBox = (function () {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function show_buttons(v) {
|
||||||
|
clmod(ebi('bbox-btns'), 'off', v);
|
||||||
|
clmod(btnPrev, 'off', v);
|
||||||
|
clmod(btnNext, 'off', v);
|
||||||
|
}
|
||||||
|
|
||||||
function bounceAnimation(direction) {
|
function bounceAnimation(direction) {
|
||||||
slider.className = options.animation == 'slideIn' ? 'bounce-from-' + direction : 'eog';
|
slider.className = options.animation == 'slideIn' ? 'bounce-from-' + direction : 'eog';
|
||||||
setTimeout(function () {
|
setTimeout(function () {
|
||||||
@@ -969,9 +1066,7 @@ window.baguetteBox = (function () {
|
|||||||
if (fx > 0.7)
|
if (fx > 0.7)
|
||||||
return showNextImage();
|
return showNextImage();
|
||||||
|
|
||||||
clmod(ebi('bbox-btns'), 'off', 't');
|
show_buttons('t');
|
||||||
clmod(btnPrev, 'off', 't');
|
|
||||||
clmod(btnNext, 'off', 't');
|
|
||||||
|
|
||||||
if (Date.now() - ctime <= 500 && !IPHONE)
|
if (Date.now() - ctime <= 500 && !IPHONE)
|
||||||
tglfull();
|
tglfull();
|
||||||
@@ -1013,6 +1108,7 @@ window.baguetteBox = (function () {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function destroyPlugin() {
|
function destroyPlugin() {
|
||||||
|
hideOverlay(undefined, true);
|
||||||
unbindEvents();
|
unbindEvents();
|
||||||
clearCachedData();
|
clearCachedData();
|
||||||
document.getElementsByTagName('body')[0].removeChild(ebi('bbox-overlay'));
|
document.getElementsByTagName('body')[0].removeChild(ebi('bbox-overlay'));
|
||||||
|
|||||||
@@ -10,7 +10,6 @@
|
|||||||
--fg2-max: #fff;
|
--fg2-max: #fff;
|
||||||
--fg-weak: #bbb;
|
--fg-weak: #bbb;
|
||||||
|
|
||||||
--bg-u7: #555;
|
|
||||||
--bg-u6: #4c4c4c;
|
--bg-u6: #4c4c4c;
|
||||||
--bg-u5: #444;
|
--bg-u5: #444;
|
||||||
--bg-u4: #383838;
|
--bg-u4: #383838;
|
||||||
@@ -28,6 +27,8 @@
|
|||||||
--row-alt: #282828;
|
--row-alt: #282828;
|
||||||
|
|
||||||
--scroll: #eb0;
|
--scroll: #eb0;
|
||||||
|
--sel-fg: var(--bg-d1);
|
||||||
|
--sel-bg: var(--fg);
|
||||||
|
|
||||||
--a: #fc5;
|
--a: #fc5;
|
||||||
--a-b: #c90;
|
--a-b: #c90;
|
||||||
@@ -41,8 +42,14 @@
|
|||||||
--btn-h-bg: #805;
|
--btn-h-bg: #805;
|
||||||
--btn-1-fg: #400;
|
--btn-1-fg: #400;
|
||||||
--btn-1-bg: var(--a);
|
--btn-1-bg: var(--a);
|
||||||
|
--btn-h-bs: var(--btn-bs);
|
||||||
|
--btn-h-bb: var(--btn-bb);
|
||||||
|
--btn-1-bs: var(--btn-bs);
|
||||||
|
--btn-1-bb: var(--btn-bb);
|
||||||
--btn-1h-fg: var(--btn-1-fg);
|
--btn-1h-fg: var(--btn-1-fg);
|
||||||
--btn-1h-bg: #fe8;
|
--btn-1h-bg: #fe8;
|
||||||
|
--btn-1h-bs: var(--btn-1-bs);
|
||||||
|
--btn-1h-bb: var(--btn-1-bb);
|
||||||
--chk-fg: var(--tab-alt);
|
--chk-fg: var(--tab-alt);
|
||||||
--txt-sh: var(--bg-d2);
|
--txt-sh: var(--bg-d2);
|
||||||
--txt-bg: var(--btn-bg);
|
--txt-bg: var(--btn-bg);
|
||||||
@@ -57,7 +64,7 @@
|
|||||||
--u2-tab-bg: linear-gradient(to bottom, var(--bg), var(--bg-u1));
|
--u2-tab-bg: linear-gradient(to bottom, var(--bg), var(--bg-u1));
|
||||||
--u2-tab-b1: rgba(128,128,128,0.8);
|
--u2-tab-b1: rgba(128,128,128,0.8);
|
||||||
--u2-tab-1-fg: #fd7;
|
--u2-tab-1-fg: #fd7;
|
||||||
--u2-tab-1-bg: linear-gradient(to bottom, var(#353), var(--bg) 80%);
|
--u2-tab-1-bg: linear-gradient(to bottom, #353, var(--bg) 80%);
|
||||||
--u2-tab-1-b1: #7c5;
|
--u2-tab-1-b1: #7c5;
|
||||||
--u2-tab-1-b2: #583;
|
--u2-tab-1-b2: #583;
|
||||||
--u2-tab-1-sh: #280;
|
--u2-tab-1-sh: #280;
|
||||||
@@ -210,22 +217,19 @@ html.y {
|
|||||||
html.a {
|
html.a {
|
||||||
--op-aa-sh: 0 0 .2em var(--bg-d3) inset;
|
--op-aa-sh: 0 0 .2em var(--bg-d3) inset;
|
||||||
|
|
||||||
--u2-o-bg: #603;
|
--btn-bs: 0 0 .2em var(--bg-d3);
|
||||||
--u2-o-b1: #a16;
|
}
|
||||||
--u2-o-sh: #a00;
|
html.az {
|
||||||
--u2-o-h-bg: var(--u2-o-bg);
|
--btn-1-bs: 0 0 .1em var(--fg) inset;
|
||||||
--u2-o-h-b1: #fb0;
|
|
||||||
--u2-o-h-sh: #fb0;
|
|
||||||
--u2-o-1-bg: #6a1;
|
|
||||||
--u2-o-1-b1: #efa;
|
|
||||||
--u2-o-1-sh: #0c0;
|
|
||||||
--u2-o-1h-bg: var(--u2-o-1-bg);
|
|
||||||
}
|
}
|
||||||
html.ay {
|
html.ay {
|
||||||
--op-aa-sh: 0 .1em .2em #ccc;
|
--op-aa-sh: 0 .1em .2em #ccc;
|
||||||
--op-aa-bg: var(--bg-max);
|
--op-aa-bg: var(--bg-max);
|
||||||
}
|
}
|
||||||
html.b {
|
html.b {
|
||||||
|
--btn-bs: 0 .05em 0 var(--bg-d3) inset;
|
||||||
|
--btn-1-bs: 0 .05em 0 var(--btn-1h-bg) inset;
|
||||||
|
|
||||||
--tree-bg: var(--bg);
|
--tree-bg: var(--bg);
|
||||||
|
|
||||||
--g-bg: var(--bg);
|
--g-bg: var(--bg);
|
||||||
@@ -242,17 +246,13 @@ html.b {
|
|||||||
--u2-b1-bg: rgba(128,128,128,0.15);
|
--u2-b1-bg: rgba(128,128,128,0.15);
|
||||||
--u2-b2-bg: var(--u2-b1-bg);
|
--u2-b2-bg: var(--u2-b1-bg);
|
||||||
|
|
||||||
--u2-o-bg: var(--btn-bg);
|
|
||||||
--u2-o-h-bg: var(--btn-h-bg);
|
|
||||||
--u2-o-1-bg: var(--a);
|
|
||||||
--u2-o-1h-bg: var(--a-hil);
|
|
||||||
|
|
||||||
--f-sh1: 0.1;
|
--f-sh1: 0.1;
|
||||||
--mp-b-bg: transparent;
|
--mp-b-bg: transparent;
|
||||||
}
|
}
|
||||||
html.bz {
|
html.bz {
|
||||||
--fg: #cce;
|
--fg: #cce;
|
||||||
--fg-weak: #bbd;
|
--fg-weak: #bbd;
|
||||||
|
|
||||||
--bg-u5: #3b3f58;
|
--bg-u5: #3b3f58;
|
||||||
--bg-u4: #1e2130;
|
--bg-u4: #1e2130;
|
||||||
--bg-u3: #1e2130;
|
--bg-u3: #1e2130;
|
||||||
@@ -264,11 +264,14 @@ html.bz {
|
|||||||
|
|
||||||
--row-alt: #181a27;
|
--row-alt: #181a27;
|
||||||
|
|
||||||
|
--a-b: #fb4;
|
||||||
|
|
||||||
--btn-bg: #202231;
|
--btn-bg: #202231;
|
||||||
--btn-h-bg: #2d2f45;
|
--btn-h-bg: #2d2f45;
|
||||||
--btn-1-bg: #ba2959;
|
--btn-1-bg: #eb6;
|
||||||
--btn-1-fg: #fff;
|
--btn-1-fg: #000;
|
||||||
--btn-1h-fg: #000;
|
--btn-1h-fg: #000;
|
||||||
|
--btn-1h-bg: #ff9;
|
||||||
--txt-sh: a;
|
--txt-sh: a;
|
||||||
|
|
||||||
--u2-tab-b1: var(--bg-u5);
|
--u2-tab-b1: var(--bg-u5);
|
||||||
@@ -303,6 +306,7 @@ html.by {
|
|||||||
}
|
}
|
||||||
html.c {
|
html.c {
|
||||||
font-weight: bold;
|
font-weight: bold;
|
||||||
|
|
||||||
--fg: #fff;
|
--fg: #fff;
|
||||||
--fg-weak: #cef;
|
--fg-weak: #cef;
|
||||||
--bg-u5: #409;
|
--bg-u5: #409;
|
||||||
@@ -323,15 +327,25 @@ html.c {
|
|||||||
--chk-fg: #d90;
|
--chk-fg: #d90;
|
||||||
|
|
||||||
--op-aa-bg: #f9dd22;
|
--op-aa-bg: #f9dd22;
|
||||||
--u2-o-1-bg: #4cf;
|
|
||||||
|
|
||||||
--srv-1: #ea0;
|
--srv-1: #ea0;
|
||||||
--mp-b-bg: transparent;
|
--mp-b-bg: transparent;
|
||||||
}
|
}
|
||||||
html.cz {
|
html.cz {
|
||||||
--bgg: var(--bg-u2);
|
--bgg: var(--bg-u2);
|
||||||
|
|
||||||
|
--sel-bg: var(--bg-u5);
|
||||||
|
--sel-fg: var(--fg);
|
||||||
|
|
||||||
|
--btn-bb: .2em solid #709;
|
||||||
|
--btn-bs: 0 .1em .6em rgba(255,0,185,0.5);
|
||||||
|
--btn-1-bb: .2em solid #e90;
|
||||||
|
--btn-1-bs: 0 .1em .8em rgba(255,205,0,0.9);
|
||||||
|
|
||||||
--srv-3: #fff;
|
--srv-3: #fff;
|
||||||
|
|
||||||
--u2-tab-b1: var(--bg-d3);
|
--u2-tab-b1: var(--bg-d3);
|
||||||
|
--u2-tab-1-bg: a;
|
||||||
}
|
}
|
||||||
html.cy {
|
html.cy {
|
||||||
--fg: #fff;
|
--fg: #fff;
|
||||||
@@ -343,6 +357,8 @@ html.cy {
|
|||||||
--bg-d3: #f77;
|
--bg-d3: #f77;
|
||||||
--bg-d2: #ff0;
|
--bg-d2: #ff0;
|
||||||
|
|
||||||
|
--sel-bg: #f77;
|
||||||
|
|
||||||
--a: #fff;
|
--a: #fff;
|
||||||
--a-hil: #fff;
|
--a-hil: #fff;
|
||||||
--a-h-bg: #000;
|
--a-h-bg: #000;
|
||||||
@@ -356,16 +372,20 @@ html.cy {
|
|||||||
--btn-h-fg: #fff;
|
--btn-h-fg: #fff;
|
||||||
--btn-1-bg: #ff0;
|
--btn-1-bg: #ff0;
|
||||||
--btn-1-fg: #000;
|
--btn-1-fg: #000;
|
||||||
|
--btn-bs: 0 .25em 0 #f00;
|
||||||
--chk-fg: #fd0;
|
--chk-fg: #fd0;
|
||||||
|
|
||||||
|
--txt-bg: #000;
|
||||||
--srv-1: #f00;
|
--srv-1: #f00;
|
||||||
--srv-3: #fff;
|
--srv-3: #fff;
|
||||||
--op-aa-bg: #fff;
|
--op-aa-bg: #fff;
|
||||||
|
|
||||||
--u2-b1-bg: #f00;
|
--u2-b1-bg: #f00;
|
||||||
--u2-b2-bg: #f00;
|
--u2-b2-bg: #f00;
|
||||||
--u2-o-bg: #ff0;
|
|
||||||
--u2-o-1-bg: #f00;
|
--g-sel-fg: #fff;
|
||||||
|
--g-sel-bg: #aaa;
|
||||||
|
--g-fsel-bg: #aaa;
|
||||||
}
|
}
|
||||||
html.dz {
|
html.dz {
|
||||||
--fg: #4d4;
|
--fg: #4d4;
|
||||||
@@ -373,7 +393,6 @@ html.dz {
|
|||||||
--fg2-max: #fff;
|
--fg2-max: #fff;
|
||||||
--fg-weak: #2a2;
|
--fg-weak: #2a2;
|
||||||
|
|
||||||
--bg-u7: #020;
|
|
||||||
--bg-u6: #020;
|
--bg-u6: #020;
|
||||||
--bg-u5: #050;
|
--bg-u5: #050;
|
||||||
--bg-u4: #020;
|
--bg-u4: #020;
|
||||||
@@ -406,6 +425,9 @@ html.dz {
|
|||||||
--btn-1-bg: #4f4;
|
--btn-1-bg: #4f4;
|
||||||
--btn-1h-fg: var(--btn-1-fg);
|
--btn-1h-fg: var(--btn-1-fg);
|
||||||
--btn-1h-bg: #3f3;
|
--btn-1h-bg: #3f3;
|
||||||
|
--btn-bs: 0 0 0 .1em #080 inset;
|
||||||
|
--btn-1-bs: a;
|
||||||
|
|
||||||
--chk-fg: var(--tab-alt);
|
--chk-fg: var(--tab-alt);
|
||||||
--txt-sh: var(--bg-d2);
|
--txt-sh: var(--bg-d2);
|
||||||
--txt-bg: var(--btn-bg);
|
--txt-bg: var(--btn-bg);
|
||||||
@@ -420,19 +442,13 @@ html.dz {
|
|||||||
--u2-tab-bg: linear-gradient(to bottom, var(--bg), var(--bg-u1));
|
--u2-tab-bg: linear-gradient(to bottom, var(--bg), var(--bg-u1));
|
||||||
--u2-tab-b1: var(--fg-weak);
|
--u2-tab-b1: var(--fg-weak);
|
||||||
--u2-tab-1-fg: #fff;
|
--u2-tab-1-fg: #fff;
|
||||||
--u2-tab-1-bg: linear-gradient(to bottom, var(#353), var(--bg) 80%);
|
--u2-tab-1-bg: linear-gradient(to bottom, #151, var(--bg) 80%);
|
||||||
--u2-tab-1-b1: #7c5;
|
--u2-tab-1-b1: #7c5;
|
||||||
--u2-tab-1-b2: #583;
|
--u2-tab-1-b2: #583;
|
||||||
--u2-tab-1-sh: #280;
|
--u2-tab-1-sh: #280;
|
||||||
--u2-b-fg: #fff;
|
--u2-b-fg: #fff;
|
||||||
--u2-b1-bg: #3a3;
|
--u2-b1-bg: #3a3;
|
||||||
--u2-b2-bg: #3a3;
|
--u2-b2-bg: #3a3;
|
||||||
--u2-o-bg: var(--btn-bg);
|
|
||||||
--u2-o-b1: var(--bg-u5);
|
|
||||||
--u2-o-h-bg: var(--fg-weak);
|
|
||||||
--u2-o-1-bg: var(--fg-weak);
|
|
||||||
--u2-o-1-b1: var(--a);
|
|
||||||
--u2-o-1h-bg: var(--a);
|
|
||||||
--u2-inf-bg: #07a;
|
--u2-inf-bg: #07a;
|
||||||
--u2-inf-b1: #0be;
|
--u2-inf-b1: #0be;
|
||||||
--u2-ok-bg: #380;
|
--u2-ok-bg: #380;
|
||||||
@@ -494,6 +510,7 @@ html.dz {
|
|||||||
|
|
||||||
text-shadow: none;
|
text-shadow: none;
|
||||||
font-family: 'scp', monospace, monospace;
|
font-family: 'scp', monospace, monospace;
|
||||||
|
font-family: var(--font-mono), 'scp', monospace, monospace;
|
||||||
}
|
}
|
||||||
html.dy {
|
html.dy {
|
||||||
--fg: #000;
|
--fg: #000;
|
||||||
@@ -543,10 +560,6 @@ html.dy {
|
|||||||
--u2-tab-1-bg: a;
|
--u2-tab-1-bg: a;
|
||||||
--u2-b1-bg: #000;
|
--u2-b1-bg: #000;
|
||||||
--u2-b2-bg: #000;
|
--u2-b2-bg: #000;
|
||||||
--u2-o-h-bg: #999;
|
|
||||||
--u2-o-1h-bg: #999;
|
|
||||||
--u2-o-bg: #eee;
|
|
||||||
--u2-o-1-bg: #000;
|
|
||||||
|
|
||||||
--ud-b1: a;
|
--ud-b1: a;
|
||||||
|
|
||||||
@@ -587,11 +600,11 @@ html.dy {
|
|||||||
line-height: 1.2em;
|
line-height: 1.2em;
|
||||||
}
|
}
|
||||||
::selection {
|
::selection {
|
||||||
color: var(--bg-d1);
|
color: var(--sel-fg);
|
||||||
background: var(--fg);
|
background: var(--sel-bg);
|
||||||
text-shadow: none;
|
text-shadow: none;
|
||||||
}
|
}
|
||||||
html,body,tr,th,td,#files,a {
|
html,body,tr,th,td,#files,a,#blogout {
|
||||||
color: inherit;
|
color: inherit;
|
||||||
background: none;
|
background: none;
|
||||||
font-weight: inherit;
|
font-weight: inherit;
|
||||||
@@ -603,6 +616,7 @@ html {
|
|||||||
color: var(--fg);
|
color: var(--fg);
|
||||||
background: var(--bgg);
|
background: var(--bgg);
|
||||||
font-family: sans-serif;
|
font-family: sans-serif;
|
||||||
|
font-family: var(--font-main), sans-serif;
|
||||||
text-shadow: 1px 1px 0px var(--bg-max);
|
text-shadow: 1px 1px 0px var(--bg-max);
|
||||||
}
|
}
|
||||||
html, body {
|
html, body {
|
||||||
@@ -611,12 +625,14 @@ html, body {
|
|||||||
}
|
}
|
||||||
pre, code, tt, #doc, #doc>code {
|
pre, code, tt, #doc, #doc>code {
|
||||||
font-family: 'scp', monospace, monospace;
|
font-family: 'scp', monospace, monospace;
|
||||||
|
font-family: var(--font-mono), 'scp', monospace, monospace;
|
||||||
}
|
}
|
||||||
.ayjump {
|
.ayjump {
|
||||||
position: fixed;
|
position: fixed;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
width: 0;
|
width: 0;
|
||||||
height: 0;
|
height: 0;
|
||||||
|
color: var(--bg);
|
||||||
}
|
}
|
||||||
html .ayjump:focus {
|
html .ayjump:focus {
|
||||||
z-index: 80386;
|
z-index: 80386;
|
||||||
@@ -671,11 +687,15 @@ html.y #path {
|
|||||||
#files tbody div a {
|
#files tbody div a {
|
||||||
color: var(--tab-alt);
|
color: var(--tab-alt);
|
||||||
}
|
}
|
||||||
a, #files tbody div a:last-child {
|
a, #blogout, #files tbody div a:last-child {
|
||||||
color: var(--a);
|
color: var(--a);
|
||||||
padding: .2em;
|
padding: .2em;
|
||||||
text-decoration: none;
|
text-decoration: none;
|
||||||
}
|
}
|
||||||
|
#blogout {
|
||||||
|
margin: -.2em;
|
||||||
|
}
|
||||||
|
#blogout:hover,
|
||||||
a:hover {
|
a:hover {
|
||||||
color: var(--a-hil);
|
color: var(--a-hil);
|
||||||
background: var(--a-h-bg);
|
background: var(--a-h-bg);
|
||||||
@@ -696,12 +716,12 @@ a:hover {
|
|||||||
.s0:after,
|
.s0:after,
|
||||||
.s1:after {
|
.s1:after {
|
||||||
content: '⌄';
|
content: '⌄';
|
||||||
margin-left: -.1em;
|
margin-left: -.15em;
|
||||||
}
|
}
|
||||||
.s0r:after,
|
.s0r:after,
|
||||||
.s1r:after {
|
.s1r:after {
|
||||||
content: '⌃';
|
content: '⌃';
|
||||||
margin-left: -.1em;
|
margin-left: -.15em;
|
||||||
}
|
}
|
||||||
.s0:after,
|
.s0:after,
|
||||||
.s0r:after {
|
.s0r:after {
|
||||||
@@ -712,7 +732,7 @@ a:hover {
|
|||||||
color: var(--sort-2);
|
color: var(--sort-2);
|
||||||
}
|
}
|
||||||
#files thead th:after {
|
#files thead th:after {
|
||||||
margin-right: -.7em;
|
margin-right: -.5em;
|
||||||
}
|
}
|
||||||
#files tbody tr:hover td,
|
#files tbody tr:hover td,
|
||||||
#files tbody tr:hover td+td {
|
#files tbody tr:hover td+td {
|
||||||
@@ -741,6 +761,15 @@ html #files.hhpick thead th {
|
|||||||
word-wrap: break-word;
|
word-wrap: break-word;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
}
|
}
|
||||||
|
#files tr.fade a {
|
||||||
|
color: #999;
|
||||||
|
color: rgba(255, 255, 255, 0.4);
|
||||||
|
font-style: italic;
|
||||||
|
}
|
||||||
|
html.y #files tr.fade a {
|
||||||
|
color: #999;
|
||||||
|
color: rgba(0, 0, 0, 0.4);
|
||||||
|
}
|
||||||
#files tr:nth-child(2n) td {
|
#files tr:nth-child(2n) td {
|
||||||
background: var(--row-alt);
|
background: var(--row-alt);
|
||||||
}
|
}
|
||||||
@@ -759,6 +788,7 @@ html #files.hhpick thead th {
|
|||||||
}
|
}
|
||||||
#files tbody td:nth-child(3) {
|
#files tbody td:nth-child(3) {
|
||||||
font-family: 'scp', monospace, monospace;
|
font-family: 'scp', monospace, monospace;
|
||||||
|
font-family: var(--font-mono), 'scp', monospace, monospace;
|
||||||
text-align: right;
|
text-align: right;
|
||||||
padding-right: 1em;
|
padding-right: 1em;
|
||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
@@ -818,6 +848,11 @@ html.y #path a:hover {
|
|||||||
.logue:empty {
|
.logue:empty {
|
||||||
display: none;
|
display: none;
|
||||||
}
|
}
|
||||||
|
.logue.raw {
|
||||||
|
white-space: pre;
|
||||||
|
font-family: 'scp', 'consolas', monospace;
|
||||||
|
font-family: var(--font-mono), 'scp', 'consolas', monospace;
|
||||||
|
}
|
||||||
#doc>iframe,
|
#doc>iframe,
|
||||||
.logue>iframe {
|
.logue>iframe {
|
||||||
background: var(--bgg);
|
background: var(--bgg);
|
||||||
@@ -904,6 +939,9 @@ html.y #path a:hover {
|
|||||||
color: var(--srv-3);
|
color: var(--srv-3);
|
||||||
border-bottom: 1px solid var(--srv-3b);
|
border-bottom: 1px solid var(--srv-3b);
|
||||||
}
|
}
|
||||||
|
#flogout {
|
||||||
|
display: inline;
|
||||||
|
}
|
||||||
#goh+span {
|
#goh+span {
|
||||||
color: var(--bg-u5);
|
color: var(--bg-u5);
|
||||||
padding-left: .5em;
|
padding-left: .5em;
|
||||||
@@ -938,6 +976,8 @@ html.y #path a:hover {
|
|||||||
#files tbody tr.play a:hover {
|
#files tbody tr.play a:hover {
|
||||||
color: var(--btn-1h-fg);
|
color: var(--btn-1h-fg);
|
||||||
background: var(--btn-1h-bg);
|
background: var(--btn-1h-bg);
|
||||||
|
box-shadow: var(--btn-1h-bs);
|
||||||
|
border-bottom: var(--btn-1h-bb);
|
||||||
}
|
}
|
||||||
#ggrid {
|
#ggrid {
|
||||||
margin: -.2em -.5em;
|
margin: -.2em -.5em;
|
||||||
@@ -946,6 +986,7 @@ html.y #path a:hover {
|
|||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
display: block;
|
display: block;
|
||||||
display: -webkit-box;
|
display: -webkit-box;
|
||||||
|
line-clamp: var(--grid-ln);
|
||||||
-webkit-line-clamp: var(--grid-ln);
|
-webkit-line-clamp: var(--grid-ln);
|
||||||
-webkit-box-orient: vertical;
|
-webkit-box-orient: vertical;
|
||||||
padding-top: .3em;
|
padding-top: .3em;
|
||||||
@@ -981,6 +1022,10 @@ html.y #path a:hover {
|
|||||||
margin: 0 auto;
|
margin: 0 auto;
|
||||||
display: block;
|
display: block;
|
||||||
}
|
}
|
||||||
|
#ggrid.nocrop>a img {
|
||||||
|
max-height: 20em;
|
||||||
|
max-height: calc(var(--grid-sz)*2);
|
||||||
|
}
|
||||||
#ggrid>a.dir:before {
|
#ggrid>a.dir:before {
|
||||||
content: '📂';
|
content: '📂';
|
||||||
}
|
}
|
||||||
@@ -988,9 +1033,6 @@ html.y #path a:hover {
|
|||||||
color: var(--g-dfg);
|
color: var(--g-dfg);
|
||||||
}
|
}
|
||||||
#ggrid>a.au:before {
|
#ggrid>a.au:before {
|
||||||
content: '💾';
|
|
||||||
}
|
|
||||||
html.np_open #ggrid>a.au:before {
|
|
||||||
content: '▶';
|
content: '▶';
|
||||||
}
|
}
|
||||||
#ggrid>a:before {
|
#ggrid>a:before {
|
||||||
@@ -1119,6 +1161,7 @@ html.y #widget.open {
|
|||||||
width: 100%;
|
width: 100%;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
}
|
}
|
||||||
|
#fshr,
|
||||||
#wtgrid,
|
#wtgrid,
|
||||||
#wtico {
|
#wtico {
|
||||||
position: relative;
|
position: relative;
|
||||||
@@ -1147,9 +1190,6 @@ html.y #widget.open {
|
|||||||
@keyframes spin {
|
@keyframes spin {
|
||||||
100% {transform: rotate(360deg)}
|
100% {transform: rotate(360deg)}
|
||||||
}
|
}
|
||||||
@media (prefers-reduced-motion) {
|
|
||||||
@keyframes spin { }
|
|
||||||
}
|
|
||||||
@keyframes fadein {
|
@keyframes fadein {
|
||||||
0% {opacity: 0}
|
0% {opacity: 0}
|
||||||
100% {opacity: 1}
|
100% {opacity: 1}
|
||||||
@@ -1243,6 +1283,13 @@ html.y #widget.open {
|
|||||||
0% {opacity:0}
|
0% {opacity:0}
|
||||||
100% {opacity:1}
|
100% {opacity:1}
|
||||||
}
|
}
|
||||||
|
#ggrid>a.glow {
|
||||||
|
animation: gexit .6s ease-out;
|
||||||
|
}
|
||||||
|
@keyframes gexit {
|
||||||
|
0% {box-shadow: 0 0 0 2em var(--a)}
|
||||||
|
100% {box-shadow: 0 0 0em 0em var(--a)}
|
||||||
|
}
|
||||||
#wzip a {
|
#wzip a {
|
||||||
font-size: .4em;
|
font-size: .4em;
|
||||||
margin: -.3em .1em;
|
margin: -.3em .1em;
|
||||||
@@ -1301,6 +1348,7 @@ html.y #widget.open {
|
|||||||
#widget.cmp #wtoggle {
|
#widget.cmp #wtoggle {
|
||||||
font-size: 1.2em;
|
font-size: 1.2em;
|
||||||
}
|
}
|
||||||
|
#widget.cmp #fshr,
|
||||||
#widget.cmp #wtgrid {
|
#widget.cmp #wtgrid {
|
||||||
display: none;
|
display: none;
|
||||||
}
|
}
|
||||||
@@ -1401,10 +1449,15 @@ input[type="checkbox"]+label {
|
|||||||
input[type="radio"]:checked+label,
|
input[type="radio"]:checked+label,
|
||||||
input[type="checkbox"]:checked+label {
|
input[type="checkbox"]:checked+label {
|
||||||
color: #0e0;
|
color: #0e0;
|
||||||
color: var(--a);
|
color: var(--btn-1-bg);
|
||||||
|
}
|
||||||
|
input[type="checkbox"]:checked+label {
|
||||||
|
box-shadow: var(--btn-1-bs);
|
||||||
|
border-bottom: var(--btn-1-bb);
|
||||||
}
|
}
|
||||||
html.dz input {
|
html.dz input {
|
||||||
font-family: 'scp', monospace, monospace;
|
font-family: 'scp', monospace, monospace;
|
||||||
|
font-family: var(--font-mono), 'scp', monospace, monospace;
|
||||||
}
|
}
|
||||||
.opwide div>span>input+label {
|
.opwide div>span>input+label {
|
||||||
padding: .3em 0 .3em .3em;
|
padding: .3em 0 .3em .3em;
|
||||||
@@ -1578,10 +1631,13 @@ html {
|
|||||||
color: var(--btn-fg);
|
color: var(--btn-fg);
|
||||||
background: #eee;
|
background: #eee;
|
||||||
background: var(--btn-bg);
|
background: var(--btn-bg);
|
||||||
|
box-shadow: var(--btn-bs);
|
||||||
|
border-bottom: var(--btn-bb);
|
||||||
border-radius: .3em;
|
border-radius: .3em;
|
||||||
padding: .2em .4em;
|
padding: .2em .4em;
|
||||||
font-size: 1.2em;
|
font-size: 1.2em;
|
||||||
margin: .2em;
|
margin: .2em;
|
||||||
|
display: inline-block;
|
||||||
white-space: pre;
|
white-space: pre;
|
||||||
position: relative;
|
position: relative;
|
||||||
top: -.12em;
|
top: -.12em;
|
||||||
@@ -1590,20 +1646,14 @@ html.c .btn,
|
|||||||
html.a .btn {
|
html.a .btn {
|
||||||
border-radius: .2em;
|
border-radius: .2em;
|
||||||
}
|
}
|
||||||
html.cz .btn {
|
|
||||||
box-shadow: 0 .1em .6em rgba(255,0,185,0.5);
|
|
||||||
border-bottom: .2em solid #709;
|
|
||||||
}
|
|
||||||
html.dz .btn {
|
html.dz .btn {
|
||||||
font-size: 1em;
|
font-size: 1em;
|
||||||
box-shadow: 0 0 0 .1em #080 inset;
|
|
||||||
}
|
|
||||||
html.dz .tgl.btn.on {
|
|
||||||
box-shadow: 0 0 0 .1em var(--btn-1-bg) inset;
|
|
||||||
}
|
}
|
||||||
.btn:hover {
|
.btn:hover {
|
||||||
color: var(--btn-h-fg);
|
color: var(--btn-h-fg);
|
||||||
background: var(--btn-h-bg);
|
background: var(--btn-h-bg);
|
||||||
|
box-shadow: var(--btn-h-bs);
|
||||||
|
border-bottom: var(--btn-h-bb);
|
||||||
}
|
}
|
||||||
.tgl.btn.on {
|
.tgl.btn.on {
|
||||||
background: #000;
|
background: #000;
|
||||||
@@ -1611,14 +1661,14 @@ html.dz .tgl.btn.on {
|
|||||||
color: #fff;
|
color: #fff;
|
||||||
color: var(--btn-1-fg);
|
color: var(--btn-1-fg);
|
||||||
text-shadow: none;
|
text-shadow: none;
|
||||||
}
|
box-shadow: var(--btn-1-bs);
|
||||||
html.cz .tgl.btn.on {
|
border-bottom: var(--btn-1-bb);
|
||||||
box-shadow: 0 .1em .8em rgba(255,205,0,0.9);
|
|
||||||
border-bottom: .2em solid #e90;
|
|
||||||
}
|
}
|
||||||
.tgl.btn.on:hover {
|
.tgl.btn.on:hover {
|
||||||
background: var(--btn-1h-bg);
|
|
||||||
color: var(--btn-1h-fg);
|
color: var(--btn-1h-fg);
|
||||||
|
background: var(--btn-1h-bg);
|
||||||
|
box-shadow: var(--btn-1h-bs);
|
||||||
|
border-bottom: var(--btn-1h-bb);
|
||||||
}
|
}
|
||||||
#detree {
|
#detree {
|
||||||
padding: .3em .5em;
|
padding: .3em .5em;
|
||||||
@@ -1653,7 +1703,9 @@ html.cz .tgl.btn.on {
|
|||||||
color: var(--fg-max);
|
color: var(--fg-max);
|
||||||
}
|
}
|
||||||
#tree ul a.hl {
|
#tree ul a.hl {
|
||||||
|
color: #fff;
|
||||||
color: var(--btn-1-fg);
|
color: var(--btn-1-fg);
|
||||||
|
background: #000;
|
||||||
background: var(--btn-1-bg);
|
background: var(--btn-1-bg);
|
||||||
text-shadow: none;
|
text-shadow: none;
|
||||||
}
|
}
|
||||||
@@ -1688,6 +1740,7 @@ html.y #tree.nowrap .ntree a+a:hover {
|
|||||||
}
|
}
|
||||||
.ntree a:first-child {
|
.ntree a:first-child {
|
||||||
font-family: 'scp', monospace, monospace;
|
font-family: 'scp', monospace, monospace;
|
||||||
|
font-family: var(--font-mono), 'scp', monospace, monospace;
|
||||||
font-size: 1.2em;
|
font-size: 1.2em;
|
||||||
line-height: 0;
|
line-height: 0;
|
||||||
}
|
}
|
||||||
@@ -1715,6 +1768,7 @@ html.y #tree.nowrap .ntree a+a:hover {
|
|||||||
}
|
}
|
||||||
#files th span {
|
#files th span {
|
||||||
position: relative;
|
position: relative;
|
||||||
|
white-space: nowrap;
|
||||||
}
|
}
|
||||||
#files>thead>tr>th.min,
|
#files>thead>tr>th.min,
|
||||||
#files td.min {
|
#files td.min {
|
||||||
@@ -1752,9 +1806,6 @@ html.y #tree.nowrap .ntree a+a:hover {
|
|||||||
margin: .7em 0 .7em .5em;
|
margin: .7em 0 .7em .5em;
|
||||||
padding-left: .5em;
|
padding-left: .5em;
|
||||||
}
|
}
|
||||||
.opwide>div>div>a {
|
|
||||||
line-height: 2em;
|
|
||||||
}
|
|
||||||
.opwide>div>h3 {
|
.opwide>div>h3 {
|
||||||
color: var(--fg-weak);
|
color: var(--fg-weak);
|
||||||
margin: 0 .4em;
|
margin: 0 .4em;
|
||||||
@@ -1769,6 +1820,7 @@ html.y #tree.nowrap .ntree a+a:hover {
|
|||||||
padding: 0;
|
padding: 0;
|
||||||
}
|
}
|
||||||
#thumbs,
|
#thumbs,
|
||||||
|
#au_prescan,
|
||||||
#au_fullpre,
|
#au_fullpre,
|
||||||
#au_os_seek,
|
#au_os_seek,
|
||||||
#au_osd_cv,
|
#au_osd_cv,
|
||||||
@@ -1776,7 +1828,8 @@ html.y #tree.nowrap .ntree a+a:hover {
|
|||||||
opacity: .3;
|
opacity: .3;
|
||||||
}
|
}
|
||||||
#griden.on+#thumbs,
|
#griden.on+#thumbs,
|
||||||
#au_preload.on+#au_fullpre,
|
#au_preload.on+#au_prescan,
|
||||||
|
#au_preload.on+#au_prescan+#au_fullpre,
|
||||||
#au_os_ctl.on+#au_os_seek,
|
#au_os_ctl.on+#au_os_seek,
|
||||||
#au_os_ctl.on+#au_os_seek+#au_osd_cv,
|
#au_os_ctl.on+#au_os_seek+#au_osd_cv,
|
||||||
#u2turbo.on+#u2tdate {
|
#u2turbo.on+#u2tdate {
|
||||||
@@ -1816,6 +1869,11 @@ html.y #tree.nowrap .ntree a+a:hover {
|
|||||||
margin: 0;
|
margin: 0;
|
||||||
padding: 0;
|
padding: 0;
|
||||||
}
|
}
|
||||||
|
#unpost td:nth-child(3),
|
||||||
|
#unpost td:nth-child(4) {
|
||||||
|
text-align: right;
|
||||||
|
}
|
||||||
|
#shui,
|
||||||
#rui {
|
#rui {
|
||||||
background: #fff;
|
background: #fff;
|
||||||
background: var(--bg);
|
background: var(--bg);
|
||||||
@@ -1831,23 +1889,41 @@ html.y #tree.nowrap .ntree a+a:hover {
|
|||||||
padding: 1em;
|
padding: 1em;
|
||||||
z-index: 765;
|
z-index: 765;
|
||||||
}
|
}
|
||||||
|
#shui div+div,
|
||||||
#rui div+div {
|
#rui div+div {
|
||||||
margin-top: 1em;
|
margin-top: 1em;
|
||||||
}
|
}
|
||||||
|
#shui table,
|
||||||
#rui table {
|
#rui table {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
border-collapse: collapse;
|
border-collapse: collapse;
|
||||||
}
|
}
|
||||||
|
#shui button {
|
||||||
|
margin: 0 1em 0 0;
|
||||||
|
}
|
||||||
|
#shui .btn {
|
||||||
|
font-size: 1em;
|
||||||
|
}
|
||||||
|
#shui td {
|
||||||
|
padding: .8em 0;
|
||||||
|
}
|
||||||
|
#shui td+td,
|
||||||
#rui td+td {
|
#rui td+td {
|
||||||
padding: .2em 0 .2em .5em;
|
padding: .2em 0 .2em .5em;
|
||||||
}
|
}
|
||||||
#rn_vadv input {
|
#rn_vadv input {
|
||||||
font-family: 'scp', monospace, monospace;
|
font-family: 'scp', monospace, monospace;
|
||||||
|
font-family: var(--font-mono), 'scp', monospace, monospace;
|
||||||
}
|
}
|
||||||
|
#shui td+td,
|
||||||
#rui td+td,
|
#rui td+td,
|
||||||
|
#shui td input[type="text"],
|
||||||
#rui td input[type="text"] {
|
#rui td input[type="text"] {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
}
|
}
|
||||||
|
#shui td.exs input[type="text"] {
|
||||||
|
width: 3em;
|
||||||
|
}
|
||||||
#rn_f.m td:first-child {
|
#rn_f.m td:first-child {
|
||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
}
|
}
|
||||||
@@ -1906,6 +1982,7 @@ html.y #doc {
|
|||||||
#doc.mdo {
|
#doc.mdo {
|
||||||
white-space: normal;
|
white-space: normal;
|
||||||
font-family: sans-serif;
|
font-family: sans-serif;
|
||||||
|
font-family: var(--font-main), sans-serif;
|
||||||
}
|
}
|
||||||
#doc.prism * {
|
#doc.prism * {
|
||||||
line-height: 1.5em;
|
line-height: 1.5em;
|
||||||
@@ -1965,6 +2042,7 @@ a.btn,
|
|||||||
}
|
}
|
||||||
#hkhelp td:first-child {
|
#hkhelp td:first-child {
|
||||||
font-family: 'scp', monospace, monospace;
|
font-family: 'scp', monospace, monospace;
|
||||||
|
font-family: var(--font-mono), 'scp', monospace, monospace;
|
||||||
}
|
}
|
||||||
html.noscroll,
|
html.noscroll,
|
||||||
html.noscroll .sbar {
|
html.noscroll .sbar {
|
||||||
@@ -2174,6 +2252,7 @@ html.y #bbox-overlay figcaption a {
|
|||||||
}
|
}
|
||||||
#bbox-halp {
|
#bbox-halp {
|
||||||
color: var(--fg-max);
|
color: var(--fg-max);
|
||||||
|
background: #fff;
|
||||||
background: var(--bg);
|
background: var(--bg);
|
||||||
position: absolute;
|
position: absolute;
|
||||||
top: 0;
|
top: 0;
|
||||||
@@ -2473,6 +2552,7 @@ html.y #bbox-overlay figcaption a {
|
|||||||
}
|
}
|
||||||
#op_up2k.srch td.prog {
|
#op_up2k.srch td.prog {
|
||||||
font-family: sans-serif;
|
font-family: sans-serif;
|
||||||
|
font-family: var(--font-main), sans-serif;
|
||||||
font-size: 1em;
|
font-size: 1em;
|
||||||
width: auto;
|
width: auto;
|
||||||
}
|
}
|
||||||
@@ -2487,6 +2567,7 @@ html.y #bbox-overlay figcaption a {
|
|||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
font-family: 'scp', monospace, monospace;
|
font-family: 'scp', monospace, monospace;
|
||||||
|
font-family: var(--font-mono), 'scp', monospace, monospace;
|
||||||
}
|
}
|
||||||
#u2etas.o {
|
#u2etas.o {
|
||||||
width: 20em;
|
width: 20em;
|
||||||
@@ -2556,6 +2637,7 @@ html.y #bbox-overlay figcaption a {
|
|||||||
#u2cards span {
|
#u2cards span {
|
||||||
color: var(--fg-max);
|
color: var(--fg-max);
|
||||||
font-family: 'scp', monospace;
|
font-family: 'scp', monospace;
|
||||||
|
font-family: var(--font-mono), 'scp', monospace;
|
||||||
}
|
}
|
||||||
#u2cards > a:nth-child(4) > span {
|
#u2cards > a:nth-child(4) > span {
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
@@ -2636,23 +2718,25 @@ html.b #u2conf a.b:hover {
|
|||||||
#u2conf input[type="checkbox"]:checked+label {
|
#u2conf input[type="checkbox"]:checked+label {
|
||||||
position: relative;
|
position: relative;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
background: var(--u2-o-bg);
|
background: var(--btn-bg);
|
||||||
border-bottom: .2em solid var(--u2-o-b1);
|
box-shadow: var(--btn-bs);
|
||||||
box-shadow: 0 .1em .3em var(--u2-o-sh) inset;
|
border-bottom: var(--btn-bb);
|
||||||
text-shadow: 1px 1px 1px #000, 1px -1px 1px #000, -1px -1px 1px #000, -1px 1px 1px #000;
|
text-shadow: 1px 1px 1px #000, 1px -1px 1px #000, -1px -1px 1px #000, -1px 1px 1px #000;
|
||||||
}
|
}
|
||||||
#u2conf input[type="checkbox"]:checked+label {
|
#u2conf input[type="checkbox"]:checked+label {
|
||||||
background: var(--u2-o-1-bg);
|
background: var(--btn-1-bg);
|
||||||
border-bottom: .2em solid var(--u2-o-1-b1);
|
box-shadow: var(--btn-1-bs);
|
||||||
box-shadow: 0 .1em .5em var(--u2-o-1-sh);
|
border-bottom: var(--btn-1-bb);
|
||||||
}
|
}
|
||||||
#u2conf input[type="checkbox"]+label:hover {
|
#u2conf input[type="checkbox"]+label:hover {
|
||||||
box-shadow: 0 .1em .3em var(--u2-o-h-sh);
|
background: var(--btn-h-bg);
|
||||||
border-color: var(--u2-o-h-b1);
|
box-shadow: var(--btn-h-bs);
|
||||||
background: var(--u2-o-h-bg);
|
border-bottom: var(--btn-h-bb);
|
||||||
}
|
}
|
||||||
#u2conf input[type="checkbox"]:checked+label:hover {
|
#u2conf input[type="checkbox"]:checked+label:hover {
|
||||||
background: var(--u2-o-1h-bg);
|
background: var(--btn-1h-bg);
|
||||||
|
box-shadow: var(--btn-1h-bs);
|
||||||
|
border-bottom: var(--btn-1h-bb);
|
||||||
}
|
}
|
||||||
#op_up2k.srch #u2conf td:nth-child(2)>*,
|
#op_up2k.srch #u2conf td:nth-child(2)>*,
|
||||||
#op_up2k.srch #u2conf td:nth-child(3)>* {
|
#op_up2k.srch #u2conf td:nth-child(3)>* {
|
||||||
@@ -2721,6 +2805,7 @@ html.b #u2conf a.b:hover {
|
|||||||
}
|
}
|
||||||
.prog {
|
.prog {
|
||||||
font-family: 'scp', monospace, monospace;
|
font-family: 'scp', monospace, monospace;
|
||||||
|
font-family: var(--font-mono), 'scp', monospace, monospace;
|
||||||
}
|
}
|
||||||
#u2tab span.inf,
|
#u2tab span.inf,
|
||||||
#u2tab span.ok,
|
#u2tab span.ok,
|
||||||
@@ -3040,18 +3125,30 @@ html.by #u2cards a.act {
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
html.cy #wrap {
|
|
||||||
color: #000;
|
|
||||||
}
|
|
||||||
html.cy .mdo a {
|
html.cy .mdo a {
|
||||||
background: #f00;
|
background: #f00;
|
||||||
}
|
}
|
||||||
|
html.cy #wrap,
|
||||||
|
html.cy #acc_info a,
|
||||||
html.cy #op_up2k,
|
html.cy #op_up2k,
|
||||||
html.cy #files,
|
html.cy #files,
|
||||||
html.cy #files a,
|
html.cy #files a,
|
||||||
html.cy #files tbody div a:last-child {
|
html.cy #files tbody div a:last-child {
|
||||||
color: #000;
|
color: #000;
|
||||||
}
|
}
|
||||||
|
html.cy #u2tab a,
|
||||||
|
html.cy #u2cards a {
|
||||||
|
color: #f00;
|
||||||
|
}
|
||||||
|
html.cy #unpost a {
|
||||||
|
color: #ff0;
|
||||||
|
}
|
||||||
|
html.cy #barbuf {
|
||||||
|
filter: hue-rotate(267deg) brightness(0.8) contrast(4);
|
||||||
|
}
|
||||||
|
html.cy #pvol {
|
||||||
|
filter: hue-rotate(4deg) contrast(2.2);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@@ -3129,7 +3226,7 @@ html.d #treepar {
|
|||||||
margin-top: 1.7em;
|
margin-top: 1.7em;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@supports (display: grid) {
|
@supports (display: grid) and (gap: 1em) {
|
||||||
#ggrid {
|
#ggrid {
|
||||||
display: grid;
|
display: grid;
|
||||||
margin: 0em 0.25em;
|
margin: 0em 0.25em;
|
||||||
@@ -3154,3 +3251,24 @@ html.d #treepar {
|
|||||||
padding: 0.2em;
|
padding: 0.2em;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
@media (prefers-reduced-motion) {
|
||||||
|
@keyframes spin { }
|
||||||
|
@keyframes gexit { }
|
||||||
|
@keyframes bounce { }
|
||||||
|
@keyframes bounceFromLeft { }
|
||||||
|
@keyframes bounceFromRight { }
|
||||||
|
|
||||||
|
#ggrid>a:before,
|
||||||
|
#widget.anim,
|
||||||
|
#u2tabw,
|
||||||
|
.dropdesc,
|
||||||
|
.dropdesc b,
|
||||||
|
.dropdesc>div>div {
|
||||||
|
transition: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -6,10 +6,10 @@
|
|||||||
<title>{{ title }}</title>
|
<title>{{ title }}</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, minimum-scale=0.6">
|
<meta name="viewport" content="width=device-width, initial-scale=0.8, minimum-scale=0.6">
|
||||||
<meta name="theme-color" content="#333">
|
<meta name="theme-color" content="#{{ tcolor }}">
|
||||||
{{ html_head }}
|
|
||||||
<link rel="stylesheet" media="screen" href="{{ r }}/.cpr/ui.css?_={{ ts }}">
|
<link rel="stylesheet" media="screen" href="{{ r }}/.cpr/ui.css?_={{ ts }}">
|
||||||
<link rel="stylesheet" media="screen" href="{{ r }}/.cpr/browser.css?_={{ ts }}">
|
<link rel="stylesheet" media="screen" href="{{ r }}/.cpr/browser.css?_={{ ts }}">
|
||||||
|
{{ html_head }}
|
||||||
{%- if css %}
|
{%- if css %}
|
||||||
<link rel="stylesheet" media="screen" href="{{ css }}_={{ ts }}">
|
<link rel="stylesheet" media="screen" href="{{ css }}_={{ ts }}">
|
||||||
{%- endif %}
|
{%- endif %}
|
||||||
@@ -67,14 +67,14 @@
|
|||||||
<div id="op_up2k" class="opview"></div>
|
<div id="op_up2k" class="opview"></div>
|
||||||
|
|
||||||
<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">🌲</a>
|
<a href="#" id="entree">🌲</a>
|
||||||
{%- for n in vpnodes %}
|
{%- for n in vpnodes %}
|
||||||
<a href="{{ r }}/{{ n[0] }}">{{ n[1] }}</a>
|
<a href="{{ r }}/{{ n[0] }}">{{ n[1] }}</a>
|
||||||
{%- endfor %}
|
{%- endfor %}
|
||||||
</h1>
|
</h1>
|
||||||
|
|
||||||
<div id="tree"></div>
|
<div id="tree"></div>
|
||||||
|
|
||||||
<div id="wrap">
|
<div id="wrap">
|
||||||
@@ -118,11 +118,11 @@
|
|||||||
|
|
||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
|
|
||||||
<div id="epi" class="logue">{{ "" if sb_lg else logues[1] }}</div>
|
<div id="epi" class="logue">{{ "" if sb_lg else logues[1] }}</div>
|
||||||
|
|
||||||
<h2 id="wfp"><a href="{{ r }}/?h" id="goh">control-panel</a></h2>
|
<h2 id="wfp"><a href="{{ r }}/?h" id="goh">control-panel</a></h2>
|
||||||
|
|
||||||
<a href="#" id="repl">π</a>
|
<a href="#" id="repl">π</a>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
@@ -148,7 +148,8 @@
|
|||||||
logues = {{ logues|tojson if sb_lg else "[]" }},
|
logues = {{ logues|tojson if sb_lg else "[]" }},
|
||||||
ls0 = {{ ls0|tojson }};
|
ls0 = {{ ls0|tojson }};
|
||||||
|
|
||||||
document.documentElement.className = localStorage.cpp_thm || dtheme;
|
var STG = window.localStorage;
|
||||||
|
document.documentElement.className = (STG && STG.cpp_thm) || dtheme;
|
||||||
</script>
|
</script>
|
||||||
<script src="{{ r }}/.cpr/util.js?_={{ ts }}"></script>
|
<script src="{{ r }}/.cpr/util.js?_={{ ts }}"></script>
|
||||||
<script src="{{ r }}/.cpr/baguettebox.js?_={{ ts }}"></script>
|
<script src="{{ r }}/.cpr/baguettebox.js?_={{ ts }}"></script>
|
||||||
@@ -160,3 +161,4 @@
|
|||||||
</body>
|
</body>
|
||||||
|
|
||||||
</html>
|
</html>
|
||||||
|
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -6,7 +6,6 @@
|
|||||||
<title>{{ title }}</title>
|
<title>{{ title }}</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 }}
|
|
||||||
<style>
|
<style>
|
||||||
html{font-family:sans-serif}
|
html{font-family:sans-serif}
|
||||||
td{border:1px solid #999;border-width:1px 1px 0 0;padding:0 5px}
|
td{border:1px solid #999;border-width:1px 1px 0 0;padding:0 5px}
|
||||||
@@ -52,12 +51,13 @@
|
|||||||
|
|
||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
|
|
||||||
{%- if logues[1] %}
|
{%- if logues[1] %}
|
||||||
<div>{{ logues[1] }}</div><br />
|
<div>{{ logues[1] }}</div><br />
|
||||||
{%- endif %}
|
{%- endif %}
|
||||||
|
|
||||||
<h2><a href="{{ r }}/{{ url_suf }}{{ url_suf and '&' or '?' }}h">control-panel</a></h2>
|
<h2><a href="{{ r }}/{{ url_suf }}{{ url_suf and '&' or '?' }}h">control-panel</a></h2>
|
||||||
|
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|
||||||
|
|||||||
@@ -25,3 +25,4 @@
|
|||||||
</body>
|
</body>
|
||||||
|
|
||||||
</html>
|
</html>
|
||||||
|
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ html, body {
|
|||||||
color: #333;
|
color: #333;
|
||||||
background: #eee;
|
background: #eee;
|
||||||
font-family: sans-serif;
|
font-family: sans-serif;
|
||||||
|
font-family: var(--font-main), sans-serif;
|
||||||
line-height: 1.5em;
|
line-height: 1.5em;
|
||||||
}
|
}
|
||||||
html.y #helpbox a {
|
html.y #helpbox a {
|
||||||
@@ -67,6 +68,7 @@ a {
|
|||||||
position: relative;
|
position: relative;
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
font-family: 'scp', monospace, monospace;
|
font-family: 'scp', monospace, monospace;
|
||||||
|
font-family: var(--font-mono), 'scp', monospace, monospace;
|
||||||
font-weight: bold;
|
font-weight: bold;
|
||||||
font-size: 1.3em;
|
font-size: 1.3em;
|
||||||
line-height: .1em;
|
line-height: .1em;
|
||||||
|
|||||||
@@ -3,13 +3,13 @@
|
|||||||
<title>📝 {{ title }}</title>
|
<title>📝 {{ title }}</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.7">
|
<meta name="viewport" content="width=device-width, initial-scale=0.7">
|
||||||
<meta name="theme-color" content="#333">
|
<meta name="theme-color" content="#{{ tcolor }}">
|
||||||
{{ html_head }}
|
|
||||||
<link rel="stylesheet" href="{{ r }}/.cpr/ui.css?_={{ ts }}">
|
<link rel="stylesheet" href="{{ r }}/.cpr/ui.css?_={{ ts }}">
|
||||||
<link rel="stylesheet" href="{{ r }}/.cpr/md.css?_={{ ts }}">
|
<link rel="stylesheet" href="{{ r }}/.cpr/md.css?_={{ ts }}">
|
||||||
{%- if edit %}
|
{%- if edit %}
|
||||||
<link rel="stylesheet" href="{{ r }}/.cpr/md2.css?_={{ ts }}">
|
<link rel="stylesheet" href="{{ r }}/.cpr/md2.css?_={{ ts }}">
|
||||||
{%- endif %}
|
{%- endif %}
|
||||||
|
{{ html_head }}
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<div id="mn"></div>
|
<div id="mn"></div>
|
||||||
@@ -49,7 +49,7 @@
|
|||||||
<div id="mp" class="mdo"></div>
|
<div id="mp" class="mdo"></div>
|
||||||
</div>
|
</div>
|
||||||
<a href="#" id="repl">π</a>
|
<a href="#" id="repl">π</a>
|
||||||
|
|
||||||
{%- if edit %}
|
{%- if edit %}
|
||||||
<div id="helpbox">
|
<div id="helpbox">
|
||||||
<textarea autocomplete="off">
|
<textarea autocomplete="off">
|
||||||
@@ -125,7 +125,7 @@ write markdown (most html is 🙆 too)
|
|||||||
</textarea>
|
</textarea>
|
||||||
</div>
|
</div>
|
||||||
{%- endif %}
|
{%- endif %}
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
|
|
||||||
var SR = {{ r|tojson }},
|
var SR = {{ r|tojson }},
|
||||||
@@ -139,16 +139,15 @@ var md_opt = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
(function () {
|
(function () {
|
||||||
var l = localStorage,
|
var l = window.localStorage,
|
||||||
drk = l.light != 1,
|
drk = (l && 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.className = 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;
|
try { l.light = drk? 0:1; } catch (ex) { }
|
||||||
};
|
};
|
||||||
|
|
||||||
btn.onclick = f;
|
btn.onclick = f;
|
||||||
f();
|
f();
|
||||||
})();
|
})();
|
||||||
@@ -160,4 +159,8 @@ l.light = drk? 0:1;
|
|||||||
{%- if edit %}
|
{%- if edit %}
|
||||||
<script src="{{ r }}/.cpr/md2.js?_={{ ts }}"></script>
|
<script src="{{ r }}/.cpr/md2.js?_={{ ts }}"></script>
|
||||||
{%- endif %}
|
{%- endif %}
|
||||||
|
{%- if js %}
|
||||||
|
<script src="{{ js }}_={{ ts }}"></script>
|
||||||
|
{%- endif %}
|
||||||
</body></html>
|
</body></html>
|
||||||
|
|
||||||
|
|||||||
@@ -216,6 +216,11 @@ function convert_markdown(md_text, dest_dom) {
|
|||||||
md_html = DOMPurify.sanitize(md_html);
|
md_html = DOMPurify.sanitize(md_html);
|
||||||
}
|
}
|
||||||
catch (ex) {
|
catch (ex) {
|
||||||
|
if (IE) {
|
||||||
|
dest_dom.innerHTML = 'IE cannot into markdown ;_;';
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
if (ext)
|
if (ext)
|
||||||
md_plug_err(ex, ext[1]);
|
md_plug_err(ex, ext[1]);
|
||||||
|
|
||||||
@@ -507,13 +512,6 @@ dom_navtgl.onclick = function () {
|
|||||||
redraw();
|
redraw();
|
||||||
};
|
};
|
||||||
|
|
||||||
if (!HTTPS && location.hostname != '127.0.0.1') try {
|
|
||||||
ebi('edit2').onclick = function (e) {
|
|
||||||
toast.err(0, "the fancy editor is only available over https");
|
|
||||||
return ev(e);
|
|
||||||
}
|
|
||||||
} catch (ex) { }
|
|
||||||
|
|
||||||
if (sread('hidenav') == 1)
|
if (sread('hidenav') == 1)
|
||||||
dom_navtgl.onclick();
|
dom_navtgl.onclick();
|
||||||
|
|
||||||
|
|||||||
@@ -9,7 +9,7 @@
|
|||||||
width: calc(100% - 56em);
|
width: calc(100% - 56em);
|
||||||
}
|
}
|
||||||
#mw {
|
#mw {
|
||||||
left: calc(100% - 55em);
|
left: max(0em, calc(100% - 55em));
|
||||||
overflow-y: auto;
|
overflow-y: auto;
|
||||||
position: fixed;
|
position: fixed;
|
||||||
bottom: 0;
|
bottom: 0;
|
||||||
@@ -56,6 +56,7 @@
|
|||||||
padding: 0;
|
padding: 0;
|
||||||
margin: 0;
|
margin: 0;
|
||||||
font-family: 'scp', monospace, monospace;
|
font-family: 'scp', monospace, monospace;
|
||||||
|
font-family: var(--font-mono), 'scp', monospace, monospace;
|
||||||
white-space: pre-wrap;
|
white-space: pre-wrap;
|
||||||
word-break: break-word;
|
word-break: break-word;
|
||||||
overflow-wrap: break-word;
|
overflow-wrap: break-word;
|
||||||
|
|||||||
@@ -163,7 +163,7 @@ redraw = (function () {
|
|||||||
dom_sbs.onclick = setsbs;
|
dom_sbs.onclick = setsbs;
|
||||||
dom_nsbs.onclick = modetoggle;
|
dom_nsbs.onclick = modetoggle;
|
||||||
|
|
||||||
onresize();
|
(IE ? modetoggle : onresize)();
|
||||||
return onresize;
|
return onresize;
|
||||||
})();
|
})();
|
||||||
|
|
||||||
@@ -368,14 +368,14 @@ function save(e) {
|
|||||||
|
|
||||||
function save_cb() {
|
function save_cb() {
|
||||||
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\nError ' + this.status + ":\n" + unpre(this.responseText));
|
||||||
|
|
||||||
var r;
|
var r;
|
||||||
try {
|
try {
|
||||||
r = JSON.parse(this.responseText);
|
r = JSON.parse(this.responseText);
|
||||||
}
|
}
|
||||||
catch (ex) {
|
catch (ex) {
|
||||||
return toast.err(0, 'Failed to parse reply from server:\n\n' + this.responseText);
|
return toast.err(0, 'Error! The file was likely NOT saved.\n\nFailed to parse reply from server:\n\n' + unpre(this.responseText));
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!r.ok) {
|
if (!r.ok) {
|
||||||
@@ -418,7 +418,7 @@ function run_savechk(lastmod, txt, btn, ntry) {
|
|||||||
|
|
||||||
function savechk_cb() {
|
function savechk_cb() {
|
||||||
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\nError ' + this.status + ":\n" + unpre(this.responseText));
|
||||||
|
|
||||||
var doc1 = this.txt.replace(/\r\n/g, "\n");
|
var doc1 = this.txt.replace(/\r\n/g, "\n");
|
||||||
var doc2 = this.responseText.replace(/\r\n/g, "\n");
|
var doc2 = this.responseText.replace(/\r\n/g, "\n");
|
||||||
@@ -607,10 +607,10 @@ function md_newline() {
|
|||||||
var s = linebounds(true),
|
var s = linebounds(true),
|
||||||
ln = s.md.substring(s.n1, s.n2),
|
ln = s.md.substring(s.n1, s.n2),
|
||||||
m1 = /^( *)([0-9]+)(\. +)/.exec(ln),
|
m1 = /^( *)([0-9]+)(\. +)/.exec(ln),
|
||||||
m2 = /^[ \t>+-]*(\* )?/.exec(ln),
|
m2 = /^[ \t]*[>+*-]{0,2}[ \t]/.exec(ln),
|
||||||
drop = dom_src.selectionEnd - dom_src.selectionStart;
|
drop = dom_src.selectionEnd - dom_src.selectionStart;
|
||||||
|
|
||||||
var pre = m2[0];
|
var pre = m2 ? m2[0] : '';
|
||||||
if (m1 !== null)
|
if (m1 !== null)
|
||||||
pre = m1[1] + (parseInt(m1[2]) + 1) + m1[3];
|
pre = m1[1] + (parseInt(m1[2]) + 1) + m1[3];
|
||||||
|
|
||||||
@@ -931,7 +931,13 @@ var set_lno = (function () {
|
|||||||
// hotkeys / toolbar
|
// hotkeys / toolbar
|
||||||
(function () {
|
(function () {
|
||||||
var keydown = function (ev) {
|
var keydown = function (ev) {
|
||||||
ev = ev || window.event;
|
if (!ev && window.event) {
|
||||||
|
ev = window.event;
|
||||||
|
if (dev_fbw == 1) {
|
||||||
|
toast.warn(10, 'hello from fallback code ;_;\ncheck console trace');
|
||||||
|
console.error('using window.event');
|
||||||
|
}
|
||||||
|
}
|
||||||
var kc = ev.code || ev.keyCode || ev.which,
|
var kc = ev.code || ev.keyCode || ev.which,
|
||||||
editing = document.activeElement == dom_src;
|
editing = document.activeElement == dom_src;
|
||||||
|
|
||||||
@@ -1003,7 +1009,7 @@ var set_lno = (function () {
|
|||||||
md_home(ev.shiftKey);
|
md_home(ev.shiftKey);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
if (!ev.shiftKey && (ev.code == "Enter" || kc == 13)) {
|
if (!ev.shiftKey && ((ev.code + '').endsWith("Enter") || kc == 13)) {
|
||||||
return md_newline();
|
return md_newline();
|
||||||
}
|
}
|
||||||
if (!ev.shiftKey && kc == 8) {
|
if (!ev.shiftKey && kc == 8) {
|
||||||
|
|||||||
@@ -17,6 +17,7 @@ html, body {
|
|||||||
padding: 0;
|
padding: 0;
|
||||||
min-height: 100%;
|
min-height: 100%;
|
||||||
font-family: sans-serif;
|
font-family: sans-serif;
|
||||||
|
font-family: var(--font-main), sans-serif;
|
||||||
background: #f7f7f7;
|
background: #f7f7f7;
|
||||||
color: #333;
|
color: #333;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,12 +3,12 @@
|
|||||||
<title>📝 {{ title }}</title>
|
<title>📝 {{ title }}</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.7">
|
<meta name="viewport" content="width=device-width, initial-scale=0.7">
|
||||||
<meta name="theme-color" content="#333">
|
<meta name="theme-color" content="#{{ tcolor }}">
|
||||||
{{ html_head }}
|
|
||||||
<link rel="stylesheet" href="{{ r }}/.cpr/ui.css?_={{ ts }}">
|
<link rel="stylesheet" href="{{ r }}/.cpr/ui.css?_={{ ts }}">
|
||||||
<link rel="stylesheet" href="{{ r }}/.cpr/mde.css?_={{ ts }}">
|
<link rel="stylesheet" href="{{ r }}/.cpr/mde.css?_={{ ts }}">
|
||||||
<link rel="stylesheet" href="{{ r }}/.cpr/deps/mini-fa.css?_={{ ts }}">
|
<link rel="stylesheet" href="{{ r }}/.cpr/deps/mini-fa.css?_={{ ts }}">
|
||||||
<link rel="stylesheet" href="{{ r }}/.cpr/deps/easymde.css?_={{ ts }}">
|
<link rel="stylesheet" href="{{ r }}/.cpr/deps/easymde.css?_={{ ts }}">
|
||||||
|
{{ html_head }}
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<div id="mw">
|
<div id="mw">
|
||||||
@@ -37,12 +37,12 @@ var md_opt = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
var lightswitch = (function () {
|
var lightswitch = (function () {
|
||||||
var l = localStorage,
|
var l = window.localStorage,
|
||||||
drk = l.light != 1,
|
drk = (l && l.light) != 1,
|
||||||
f = function (e) {
|
f = function (e) {
|
||||||
if (e) drk = !drk;
|
if (e) drk = !drk;
|
||||||
document.documentElement.className = drk? "z":"y";
|
document.documentElement.className = drk? "z":"y";
|
||||||
l.light = drk? 0:1;
|
try { l.light = drk? 0:1; } catch (ex) { }
|
||||||
};
|
};
|
||||||
f();
|
f();
|
||||||
return f;
|
return f;
|
||||||
@@ -53,4 +53,8 @@ l.light = drk? 0:1;
|
|||||||
<script src="{{ r }}/.cpr/deps/marked.js?_={{ ts }}"></script>
|
<script src="{{ r }}/.cpr/deps/marked.js?_={{ ts }}"></script>
|
||||||
<script src="{{ r }}/.cpr/deps/easymde.js?_={{ ts }}"></script>
|
<script src="{{ r }}/.cpr/deps/easymde.js?_={{ ts }}"></script>
|
||||||
<script src="{{ r }}/.cpr/mde.js?_={{ ts }}"></script>
|
<script src="{{ r }}/.cpr/mde.js?_={{ ts }}"></script>
|
||||||
|
{%- if js %}
|
||||||
|
<script src="{{ js }}_={{ ts }}"></script>
|
||||||
|
{%- endif %}
|
||||||
</body></html>
|
</body></html>
|
||||||
|
|
||||||
|
|||||||
@@ -134,14 +134,14 @@ function save(mde) {
|
|||||||
|
|
||||||
function save_cb() {
|
function save_cb() {
|
||||||
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\nError ' + this.status + ":\n" + unpre(this.responseText));
|
||||||
|
|
||||||
var r;
|
var r;
|
||||||
try {
|
try {
|
||||||
r = JSON.parse(this.responseText);
|
r = JSON.parse(this.responseText);
|
||||||
}
|
}
|
||||||
catch (ex) {
|
catch (ex) {
|
||||||
return toast.err(0, 'Failed to parse reply from server:\n\n' + this.responseText);
|
return toast.err(0, 'Error! The file was likely NOT saved.\n\nFailed to parse reply from server:\n\n' + unpre(this.responseText));
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!r.ok) {
|
if (!r.ok) {
|
||||||
@@ -180,7 +180,7 @@ function save_cb() {
|
|||||||
|
|
||||||
function save_chk() {
|
function save_chk() {
|
||||||
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\nError ' + this.status + ":\n" + unpre(this.responseText));
|
||||||
|
|
||||||
var doc1 = this.txt.replace(/\r\n/g, "\n");
|
var doc1 = this.txt.replace(/\r\n/g, "\n");
|
||||||
var doc2 = this.responseText.replace(/\r\n/g, "\n");
|
var doc2 = this.responseText.replace(/\r\n/g, "\n");
|
||||||
|
|||||||
@@ -1,3 +1,8 @@
|
|||||||
|
:root {
|
||||||
|
--font-main: sans-serif;
|
||||||
|
--font-serif: serif;
|
||||||
|
--font-mono: 'scp';
|
||||||
|
}
|
||||||
html,body,tr,th,td,#files,a {
|
html,body,tr,th,td,#files,a {
|
||||||
color: inherit;
|
color: inherit;
|
||||||
background: none;
|
background: none;
|
||||||
@@ -10,6 +15,7 @@ html {
|
|||||||
color: #ccc;
|
color: #ccc;
|
||||||
background: #333;
|
background: #333;
|
||||||
font-family: sans-serif;
|
font-family: sans-serif;
|
||||||
|
font-family: var(--font-main), sans-serif;
|
||||||
text-shadow: 1px 1px 0px #000;
|
text-shadow: 1px 1px 0px #000;
|
||||||
touch-action: manipulation;
|
touch-action: manipulation;
|
||||||
}
|
}
|
||||||
@@ -23,6 +29,7 @@ html, body {
|
|||||||
}
|
}
|
||||||
pre {
|
pre {
|
||||||
font-family: monospace, monospace;
|
font-family: monospace, monospace;
|
||||||
|
font-family: var(--font-mono), monospace, monospace;
|
||||||
}
|
}
|
||||||
a {
|
a {
|
||||||
color: #fc5;
|
color: #fc5;
|
||||||
|
|||||||
@@ -6,9 +6,9 @@
|
|||||||
<title>{{ s_doctitle }}</title>
|
<title>{{ s_doctitle }}</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">
|
||||||
<meta name="theme-color" content="#333">
|
<meta name="theme-color" content="#{{ tcolor }}">
|
||||||
{{ html_head }}
|
|
||||||
<link rel="stylesheet" media="screen" href="{{ r }}/.cpr/msg.css?_={{ ts }}">
|
<link rel="stylesheet" media="screen" href="{{ r }}/.cpr/msg.css?_={{ ts }}">
|
||||||
|
{{ html_head }}
|
||||||
</head>
|
</head>
|
||||||
|
|
||||||
<body>
|
<body>
|
||||||
@@ -46,6 +46,10 @@
|
|||||||
}, 1000);
|
}, 1000);
|
||||||
</script>
|
</script>
|
||||||
{%- endif %}
|
{%- endif %}
|
||||||
|
{%- if js %}
|
||||||
|
<script src="{{ js }}_={{ ts }}"></script>
|
||||||
|
{%- endif %}
|
||||||
</body>
|
</body>
|
||||||
|
|
||||||
</html>
|
</html>
|
||||||
|
|
||||||
|
|||||||
82
copyparty/web/shares.css
Normal file
82
copyparty/web/shares.css
Normal file
@@ -0,0 +1,82 @@
|
|||||||
|
html {
|
||||||
|
color: #333;
|
||||||
|
background: #f7f7f7;
|
||||||
|
font-family: sans-serif;
|
||||||
|
font-family: var(--font-main), sans-serif;
|
||||||
|
touch-action: manipulation;
|
||||||
|
}
|
||||||
|
#wrap {
|
||||||
|
margin: 2em auto;
|
||||||
|
padding: 0 1em 3em 1em;
|
||||||
|
line-height: 2.3em;
|
||||||
|
}
|
||||||
|
#wrap>span {
|
||||||
|
margin: 0 0 0 1em;
|
||||||
|
border-bottom: 1px solid #999;
|
||||||
|
}
|
||||||
|
li {
|
||||||
|
margin: 1em 0;
|
||||||
|
}
|
||||||
|
a {
|
||||||
|
color: #047;
|
||||||
|
background: #fff;
|
||||||
|
text-decoration: none;
|
||||||
|
white-space: nowrap;
|
||||||
|
border-bottom: 1px solid #8ab;
|
||||||
|
border-radius: .2em;
|
||||||
|
padding: .2em .6em;
|
||||||
|
margin: 0 .3em;
|
||||||
|
}
|
||||||
|
td a {
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
#w {
|
||||||
|
color: #fff;
|
||||||
|
background: #940;
|
||||||
|
border-color: #b70;
|
||||||
|
}
|
||||||
|
#repl {
|
||||||
|
border: none;
|
||||||
|
background: none;
|
||||||
|
color: inherit;
|
||||||
|
padding: 0;
|
||||||
|
position: fixed;
|
||||||
|
bottom: .25em;
|
||||||
|
left: .2em;
|
||||||
|
}
|
||||||
|
table {
|
||||||
|
border-collapse: collapse;
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
th {
|
||||||
|
top: -1px;
|
||||||
|
position: sticky;
|
||||||
|
background: #f7f7f7;
|
||||||
|
}
|
||||||
|
td, th {
|
||||||
|
padding: .3em .6em;
|
||||||
|
text-align: left;
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
td+td+td+td+td+td+td+td {
|
||||||
|
font-family: var(--font-mono), monospace, monospace;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
html.z {
|
||||||
|
background: #222;
|
||||||
|
color: #ccc;
|
||||||
|
}
|
||||||
|
html.z a {
|
||||||
|
color: #fff;
|
||||||
|
background: #057;
|
||||||
|
border-color: #37a;
|
||||||
|
}
|
||||||
|
html.z th {
|
||||||
|
background: #222;
|
||||||
|
}
|
||||||
|
html.bz {
|
||||||
|
color: #bbd;
|
||||||
|
background: #11121d;
|
||||||
|
}
|
||||||
76
copyparty/web/shares.html
Normal file
76
copyparty/web/shares.html
Normal file
@@ -0,0 +1,76 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8">
|
||||||
|
<title>{{ s_doctitle }}</title>
|
||||||
|
<meta http-equiv="X-UA-Compatible" content="IE=edge">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=0.8">
|
||||||
|
<meta name="theme-color" content="#{{ tcolor }}">
|
||||||
|
<link rel="stylesheet" media="screen" href="{{ r }}/.cpr/shares.css?_={{ ts }}">
|
||||||
|
<link rel="stylesheet" media="screen" href="{{ r }}/.cpr/ui.css?_={{ ts }}">
|
||||||
|
{{ html_head }}
|
||||||
|
</head>
|
||||||
|
|
||||||
|
<body>
|
||||||
|
<div id="wrap">
|
||||||
|
<a id="a" href="{{ r }}/?shares" class="af">refresh</a>
|
||||||
|
<a id="a" href="{{ r }}/?h" class="af">control-panel</a>
|
||||||
|
|
||||||
|
<span>axs = perms (read,write,move,delet)</span>
|
||||||
|
<span>nf = numFiles (0=dir)</span>
|
||||||
|
<span>min/hrs = time left</span>
|
||||||
|
|
||||||
|
<table id="tab"><thead><tr>
|
||||||
|
<th>delete</th>
|
||||||
|
<th>sharekey</th>
|
||||||
|
<th>pw</th>
|
||||||
|
<th>source</th>
|
||||||
|
<th>axs</th>
|
||||||
|
<th>nf</th>
|
||||||
|
<th>user</th>
|
||||||
|
<th>created</th>
|
||||||
|
<th>expires</th>
|
||||||
|
<th>min</th>
|
||||||
|
<th>hrs</th>
|
||||||
|
<th>add time</th>
|
||||||
|
</tr></thead><tbody>
|
||||||
|
{% for k, pw, vp, pr, st, un, t0, t1 in rows %}
|
||||||
|
<tr>
|
||||||
|
<td><a href="#" k="{{ k }}">delete</a></td>
|
||||||
|
<td><a href="{{ r }}{{ shr }}{{ k }}">{{ k }}</a></td>
|
||||||
|
<td>{{ pw }}</td>
|
||||||
|
<td><a href="{{ r }}/{{ vp|e }}">{{ vp|e }}</a></td>
|
||||||
|
<td>{{ pr }}</td>
|
||||||
|
<td>{{ st }}</td>
|
||||||
|
<td>{{ un|e }}</td>
|
||||||
|
<td>{{ t0 }}</td>
|
||||||
|
<td>{{ t1 }}</td>
|
||||||
|
<td>{{ "inf" if not t1 else "dead" if t1 < now else ((t1 - now) / 60) | round(1) }}</td>
|
||||||
|
<td>{{ "inf" if not t1 else "dead" if t1 < now else ((t1 - now) / 3600) | round(1) }}</td>
|
||||||
|
<td></td>
|
||||||
|
</tr>
|
||||||
|
{% endfor %}
|
||||||
|
</tbody></table>
|
||||||
|
{% if not rows %}
|
||||||
|
(you don't have any active shares btw)
|
||||||
|
{% endif %}
|
||||||
|
<script>
|
||||||
|
|
||||||
|
var SR = {{ r|tojson }},
|
||||||
|
shr="{{ shr }}",
|
||||||
|
lang="{{ lang }}",
|
||||||
|
dfavico="{{ favico }}";
|
||||||
|
|
||||||
|
var STG = window.localStorage;
|
||||||
|
document.documentElement.className = (STG && STG.cpp_thm) || "{{ this.args.theme }}";
|
||||||
|
|
||||||
|
</script>
|
||||||
|
<script src="{{ r }}/.cpr/util.js?_={{ ts }}"></script>
|
||||||
|
<script src="{{ r }}/.cpr/shares.js?_={{ ts }}"></script>
|
||||||
|
{%- if js %}
|
||||||
|
<script src="{{ js }}_={{ ts }}"></script>
|
||||||
|
{%- endif %}
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
|
|
||||||
56
copyparty/web/shares.js
Normal file
56
copyparty/web/shares.js
Normal file
@@ -0,0 +1,56 @@
|
|||||||
|
var t = QSA('a[k]');
|
||||||
|
for (var a = 0; a < t.length; a++)
|
||||||
|
t[a].onclick = rm;
|
||||||
|
|
||||||
|
function rm() {
|
||||||
|
var u = SR + shr + uricom_enc(this.getAttribute('k')) + '?eshare=rm',
|
||||||
|
xhr = new XHR();
|
||||||
|
|
||||||
|
xhr.open('POST', u, true);
|
||||||
|
xhr.onload = xhr.onerror = cb;
|
||||||
|
xhr.send();
|
||||||
|
}
|
||||||
|
|
||||||
|
function bump() {
|
||||||
|
var k = this.closest('tr').getElementsByTagName('a')[0].getAttribute('k'),
|
||||||
|
u = SR + shr + uricom_enc(k) + '?eshare=' + this.value,
|
||||||
|
xhr = new XHR();
|
||||||
|
|
||||||
|
xhr.open('POST', u, true);
|
||||||
|
xhr.onload = xhr.onerror = cb;
|
||||||
|
xhr.send();
|
||||||
|
}
|
||||||
|
|
||||||
|
function cb() {
|
||||||
|
if (this.status !== 200)
|
||||||
|
return modal.alert('<h6>server error</h6>' + esc(unpre(this.responseText)));
|
||||||
|
|
||||||
|
document.location = '?shares';
|
||||||
|
}
|
||||||
|
|
||||||
|
(function() {
|
||||||
|
var tab = ebi('tab').tBodies[0],
|
||||||
|
tr = Array.prototype.slice.call(tab.rows, 0);
|
||||||
|
|
||||||
|
var buf = [];
|
||||||
|
for (var a = 0; a < tr.length; a++)
|
||||||
|
for (var b = 7; b < 9; b++)
|
||||||
|
buf.push(parseInt(tr[a].cells[b].innerHTML));
|
||||||
|
|
||||||
|
var ibuf = 0;
|
||||||
|
for (var a = 0; a < tr.length; a++)
|
||||||
|
for (var b = 7; b < 9; b++) {
|
||||||
|
var v = buf[ibuf++];
|
||||||
|
tr[a].cells[b].innerHTML =
|
||||||
|
v ? unix2iso(v).replace(' ', ', ') : 'never';
|
||||||
|
}
|
||||||
|
|
||||||
|
for (var a = 0; a < tr.length; a++)
|
||||||
|
tr[a].cells[11].innerHTML =
|
||||||
|
'<button value="1">1min</button> ' +
|
||||||
|
'<button value="60">1h</button>';
|
||||||
|
|
||||||
|
var btns = QSA('td button'), aa = btns.length;
|
||||||
|
for (var a = 0; a < aa; a++)
|
||||||
|
btns[a].onclick = bump;
|
||||||
|
})();
|
||||||
@@ -2,6 +2,7 @@ html {
|
|||||||
color: #333;
|
color: #333;
|
||||||
background: #f7f7f7;
|
background: #f7f7f7;
|
||||||
font-family: sans-serif;
|
font-family: sans-serif;
|
||||||
|
font-family: var(--font-main), sans-serif;
|
||||||
touch-action: manipulation;
|
touch-action: manipulation;
|
||||||
}
|
}
|
||||||
#wrap {
|
#wrap {
|
||||||
@@ -52,7 +53,7 @@ a.r {
|
|||||||
border-color: #c7a;
|
border-color: #c7a;
|
||||||
}
|
}
|
||||||
a.g {
|
a.g {
|
||||||
color: #2b0;
|
color: #0a0;
|
||||||
border-color: #3a0;
|
border-color: #3a0;
|
||||||
box-shadow: 0 .3em 1em #4c0;
|
box-shadow: 0 .3em 1em #4c0;
|
||||||
}
|
}
|
||||||
@@ -127,6 +128,7 @@ pre, code {
|
|||||||
color: #480;
|
color: #480;
|
||||||
background: #fff;
|
background: #fff;
|
||||||
font-family: 'scp', monospace, monospace;
|
font-family: 'scp', monospace, monospace;
|
||||||
|
font-family: var(--font-mono), 'scp', monospace, monospace;
|
||||||
border: 1px solid rgba(128,128,128,0.3);
|
border: 1px solid rgba(128,128,128,0.3);
|
||||||
border-radius: .2em;
|
border-radius: .2em;
|
||||||
padding: .15em .2em;
|
padding: .15em .2em;
|
||||||
@@ -150,11 +152,13 @@ pre b,
|
|||||||
code b {
|
code b {
|
||||||
color: #000;
|
color: #000;
|
||||||
font-weight: normal;
|
font-weight: normal;
|
||||||
text-shadow: 0 0 .2em #0f0;
|
text-shadow: 0 0 .2em #3f3;
|
||||||
|
border-bottom: 1px solid #090;
|
||||||
}
|
}
|
||||||
html.z pre b,
|
html.z pre b,
|
||||||
html.z code b {
|
html.z code b {
|
||||||
color: #fff;
|
color: #fff;
|
||||||
|
border-bottom: 1px solid #9f9;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -180,19 +184,35 @@ html.z a.g {
|
|||||||
border-color: #af4;
|
border-color: #af4;
|
||||||
box-shadow: 0 .3em 1em #7d0;
|
box-shadow: 0 .3em 1em #7d0;
|
||||||
}
|
}
|
||||||
|
form {
|
||||||
|
line-height: 2.5em;
|
||||||
|
}
|
||||||
|
#x,
|
||||||
input {
|
input {
|
||||||
color: #a50;
|
color: #a50;
|
||||||
background: #fff;
|
background: #fff;
|
||||||
border: 1px solid #a50;
|
border: 1px solid #a50;
|
||||||
border-radius: .5em;
|
border-radius: .3em;
|
||||||
padding: .5em .7em;
|
padding: .25em .6em;
|
||||||
margin: 0 .5em 0 0;
|
margin: 0 .3em 0 0;
|
||||||
|
font-size: 1em;
|
||||||
}
|
}
|
||||||
|
input::placeholder {
|
||||||
|
font-size: 1.2em;
|
||||||
|
font-style: italic;
|
||||||
|
letter-spacing: .04em;
|
||||||
|
opacity: 0.64;
|
||||||
|
color: #930;
|
||||||
|
}
|
||||||
|
#x,
|
||||||
html.z input {
|
html.z input {
|
||||||
color: #fff;
|
color: #fff;
|
||||||
background: #626;
|
background: #626;
|
||||||
border-color: #c2c;
|
border-color: #c2c;
|
||||||
}
|
}
|
||||||
|
html.z input::placeholder {
|
||||||
|
color: #fff;
|
||||||
|
}
|
||||||
html.z .num {
|
html.z .num {
|
||||||
border-color: #777;
|
border-color: #777;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,14 +6,15 @@
|
|||||||
<title>{{ s_doctitle }}</title>
|
<title>{{ s_doctitle }}</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">
|
||||||
<meta name="theme-color" content="#333">
|
<meta name="theme-color" content="#{{ tcolor }}">
|
||||||
{{ html_head }}
|
|
||||||
<link rel="stylesheet" media="screen" href="{{ r }}/.cpr/splash.css?_={{ ts }}">
|
<link rel="stylesheet" media="screen" href="{{ r }}/.cpr/splash.css?_={{ ts }}">
|
||||||
<link rel="stylesheet" media="screen" href="{{ r }}/.cpr/ui.css?_={{ ts }}">
|
<link rel="stylesheet" media="screen" href="{{ r }}/.cpr/ui.css?_={{ ts }}">
|
||||||
|
{{ html_head }}
|
||||||
</head>
|
</head>
|
||||||
|
|
||||||
<body>
|
<body>
|
||||||
<div id="wrap">
|
<div id="wrap">
|
||||||
|
{%- if not in_shr %}
|
||||||
<a id="a" href="{{ r }}/?h" class="af">refresh</a>
|
<a id="a" href="{{ r }}/?h" class="af">refresh</a>
|
||||||
<a id="v" href="{{ r }}/?hc" class="af">connect</a>
|
<a id="v" href="{{ r }}/?hc" class="af">connect</a>
|
||||||
|
|
||||||
@@ -21,7 +22,8 @@
|
|||||||
<p id="b">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 id="c" href="{{ r }}/?pw=x" class="logout">logout</a>
|
<a id="c" href="{{ r }}/?pw=x" class="logout">logout</a>
|
||||||
<p><span id="m">welcome back,</span> <strong>{{ this.uname }}</strong></p>
|
<p><span id="m">welcome back,</span> <strong>{{ this.uname|e }}</strong></p>
|
||||||
|
{%- endif %}
|
||||||
{%- endif %}
|
{%- endif %}
|
||||||
|
|
||||||
{%- if msg %}
|
{%- if msg %}
|
||||||
@@ -58,6 +60,18 @@
|
|||||||
</div>
|
</div>
|
||||||
{%- endif %}
|
{%- endif %}
|
||||||
|
|
||||||
|
{%- if ups %}
|
||||||
|
<h1 id="aa">incoming files:</h1>
|
||||||
|
<table class="vols">
|
||||||
|
<thead><tr><th>%</th><th>speed</th><th>eta</th><th>idle</th><th>dir</th><th>file</th></tr></thead>
|
||||||
|
<tbody>
|
||||||
|
{% for u in ups %}
|
||||||
|
<tr><td>{{ u[0] }}</td><td>{{ u[1] }}</td><td>{{ u[2] }}</td><td>{{ u[3] }}</td><td><a href="{{ u[4] }}">{{ u[5]|e }}</a></td><td>{{ u[6]|e }}</td></tr>
|
||||||
|
{% endfor %}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
{%- endif %}
|
||||||
|
|
||||||
{%- if rvol %}
|
{%- if rvol %}
|
||||||
<h1 id="f">you can browse:</h1>
|
<h1 id="f">you can browse:</h1>
|
||||||
<ul>
|
<ul>
|
||||||
@@ -76,29 +90,55 @@
|
|||||||
</ul>
|
</ul>
|
||||||
{%- endif %}
|
{%- endif %}
|
||||||
|
|
||||||
<h1 id="cc">client config:</h1>
|
{%- if in_shr %}
|
||||||
|
<h1 id="z">unlock this share:</h1>
|
||||||
|
<div>
|
||||||
|
<form id="lf" method="post" enctype="multipart/form-data" action="{{ r }}/{{ qvpath }}">
|
||||||
|
<input type="hidden" id="la" name="act" value="login" />
|
||||||
|
<input type="password" id="lp" name="cppwd" placeholder=" password" />
|
||||||
|
<input type="hidden" name="uhash" id="uhash" value="x" />
|
||||||
|
<input type="submit" id="ls" value="Unlock" />
|
||||||
|
{% if ahttps %}
|
||||||
|
<a id="w" href="{{ ahttps }}">switch to https</a>
|
||||||
|
{% endif %}
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
{%- else %}
|
||||||
|
<h1 id="l">login for more:</h1>
|
||||||
|
<div>
|
||||||
|
<form id="lf" method="post" enctype="multipart/form-data" action="{{ r }}/{{ qvpath }}">
|
||||||
|
<input type="hidden" id="la" name="act" value="login" />
|
||||||
|
<input type="password" id="lp" name="cppwd" placeholder=" password" />
|
||||||
|
<input type="hidden" name="uhash" id="uhash" value="x" />
|
||||||
|
<input type="submit" id="ls" value="Login" />
|
||||||
|
{% if chpw %}
|
||||||
|
<a id="x" href="#">change password</a>
|
||||||
|
{% endif %}
|
||||||
|
{% if ahttps %}
|
||||||
|
<a id="w" href="{{ ahttps }}">switch to https</a>
|
||||||
|
{% endif %}
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
{%- endif %}
|
||||||
|
|
||||||
|
<h1 id="cc">other stuff:</h1>
|
||||||
<ul>
|
<ul>
|
||||||
|
{%- if this.uname != '*' and this.args.shr %}
|
||||||
|
<li><a id="y" href="{{ r }}/?shares">edit shares</a></li>
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
{% if k304 or k304vis %}
|
||||||
{% if k304 %}
|
{% if k304 %}
|
||||||
<li><a id="h" href="{{ r }}/?k304=n">disable k304</a> (currently enabled)
|
<li><a id="h" href="{{ r }}/?k304=n">disable k304</a> (currently enabled)
|
||||||
{%- else %}
|
{%- else %}
|
||||||
<li><a id="i" href="{{ r }}/?k304=y" class="r">enable k304</a> (currently disabled)
|
<li><a id="i" href="{{ r }}/?k304=y" class="r">enable k304</a> (currently disabled)
|
||||||
{% endif %}
|
{% endif %}
|
||||||
<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>
|
<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>
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
<li><a id="k" href="{{ r }}/?reset" class="r" onclick="localStorage.clear();return true">reset client settings</a></li>
|
<li><a id="k" href="{{ r }}/?reset" class="r" onclick="localStorage.clear();return true">reset client settings</a></li>
|
||||||
</ul>
|
</ul>
|
||||||
|
|
||||||
<h1 id="l">login for more:</h1>
|
|
||||||
<div>
|
|
||||||
<form method="post" enctype="multipart/form-data" action="{{ r }}/{{ qvpath }}">
|
|
||||||
<input type="hidden" name="act" value="login" />
|
|
||||||
<input type="password" name="cppwd" />
|
|
||||||
<input type="submit" value="Login" />
|
|
||||||
{% if ahttps %}
|
|
||||||
<a id="w" href="{{ ahttps }}">switch to https</a>
|
|
||||||
{% endif %}
|
|
||||||
</form>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
<a href="#" id="repl">π</a>
|
<a href="#" id="repl">π</a>
|
||||||
{%- if not this.args.nb %}
|
{%- if not this.args.nb %}
|
||||||
@@ -110,10 +150,15 @@ var SR = {{ r|tojson }},
|
|||||||
lang="{{ lang }}",
|
lang="{{ lang }}",
|
||||||
dfavico="{{ favico }}";
|
dfavico="{{ favico }}";
|
||||||
|
|
||||||
document.documentElement.className=localStorage.cpp_thm||"{{ this.args.theme }}";
|
var STG = window.localStorage;
|
||||||
|
document.documentElement.className = (STG && STG.cpp_thm) || "{{ this.args.theme }}";
|
||||||
|
|
||||||
</script>
|
</script>
|
||||||
<script src="{{ r }}/.cpr/util.js?_={{ ts }}"></script>
|
<script src="{{ r }}/.cpr/util.js?_={{ ts }}"></script>
|
||||||
<script src="{{ r }}/.cpr/splash.js?_={{ ts }}"></script>
|
<script src="{{ r }}/.cpr/splash.js?_={{ ts }}"></script>
|
||||||
|
{%- if js %}
|
||||||
|
<script src="{{ js }}_={{ ts }}"></script>
|
||||||
|
{%- endif %}
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|
||||||
|
|||||||
@@ -6,10 +6,10 @@ var Ls = {
|
|||||||
"d1": "tilstand",
|
"d1": "tilstand",
|
||||||
"d2": "vis tilstanden til alle tråder",
|
"d2": "vis tilstanden til alle tråder",
|
||||||
"e1": "last innst.",
|
"e1": "last innst.",
|
||||||
"e2": "leser inn konfigurasjonsfiler på nytt$N(kontoer, volumer, volumbrytere)$Nog kartlegger alle e2ds-volumer",
|
"e2": "leser inn konfigurasjonsfiler på nytt$N(kontoer, volumer, volumbrytere)$Nog kartlegger alle e2ds-volumer$N$Nmerk: endringer i globale parametere$Nkrever en full restart for å ta gjenge",
|
||||||
"f1": "du kan betrakte:",
|
"f1": "du kan betrakte:",
|
||||||
"g1": "du kan laste opp til:",
|
"g1": "du kan laste opp til:",
|
||||||
"cc1": "klient-konfigurasjon",
|
"cc1": "brytere og sånt",
|
||||||
"h1": "skru av k304",
|
"h1": "skru av k304",
|
||||||
"i1": "skru på k304",
|
"i1": "skru på k304",
|
||||||
"j1": "k304 bryter tilkoplingen for hver HTTP 304. Dette hjelper mot visse mellomtjenere som kan sette seg fast / plutselig slutter å laste sider, men det reduserer også ytelsen betydelig",
|
"j1": "k304 bryter tilkoplingen for hver HTTP 304. Dette hjelper mot visse mellomtjenere som kan sette seg fast / plutselig slutter å laste sider, men det reduserer også ytelsen betydelig",
|
||||||
@@ -17,9 +17,9 @@ var Ls = {
|
|||||||
"l1": "logg inn:",
|
"l1": "logg inn:",
|
||||||
"m1": "velkommen tilbake,",
|
"m1": "velkommen tilbake,",
|
||||||
"n1": "404: filen finnes ikke ┐( ´ -`)┌",
|
"n1": "404: filen finnes ikke ┐( ´ -`)┌",
|
||||||
"o1": 'eller kanskje du ikke har tilgang? prøv å logge inn eller <a href="' + SR + '/?h">gå hjem</a>',
|
"o1": 'eller kanskje du ikke har tilgang? prøv et passord eller <a href="' + SR + '/?h">gå hjem</a>',
|
||||||
"p1": "403: tilgang nektet ~┻━┻",
|
"p1": "403: tilgang nektet ~┻━┻",
|
||||||
"q1": 'du må logge inn eller <a href="' + SR + '/?h">gå hjem</a>',
|
"q1": 'prøv et passord eller <a href="' + SR + '/?h">gå hjem</a>',
|
||||||
"r1": "gå hjem",
|
"r1": "gå hjem",
|
||||||
".s1": "kartlegg",
|
".s1": "kartlegg",
|
||||||
"t1": "handling",
|
"t1": "handling",
|
||||||
@@ -27,15 +27,67 @@ var Ls = {
|
|||||||
"v1": "koble til",
|
"v1": "koble til",
|
||||||
"v2": "bruk denne serveren som en lokal harddisk$N$NADVARSEL: kommer til å vise passordet ditt!",
|
"v2": "bruk denne serveren som en lokal harddisk$N$NADVARSEL: kommer til å vise passordet ditt!",
|
||||||
"w1": "bytt til https",
|
"w1": "bytt til https",
|
||||||
|
"x1": "bytt passord",
|
||||||
|
"y1": "dine delinger",
|
||||||
|
"z1": "lås opp område",
|
||||||
|
"ta1": "du må skrive et nytt passord først",
|
||||||
|
"ta2": "gjenta for å bekrefte nytt passord:",
|
||||||
|
"ta3": "fant en skrivefeil; vennligst prøv igjen",
|
||||||
|
"aa1": "innkommende:",
|
||||||
},
|
},
|
||||||
"eng": {
|
"eng": {
|
||||||
"d2": "shows the state of all active threads",
|
"d2": "shows the state of all active threads",
|
||||||
"e2": "reload config files (accounts/volumes/volflags),$Nand rescan all e2ds volumes",
|
"e2": "reload config files (accounts/volumes/volflags),$Nand rescan all e2ds volumes$N$Nnote: any changes to global settings$Nrequire a full restart to take effect",
|
||||||
"u2": "time since the last server write$N( upload / rename / ... )$N$N17d = 17 days$N1h23 = 1 hour 23 minutes$N4m56 = 4 minutes 56 seconds",
|
"u2": "time since the last server write$N( upload / rename / ... )$N$N17d = 17 days$N1h23 = 1 hour 23 minutes$N4m56 = 4 minutes 56 seconds",
|
||||||
"v2": "use this server as a local HDD$N$NWARNING: this will show your password!",
|
"v2": "use this server as a local HDD$N$NWARNING: this will show your password!",
|
||||||
|
"ta1": "fill in your new password first",
|
||||||
|
"ta2": "repeat to confirm new password:",
|
||||||
|
"ta3": "found a typo; please try again",
|
||||||
|
},
|
||||||
|
|
||||||
|
"chi": {
|
||||||
|
"a1": "更新",
|
||||||
|
"b1": "你好 <small>(你尚未登录)</small>",
|
||||||
|
"c1": "登出",
|
||||||
|
"d1": "状态",
|
||||||
|
"d2": "显示所有活动线程的状态",
|
||||||
|
"e1": "重新加载配置",
|
||||||
|
"e2": "重新加载配置文件(账户/卷/卷标),$N并重新扫描所有 e2ds 卷$N$N注意:任何全局设置的更改$N都需要完全重启才能生效",
|
||||||
|
"f1": "你可以查看:",
|
||||||
|
"g1": "你可以上传到:",
|
||||||
|
"cc1": "开关等",
|
||||||
|
"h1": "关闭 k304",
|
||||||
|
"i1": "开启 k304",
|
||||||
|
"j1": "k304 会在每个 HTTP 304 时断开连接。这有助于避免某些代理服务器卡住或突然停止加载页面,但也会显著降低性能。",
|
||||||
|
"k1": "重置设置",
|
||||||
|
"l1": "登录:",
|
||||||
|
"m1": "欢迎回来,",
|
||||||
|
"n1": "404: 文件不存在 ┐( ´ -`)┌",
|
||||||
|
"o1": '或者你可能没有权限?尝试输入密码或 <a href="' + SR + '/?h">回家</a>',
|
||||||
|
"p1": "403: 访问被拒绝 ~┻━┻",
|
||||||
|
"q1": '尝试输入密码或 <a href="' + SR + '/?h">回家</a>',
|
||||||
|
"r1": "回家",
|
||||||
|
".s1": "映射",
|
||||||
|
"t1": "操作",
|
||||||
|
"u2": "自上次服务器写入的时间$N( 上传 / 重命名 / ... )$N$N17d = 17 天$N1h23 = 1 小时 23 分钟$N4m56 = 4 分钟 56 秒",
|
||||||
|
"v1": "连接",
|
||||||
|
"v2": "将此服务器用作本地硬盘$N$N警告:这将显示你的密码!",
|
||||||
|
"w1": "切换到 https",
|
||||||
|
"x1": "更改密码",
|
||||||
|
"y1": "你的分享",
|
||||||
|
"z1": "解锁区域",
|
||||||
|
"ta1": "请先输入新密码",
|
||||||
|
"ta2": "重复以确认新密码:",
|
||||||
|
"ta3": "发现拼写错误;请重试",
|
||||||
|
"aa1": "正在接收的文件:", //m
|
||||||
}
|
}
|
||||||
},
|
};
|
||||||
d = Ls[sread("cpp_lang", ["eng", "nor"]) || lang] || Ls.eng || Ls.nor;
|
|
||||||
|
if (window.langmod)
|
||||||
|
langmod();
|
||||||
|
|
||||||
|
var d = Ls[sread("cpp_lang", Object.keys(Ls)) || lang] ||
|
||||||
|
Ls.eng || Ls.nor || Ls.chi;
|
||||||
|
|
||||||
for (var k in (d || {})) {
|
for (var k in (d || {})) {
|
||||||
var f = k.slice(-1),
|
var f = k.slice(-1),
|
||||||
@@ -49,6 +101,15 @@ for (var k in (d || {})) {
|
|||||||
o[a].setAttribute("tt", d[k]);
|
o[a].setAttribute("tt", d[k]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
if (is_idp) {
|
||||||
|
var z = ['#l+div', '#l', '#c'];
|
||||||
|
for (var a = 0; a < z.length; a++)
|
||||||
|
QS(z[a]).style.display = 'none';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (ex) { }
|
||||||
|
|
||||||
tt.init();
|
tt.init();
|
||||||
var o = QS('input[name="cppwd"]');
|
var o = QS('input[name="cppwd"]');
|
||||||
if (!ebi('c') && o.offsetTop + o.offsetHeight < window.innerHeight)
|
if (!ebi('c') && o.offsetTop + o.offsetHeight < window.innerHeight)
|
||||||
@@ -57,3 +118,44 @@ if (!ebi('c') && o.offsetTop + o.offsetHeight < window.innerHeight)
|
|||||||
o = ebi('u');
|
o = ebi('u');
|
||||||
if (o && /[0-9]+$/.exec(o.innerHTML))
|
if (o && /[0-9]+$/.exec(o.innerHTML))
|
||||||
o.innerHTML = shumantime(o.innerHTML);
|
o.innerHTML = shumantime(o.innerHTML);
|
||||||
|
|
||||||
|
ebi('uhash').value = '' + location.hash;
|
||||||
|
|
||||||
|
(function() {
|
||||||
|
if (!ebi('x'))
|
||||||
|
return;
|
||||||
|
|
||||||
|
var pwi = ebi('lp');
|
||||||
|
|
||||||
|
function redo(msg) {
|
||||||
|
modal.alert(msg, function() {
|
||||||
|
pwi.value = '';
|
||||||
|
pwi.focus();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
function mok(v) {
|
||||||
|
if (v !== pwi.value)
|
||||||
|
return redo(d.ta3);
|
||||||
|
|
||||||
|
pwi.setAttribute('name', 'pw');
|
||||||
|
ebi('la').value = 'chpw';
|
||||||
|
ebi('lf').submit();
|
||||||
|
}
|
||||||
|
function stars() {
|
||||||
|
var m = ebi('modali');
|
||||||
|
function enstars(n) {
|
||||||
|
setTimeout(function() { m.value = ''; }, n);
|
||||||
|
}
|
||||||
|
m.setAttribute('type', 'password');
|
||||||
|
enstars(17);
|
||||||
|
enstars(32);
|
||||||
|
enstars(69);
|
||||||
|
}
|
||||||
|
ebi('x').onclick = function (e) {
|
||||||
|
ev(e);
|
||||||
|
if (!pwi.value)
|
||||||
|
return redo(d.ta1);
|
||||||
|
|
||||||
|
modal.prompt(d.ta2, "y", mok, null, stars);
|
||||||
|
};
|
||||||
|
})();
|
||||||
|
|||||||
@@ -6,11 +6,11 @@
|
|||||||
<title>{{ s_doctitle }}</title>
|
<title>{{ s_doctitle }}</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">
|
||||||
<meta name="theme-color" content="#333">
|
<meta name="theme-color" content="#{{ tcolor }}">
|
||||||
{{ html_head }}
|
|
||||||
<link rel="stylesheet" media="screen" href="{{ r }}/.cpr/splash.css?_={{ ts }}">
|
<link rel="stylesheet" media="screen" href="{{ r }}/.cpr/splash.css?_={{ ts }}">
|
||||||
<link rel="stylesheet" media="screen" href="{{ r }}/.cpr/ui.css?_={{ ts }}">
|
<link rel="stylesheet" media="screen" href="{{ r }}/.cpr/ui.css?_={{ ts }}">
|
||||||
<style>ul{padding-left:1.3em}li{margin:.4em 0}</style>
|
<style>ul{padding-left:1.3em}li{margin:.4em 0}</style>
|
||||||
|
{{ html_head }}
|
||||||
</head>
|
</head>
|
||||||
|
|
||||||
<body>
|
<body>
|
||||||
@@ -56,7 +56,7 @@
|
|||||||
<li>running <code>rclone mount</code> as root? add <code>--allow-other</code></li>
|
<li>running <code>rclone mount</code> as root? add <code>--allow-other</code></li>
|
||||||
<li>old version of rclone? replace all <code>=</code> with <code> </code> (space)</li>
|
<li>old version of rclone? replace all <code>=</code> with <code> </code> (space)</li>
|
||||||
</ul>
|
</ul>
|
||||||
|
|
||||||
<p>if you want to use the native WebDAV client in windows instead (slow and buggy), first run <a href="{{ r }}/.cpr/a/webdav-cfg.bat">webdav-cfg.bat</a> to remove the 47 MiB filesize limit (also fixes latency and password login), then connect:</p>
|
<p>if you want to use the native WebDAV client in windows instead (slow and buggy), first run <a href="{{ r }}/.cpr/a/webdav-cfg.bat">webdav-cfg.bat</a> to remove the 47 MiB filesize limit (also fixes latency and password login), then connect:</p>
|
||||||
<pre>
|
<pre>
|
||||||
net use <b>w:</b> http{{ s }}://{{ ep }}/{{ rvp }}{% if accs %} k /user:<b>{{ pw }}</b>{% endif %}
|
net use <b>w:</b> http{{ s }}://{{ ep }}/{{ rvp }}{% if accs %} k /user:<b>{{ pw }}</b>{% endif %}
|
||||||
@@ -64,16 +64,7 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="os lin">
|
<div class="os lin">
|
||||||
<pre>
|
<p>rclone (v1.63 or later) is recommended:</p>
|
||||||
yum install davfs2
|
|
||||||
{% if accs %}printf '%s\n' <b>{{ pw }}</b> k | {% endif %}mount -t davfs -ouid=1000 http{{ s }}://{{ ep }}/{{ rvp }} <b>mp</b>
|
|
||||||
</pre>
|
|
||||||
<p>make it automount on boot:</p>
|
|
||||||
<pre>
|
|
||||||
printf '%s\n' "http{{ s }}://{{ ep }}/{{ rvp }} <b>{{ pw }}</b> k" >> /etc/davfs2/secrets
|
|
||||||
printf '%s\n' "http{{ s }}://{{ ep }}/{{ rvp }} <b>mp</b> davfs rw,user,uid=1000,noauto 0 0" >> /etc/fstab
|
|
||||||
</pre>
|
|
||||||
<p>or you can use rclone instead, which is much slower but doesn't require root (plus it keeps lastmodified on upload):</p>
|
|
||||||
<pre>
|
<pre>
|
||||||
rclone config create {{ aname }}-dav webdav url=http{{ s }}://{{ rip }}{{ hport }} vendor=owncloud pacer_min_sleep=0.01ms{% if accs %} user=k pass=<b>{{ pw }}</b>{% endif %}
|
rclone config create {{ aname }}-dav webdav url=http{{ s }}://{{ rip }}{{ hport }} vendor=owncloud pacer_min_sleep=0.01ms{% if accs %} user=k pass=<b>{{ pw }}</b>{% endif %}
|
||||||
rclone mount --vfs-cache-mode writes --dir-cache-time 5s {{ aname }}-dav:{{ rvp }} <b>mp</b>
|
rclone mount --vfs-cache-mode writes --dir-cache-time 5s {{ aname }}-dav:{{ rvp }} <b>mp</b>
|
||||||
@@ -85,6 +76,16 @@
|
|||||||
<li>running <code>rclone mount</code> as root? add <code>--allow-other</code></li>
|
<li>running <code>rclone mount</code> as root? add <code>--allow-other</code></li>
|
||||||
<li>old version of rclone? replace all <code>=</code> with <code> </code> (space)</li>
|
<li>old version of rclone? replace all <code>=</code> with <code> </code> (space)</li>
|
||||||
</ul>
|
</ul>
|
||||||
|
<p>alternatively use davfs2 (requires root, is slower, forgets lastmodified-timestamp on upload):</p>
|
||||||
|
<pre>
|
||||||
|
yum install davfs2
|
||||||
|
{% if accs %}printf '%s\n' <b>{{ pw }}</b> k | {% endif %}mount -t davfs -ouid=1000 http{{ s }}://{{ ep }}/{{ rvp }} <b>mp</b>
|
||||||
|
</pre>
|
||||||
|
<p>make davfs2 automount on boot:</p>
|
||||||
|
<pre>
|
||||||
|
printf '%s\n' "http{{ s }}://{{ ep }}/{{ rvp }} <b>{{ pw }}</b> k" >> /etc/davfs2/secrets
|
||||||
|
printf '%s\n' "http{{ s }}://{{ ep }}/{{ rvp }} <b>mp</b> davfs rw,user,uid=1000,noauto 0 0" >> /etc/fstab
|
||||||
|
</pre>
|
||||||
<p>or the emergency alternative (gnome/gui-only):</p>
|
<p>or the emergency alternative (gnome/gui-only):</p>
|
||||||
<!-- gnome-bug: ignores vp -->
|
<!-- gnome-bug: ignores vp -->
|
||||||
<pre>
|
<pre>
|
||||||
@@ -104,7 +105,7 @@
|
|||||||
<pre>
|
<pre>
|
||||||
http{{ s }}://k:<b>{{ pw }}</b>@{{ ep }}/{{ rvp }}
|
http{{ s }}://k:<b>{{ pw }}</b>@{{ ep }}/{{ rvp }}
|
||||||
</pre>
|
</pre>
|
||||||
|
|
||||||
{% if s %}
|
{% if s %}
|
||||||
<p><em>replace <code>https</code> with <code>http</code> if it doesn't work</em></p>
|
<p><em>replace <code>https</code> with <code>http</code> if it doesn't work</em></p>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
@@ -238,10 +239,15 @@ var SR = {{ r|tojson }},
|
|||||||
lang="{{ lang }}",
|
lang="{{ lang }}",
|
||||||
dfavico="{{ favico }}";
|
dfavico="{{ favico }}";
|
||||||
|
|
||||||
document.documentElement.className=localStorage.cpp_thm||"{{ args.theme }}";
|
var STG = window.localStorage;
|
||||||
|
document.documentElement.className = (STG && STG.cpp_thm) || "{{ args.theme }}";
|
||||||
|
|
||||||
</script>
|
</script>
|
||||||
<script src="{{ r }}/.cpr/util.js?_={{ ts }}"></script>
|
<script src="{{ r }}/.cpr/util.js?_={{ ts }}"></script>
|
||||||
<script src="{{ r }}/.cpr/svcs.js?_={{ ts }}"></script>
|
<script src="{{ r }}/.cpr/svcs.js?_={{ ts }}"></script>
|
||||||
|
{%- if js %}
|
||||||
|
<script src="{{ js }}_={{ ts }}"></script>
|
||||||
|
{%- endif %}
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,8 @@
|
|||||||
:root {
|
:root {
|
||||||
|
--font-main: sans-serif;
|
||||||
|
--font-serif: serif;
|
||||||
|
--font-mono: 'scp';
|
||||||
|
|
||||||
--fg: #ccc;
|
--fg: #ccc;
|
||||||
--fg-max: #fff;
|
--fg-max: #fff;
|
||||||
--bg-u2: #2b2b2b;
|
--bg-u2: #2b2b2b;
|
||||||
@@ -105,6 +109,9 @@ html {
|
|||||||
#toast pre {
|
#toast pre {
|
||||||
margin: 0;
|
margin: 0;
|
||||||
}
|
}
|
||||||
|
#toast.hide {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
#toast.vis {
|
#toast.vis {
|
||||||
right: 1.3em;
|
right: 1.3em;
|
||||||
transform: inherit;
|
transform: inherit;
|
||||||
@@ -144,6 +151,10 @@ html {
|
|||||||
#toast.err #toastc {
|
#toast.err #toastc {
|
||||||
background: #d06;
|
background: #d06;
|
||||||
}
|
}
|
||||||
|
#toast code {
|
||||||
|
padding: 0 .2em;
|
||||||
|
background: rgba(0,0,0,0.2);
|
||||||
|
}
|
||||||
#tth {
|
#tth {
|
||||||
color: #fff;
|
color: #fff;
|
||||||
background: #111;
|
background: #111;
|
||||||
@@ -173,6 +184,7 @@ html {
|
|||||||
padding: 1.5em 2em;
|
padding: 1.5em 2em;
|
||||||
border-width: .5em 0;
|
border-width: .5em 0;
|
||||||
}
|
}
|
||||||
|
.logue code,
|
||||||
#modalc code,
|
#modalc code,
|
||||||
#tt code {
|
#tt code {
|
||||||
color: #eee;
|
color: #eee;
|
||||||
@@ -253,7 +265,11 @@ html.y #tth {
|
|||||||
box-shadow: 0 .3em 3em rgba(0,0,0,0.5);
|
box-shadow: 0 .3em 3em rgba(0,0,0,0.5);
|
||||||
max-width: 50em;
|
max-width: 50em;
|
||||||
max-height: 30em;
|
max-height: 30em;
|
||||||
overflow: auto;
|
overflow-x: auto;
|
||||||
|
overflow-y: scroll;
|
||||||
|
}
|
||||||
|
#modalc.yk {
|
||||||
|
overflow-y: auto;
|
||||||
}
|
}
|
||||||
#modalc td {
|
#modalc td {
|
||||||
text-align: unset;
|
text-align: unset;
|
||||||
@@ -369,8 +385,10 @@ html.y textarea:focus {
|
|||||||
}
|
}
|
||||||
.mdo pre,
|
.mdo pre,
|
||||||
.mdo code,
|
.mdo code,
|
||||||
|
.mdo code[class*="language-"],
|
||||||
.mdo tt {
|
.mdo tt {
|
||||||
font-family: 'scp', monospace, monospace;
|
font-family: 'scp', monospace, monospace;
|
||||||
|
font-family: var(--font-mono), 'scp', monospace, monospace;
|
||||||
white-space: pre-wrap;
|
white-space: pre-wrap;
|
||||||
word-break: break-all;
|
word-break: break-all;
|
||||||
}
|
}
|
||||||
@@ -440,6 +458,7 @@ html.y textarea:focus {
|
|||||||
}
|
}
|
||||||
.mdo blockquote {
|
.mdo blockquote {
|
||||||
font-family: serif;
|
font-family: serif;
|
||||||
|
font-family: var(--font-serif), serif;
|
||||||
background: #f7f7f7;
|
background: #f7f7f7;
|
||||||
border: .07em dashed #ccc;
|
border: .07em dashed #ccc;
|
||||||
padding: 0 2em;
|
padding: 0 2em;
|
||||||
@@ -573,3 +592,11 @@ hr {
|
|||||||
border: .07em dashed #444;
|
border: .07em dashed #444;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@media (prefers-reduced-motion) {
|
||||||
|
#toast,
|
||||||
|
#toast a#toastc,
|
||||||
|
#tt {
|
||||||
|
transition: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -17,7 +17,7 @@ function goto_up2k() {
|
|||||||
var up2k = null,
|
var up2k = null,
|
||||||
up2k_hooks = [],
|
up2k_hooks = [],
|
||||||
hws = [],
|
hws = [],
|
||||||
sha_js = window.WebAssembly ? 'hw' : 'ac', // ff53,c57,sa11
|
sha_js = WebAssembly ? 'hw' : 'ac', // ff53,c57,sa11
|
||||||
m = 'will use ' + sha_js + ' instead of native sha512 due to';
|
m = 'will use ' + sha_js + ' instead of native sha512 due to';
|
||||||
|
|
||||||
try {
|
try {
|
||||||
@@ -152,12 +152,13 @@ function U2pvis(act, btns, uc, st) {
|
|||||||
r.mod0 = null;
|
r.mod0 = null;
|
||||||
|
|
||||||
var markup = {
|
var markup = {
|
||||||
'404': '<span class="err">404</span>',
|
'404': '<span class="err">' + L.utl_404 + '</span>',
|
||||||
'ERROR': '<span class="err">ERROR</span>',
|
'ERROR': '<span class="err">' + L.utl_err + '</span>',
|
||||||
'OS-error': '<span class="err">OS-error</span>',
|
'OS-error': '<span class="err">' + L.utl_oserr + '</span>',
|
||||||
'found': '<span class="inf">found</span>',
|
'found': '<span class="inf">' + L.utl_found + '</span>',
|
||||||
'YOLO': '<span class="inf">YOLO</span>',
|
'defer': '<span class="inf">' + L.utl_defer + '</span>',
|
||||||
'done': '<span class="ok">done</span>',
|
'YOLO': '<span class="inf">' + L.utl_yolo + '</span>',
|
||||||
|
'done': '<span class="ok">' + L.utl_done + '</span>',
|
||||||
};
|
};
|
||||||
|
|
||||||
r.addfile = function (entry, sz, draw) {
|
r.addfile = function (entry, sz, draw) {
|
||||||
@@ -431,7 +432,7 @@ function U2pvis(act, btns, uc, st) {
|
|||||||
if (sread('potato') === null) {
|
if (sread('potato') === null) {
|
||||||
btn.click();
|
btn.click();
|
||||||
toast.inf(30, L.u_gotpot);
|
toast.inf(30, L.u_gotpot);
|
||||||
localStorage.removeItem('potato');
|
sdrop('potato');
|
||||||
}
|
}
|
||||||
|
|
||||||
u2f.appendChild(ode);
|
u2f.appendChild(ode);
|
||||||
@@ -445,9 +446,7 @@ function U2pvis(act, btns, uc, st) {
|
|||||||
return;
|
return;
|
||||||
|
|
||||||
r.npotato = 0;
|
r.npotato = 0;
|
||||||
var html = [
|
var html = [L.u_pott.format(r.ctr.ok, r.ctr.ng, r.ctr.bz, r.ctr.q)];
|
||||||
"<p>files: <b>{0}</b> finished, <b>{1}</b> failed, <b>{2}</b> busy, <b>{3}</b> queued</p>".format(
|
|
||||||
r.ctr.ok, r.ctr.ng, r.ctr.bz, r.ctr.q)];
|
|
||||||
|
|
||||||
while (r.head < r.tab.length && has(["ok", "ng"], r.tab[r.head].in))
|
while (r.head < r.tab.length && has(["ok", "ng"], r.tab[r.head].in))
|
||||||
r.head++;
|
r.head++;
|
||||||
@@ -602,7 +601,7 @@ function U2pvis(act, btns, uc, st) {
|
|||||||
if (nf < 9000)
|
if (nf < 9000)
|
||||||
return go();
|
return go();
|
||||||
|
|
||||||
modal.confirm('about to show ' + nf + ' files\n\nthis may crash your browser, are you sure?', go, null);
|
modal.confirm(L.u_bigtab.format(nf), go, null);
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -658,7 +657,9 @@ function Donut(uc, st) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function pos() {
|
function pos() {
|
||||||
return uc.fsearch ? Math.max(st.bytes.hashed, st.bytes.finished) : st.bytes.finished;
|
return uc.fsearch ?
|
||||||
|
Math.max(st.bytes.hashed, st.bytes.finished) :
|
||||||
|
st.bytes.inflight + st.bytes.finished;
|
||||||
}
|
}
|
||||||
|
|
||||||
r.on = function (ya) {
|
r.on = function (ya) {
|
||||||
@@ -717,7 +718,7 @@ function Donut(uc, st) {
|
|||||||
sfx();
|
sfx();
|
||||||
|
|
||||||
// firefox may forget that filedrops are user-gestures so it can skip this:
|
// firefox may forget that filedrops are user-gestures so it can skip this:
|
||||||
if (uc.upnag && window.Notification && Notification.permission == 'granted')
|
if (uc.upnag && Notification && Notification.permission == 'granted')
|
||||||
new Notification(uc.nagtxt);
|
new Notification(uc.nagtxt);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -779,8 +780,8 @@ function up2k_init(subtle) {
|
|||||||
};
|
};
|
||||||
|
|
||||||
setTimeout(function () {
|
setTimeout(function () {
|
||||||
if (window.WebAssembly && !hws.length)
|
if (WebAssembly && !hws.length)
|
||||||
fetch(SR + '/.cpr/w.hash.js' + CB);
|
fetch(SR + '/.cpr/w.hash.js?_=' + TS);
|
||||||
}, 1000);
|
}, 1000);
|
||||||
|
|
||||||
function showmodal(msg) {
|
function showmodal(msg) {
|
||||||
@@ -852,7 +853,8 @@ function up2k_init(subtle) {
|
|||||||
|
|
||||||
setmsg(suggest_up2k, 'msg');
|
setmsg(suggest_up2k, 'msg');
|
||||||
|
|
||||||
var parallel_uploads = icfg_get('nthread'),
|
var parallel_uploads = ebi('nthread').value = icfg_get('nthread', u2j),
|
||||||
|
stitch_tgt = ebi('u2szg').value = icfg_get('u2sz', u2sz.split(',')[1]),
|
||||||
uc = {},
|
uc = {},
|
||||||
fdom_ctr = 0,
|
fdom_ctr = 0,
|
||||||
biggest_file = 0;
|
biggest_file = 0;
|
||||||
@@ -861,6 +863,7 @@ function up2k_init(subtle) {
|
|||||||
bcfg_bind(uc, 'multitask', 'multitask', true, null, false);
|
bcfg_bind(uc, 'multitask', 'multitask', true, null, false);
|
||||||
bcfg_bind(uc, 'potato', 'potato', false, set_potato, false);
|
bcfg_bind(uc, 'potato', 'potato', false, set_potato, false);
|
||||||
bcfg_bind(uc, 'ask_up', 'ask_up', true, null, false);
|
bcfg_bind(uc, 'ask_up', 'ask_up', true, null, false);
|
||||||
|
bcfg_bind(uc, 'umod', 'umod', false, null, false);
|
||||||
bcfg_bind(uc, 'u2ts', 'u2ts', !u2ts.endsWith('u'), set_u2ts, false);
|
bcfg_bind(uc, 'u2ts', 'u2ts', !u2ts.endsWith('u'), set_u2ts, false);
|
||||||
bcfg_bind(uc, 'fsearch', 'fsearch', false, set_fsearch, false);
|
bcfg_bind(uc, 'fsearch', 'fsearch', false, set_fsearch, false);
|
||||||
|
|
||||||
@@ -868,7 +871,7 @@ function up2k_init(subtle) {
|
|||||||
bcfg_bind(uc, 'turbo', 'u2turbo', turbolvl > 1, draw_turbo);
|
bcfg_bind(uc, 'turbo', 'u2turbo', turbolvl > 1, draw_turbo);
|
||||||
bcfg_bind(uc, 'datechk', 'u2tdate', turbolvl < 3, null);
|
bcfg_bind(uc, 'datechk', 'u2tdate', turbolvl < 3, null);
|
||||||
bcfg_bind(uc, 'az', 'u2sort', u2sort.indexOf('n') + 1, set_u2sort);
|
bcfg_bind(uc, 'az', 'u2sort', u2sort.indexOf('n') + 1, set_u2sort);
|
||||||
bcfg_bind(uc, 'hashw', 'hashw', !!window.WebAssembly && (!subtle || !CHROME || MOBILE || VCHROME >= 107), set_hashw);
|
bcfg_bind(uc, 'hashw', 'hashw', !!WebAssembly && (!subtle || !CHROME || MOBILE || VCHROME >= 107), set_hashw);
|
||||||
bcfg_bind(uc, 'upnag', 'upnag', false, set_upnag);
|
bcfg_bind(uc, 'upnag', 'upnag', false, set_upnag);
|
||||||
bcfg_bind(uc, 'upsfx', 'upsfx', false, set_upsfx);
|
bcfg_bind(uc, 'upsfx', 'upsfx', false, set_upsfx);
|
||||||
|
|
||||||
@@ -894,6 +897,7 @@ function up2k_init(subtle) {
|
|||||||
"bytes": {
|
"bytes": {
|
||||||
"total": 0,
|
"total": 0,
|
||||||
"hashed": 0,
|
"hashed": 0,
|
||||||
|
"inflight": 0,
|
||||||
"uploaded": 0,
|
"uploaded": 0,
|
||||||
"finished": 0
|
"finished": 0
|
||||||
},
|
},
|
||||||
@@ -1032,7 +1036,7 @@ function up2k_init(subtle) {
|
|||||||
}
|
}
|
||||||
catch (ex) {
|
catch (ex) {
|
||||||
document.body.ondragenter = document.body.ondragleave = document.body.ondragover = null;
|
document.body.ondragenter = document.body.ondragleave = document.body.ondragover = null;
|
||||||
return modal.alert('your browser does not support drag-and-drop uploading');
|
return modal.alert(L.u_nodrop);
|
||||||
}
|
}
|
||||||
if (btn)
|
if (btn)
|
||||||
return;
|
return;
|
||||||
@@ -1043,7 +1047,7 @@ function up2k_init(subtle) {
|
|||||||
clmod(ebi(v), 'hl', 1);
|
clmod(ebi(v), 'hl', 1);
|
||||||
}
|
}
|
||||||
function offdrag(e) {
|
function offdrag(e) {
|
||||||
ev(e);
|
noope(e);
|
||||||
|
|
||||||
var v = this.getAttribute('v');
|
var v = this.getAttribute('v');
|
||||||
if (v)
|
if (v)
|
||||||
@@ -1099,7 +1103,7 @@ function up2k_init(subtle) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (!good_files.length && bad_files.length)
|
if (!good_files.length && bad_files.length)
|
||||||
return toast.err(30, "that's not a folder!\n\nyour browser is too old,\nplease try dragdrop instead");
|
return toast.err(30, L.u_notdir);
|
||||||
|
|
||||||
return read_dirs(null, [], [], good_files, nil_files, bad_files);
|
return read_dirs(null, [], [], good_files, nil_files, bad_files);
|
||||||
}
|
}
|
||||||
@@ -1117,7 +1121,7 @@ function up2k_init(subtle) {
|
|||||||
if (err)
|
if (err)
|
||||||
return modal.alert('sorry, ' + err);
|
return modal.alert('sorry, ' + err);
|
||||||
|
|
||||||
toast.inf(0, 'Scanning files...');
|
toast.inf(0, L.u_scan);
|
||||||
|
|
||||||
if ((dz == 'up_dz' && uc.fsearch) || (dz == 'srch_dz' && !uc.fsearch))
|
if ((dz == 'up_dz' && uc.fsearch) || (dz == 'srch_dz' && !uc.fsearch))
|
||||||
tgl_fsearch();
|
tgl_fsearch();
|
||||||
@@ -1205,7 +1209,7 @@ function up2k_init(subtle) {
|
|||||||
match = false;
|
match = false;
|
||||||
|
|
||||||
if (match) {
|
if (match) {
|
||||||
var msg = ['directory iterator got stuck on the following {0} items; good chance your browser is about to spinlock:<ul>'.format(missing.length)];
|
var msg = [L.u_dirstuck.format(missing.length) + '<ul>'];
|
||||||
for (var a = 0; a < Math.min(20, missing.length); a++)
|
for (var a = 0; a < Math.min(20, missing.length); a++)
|
||||||
msg.push('<li>' + esc(missing[a]) + '</li>');
|
msg.push('<li>' + esc(missing[a]) + '</li>');
|
||||||
|
|
||||||
@@ -1276,7 +1280,7 @@ function up2k_init(subtle) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function gotallfiles(good_files, nil_files, bad_files) {
|
function gotallfiles(good_files, nil_files, bad_files) {
|
||||||
if (toast.txt == 'Scanning files...')
|
if (toast.txt == L.u_scan)
|
||||||
toast.hide();
|
toast.hide();
|
||||||
|
|
||||||
if (uc.fsearch && !uc.turbo)
|
if (uc.fsearch && !uc.turbo)
|
||||||
@@ -1332,7 +1336,8 @@ function up2k_init(subtle) {
|
|||||||
return modal.confirm(msg.join('') + '</ul>', function () {
|
return modal.confirm(msg.join('') + '</ul>', function () {
|
||||||
start_actx();
|
start_actx();
|
||||||
up_them(good_files);
|
up_them(good_files);
|
||||||
toast.inf(15, L.u_unpt, L.u_unpt);
|
if (have_up2k_idx)
|
||||||
|
toast.inf(15, L.u_unpt, L.u_unpt);
|
||||||
}, null);
|
}, null);
|
||||||
|
|
||||||
up_them(good_files);
|
up_them(good_files);
|
||||||
@@ -1344,9 +1349,9 @@ function up2k_init(subtle) {
|
|||||||
var evpath = get_evpath(),
|
var evpath = get_evpath(),
|
||||||
draw_each = good_files.length < 50;
|
draw_each = good_files.length < 50;
|
||||||
|
|
||||||
if (window.WebAssembly && !hws.length) {
|
if (WebAssembly && !hws.length) {
|
||||||
for (var a = 0; a < Math.min(navigator.hardwareConcurrency || 4, 16); a++)
|
for (var a = 0; a < Math.min(navigator.hardwareConcurrency || 4, 16); a++)
|
||||||
hws.push(new Worker(SR + '/.cpr/w.hash.js' + CB));
|
hws.push(new Worker(SR + '/.cpr/w.hash.js?_=' + TS));
|
||||||
|
|
||||||
console.log(hws.length + " hashers");
|
console.log(hws.length + " hashers");
|
||||||
}
|
}
|
||||||
@@ -1391,6 +1396,8 @@ function up2k_init(subtle) {
|
|||||||
entry.rand = true;
|
entry.rand = true;
|
||||||
entry.name = 'a\n' + entry.name;
|
entry.name = 'a\n' + entry.name;
|
||||||
}
|
}
|
||||||
|
else if (uc.umod)
|
||||||
|
entry.umod = true;
|
||||||
|
|
||||||
if (biggest_file < entry.size)
|
if (biggest_file < entry.size)
|
||||||
biggest_file = entry.size;
|
biggest_file = entry.size;
|
||||||
@@ -1429,7 +1436,7 @@ function up2k_init(subtle) {
|
|||||||
if (!actx || actx.state != 'suspended' || toast.visible)
|
if (!actx || actx.state != 'suspended' || toast.visible)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
toast.warn(30, "<div onclick=\"start_actx();toast.inf(3,'thanks!')\">please click this text to<br />unlock full upload speed</div>");
|
toast.warn(30, "<div onclick=\"start_actx();toast.inf(3,'thanks!')\">" + L.u_actx + "</div>");
|
||||||
}, 500);
|
}, 500);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1471,7 +1478,7 @@ function up2k_init(subtle) {
|
|||||||
ev(e);
|
ev(e);
|
||||||
var txt = linklist();
|
var txt = linklist();
|
||||||
cliptxt(txt + '\n', function () {
|
cliptxt(txt + '\n', function () {
|
||||||
toast.inf(5, txt.split('\n').length + ' links copied to clipboard');
|
toast.inf(5, un_clip.format(txt.split('\n').length));
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -1539,17 +1546,21 @@ function up2k_init(subtle) {
|
|||||||
if (uc.fsearch)
|
if (uc.fsearch)
|
||||||
t.push(['u2etat', st.bytes.hashed, st.bytes.hashed, st.time.hashing]);
|
t.push(['u2etat', st.bytes.hashed, st.bytes.hashed, st.time.hashing]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var b_up = st.bytes.inflight + st.bytes.uploaded,
|
||||||
|
b_fin = st.bytes.inflight + st.bytes.finished;
|
||||||
|
|
||||||
if (nsend) {
|
if (nsend) {
|
||||||
st.time.uploading += td;
|
st.time.uploading += td;
|
||||||
t.push(['u2etau', st.bytes.uploaded, st.bytes.finished, st.time.uploading]);
|
t.push(['u2etau', b_up, b_fin, st.time.uploading]);
|
||||||
}
|
}
|
||||||
if ((nhash || nsend) && !uc.fsearch) {
|
if ((nhash || nsend) && !uc.fsearch) {
|
||||||
if (!st.bytes.finished) {
|
if (!b_fin) {
|
||||||
ebi('u2etat').innerHTML = L.u_etaprep;
|
ebi('u2etat').innerHTML = L.u_etaprep;
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
st.time.busy += td;
|
st.time.busy += td;
|
||||||
t.push(['u2etat', st.bytes.finished, st.bytes.finished, st.time.busy]);
|
t.push(['u2etat', b_fin, b_fin, st.time.busy]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
for (var a = 0; a < t.length; a++) {
|
for (var a = 0; a < t.length; a++) {
|
||||||
@@ -1713,8 +1724,6 @@ function up2k_init(subtle) {
|
|||||||
ebi('u2etas').style.textAlign = 'left';
|
ebi('u2etas').style.textAlign = 'left';
|
||||||
}
|
}
|
||||||
etafun();
|
etafun();
|
||||||
if (pvis.act == 'bz')
|
|
||||||
pvis.changecard('bz');
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (flag) {
|
if (flag) {
|
||||||
@@ -1729,16 +1738,13 @@ function up2k_init(subtle) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
var mou_ikkai = false;
|
if (st.bytes.inflight && (st.bytes.inflight < 0 || !st.busy.upload.length)) {
|
||||||
|
console.log('insane inflight ' + st.bytes.inflight);
|
||||||
if (st.busy.handshake.length &&
|
st.bytes.inflight = 0;
|
||||||
st.busy.handshake[0].t_busied < now - 30 * 1000
|
|
||||||
) {
|
|
||||||
console.log("retrying stuck handshake");
|
|
||||||
var t = st.busy.handshake.shift();
|
|
||||||
st.todo.handshake.unshift(t);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var mou_ikkai = false;
|
||||||
|
|
||||||
var nprev = -1;
|
var nprev = -1;
|
||||||
for (var a = 0; a < st.todo.upload.length; a++) {
|
for (var a = 0; a < st.todo.upload.length; a++) {
|
||||||
var nf = st.todo.upload[a].nfile;
|
var nf = st.todo.upload[a].nfile;
|
||||||
@@ -1850,6 +1856,9 @@ function up2k_init(subtle) {
|
|||||||
timer.rm(donut.do);
|
timer.rm(donut.do);
|
||||||
ebi('u2tabw').style.minHeight = '0px';
|
ebi('u2tabw').style.minHeight = '0px';
|
||||||
utw_minh = 0;
|
utw_minh = 0;
|
||||||
|
|
||||||
|
if (pvis.act == 'bz')
|
||||||
|
pvis.changecard('bz');
|
||||||
}
|
}
|
||||||
|
|
||||||
function chill(t) {
|
function chill(t) {
|
||||||
@@ -2168,7 +2177,7 @@ function up2k_init(subtle) {
|
|||||||
st.busy.head.push(t);
|
st.busy.head.push(t);
|
||||||
|
|
||||||
var xhr = new XMLHttpRequest();
|
var xhr = new XMLHttpRequest();
|
||||||
xhr.onerror = function () {
|
xhr.onerror = xhr.ontimeout = function () {
|
||||||
console.log('head onerror, retrying', t.name, t);
|
console.log('head onerror, retrying', t.name, t);
|
||||||
if (!toast.visible)
|
if (!toast.visible)
|
||||||
toast.warn(9.98, L.u_enethd + "\n\nfile: " + t.name, t);
|
toast.warn(9.98, L.u_enethd + "\n\nfile: " + t.name, t);
|
||||||
@@ -2212,6 +2221,7 @@ function up2k_init(subtle) {
|
|||||||
try { orz(e); } catch (ex) { vis_exh(ex + '', 'up2k.js', '', '', ex); }
|
try { orz(e); } catch (ex) { vis_exh(ex + '', 'up2k.js', '', '', ex); }
|
||||||
};
|
};
|
||||||
|
|
||||||
|
xhr.timeout = 34000;
|
||||||
xhr.open('HEAD', t.purl + uricom_enc(t.name), true);
|
xhr.open('HEAD', t.purl + uricom_enc(t.name), true);
|
||||||
xhr.send();
|
xhr.send();
|
||||||
}
|
}
|
||||||
@@ -2236,8 +2246,11 @@ function up2k_init(subtle) {
|
|||||||
if (keepalive)
|
if (keepalive)
|
||||||
console.log("sending keepalive handshake", t.name, t);
|
console.log("sending keepalive handshake", t.name, t);
|
||||||
|
|
||||||
|
if (!t.srch && !t.t_handshake)
|
||||||
|
pvis.seth(t.n, 2, L.u_hs);
|
||||||
|
|
||||||
var xhr = new XMLHttpRequest();
|
var xhr = new XMLHttpRequest();
|
||||||
xhr.onerror = function () {
|
xhr.onerror = xhr.ontimeout = function () {
|
||||||
if (t.t_busied != me) // t.done ok
|
if (t.t_busied != me) // t.done ok
|
||||||
return console.log('zombie handshake onerror', t.name, t);
|
return console.log('zombie handshake onerror', t.name, t);
|
||||||
|
|
||||||
@@ -2247,6 +2260,7 @@ function up2k_init(subtle) {
|
|||||||
console.log('handshake onerror, retrying', t.name, t);
|
console.log('handshake onerror, retrying', t.name, t);
|
||||||
apop(st.busy.handshake, t);
|
apop(st.busy.handshake, t);
|
||||||
st.todo.handshake.unshift(t);
|
st.todo.handshake.unshift(t);
|
||||||
|
t.cooldown = Date.now() + 5000 + Math.floor(Math.random() * 3000);
|
||||||
t.keepalive = keepalive;
|
t.keepalive = keepalive;
|
||||||
};
|
};
|
||||||
var orz = function (e) {
|
var orz = function (e) {
|
||||||
@@ -2254,16 +2268,27 @@ function up2k_init(subtle) {
|
|||||||
return console.log('zombie handshake onload', t.name, t);
|
return console.log('zombie handshake onload', t.name, t);
|
||||||
|
|
||||||
if (xhr.status == 200) {
|
if (xhr.status == 200) {
|
||||||
|
try {
|
||||||
|
var response = JSON.parse(xhr.responseText);
|
||||||
|
}
|
||||||
|
catch (ex) {
|
||||||
|
apop(st.busy.handshake, t);
|
||||||
|
st.todo.handshake.unshift(t);
|
||||||
|
t.cooldown = Date.now() + 5000 + Math.floor(Math.random() * 3000);
|
||||||
|
var txt = t.t_uploading ? L.u_ehsfin : t.srch ? L.u_ehssrch : L.u_ehsinit;
|
||||||
|
return toast.err(0, txt + '\n\n' + L.badreply + ':\n\n' + unpre(xhr.responseText));
|
||||||
|
}
|
||||||
|
|
||||||
t.t_handshake = Date.now();
|
t.t_handshake = Date.now();
|
||||||
if (keepalive) {
|
if (keepalive) {
|
||||||
apop(st.busy.handshake, t);
|
apop(st.busy.handshake, t);
|
||||||
|
tasker();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (toast.tag === t)
|
if (toast.tag === t)
|
||||||
toast.ok(5, L.u_fixed);
|
toast.ok(5, L.u_fixed);
|
||||||
|
|
||||||
var response = JSON.parse(xhr.responseText);
|
|
||||||
if (!response.name) {
|
if (!response.name) {
|
||||||
var msg = '',
|
var msg = '',
|
||||||
smsg = '';
|
smsg = '';
|
||||||
@@ -2353,11 +2378,39 @@ function up2k_init(subtle) {
|
|||||||
var arr = st.todo.upload,
|
var arr = st.todo.upload,
|
||||||
sort = arr.length && arr[arr.length - 1].nfile > t.n;
|
sort = arr.length && arr[arr.length - 1].nfile > t.n;
|
||||||
|
|
||||||
for (var a = 0; a < t.postlist.length; a++)
|
if (!t.stitch_sz) {
|
||||||
|
// keep all connections busy
|
||||||
|
var bpc = (st.bytes.total - st.bytes.finished) / (parallel_uploads || 1),
|
||||||
|
ocs = 1024 * 1024,
|
||||||
|
stp = 1024 * 512,
|
||||||
|
ccs = ocs;
|
||||||
|
while (ccs < bpc) {
|
||||||
|
ocs = ccs;
|
||||||
|
ccs += stp; if (ccs < bpc) ocs = ccs;
|
||||||
|
ccs += stp; stp *= 2;
|
||||||
|
}
|
||||||
|
ocs = Math.floor(ocs / 1024 / 1024);
|
||||||
|
t.stitch_sz = Math.min(ocs, stitch_tgt);
|
||||||
|
}
|
||||||
|
|
||||||
|
for (var a = 0; a < t.postlist.length; a++) {
|
||||||
|
var nparts = [], tbytes = 0, stitch = t.stitch_sz;
|
||||||
|
if (t.nojoin && t.nojoin - t.postlist.length < 6)
|
||||||
|
stitch = 1;
|
||||||
|
|
||||||
|
--a;
|
||||||
|
for (var b = 0; b < stitch; b++) {
|
||||||
|
nparts.push(t.postlist[++a]);
|
||||||
|
tbytes += chunksize;
|
||||||
|
if (tbytes + chunksize > stitch * 1024 * 1024 || t.postlist[a + 1] - t.postlist[a] !== 1)
|
||||||
|
break;
|
||||||
|
}
|
||||||
arr.push({
|
arr.push({
|
||||||
'nfile': t.n,
|
'nfile': t.n,
|
||||||
'npart': t.postlist[a]
|
'nparts': nparts
|
||||||
});
|
});
|
||||||
|
}
|
||||||
|
t.nojoin = 0;
|
||||||
|
|
||||||
msg = null;
|
msg = null;
|
||||||
done = false;
|
done = false;
|
||||||
@@ -2366,7 +2419,7 @@ function up2k_init(subtle) {
|
|||||||
arr.sort(function (a, b) {
|
arr.sort(function (a, b) {
|
||||||
return a.nfile < b.nfile ? -1 :
|
return a.nfile < b.nfile ? -1 :
|
||||||
/* */ a.nfile > b.nfile ? 1 :
|
/* */ a.nfile > b.nfile ? 1 :
|
||||||
a.npart < b.npart ? -1 : 1;
|
/* */ a.nparts[0] < b.nparts[0] ? -1 : 1;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -2402,6 +2455,7 @@ function up2k_init(subtle) {
|
|||||||
pvis.seth(t.n, 2, L.u_ehstmp, t);
|
pvis.seth(t.n, 2, L.u_ehstmp, t);
|
||||||
|
|
||||||
var err = "",
|
var err = "",
|
||||||
|
cls = "ERROR",
|
||||||
rsp = unpre(xhr.responseText),
|
rsp = unpre(xhr.responseText),
|
||||||
ofs = rsp.lastIndexOf('\nURL: ');
|
ofs = rsp.lastIndexOf('\nURL: ');
|
||||||
|
|
||||||
@@ -2431,6 +2485,8 @@ function up2k_init(subtle) {
|
|||||||
if (!t.rechecks && (err_pend || err_srcb)) {
|
if (!t.rechecks && (err_pend || err_srcb)) {
|
||||||
t.rechecks = 0;
|
t.rechecks = 0;
|
||||||
t.want_recheck = true;
|
t.want_recheck = true;
|
||||||
|
err = L.u_dupdefer;
|
||||||
|
cls = 'defer';
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (rsp.indexOf('server HDD is full') + 1)
|
if (rsp.indexOf('server HDD is full') + 1)
|
||||||
@@ -2440,7 +2496,7 @@ function up2k_init(subtle) {
|
|||||||
if (!t.t_uploading)
|
if (!t.t_uploading)
|
||||||
st.bytes.finished += t.size;
|
st.bytes.finished += t.size;
|
||||||
|
|
||||||
pvis.seth(t.n, 1, "ERROR");
|
pvis.seth(t.n, 1, cls);
|
||||||
pvis.seth(t.n, 2, err);
|
pvis.seth(t.n, 2, err);
|
||||||
pvis.move(t.n, 'ng');
|
pvis.move(t.n, 'ng');
|
||||||
|
|
||||||
@@ -2467,9 +2523,13 @@ function up2k_init(subtle) {
|
|||||||
req.srch = 1;
|
req.srch = 1;
|
||||||
else if (t.rand)
|
else if (t.rand)
|
||||||
req.rand = true;
|
req.rand = true;
|
||||||
|
else if (t.umod)
|
||||||
|
req.umod = true;
|
||||||
|
|
||||||
xhr.open('POST', t.purl, true);
|
xhr.open('POST', t.purl, true);
|
||||||
xhr.responseType = 'text';
|
xhr.responseType = 'text';
|
||||||
|
xhr.timeout = 42000 + (t.srch || t.t_uploaded ? 0 :
|
||||||
|
(t.size / (1048 * 20))); // safededup 20M/s hdd
|
||||||
xhr.send(JSON.stringify(req));
|
xhr.send(JSON.stringify(req));
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -2511,7 +2571,10 @@ function up2k_init(subtle) {
|
|||||||
function exec_upload() {
|
function exec_upload() {
|
||||||
var upt = st.todo.upload.shift(),
|
var upt = st.todo.upload.shift(),
|
||||||
t = st.files[upt.nfile],
|
t = st.files[upt.nfile],
|
||||||
npart = upt.npart,
|
nparts = upt.nparts,
|
||||||
|
pcar = nparts[0],
|
||||||
|
pcdr = nparts[nparts.length - 1],
|
||||||
|
snpart = pcar == pcdr ? pcar : ('' + pcar + '~' + pcdr),
|
||||||
tries = 0;
|
tries = 0;
|
||||||
|
|
||||||
if (t.done)
|
if (t.done)
|
||||||
@@ -2526,24 +2589,30 @@ function up2k_init(subtle) {
|
|||||||
pvis.seth(t.n, 1, "🚀 send");
|
pvis.seth(t.n, 1, "🚀 send");
|
||||||
|
|
||||||
var chunksize = get_chunksize(t.size),
|
var chunksize = get_chunksize(t.size),
|
||||||
car = npart * chunksize,
|
car = pcar * chunksize,
|
||||||
cdr = car + chunksize;
|
cdr = (pcdr + 1) * chunksize;
|
||||||
|
|
||||||
if (cdr >= t.size)
|
if (cdr >= t.size)
|
||||||
cdr = t.size;
|
cdr = t.size;
|
||||||
|
|
||||||
var orz = function (xhr) {
|
var orz = function (xhr) {
|
||||||
|
st.bytes.inflight -= xhr.bsent;
|
||||||
var txt = unpre((xhr.response && xhr.response.err) || xhr.responseText);
|
var txt = unpre((xhr.response && xhr.response.err) || xhr.responseText);
|
||||||
if (txt.indexOf('upload blocked by x') + 1) {
|
if (txt.indexOf('upload blocked by x') + 1) {
|
||||||
apop(st.busy.upload, upt);
|
apop(st.busy.upload, upt);
|
||||||
apop(t.postlist, npart);
|
for (var a = pcar; a <= pcdr; a++)
|
||||||
|
apop(t.postlist, a);
|
||||||
pvis.seth(t.n, 1, "ERROR");
|
pvis.seth(t.n, 1, "ERROR");
|
||||||
pvis.seth(t.n, 2, txt.split(/\n/)[0]);
|
pvis.seth(t.n, 2, txt.split(/\n/)[0]);
|
||||||
pvis.move(t.n, 'ng');
|
pvis.move(t.n, 'ng');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (xhr.status == 200) {
|
if (xhr.status == 200) {
|
||||||
pvis.prog(t, npart, cdr - car);
|
var bdone = cdr - car;
|
||||||
|
for (var a = pcar; a <= pcdr; a++) {
|
||||||
|
pvis.prog(t, a, Math.min(bdone, chunksize));
|
||||||
|
bdone -= chunksize;
|
||||||
|
}
|
||||||
st.bytes.finished += cdr - car;
|
st.bytes.finished += cdr - car;
|
||||||
st.bytes.uploaded += cdr - car;
|
st.bytes.uploaded += cdr - car;
|
||||||
t.bytes_uploaded += cdr - car;
|
t.bytes_uploaded += cdr - car;
|
||||||
@@ -2552,18 +2621,21 @@ function up2k_init(subtle) {
|
|||||||
}
|
}
|
||||||
else if (txt.indexOf('already got that') + 1 ||
|
else if (txt.indexOf('already got that') + 1 ||
|
||||||
txt.indexOf('already being written') + 1) {
|
txt.indexOf('already being written') + 1) {
|
||||||
console.log("ignoring dupe-segment error", t.name, t);
|
t.nojoin = t.nojoin || t.postlist.length;
|
||||||
|
console.log("ignoring dupe-segment with backoff", t.nojoin, t.name, t);
|
||||||
|
if (!toast.visible && st.todo.upload.length < 4)
|
||||||
|
toast.inf(10, L.u_cbusy);
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
xhrchk(xhr, L.u_cuerr2.format(npart, Math.ceil(t.size / chunksize), t.name), "404, target folder not found (???)", "warn", t);
|
xhrchk(xhr, L.u_cuerr2.format(snpart, Math.ceil(t.size / chunksize), t.name), "404, target folder not found (???)", "warn", t);
|
||||||
|
|
||||||
chill(t);
|
chill(t);
|
||||||
}
|
}
|
||||||
orz2(xhr);
|
orz2(xhr);
|
||||||
}
|
}
|
||||||
var orz2 = function (xhr) {
|
var orz2 = function (xhr) {
|
||||||
apop(st.busy.upload, upt);
|
apop(st.busy.upload, upt);
|
||||||
apop(t.postlist, npart);
|
for (var a = pcar; a <= pcdr; a++)
|
||||||
|
apop(t.postlist, a);
|
||||||
if (!t.postlist.length) {
|
if (!t.postlist.length) {
|
||||||
t.t_uploaded = Date.now();
|
t.t_uploaded = Date.now();
|
||||||
pvis.seth(t.n, 1, 'verifying');
|
pvis.seth(t.n, 1, 'verifying');
|
||||||
@@ -2577,23 +2649,48 @@ function up2k_init(subtle) {
|
|||||||
btot = Math.floor(st.bytes.total / 1024 / 1024);
|
btot = Math.floor(st.bytes.total / 1024 / 1024);
|
||||||
|
|
||||||
xhr.upload.onprogress = function (xev) {
|
xhr.upload.onprogress = function (xev) {
|
||||||
pvis.prog(t, npart, xev.loaded);
|
var nb = xev.loaded,
|
||||||
|
db = nb - xhr.bsent;
|
||||||
|
|
||||||
|
if (!db)
|
||||||
|
return;
|
||||||
|
|
||||||
|
st.bytes.inflight += db;
|
||||||
|
xhr.bsent = nb;
|
||||||
|
xhr.timeout = 64000 + Date.now() - xhr.t0;
|
||||||
|
pvis.prog(t, pcar, nb);
|
||||||
};
|
};
|
||||||
xhr.onload = function (xev) {
|
xhr.onload = function (xev) {
|
||||||
try { orz(xhr); } catch (ex) { vis_exh(ex + '', 'up2k.js', '', '', ex); }
|
try { orz(xhr); } catch (ex) { vis_exh(ex + '', 'up2k.js', '', '', ex); }
|
||||||
};
|
};
|
||||||
xhr.onerror = function (xev) {
|
xhr.onerror = xhr.ontimeout = function (xev) {
|
||||||
if (crashed)
|
if (crashed)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
if (!toast.visible)
|
st.bytes.inflight -= (xhr.bsent || 0);
|
||||||
toast.warn(9.98, L.u_cuerr.format(npart, Math.ceil(t.size / chunksize), t.name), t);
|
xhr.bsent = 0;
|
||||||
|
|
||||||
|
if (!toast.visible)
|
||||||
|
toast.warn(9.98, L.u_cuerr.format(snpart, Math.ceil(t.size / chunksize), t.name), t);
|
||||||
|
|
||||||
|
t.nojoin = t.nojoin || t.postlist.length; // maybe rproxy postsize limit
|
||||||
console.log('chunkpit onerror,', ++tries, t.name, t);
|
console.log('chunkpit onerror,', ++tries, t.name, t);
|
||||||
orz2(xhr);
|
orz2(xhr);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
var chashes = [],
|
||||||
|
ctxt = t.hash[pcar],
|
||||||
|
plen = Math.floor(192 / nparts.length);
|
||||||
|
|
||||||
|
plen = plen > 9 ? 9 : plen < 2 ? 2 : plen;
|
||||||
|
for (var a = pcar + 1; a <= pcdr; a++)
|
||||||
|
chashes.push(t.hash[a].slice(0, plen));
|
||||||
|
|
||||||
|
if (chashes.length)
|
||||||
|
ctxt += ',' + plen + ',' + chashes.join('');
|
||||||
|
|
||||||
xhr.open('POST', t.purl, true);
|
xhr.open('POST', t.purl, true);
|
||||||
xhr.setRequestHeader("X-Up2k-Hash", t.hash[npart]);
|
xhr.setRequestHeader("X-Up2k-Hash", ctxt);
|
||||||
xhr.setRequestHeader("X-Up2k-Wark", t.wark);
|
xhr.setRequestHeader("X-Up2k-Wark", t.wark);
|
||||||
xhr.setRequestHeader("X-Up2k-Stat", "{0}/{1}/{2}/{3} {4}/{5} {6}".format(
|
xhr.setRequestHeader("X-Up2k-Stat", "{0}/{1}/{2}/{3} {4}/{5} {6}".format(
|
||||||
pvis.ctr.ok, pvis.ctr.ng, pvis.ctr.bz, pvis.ctr.q, btot, btot - bfin,
|
pvis.ctr.ok, pvis.ctr.ng, pvis.ctr.bz, pvis.ctr.q, btot, btot - bfin,
|
||||||
@@ -2602,6 +2699,9 @@ function up2k_init(subtle) {
|
|||||||
if (xhr.overrideMimeType)
|
if (xhr.overrideMimeType)
|
||||||
xhr.overrideMimeType('Content-Type', 'application/octet-stream');
|
xhr.overrideMimeType('Content-Type', 'application/octet-stream');
|
||||||
|
|
||||||
|
xhr.bsent = 0;
|
||||||
|
xhr.t0 = Date.now();
|
||||||
|
xhr.timeout = 42000;
|
||||||
xhr.responseType = 'text';
|
xhr.responseType = 'text';
|
||||||
xhr.send(t.fobj.slice(car, cdr));
|
xhr.send(t.fobj.slice(car, cdr));
|
||||||
}
|
}
|
||||||
@@ -2685,7 +2785,11 @@ function up2k_init(subtle) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
parallel_uploads = v;
|
parallel_uploads = v;
|
||||||
swrite('nthread', v);
|
if (v == u2j)
|
||||||
|
sdrop('nthread');
|
||||||
|
else
|
||||||
|
swrite('nthread', v);
|
||||||
|
|
||||||
clmod(obj, 'err');
|
clmod(obj, 'err');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -2698,8 +2802,32 @@ function up2k_init(subtle) {
|
|||||||
if (parallel_uploads > 16)
|
if (parallel_uploads > 16)
|
||||||
parallel_uploads = 16;
|
parallel_uploads = 16;
|
||||||
|
|
||||||
|
if (parallel_uploads > 6)
|
||||||
|
toast.warn(10, L.u_maxconn);
|
||||||
|
else if (toast.txt == L.u_maxconn)
|
||||||
|
toast.hide();
|
||||||
|
|
||||||
obj.value = parallel_uploads;
|
obj.value = parallel_uploads;
|
||||||
bumpthread({ "target": 1 })
|
bumpthread({ "target": 1 });
|
||||||
|
}
|
||||||
|
|
||||||
|
var read_u2sz = function () {
|
||||||
|
var el = ebi('u2szg'), n = parseInt(el.value), dv = u2sz.split(',');
|
||||||
|
stitch_tgt = n = (
|
||||||
|
isNaN(n) ? dv[1] :
|
||||||
|
n < dv[0] ? dv[0] :
|
||||||
|
n > dv[2] ? dv[2] : n
|
||||||
|
);
|
||||||
|
if (n == dv[1]) sdrop('u2sz'); else swrite('u2sz', n);
|
||||||
|
if (el.value != n) el.value = n;
|
||||||
|
};
|
||||||
|
ebi('u2szg').addEventListener('blur', read_u2sz);
|
||||||
|
ebi('u2szg').onkeydown = function (e) {
|
||||||
|
if (anymod(e)) return;
|
||||||
|
var n = e.code == 'ArrowUp' ? 1 : e.code == 'ArrowDown' ? -1 : 0;
|
||||||
|
if (!n) return;
|
||||||
|
this.value = parseInt(this.value) + n;
|
||||||
|
read_u2sz();
|
||||||
}
|
}
|
||||||
|
|
||||||
function tgl_fsearch() {
|
function tgl_fsearch() {
|
||||||
@@ -2831,6 +2959,8 @@ function up2k_init(subtle) {
|
|||||||
new_state = false;
|
new_state = false;
|
||||||
fixed = true;
|
fixed = true;
|
||||||
}
|
}
|
||||||
|
if (new_state === undefined)
|
||||||
|
new_state = can_write ? false : have_up2k_idx ? true : undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (new_state === undefined)
|
if (new_state === undefined)
|
||||||
@@ -2911,7 +3041,7 @@ function up2k_init(subtle) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function set_hashw() {
|
function set_hashw() {
|
||||||
if (!window.WebAssembly) {
|
if (!WebAssembly) {
|
||||||
bcfg_set('hashw', uc.hashw = false);
|
bcfg_set('hashw', uc.hashw = false);
|
||||||
toast.err(10, L.u_nowork);
|
toast.err(10, L.u_nowork);
|
||||||
}
|
}
|
||||||
@@ -2928,7 +3058,7 @@ function up2k_init(subtle) {
|
|||||||
nopenag();
|
nopenag();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!window.Notification || !HTTPS)
|
if (!Notification || !HTTPS)
|
||||||
return nopenag();
|
return nopenag();
|
||||||
|
|
||||||
if (en && Notification.permission == 'default')
|
if (en && Notification.permission == 'default')
|
||||||
@@ -2950,7 +3080,7 @@ function up2k_init(subtle) {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
if (uc.upnag && (!window.Notification || Notification.permission != 'granted'))
|
if (uc.upnag && (!Notification || Notification.permission != 'granted'))
|
||||||
bcfg_set('upnag', uc.upnag = false);
|
bcfg_set('upnag', uc.upnag = false);
|
||||||
|
|
||||||
ebi('nthread_add').onclick = function (e) {
|
ebi('nthread_add').onclick = function (e) {
|
||||||
|
|||||||
@@ -12,9 +12,10 @@ if (window.CGV)
|
|||||||
|
|
||||||
|
|
||||||
var wah = '',
|
var wah = '',
|
||||||
|
STG = null,
|
||||||
NOAC = 'autocorrect="off" autocapitalize="off"',
|
NOAC = 'autocorrect="off" autocapitalize="off"',
|
||||||
L, tt, treectl, thegrid, up2k, asmCrypto, hashwasm, vbar, marked,
|
L, tt, treectl, thegrid, up2k, asmCrypto, hashwasm, vbar, marked,
|
||||||
CB = '?_=' + Date.now(),
|
T0 = Date.now(),
|
||||||
R = SR.slice(1),
|
R = SR.slice(1),
|
||||||
RS = R ? "/" + R : "",
|
RS = R ? "/" + R : "",
|
||||||
HALFMAX = 8192 * 8192 * 8192 * 8192,
|
HALFMAX = 8192 * 8192 * 8192 * 8192,
|
||||||
@@ -40,8 +41,16 @@ if (!window.FormData)
|
|||||||
window.FormData = false;
|
window.FormData = false;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
CB = '?' + document.currentScript.src.split('?').pop();
|
STG = window.localStorage;
|
||||||
|
STG.STG;
|
||||||
|
}
|
||||||
|
catch (ex) {
|
||||||
|
STG = null;
|
||||||
|
if ((ex + '').indexOf('sandbox') < 0)
|
||||||
|
console.log('no localStorage: ' + ex);
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
if (navigator.userAgentData.mobile)
|
if (navigator.userAgentData.mobile)
|
||||||
MOBILE = true;
|
MOBILE = true;
|
||||||
|
|
||||||
@@ -118,13 +127,13 @@ if ((document.location + '').indexOf(',rej,') + 1)
|
|||||||
|
|
||||||
try {
|
try {
|
||||||
console.hist = [];
|
console.hist = [];
|
||||||
var CMAXHIST = 100;
|
var CMAXHIST = MOBILE ? 9000 : 44000;
|
||||||
var hook = function (t) {
|
var hook = function (t) {
|
||||||
var orig = console[t].bind(console),
|
var orig = console[t].bind(console),
|
||||||
cfun = function () {
|
cfun = function () {
|
||||||
console.hist.push(Date.now() + ' ' + t + ': ' + Array.from(arguments).join(', '));
|
console.hist.push(Date.now() + ' ' + t + ': ' + Array.from(arguments).join(', '));
|
||||||
if (console.hist.length > CMAXHIST)
|
if (console.hist.length > CMAXHIST)
|
||||||
console.hist = console.hist.slice(CMAXHIST / 2);
|
console.hist = console.hist.slice(CMAXHIST / 4);
|
||||||
|
|
||||||
orig.apply(console, arguments);
|
orig.apply(console, arguments);
|
||||||
};
|
};
|
||||||
@@ -145,6 +154,10 @@ catch (ex) {
|
|||||||
}
|
}
|
||||||
var crashed = false, ignexd = {}, evalex_fatal = false;
|
var crashed = false, ignexd = {}, evalex_fatal = false;
|
||||||
function vis_exh(msg, url, lineNo, columnNo, error) {
|
function vis_exh(msg, url, lineNo, columnNo, error) {
|
||||||
|
var ekey = url + '\n' + lineNo + '\n' + msg;
|
||||||
|
if (ignexd[ekey] || crashed)
|
||||||
|
return;
|
||||||
|
|
||||||
msg = String(msg);
|
msg = String(msg);
|
||||||
url = String(url);
|
url = String(url);
|
||||||
|
|
||||||
@@ -160,11 +173,13 @@ function vis_exh(msg, url, lineNo, columnNo, error) {
|
|||||||
if (url.indexOf(' > eval') + 1 && !evalex_fatal)
|
if (url.indexOf(' > eval') + 1 && !evalex_fatal)
|
||||||
return; // md timer
|
return; // md timer
|
||||||
|
|
||||||
var ekey = url + '\n' + lineNo + '\n' + msg;
|
if (IE && url.indexOf('prism.js') + 1)
|
||||||
if (ignexd[ekey] || crashed)
|
|
||||||
return;
|
return;
|
||||||
|
|
||||||
if (url.indexOf('deps/marked.js') + 1 && !window.WebAssembly)
|
if (url.indexOf('easymde.js') + 1)
|
||||||
|
return; // clicking the preview pane
|
||||||
|
|
||||||
|
if (url.indexOf('deps/marked.js') + 1 && !WebAssembly)
|
||||||
return; // ff<52
|
return; // ff<52
|
||||||
|
|
||||||
crashed = true;
|
crashed = true;
|
||||||
@@ -202,19 +217,24 @@ function vis_exh(msg, url, lineNo, columnNo, error) {
|
|||||||
}
|
}
|
||||||
ignexd[ekey] = true;
|
ignexd[ekey] = true;
|
||||||
|
|
||||||
var ls = jcp(localStorage);
|
var ls = {},
|
||||||
if (ls.fman_clip)
|
lsk = Object.keys(localStorage),
|
||||||
ls.fman_clip = ls.fman_clip.length + ' items';
|
nka = lsk.length,
|
||||||
|
nk = Math.min(200, nka);
|
||||||
|
|
||||||
var lsk = Object.keys(ls);
|
for (var a = 0; a < nk; a++) {
|
||||||
lsk.sort();
|
var k = lsk[a],
|
||||||
html.push('<p class="b">');
|
v = localStorage.getItem(k);
|
||||||
for (var a = 0; a < lsk.length; a++) {
|
|
||||||
if (ls[lsk[a]].length > 9000)
|
|
||||||
continue;
|
|
||||||
|
|
||||||
html.push(' <b>' + esc(lsk[a]) + '</b> <code>' + esc(ls[lsk[a]]) + '</code> ');
|
ls[k] = v.length > 256 ? v.slice(0, 32) + '[...' + v.length + 'b]' : v;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
lsk = Object.keys(ls);
|
||||||
|
lsk.sort();
|
||||||
|
html.push('<p class="b"><b>' + nka + ': </b>');
|
||||||
|
for (var a = 0; a < nk; a++)
|
||||||
|
html.push(' <b>' + esc(lsk[a]) + '</b> <code>' + esc(ls[lsk[a]]) + '</code> ');
|
||||||
|
|
||||||
html.push('</p>');
|
html.push('</p>');
|
||||||
}
|
}
|
||||||
catch (e) { }
|
catch (e) { }
|
||||||
@@ -276,8 +296,15 @@ function anymod(e, shift_ok) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
var dev_fbw = sread('dev_fbw');
|
||||||
function ev(e) {
|
function ev(e) {
|
||||||
e = e || window.event;
|
if (!e && window.event) {
|
||||||
|
e = window.event;
|
||||||
|
if (dev_fbw == 1) {
|
||||||
|
toast.warn(10, 'hello from fallback code ;_;\ncheck console trace');
|
||||||
|
console.error('using window.event');
|
||||||
|
}
|
||||||
|
}
|
||||||
if (!e)
|
if (!e)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
@@ -296,7 +323,7 @@ function ev(e) {
|
|||||||
|
|
||||||
|
|
||||||
function noope(e) {
|
function noope(e) {
|
||||||
ev(e);
|
try { ev(e); } catch (ex) { }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -364,6 +391,22 @@ catch (ex) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!window.Set)
|
||||||
|
window.Set = function () {
|
||||||
|
var r = this;
|
||||||
|
r.size = 0;
|
||||||
|
r.d = {};
|
||||||
|
r.add = function (k) {
|
||||||
|
if (!r.d[k]) {
|
||||||
|
r.d[k] = 1;
|
||||||
|
r.size++;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
r.has = function (k) {
|
||||||
|
return r.d[k];
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
// https://stackoverflow.com/a/950146
|
// https://stackoverflow.com/a/950146
|
||||||
function import_js(url, cb, ecb) {
|
function import_js(url, cb, ecb) {
|
||||||
var head = document.head || document.getElementsByTagName('head')[0];
|
var head = document.head || document.getElementsByTagName('head')[0];
|
||||||
@@ -389,6 +432,25 @@ function unsmart(txt) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
function namesan(txt, win, fslash) {
|
||||||
|
if (win)
|
||||||
|
txt = (txt.
|
||||||
|
replace(/</g, "<").
|
||||||
|
replace(/>/g, ">").
|
||||||
|
replace(/:/g, ":").
|
||||||
|
replace(/"/g, """).
|
||||||
|
replace(/\\/g, "\").
|
||||||
|
replace(/\|/g, "|").
|
||||||
|
replace(/\?/g, "?").
|
||||||
|
replace(/\*/g, "*"));
|
||||||
|
|
||||||
|
if (fslash)
|
||||||
|
txt = txt.replace(/\//g, "/");
|
||||||
|
|
||||||
|
return txt;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
var crctab = (function () {
|
var crctab = (function () {
|
||||||
var c, tab = [];
|
var c, tab = [];
|
||||||
for (var n = 0; n < 256; n++) {
|
for (var n = 0; n < 256; n++) {
|
||||||
@@ -411,6 +473,24 @@ function crc32(str) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
function randstr(len) {
|
||||||
|
var ret = '';
|
||||||
|
try {
|
||||||
|
var ar = new Uint32Array(Math.floor((len + 3) / 4));
|
||||||
|
crypto.getRandomValues(ar);
|
||||||
|
for (var a = 0; a < ar.length; a++)
|
||||||
|
ret += ('000' + ar[a].toString(36)).slice(-4);
|
||||||
|
return ret.slice(0, len);
|
||||||
|
}
|
||||||
|
catch (ex) {
|
||||||
|
console.log('using unsafe randstr because ' + ex);
|
||||||
|
while (ret.length < len)
|
||||||
|
ret += ('000' + Math.floor(Math.random() * 1679616).toString(36)).slice(-4);
|
||||||
|
return ret.slice(0, len);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
function clmod(el, cls, add) {
|
function clmod(el, cls, add) {
|
||||||
if (!el)
|
if (!el)
|
||||||
return false;
|
return false;
|
||||||
@@ -675,6 +755,15 @@ function vjoin(p1, p2) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
function addq(url, q) {
|
||||||
|
var uh = url.split('#', 1),
|
||||||
|
u = uh[0],
|
||||||
|
h = uh.length == 1 ? '' : '#' + uh[1];
|
||||||
|
|
||||||
|
return u + (u.indexOf('?') < 0 ? '?' : '&') + (q === undefined ? '' : q) + h;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
function uricom_enc(txt, do_fb_enc) {
|
function uricom_enc(txt, do_fb_enc) {
|
||||||
try {
|
try {
|
||||||
return encodeURIComponent(txt);
|
return encodeURIComponent(txt);
|
||||||
@@ -755,17 +844,6 @@ function noq_href(el) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
function get_pwd() {
|
|
||||||
var k = HTTPS ? 's=' : 'd=',
|
|
||||||
pwd = ('; ' + document.cookie).split('; cppw' + k);
|
|
||||||
|
|
||||||
if (pwd.length < 2)
|
|
||||||
return null;
|
|
||||||
|
|
||||||
return decodeURIComponent(pwd[1].split(';')[0]);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
function unix2iso(ts) {
|
function unix2iso(ts) {
|
||||||
return new Date(ts * 1000).toISOString().replace("T", " ").slice(0, -5);
|
return new Date(ts * 1000).toISOString().replace("T", " ").slice(0, -5);
|
||||||
}
|
}
|
||||||
@@ -886,9 +964,16 @@ function jcp(obj) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
function sdrop(key) {
|
||||||
|
try {
|
||||||
|
STG.removeItem(key);
|
||||||
|
}
|
||||||
|
catch (ex) { }
|
||||||
|
}
|
||||||
|
|
||||||
function sread(key, al) {
|
function sread(key, al) {
|
||||||
try {
|
try {
|
||||||
var ret = localStorage.getItem(key);
|
var ret = STG.getItem(key);
|
||||||
return (!al || has(al, ret)) ? ret : null;
|
return (!al || has(al, ret)) ? ret : null;
|
||||||
}
|
}
|
||||||
catch (e) {
|
catch (e) {
|
||||||
@@ -899,9 +984,9 @@ function sread(key, al) {
|
|||||||
function swrite(key, val) {
|
function swrite(key, val) {
|
||||||
try {
|
try {
|
||||||
if (val === undefined || val === null)
|
if (val === undefined || val === null)
|
||||||
localStorage.removeItem(key);
|
STG.removeItem(key);
|
||||||
else
|
else
|
||||||
localStorage.setItem(key, val);
|
STG.setItem(key, val);
|
||||||
}
|
}
|
||||||
catch (e) { }
|
catch (e) { }
|
||||||
}
|
}
|
||||||
@@ -936,7 +1021,7 @@ function fcfg_get(name, defval) {
|
|||||||
val = parseFloat(sread(name));
|
val = parseFloat(sread(name));
|
||||||
|
|
||||||
if (!isNum(val))
|
if (!isNum(val))
|
||||||
return parseFloat(o ? o.value : defval);
|
return parseFloat(o && o.value !== '' ? o.value : defval);
|
||||||
|
|
||||||
if (o)
|
if (o)
|
||||||
o.value = val;
|
o.value = val;
|
||||||
@@ -1062,7 +1147,7 @@ function dl_file(url) {
|
|||||||
|
|
||||||
function cliptxt(txt, ok) {
|
function cliptxt(txt, ok) {
|
||||||
var fb = function () {
|
var fb = function () {
|
||||||
console.log('fb');
|
console.log('clip-fb');
|
||||||
var o = mknod('input');
|
var o = mknod('input');
|
||||||
o.value = txt;
|
o.value = txt;
|
||||||
document.body.appendChild(o);
|
document.body.appendChild(o);
|
||||||
@@ -1329,10 +1414,10 @@ var tt = (function () {
|
|||||||
o = ctr.querySelectorAll('*[tt]');
|
o = ctr.querySelectorAll('*[tt]');
|
||||||
|
|
||||||
for (var a = o.length - 1; a >= 0; a--) {
|
for (var a = o.length - 1; a >= 0; a--) {
|
||||||
o[a].onfocus = _cshow;
|
o[a].addEventListener('focus', _cshow);
|
||||||
o[a].onblur = _hide;
|
o[a].addEventListener('blur', _hide);
|
||||||
o[a].onmouseenter = _dshow;
|
o[a].addEventListener('mouseenter', _dshow);
|
||||||
o[a].onmouseleave = _hide;
|
o[a].addEventListener('mouseleave', _hide);
|
||||||
}
|
}
|
||||||
r.hide();
|
r.hide();
|
||||||
}
|
}
|
||||||
@@ -1356,9 +1441,12 @@ function lf2br(txt) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
function unpre(txt) {
|
function hunpre(txt) {
|
||||||
return ('' + txt).replace(/^<pre>/, '');
|
return ('' + txt).replace(/^<pre>/, '');
|
||||||
}
|
}
|
||||||
|
function unpre(txt) {
|
||||||
|
return esc(hunpre(txt));
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
var toast = (function () {
|
var toast = (function () {
|
||||||
@@ -1397,15 +1485,23 @@ var toast = (function () {
|
|||||||
}
|
}
|
||||||
|
|
||||||
r.hide = function (e) {
|
r.hide = function (e) {
|
||||||
ev(e);
|
if (this === ebi('toastc'))
|
||||||
|
ev(e);
|
||||||
|
|
||||||
unscroll();
|
unscroll();
|
||||||
clearTimeout(te);
|
clearTimeout(te);
|
||||||
clmod(obj, 'vis');
|
clmod(obj, 'vis');
|
||||||
r.visible = false;
|
r.visible = false;
|
||||||
r.tag = obj;
|
r.tag = obj;
|
||||||
|
if (!WebAssembly)
|
||||||
|
te = setTimeout(function () {
|
||||||
|
obj.className = 'hide';
|
||||||
|
}, 500);
|
||||||
};
|
};
|
||||||
|
|
||||||
r.show = function (cl, sec, txt, tag) {
|
r.show = function (cl, sec, txt, tag) {
|
||||||
|
txt = (txt + '').slice(0, 16384);
|
||||||
|
|
||||||
var same = r.visible && txt == r.p_txt && r.p_sec == sec,
|
var same = r.visible && txt == r.p_txt && r.p_sec == sec,
|
||||||
delta = Date.now() - r.p_t;
|
delta = Date.now() - r.p_t;
|
||||||
|
|
||||||
@@ -1458,9 +1554,12 @@ var modal = (function () {
|
|||||||
var r = {},
|
var r = {},
|
||||||
q = [],
|
q = [],
|
||||||
o = null,
|
o = null,
|
||||||
|
scrolling = null,
|
||||||
cb_up = null,
|
cb_up = null,
|
||||||
cb_ok = null,
|
cb_ok = null,
|
||||||
cb_ng = null,
|
cb_ng = null,
|
||||||
|
sel_0 = 0,
|
||||||
|
sel_1 = 0,
|
||||||
tok, tng, prim, sec, ok_cancel;
|
tok, tng, prim, sec, ok_cancel;
|
||||||
|
|
||||||
r.load = function () {
|
r.load = function () {
|
||||||
@@ -1473,8 +1572,10 @@ var modal = (function () {
|
|||||||
r.load();
|
r.load();
|
||||||
|
|
||||||
r.busy = false;
|
r.busy = false;
|
||||||
|
r.nofocus = 0;
|
||||||
|
|
||||||
r.show = function (html) {
|
r.show = function (html) {
|
||||||
|
tt.hide();
|
||||||
o = mknod('div', 'modal');
|
o = mknod('div', 'modal');
|
||||||
o.innerHTML = '<table><tr><td><div id="modalc">' + html + '</div></td></tr></table>';
|
o.innerHTML = '<table><tr><td><div id="modalc">' + html + '</div></td></tr></table>';
|
||||||
document.body.appendChild(o);
|
document.body.appendChild(o);
|
||||||
@@ -1486,16 +1587,19 @@ var modal = (function () {
|
|||||||
a.onclick = ng;
|
a.onclick = ng;
|
||||||
|
|
||||||
a = ebi('modal-ok');
|
a = ebi('modal-ok');
|
||||||
|
a.addEventListener('blur', onblur);
|
||||||
a.onclick = ok;
|
a.onclick = ok;
|
||||||
|
|
||||||
var inp = ebi('modali');
|
var inp = ebi('modali');
|
||||||
(inp || a).focus();
|
(inp || a).focus();
|
||||||
if (inp)
|
if (inp)
|
||||||
setTimeout(function () {
|
setTimeout(function () {
|
||||||
inp.setSelectionRange(0, inp.value.length, "forward");
|
inp.setSelectionRange(sel_0, sel_1, "forward");
|
||||||
}, 0);
|
}, 0);
|
||||||
|
|
||||||
document.addEventListener('focus', onfocus);
|
document.addEventListener('focus', onfocus);
|
||||||
|
document.addEventListener('selectionchange', onselch);
|
||||||
|
timer.add(scrollchk, 1);
|
||||||
timer.add(onfocus);
|
timer.add(onfocus);
|
||||||
if (cb_up)
|
if (cb_up)
|
||||||
setTimeout(cb_up, 1);
|
setTimeout(cb_up, 1);
|
||||||
@@ -1503,6 +1607,13 @@ var modal = (function () {
|
|||||||
|
|
||||||
r.hide = function () {
|
r.hide = function () {
|
||||||
timer.rm(onfocus);
|
timer.rm(onfocus);
|
||||||
|
timer.rm(scrollchk);
|
||||||
|
scrolling = null;
|
||||||
|
try {
|
||||||
|
ebi('modal-ok').removeEventListener('blur', onblur);
|
||||||
|
}
|
||||||
|
catch (ex) { }
|
||||||
|
document.removeEventListener('selectionchange', onselch);
|
||||||
document.removeEventListener('focus', onfocus);
|
document.removeEventListener('focus', onfocus);
|
||||||
document.removeEventListener('keydown', onkey);
|
document.removeEventListener('keydown', onkey);
|
||||||
o.parentNode.removeChild(o);
|
o.parentNode.removeChild(o);
|
||||||
@@ -1516,28 +1627,61 @@ var modal = (function () {
|
|||||||
r.hide();
|
r.hide();
|
||||||
if (cb_ok)
|
if (cb_ok)
|
||||||
cb_ok(v);
|
cb_ok(v);
|
||||||
}
|
};
|
||||||
var ng = function (e) {
|
var ng = function (e) {
|
||||||
ev(e);
|
ev(e);
|
||||||
r.hide();
|
r.hide();
|
||||||
if (cb_ng)
|
if (cb_ng)
|
||||||
cb_ng(null);
|
cb_ng(null);
|
||||||
}
|
};
|
||||||
|
|
||||||
|
var scrollchk = function () {
|
||||||
|
if (scrolling === true)
|
||||||
|
return;
|
||||||
|
|
||||||
|
var o = ebi('modalc'),
|
||||||
|
vis = o.offsetHeight,
|
||||||
|
all = o.scrollHeight,
|
||||||
|
nsc = 8 + vis < all;
|
||||||
|
|
||||||
|
if (scrolling !== nsc)
|
||||||
|
clmod(o, 'yk', !nsc);
|
||||||
|
|
||||||
|
scrolling = nsc;
|
||||||
|
};
|
||||||
|
|
||||||
|
var onselch = function () {
|
||||||
|
try {
|
||||||
|
if (window.getSelection() + '')
|
||||||
|
r.nofocus = 15;
|
||||||
|
}
|
||||||
|
catch (ex) { }
|
||||||
|
};
|
||||||
|
|
||||||
|
var onblur = function () {
|
||||||
|
r.nofocus = 3;
|
||||||
|
};
|
||||||
|
|
||||||
var onfocus = function (e) {
|
var onfocus = function (e) {
|
||||||
|
if (MOBILE)
|
||||||
|
return;
|
||||||
|
|
||||||
var ctr = ebi('modalc');
|
var ctr = ebi('modalc');
|
||||||
if (!ctr || !ctr.contains || !document.activeElement || ctr.contains(document.activeElement))
|
if (!ctr || !ctr.contains || !document.activeElement || ctr.contains(document.activeElement))
|
||||||
return;
|
return;
|
||||||
|
|
||||||
setTimeout(function () {
|
setTimeout(function () {
|
||||||
|
if (--r.nofocus >= 0)
|
||||||
|
return;
|
||||||
|
|
||||||
if (ctr = ebi('modal-ok'))
|
if (ctr = ebi('modal-ok'))
|
||||||
ctr.focus();
|
ctr.focus();
|
||||||
}, 20);
|
}, 20);
|
||||||
ev(e);
|
ev(e);
|
||||||
}
|
};
|
||||||
|
|
||||||
var onkey = function (e) {
|
var onkey = function (e) {
|
||||||
var k = e.code,
|
var k = (e.code || e.key) + '',
|
||||||
eok = ebi('modal-ok'),
|
eok = ebi('modal-ok'),
|
||||||
eng = ebi('modal-ng'),
|
eng = ebi('modal-ng'),
|
||||||
ae = document.activeElement;
|
ae = document.activeElement;
|
||||||
@@ -1545,18 +1689,18 @@ var modal = (function () {
|
|||||||
if (k == 'Space' && ae && (ae === eok || ae === eng))
|
if (k == 'Space' && ae && (ae === eok || ae === eng))
|
||||||
k = 'Enter';
|
k = 'Enter';
|
||||||
|
|
||||||
if (k == 'Enter') {
|
if (k.endsWith('Enter')) {
|
||||||
if (ae && ae == eng)
|
if (ae && ae == eng)
|
||||||
return ng();
|
return ng(e);
|
||||||
|
|
||||||
return ok();
|
return ok(e);
|
||||||
}
|
}
|
||||||
|
|
||||||
if ((k == 'ArrowLeft' || k == 'ArrowRight') && eng && (ae == eok || ae == eng))
|
if ((k == 'ArrowLeft' || k == 'ArrowRight' || k == 'Left' || k == 'Right') && eng && (ae == eok || ae == eng))
|
||||||
return (ae == eok ? eng : eok).focus() || ev(e);
|
return (ae == eok ? eng : eok).focus() || ev(e);
|
||||||
|
|
||||||
if (k == 'Escape')
|
if (k == 'Escape' || k == 'Esc')
|
||||||
return ng();
|
return ng(e);
|
||||||
}
|
}
|
||||||
|
|
||||||
var next = function () {
|
var next = function () {
|
||||||
@@ -1591,16 +1735,18 @@ var modal = (function () {
|
|||||||
r.show(html);
|
r.show(html);
|
||||||
}
|
}
|
||||||
|
|
||||||
r.prompt = function (html, v, cok, cng, fun) {
|
r.prompt = function (html, v, cok, cng, fun, so0, so1) {
|
||||||
q.push(function () {
|
q.push(function () {
|
||||||
_prompt(lf2br(html), v, cok, cng, fun);
|
_prompt(lf2br(html), v, cok, cng, fun, so0, so1);
|
||||||
});
|
});
|
||||||
next();
|
next();
|
||||||
}
|
}
|
||||||
var _prompt = function (html, v, cok, cng, fun) {
|
var _prompt = function (html, v, cok, cng, fun, so0, so1) {
|
||||||
cb_ok = cok;
|
cb_ok = cok;
|
||||||
cb_ng = cng === undefined ? cok : null;
|
cb_ng = cng === undefined ? cok : null;
|
||||||
cb_up = fun;
|
cb_up = fun;
|
||||||
|
sel_0 = so0 || 0;
|
||||||
|
sel_1 = so1 === undefined ? v.length : so1;
|
||||||
html += '<input id="modali" type="text" ' + NOAC + ' /><div id="modalb">' + ok_cancel + '</div>';
|
html += '<input id="modali" type="text" ' + NOAC + ' /><div id="modalb">' + ok_cancel + '</div>';
|
||||||
r.show(html);
|
r.show(html);
|
||||||
|
|
||||||
@@ -1787,7 +1933,7 @@ function md_thumbs(md) {
|
|||||||
float = has(flags, 'l') ? 'left' : has(flags, 'r') ? 'right' : '';
|
float = has(flags, 'l') ? 'left' : has(flags, 'r') ? 'right' : '';
|
||||||
|
|
||||||
if (!/[?&]cache/.exec(url))
|
if (!/[?&]cache/.exec(url))
|
||||||
url += (url.indexOf('?') < 0 ? '?' : '&') + 'cache=i';
|
url = addq(url, 'cache=i');
|
||||||
|
|
||||||
md[a] = '<a href="' + url + '" class="mdth mdth' + float.slice(0, 1) + '"><img src="' + url + '&th=w" alt="' + alt + '" /></a>' + md[a].slice(o2 + 1);
|
md[a] = '<a href="' + url + '" class="mdth mdth' + float.slice(0, 1) + '"><img src="' + url + '&th=w" alt="' + alt + '" /></a>' + md[a].slice(o2 + 1);
|
||||||
}
|
}
|
||||||
@@ -1833,21 +1979,17 @@ var favico = (function () {
|
|||||||
var b64;
|
var b64;
|
||||||
try {
|
try {
|
||||||
b64 = btoa(svg ? svg_decl + svg : gx(r.txt));
|
b64 = btoa(svg ? svg_decl + svg : gx(r.txt));
|
||||||
//console.log('f1');
|
|
||||||
}
|
}
|
||||||
catch (e1) {
|
catch (e1) {
|
||||||
try {
|
try {
|
||||||
b64 = btoa(gx(encodeURIComponent(r.txt).replace(/%([0-9A-F]{2})/g,
|
b64 = btoa(gx(encodeURIComponent(r.txt).replace(/%([0-9A-F]{2})/g,
|
||||||
function x(m, v) { return String.fromCharCode('0x' + v); })));
|
function x(m, v) { return String.fromCharCode('0x' + v); })));
|
||||||
//console.log('f2');
|
|
||||||
}
|
}
|
||||||
catch (e2) {
|
catch (e2) {
|
||||||
try {
|
try {
|
||||||
b64 = btoa(gx(unescape(encodeURIComponent(r.txt))));
|
b64 = btoa(gx(unescape(encodeURIComponent(r.txt))));
|
||||||
//console.log('f3');
|
|
||||||
}
|
}
|
||||||
catch (e3) {
|
catch (e3) {
|
||||||
//console.log('fe');
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1901,15 +2043,27 @@ function xhrchk(xhr, prefix, e404, lvl, tag) {
|
|||||||
if (xhr.status < 400 && xhr.status >= 200)
|
if (xhr.status < 400 && xhr.status >= 200)
|
||||||
return true;
|
return true;
|
||||||
|
|
||||||
var errtxt = (xhr.response && xhr.response.err) || xhr.responseText,
|
if (tag === undefined)
|
||||||
|
tag = prefix;
|
||||||
|
|
||||||
|
var errtxt = ((xhr.response && xhr.response.err) || xhr.responseText) || '',
|
||||||
|
suf = '',
|
||||||
fun = toast[lvl || 'err'],
|
fun = toast[lvl || 'err'],
|
||||||
is_cf = /[Cc]loud[f]lare|>Just a mo[m]ent|#cf-b[u]bbles|Chec[k]ing your br[o]wser|\/chall[e]nge-platform|"chall[e]nge-error|nable Ja[v]aScript and cook/.test(errtxt);
|
is_cf = /[Cc]loud[f]lare|>Just a mo[m]ent|#cf-b[u]bbles|Chec[k]ing your br[o]wser|\/chall[e]nge-platform|"chall[e]nge-error|nable Ja[v]aScript and cook/.test(errtxt);
|
||||||
|
|
||||||
|
if (errtxt.startsWith('<pre>'))
|
||||||
|
suf = '\n\nerror-details: «' + unpre(errtxt).split('\n')[0].trim() + '»';
|
||||||
|
else
|
||||||
|
errtxt = esc(errtxt).slice(0, 32768);
|
||||||
|
|
||||||
if (xhr.status == 403 && !is_cf)
|
if (xhr.status == 403 && !is_cf)
|
||||||
return toast.err(0, prefix + (L && L.xhr403 || "403: access denied\n\ntry pressing F5, maybe you got logged out"), tag);
|
return toast.err(0, prefix + (L && L.xhr403 || "403: access denied\n\ntry pressing F5, maybe you got logged out") + suf, tag);
|
||||||
|
|
||||||
if (xhr.status == 404)
|
if (xhr.status == 404)
|
||||||
return toast.err(0, prefix + e404, tag);
|
return toast.err(0, prefix + e404 + suf, tag);
|
||||||
|
|
||||||
|
if (!xhr.status && !errtxt)
|
||||||
|
return toast.err(0, prefix + L.xhr0);
|
||||||
|
|
||||||
if (is_cf && (xhr.status == 403 || xhr.status == 503)) {
|
if (is_cf && (xhr.status == 403 || xhr.status == 503)) {
|
||||||
var now = Date.now(), td = now - cf_cha_t;
|
var now = Date.now(), td = now - cf_cha_t;
|
||||||
|
|||||||
@@ -13,6 +13,9 @@
|
|||||||
|
|
||||||
# other stuff
|
# other stuff
|
||||||
|
|
||||||
|
## [`TODO.md`](TODO.md)
|
||||||
|
* planned features / fixes / changes
|
||||||
|
|
||||||
## [`example.conf`](example.conf)
|
## [`example.conf`](example.conf)
|
||||||
* example config file for `-c`
|
* example config file for `-c`
|
||||||
|
|
||||||
|
|||||||
15
docs/TODO.md
Normal file
15
docs/TODO.md
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
a living list of upcoming features / fixes / changes, very roughly in order of priority
|
||||||
|
|
||||||
|
* download accelerator
|
||||||
|
* definitely download chunks in parallel
|
||||||
|
* maybe resumable downloads (chrome-only, jank api)
|
||||||
|
* maybe checksum validation (return sha512 of requested range in responses, and probably also warks)
|
||||||
|
|
||||||
|
* [github issue #37](https://github.com/9001/copyparty/issues/37) - upload PWA
|
||||||
|
* or [maybe not](https://arstechnica.com/tech-policy/2024/02/apple-under-fire-for-disabling-iphone-web-apps-eu-asks-developers-to-weigh-in/), or [maybe](https://arstechnica.com/gadgets/2024/03/apple-changes-course-will-keep-iphone-eu-web-apps-how-they-are-in-ios-17-4/)
|
||||||
|
|
||||||
|
* [github issue #57](https://github.com/9001/copyparty/issues/57) - config GUI
|
||||||
|
* configs given to -c can be ordered with numerical prefix
|
||||||
|
* autorevert settings if it fails to apply
|
||||||
|
* countdown until session invalidates in settings gui, with refresh-button
|
||||||
|
|
||||||
96
docs/bufsize.txt
Normal file
96
docs/bufsize.txt
Normal file
@@ -0,0 +1,96 @@
|
|||||||
|
notes from testing various buffer sizes of files and sockets
|
||||||
|
|
||||||
|
summary:
|
||||||
|
|
||||||
|
download-folder-as-tar: would be 7% faster with --iobuf 65536 (but got 20% faster in v1.11.2)
|
||||||
|
|
||||||
|
download-folder-as-zip: optimal with default --iobuf 262144
|
||||||
|
|
||||||
|
download-file-over-https: optimal with default --iobuf 262144
|
||||||
|
|
||||||
|
put-large-file: optimal with default --iobuf 262144, --s-rd-sz 262144 (and got 14% faster in v1.11.2)
|
||||||
|
|
||||||
|
post-large-file: optimal with default --iobuf 262144, --s-rd-sz 262144 (and got 18% faster in v1.11.2)
|
||||||
|
|
||||||
|
----
|
||||||
|
|
||||||
|
oha -z10s -c1 --ipv4 --insecure http://127.0.0.1:3923/bigs/?tar
|
||||||
|
3.3 req/s 1.11.1
|
||||||
|
4.3 4.0 3.3 req/s 1.12.2
|
||||||
|
64 256 512 --iobuf 256 (prefer smaller)
|
||||||
|
32 32 32 --s-rd-sz
|
||||||
|
|
||||||
|
oha -z10s -c1 --ipv4 --insecure http://127.0.0.1:3923/bigs/?zip
|
||||||
|
2.9 req/s 1.11.1
|
||||||
|
2.5 2.9 2.9 req/s 1.12.2
|
||||||
|
64 256 512 --iobuf 256 (prefer bigger)
|
||||||
|
32 32 32 --s-rd-sz
|
||||||
|
|
||||||
|
oha -z10s -c1 --ipv4 --insecure http://127.0.0.1:3923/pairdupes/?tar
|
||||||
|
8.3 req/s 1.11.1
|
||||||
|
8.4 8.4 8.5 req/s 1.12.2
|
||||||
|
64 256 512 --iobuf 256 (prefer bigger)
|
||||||
|
32 32 32 --s-rd-sz
|
||||||
|
|
||||||
|
oha -z10s -c1 --ipv4 --insecure http://127.0.0.1:3923/pairdupes/?zip
|
||||||
|
13.9 req/s 1.11.1
|
||||||
|
14.1 14.0 13.8 req/s 1.12.2
|
||||||
|
64 256 512 --iobuf 256 (prefer smaller)
|
||||||
|
32 32 32 --s-rd-sz
|
||||||
|
|
||||||
|
oha -z10s -c1 --ipv4 --insecure http://127.0.0.1:3923/pairdupes/987a
|
||||||
|
5260 req/s 1.11.1
|
||||||
|
5246 5246 5280 5268 req/s 1.12.2
|
||||||
|
64 256 512 256 --iobuf dontcare
|
||||||
|
32 32 32 512 --s-rd-sz dontcare
|
||||||
|
|
||||||
|
oha -z10s -c1 --ipv4 --insecure https://127.0.0.1:3923/pairdupes/987a
|
||||||
|
4445 req/s 1.11.1
|
||||||
|
4462 4494 4444 req/s 1.12.2
|
||||||
|
64 256 512 --iobuf dontcare
|
||||||
|
32 32 32 --s-rd-sz
|
||||||
|
|
||||||
|
oha -z10s -c1 --ipv4 --insecure http://127.0.0.1:3923/bigs/gssc-02-cannonball-skydrift/track10.cdda.flac
|
||||||
|
95 req/s 1.11.1
|
||||||
|
95 97 req/s 1.12.2
|
||||||
|
64 512 --iobuf dontcare
|
||||||
|
32 32 --s-rd-sz
|
||||||
|
|
||||||
|
oha -z10s -c1 --ipv4 --insecure https://127.0.0.1:3923/bigs/gssc-02-cannonball-skydrift/track10.cdda.flac
|
||||||
|
15.4 req/s 1.11.1
|
||||||
|
15.4 15.3 14.9 15.4 req/s 1.12.2
|
||||||
|
64 256 512 512 --iobuf 256 (prefer smaller, and smaller than s-wr-sz)
|
||||||
|
32 32 32 32 --s-rd-sz
|
||||||
|
256 256 256 512 --s-wr-sz
|
||||||
|
|
||||||
|
----
|
||||||
|
|
||||||
|
python3 ~/dev/old/copyparty\ v1.11.1\ dont\ ban\ the\ pipes.py -q -i 127.0.0.1 -v .::A --daw
|
||||||
|
python3 ~/dev/copyparty/dist/copyparty-sfx.py -q -i 127.0.0.1 -v .::A --daw --iobuf $((1024*512))
|
||||||
|
|
||||||
|
oha -z10s -c1 --ipv4 --insecure -mPUT -r0 -D ~/Music/gssc-02-cannonball-skydrift/track10.cdda.flac http://127.0.0.1:3923/a.bin
|
||||||
|
10.8 req/s 1.11.1
|
||||||
|
10.8 11.5 11.8 12.1 12.2 12.3 req/s new
|
||||||
|
512 512 512 512 512 256 --iobuf 256
|
||||||
|
32 64 128 256 512 256 --s-rd-sz 256 (prefer bigger)
|
||||||
|
|
||||||
|
----
|
||||||
|
|
||||||
|
buildpost() {
|
||||||
|
b=--jeg-er-grensestaven;
|
||||||
|
printf -- "$b\r\nContent-Disposition: form-data; name=\"act\"\r\n\r\nbput\r\n$b\r\nContent-Disposition: form-data; name=\"f\"; filename=\"a.bin\"\r\nContent-Type: audio/mpeg\r\n\r\n"
|
||||||
|
cat "$1"
|
||||||
|
printf -- "\r\n${b}--\r\n"
|
||||||
|
}
|
||||||
|
buildpost ~/Music/gssc-02-cannonball-skydrift/track10.cdda.flac >big.post
|
||||||
|
buildpost ~/Music/bottomtext.txt >smol.post
|
||||||
|
|
||||||
|
oha -z10s -c1 --ipv4 --insecure -mPOST -r0 -T 'multipart/form-data; boundary=jeg-er-grensestaven' -D big.post http://127.0.0.1:3923/?replace
|
||||||
|
9.6 11.2 11.3 11.1 10.9 req/s v1.11.2
|
||||||
|
512 512 256 128 256 --iobuf 256
|
||||||
|
32 512 256 128 128 --s-rd-sz 256
|
||||||
|
|
||||||
|
oha -z10s -c1 --ipv4 --insecure -mPOST -r0 -T 'multipart/form-data; boundary=jeg-er-grensestaven' -D smol.post http://127.0.0.1:3923/?replace
|
||||||
|
2445 2414 2401 2437
|
||||||
|
256 128 256 256 --iobuf 256
|
||||||
|
128 128 256 64 --s-rd-sz 128 (but use 256 since big posts are more important)
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user