Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
f13339d64d | ||
|
|
fa5b44be99 | ||
|
|
08bc7c5b9d | ||
|
|
d9b5dd549a | ||
|
|
8ec53a3bce | ||
|
|
0aac9a5e5c | ||
|
|
11a880d040 | ||
|
|
67b66beb13 | ||
|
|
1da633442b | ||
|
|
13de40881e | ||
|
|
1c6419ea65 | ||
|
|
a2adeffc1a | ||
|
|
71fa3c544a | ||
|
|
b739fb7f07 | ||
|
|
860728beae | ||
|
|
1bdbf1c6a8 | ||
|
|
abbed4cd77 | ||
|
|
d06c11673f | ||
|
|
67d67f5ff6 | ||
|
|
2386d65b84 | ||
|
|
be638ecca1 | ||
|
|
c32bcea3f6 | ||
|
|
bf0aa6569b | ||
|
|
e64df8ed60 | ||
|
|
f7c3a4381d | ||
|
|
55efb34f03 | ||
|
|
ae8e9d83f1 | ||
|
|
c4406df73f | ||
|
|
12004802b6 | ||
|
|
6068ca6376 | ||
|
|
613ba49165 | ||
|
|
9cd21d84ee | ||
|
|
8d813f125e | ||
|
|
cb66bc28ab | ||
|
|
30b13b1856 | ||
|
|
67a133068c | ||
|
|
731a3bcb22 | ||
|
|
724ec8ca9f | ||
|
|
d28f775c71 | ||
|
|
a834c1c7a7 | ||
|
|
04e595e706 | ||
|
|
46c28dbf68 | ||
|
|
8594bfe817 | ||
|
|
e61a01512b | ||
|
|
657e3bb594 | ||
|
|
bbbdca6a00 | ||
|
|
a95c705e4c | ||
|
|
446c5ba80f | ||
|
|
7641c0e1cc | ||
|
|
42fdd9c890 | ||
|
|
dca63878db | ||
|
|
dc67fc414c | ||
|
|
90be3cc5a0 | ||
|
|
42cdba5ce3 | ||
|
|
9c069cfb2c | ||
|
|
544b420baa | ||
|
|
094c96f270 | ||
|
|
aa7bad56f0 | ||
|
|
977f4e1036 | ||
|
|
e05a98d22b | ||
|
|
452e955a1e | ||
|
|
a724f6a979 | ||
|
|
715b25b52f | ||
|
|
4d3f370b3a | ||
|
|
07f6717728 | ||
|
|
35b4aa6b7a | ||
|
|
a7a04d912c | ||
|
|
ce558b0850 | ||
|
|
8ab7c294ee | ||
|
|
306228462e | ||
|
|
9e7a8468e2 | ||
|
|
4c2445485a | ||
|
|
103c46e2b4 | ||
|
|
b4922d69a2 | ||
|
|
110a06a3cd | ||
|
|
6f0f67110f | ||
|
|
848721da84 | ||
|
|
127d7ab40c | ||
|
|
4fb7246082 | ||
|
|
8c42237d51 | ||
|
|
6a87f0c4e4 | ||
|
|
f8630a878c | ||
|
|
7f6ef5e204 | ||
|
|
547d4dbf0a | ||
|
|
65e70a431c | ||
|
|
f85f4de5ff | ||
|
|
97644dea16 | ||
|
|
0a6105ebc1 | ||
|
|
de3d4f8d14 | ||
|
|
f337053aea | ||
|
|
2a4b49a679 | ||
|
|
ebe526f8cf | ||
|
|
926cd7b132 | ||
|
|
99667aa410 | ||
|
|
67cab3465e | ||
|
|
ce68a0654b | ||
|
|
dffdc3ae1f | ||
|
|
28e5311c6c | ||
|
|
ebf0526420 | ||
|
|
a2f73ca1f0 | ||
|
|
8ca150c48d | ||
|
|
d765fa09f1 | ||
|
|
878d68d343 | ||
|
|
fc7bd78dfa | ||
|
|
aca6ed360c | ||
|
|
53672d1d73 | ||
|
|
07d316ed4f | ||
|
|
8aa57bf406 | ||
|
|
d369097573 | ||
|
|
f4960715fa | ||
|
|
83ba676c43 | ||
|
|
12cca9dea1 | ||
|
|
5f52535c44 | ||
|
|
2f95410ab4 | ||
|
|
5749c0c008 | ||
|
|
73c71ef4bf | ||
|
|
6bc1c51013 | ||
|
|
bcdd3302a6 | ||
|
|
0fc3b60054 | ||
|
|
e9b0d4d691 | ||
|
|
0dc2c6687d | ||
|
|
b061e582b6 | ||
|
|
690731fd79 | ||
|
|
068b7ed7f5 | ||
|
|
aae2fdcd32 | ||
|
|
d3628a1eb7 | ||
|
|
9cc8176d87 | ||
|
|
27f83f21be | ||
|
|
5e31a31a21 | ||
|
|
a077012478 | ||
|
|
fed0e0f765 | ||
|
|
fbdbf7ab22 | ||
|
|
f013d38d00 | ||
|
|
93b9c8a6da | ||
|
|
e3a779bbc6 | ||
|
|
adfce8c8b6 | ||
|
|
a49d68c0db | ||
|
|
e4156e76d1 | ||
|
|
35b66eea0e | ||
|
|
4d0cf8d45f | ||
|
|
ad9fef5f41 | ||
|
|
6235174995 | ||
|
|
4b9ca989c4 | ||
|
|
4d54aecceb | ||
|
|
11eeb6f2e9 | ||
|
|
00364b1317 | ||
|
|
6666663f78 | ||
|
|
3d6dfec47a | ||
|
|
0f3d44aa4b | ||
|
|
d2d2471950 | ||
|
|
b71343e8ab | ||
|
|
489f3f4ba0 | ||
|
|
3765e8c350 | ||
|
|
28d4f527b8 | ||
|
|
1d8af8f97d | ||
|
|
829ef4bee8 | ||
|
|
7e40c12e47 | ||
|
|
37d8d659f5 | ||
|
|
0a29291be2 | ||
|
|
7f3a5f309b | ||
|
|
60ec5f9191 | ||
|
|
d03e801e74 | ||
|
|
56bf484e77 | ||
|
|
66674469d5 | ||
|
|
09a86683e5 | ||
|
|
fc9a13879e | ||
|
|
73f0885566 | ||
|
|
090b22f193 | ||
|
|
f9c092ae8f | ||
|
|
4246bc2aea | ||
|
|
c4fa047393 | ||
|
|
22e4d24a71 | ||
|
|
96fccff63b | ||
|
|
6b46a15b49 | ||
|
|
98cc0dad55 | ||
|
|
6fdeab6948 | ||
|
|
553dd04cea | ||
|
|
0baa316a72 | ||
|
|
3ac209f9a9 | ||
|
|
ec55f64a8a | ||
|
|
932e1e577b | ||
|
|
56d5b1d9f8 | ||
|
|
28bdebb147 | ||
|
|
827fc7b64e | ||
|
|
f5dde93644 | ||
|
|
60c574828e | ||
|
|
14ca8342f9 | ||
|
|
5d5b1bf053 | ||
|
|
ea4cdba3eb | ||
|
|
d6ecebc75a | ||
|
|
b0af6a1761 | ||
|
|
6e350f30fc | ||
|
|
169137c631 | ||
|
|
6393dc0dca | ||
|
|
1a27b4824b | ||
|
|
2b59a383cf | ||
|
|
efbaaade22 | ||
|
|
cf7e7b1f62 | ||
|
|
2c1746a92d | ||
|
|
a2e57fd3d8 | ||
|
|
932f8d9176 | ||
|
|
5ffd82da89 | ||
|
|
8b3de191d9 | ||
|
|
83d8a23e2c | ||
|
|
58b107a4b5 | ||
|
|
a40609b39d | ||
|
|
0faa5d3dff | ||
|
|
374239777e | ||
|
|
9a7701d7e6 | ||
|
|
01ff04f338 | ||
|
|
eac39767dd | ||
|
|
0d0adf99fa | ||
|
|
16905ce34f | ||
|
|
5287fa8a0c | ||
|
|
b72ab4fb8e | ||
|
|
81054c675c | ||
|
|
7362be8748 | ||
|
|
b4ba2b3463 | ||
|
|
8bed6938c1 | ||
|
|
ecf16f6201 | ||
|
|
bf240357df | ||
|
|
ddcf447957 | ||
|
|
d9642611e2 | ||
|
|
0018c6f263 | ||
|
|
6398bfa12f | ||
|
|
01dfb7538d | ||
|
|
3f0d4675b6 | ||
|
|
f23c5caf80 | ||
|
|
bd22430b26 | ||
|
|
1189a7fdbc | ||
|
|
f3aa4f84fc | ||
|
|
ea26ce4700 | ||
|
|
a1e649b7e2 | ||
|
|
3b9f2b2cf0 | ||
|
|
7333d19e1c | ||
|
|
232d537d23 | ||
|
|
c6e17e7bcb | ||
|
|
54c6fd55dd | ||
|
|
0625aa1ca8 | ||
|
|
83643f3298 | ||
|
|
ff3c46fe1f | ||
|
|
0930f0dcee | ||
|
|
3221257310 | ||
|
|
8048a73156 | ||
|
|
ea552cd402 | ||
|
|
dcb925f621 | ||
|
|
cce91e1985 | ||
|
|
e17d417c2e | ||
|
|
a69f5bd2df | ||
|
|
97e53eb4d3 | ||
|
|
a6da2b7bee | ||
|
|
4a21e7c217 | ||
|
|
9bd3c7be44 | ||
|
|
434f5c4b2d | ||
|
|
d3cc4f9f07 | ||
|
|
a16aa17c17 | ||
|
|
68445d0409 | ||
|
|
32b68a45cc | ||
|
|
345f8359cc | ||
|
|
81f9886584 | ||
|
|
adbc618808 | ||
|
|
41eafc6b4b | ||
|
|
9f18d8e8c1 | ||
|
|
8c2c853166 | ||
|
|
97914906a0 | ||
|
|
f1ce4ed19b | ||
|
|
99185d8151 | ||
|
|
385b6b7ade | ||
|
|
81ea513f8c | ||
|
|
336b1ddba3 | ||
|
|
7274973322 | ||
|
|
af132965de | ||
|
|
5586742886 | ||
|
|
5868b51490 | ||
|
|
7f17a38b9b | ||
|
|
415e843ebb | ||
|
|
7ffc1192bb | ||
|
|
945e769a03 | ||
|
|
86c7fb86cc | ||
|
|
ff20f3f620 | ||
|
|
e8bef94706 | ||
|
|
d05fe2d680 | ||
|
|
4f8cc3f697 | ||
|
|
6fa619fa37 | ||
|
|
a43f5369ea | ||
|
|
2040173dc2 | ||
|
|
a15b7ec7ac | ||
|
|
6adcf2ce10 | ||
|
|
e756b9b5c1 | ||
|
|
b3de745849 | ||
|
|
77f3dc18b5 | ||
|
|
6b2f15f82e | ||
|
|
570e58611d | ||
|
|
6b69010aec | ||
|
|
e3e2fb7057 | ||
|
|
ece04909e7 | ||
|
|
963920eb88 | ||
|
|
cf5fa542b6 | ||
|
|
1be7e99754 | ||
|
|
14e3334682 | ||
|
|
b1e033dd55 | ||
|
|
111feb1b57 | ||
|
|
886b23d034 | ||
|
|
f2590792b3 | ||
|
|
02a497ed74 | ||
|
|
48df0eed84 | ||
|
|
0f58cbb671 | ||
|
|
9d71670f59 | ||
|
|
7f838ebb38 | ||
|
|
ef1cb05bc8 | ||
|
|
c14b3ed82a | ||
|
|
792e337936 | ||
|
|
6cd2e66052 | ||
|
|
728022b86d | ||
|
|
7718446313 | ||
|
|
66dea54053 | ||
|
|
f19b60bd41 | ||
|
|
09f1c92856 | ||
|
|
589715901d | ||
|
|
3f1a5ff5e0 | ||
|
|
49cd956d4c | ||
|
|
f9acde862f | ||
|
|
503e1dd899 | ||
|
|
c8e12b948d | ||
|
|
18949d68c0 | ||
|
|
0c51b6c016 | ||
|
|
63a5c22c1f | ||
|
|
f76e2a7b56 | ||
|
|
bab151d6f5 | ||
|
|
d43fec088b | ||
|
|
a8ca1cbcd7 | ||
|
|
ada3494483 | ||
|
|
43c238b7f1 | ||
|
|
128d10c51e | ||
|
|
1a1e01f9f6 | ||
|
|
8483e4ab8a | ||
|
|
f6c163b505 | ||
|
|
8f30173db0 | ||
|
|
0372ff95bb | ||
|
|
6fa29c7877 | ||
|
|
d4c9121593 | ||
|
|
76a8df0282 | ||
|
|
0b6d8309a0 | ||
|
|
10a9bc0817 | ||
|
|
2a14af4ffa | ||
|
|
d1a4a292e3 | ||
|
|
14c0efa151 | ||
|
|
4fc03f2581 | ||
|
|
3205b9fda9 | ||
|
|
953e0d6c22 | ||
|
|
b50ce54ca9 | ||
|
|
5e7558ce4a | ||
|
|
8aa6362432 | ||
|
|
02ebb97a8b | ||
|
|
b36063403d | ||
|
|
526ffa2afb | ||
|
|
5b3fd812d8 | ||
|
|
af6dac9cdc | ||
|
|
bc25d936bb | ||
|
|
b497fe1444 | ||
|
|
3f456cce05 | ||
|
|
4dd2f089ec | ||
|
|
b1b1bc248d | ||
|
|
d9e675469c | ||
|
|
ede0ca1772 | ||
|
|
2d098a1477 | ||
|
|
e5f014b68e | ||
|
|
b3a9dc9eeb | ||
|
|
2a06cec27c | ||
|
|
19230c889d | ||
|
|
c969ce552c | ||
|
|
2def600d21 | ||
|
|
02aa8f18c8 | ||
|
|
fcd9522dae | ||
|
|
72d3ce885e | ||
|
|
b428996eb7 | ||
|
|
2b4eb58fad | ||
|
|
240e8dff60 | ||
|
|
1c286afde6 | ||
|
|
2eeb908540 | ||
|
|
562e6ecce9 | ||
|
|
4bd0d32508 | ||
|
|
6f2ccbef80 | ||
|
|
61a6cb6d96 | ||
|
|
443efb5eda | ||
|
|
ba3c731fee | ||
|
|
e55f72dd1d | ||
|
|
b28c0a60a1 | ||
|
|
b7a80bf026 | ||
|
|
fe7218e64b | ||
|
|
0857a9046d | ||
|
|
354131b78a | ||
|
|
e3ae91a4f8 | ||
|
|
52cc5e2e4f | ||
|
|
0c04451442 | ||
|
|
f5ab4a2253 | ||
|
|
1303dfe17a | ||
|
|
55d80f26fa | ||
|
|
7aee585748 | ||
|
|
196858409c | ||
|
|
21467dd62f | ||
|
|
bb30eb7d11 | ||
|
|
fb9f4a7373 | ||
|
|
ccd5c1c75e | ||
|
|
015c578cdd | ||
|
|
8eb4ce2914 | ||
|
|
a6b8108ee6 | ||
|
|
181a56218a | ||
|
|
24feaaebd6 | ||
|
|
e1945e7a35 | ||
|
|
072f65dd9c | ||
|
|
bde03ecc63 | ||
|
|
73c2e23da4 | ||
|
|
e6233831d1 | ||
|
|
82ccbdaa7b | ||
|
|
bf5212a81c | ||
|
|
5c6cc932cf | ||
|
|
ed4430a7e0 | ||
|
|
eb73f78b1f | ||
|
|
314aad0009 | ||
|
|
98d0c5c52f | ||
|
|
4d4da889ec | ||
|
|
4a622f59ba | ||
|
|
913b92088a | ||
|
|
80f4690df8 | ||
|
|
f552531703 | ||
|
|
707d4a7a0c | ||
|
|
be5bebb574 | ||
|
|
4ca2a7a65e | ||
|
|
eed8c9bf50 | ||
|
|
11d5855430 | ||
|
|
b13413b1e5 | ||
|
|
9809474615 | ||
|
|
9ee3a61ae9 | ||
|
|
c71ffa02f8 | ||
|
|
2e862da292 | ||
|
|
08d762c6c9 | ||
|
|
2ef6f9e0e7 | ||
|
|
b8f84cf18d | ||
|
|
7e88af7047 | ||
|
|
2fc365dd57 | ||
|
|
434170862c | ||
|
|
1eb6c426fd | ||
|
|
e2c46d73e4 | ||
|
|
eef02ac7ce | ||
|
|
0d9614755e | ||
|
|
aa0557656c | ||
|
|
2c750f98cb | ||
|
|
82cf2b33cf | ||
|
|
b9cfe0d6f0 | ||
|
|
a0166a4011 | ||
|
|
c9f765813c | ||
|
|
2f52590587 | ||
|
|
fd581fffa5 | ||
|
|
fcdac1cb32 | ||
|
|
a824fa617b | ||
|
|
1fd0c23e55 | ||
|
|
884d4ee91b | ||
|
|
0c7d303568 | ||
|
|
a8ac8609d6 | ||
|
|
0d98a24c67 | ||
|
|
31eeb5e539 | ||
|
|
82dacda359 | ||
|
|
c3f82e49bf | ||
|
|
86a0e734b1 | ||
|
|
934db752bf | ||
|
|
ed379da657 | ||
|
|
55c4bef524 | ||
|
|
ccb329160d | ||
|
|
424626d64b | ||
|
|
026893e10d | ||
|
|
840b03c875 | ||
|
|
325f876010 | ||
|
|
91606a24b8 | ||
|
|
f001d8b749 | ||
|
|
31c0c239f9 | ||
|
|
918c4dbfce | ||
|
|
f587319ef0 | ||
|
|
b4dd942899 | ||
|
|
97a6720fba | ||
|
|
f05c1ef9e8 | ||
|
|
91c1ea97fd | ||
|
|
74faa159e7 | ||
|
|
7e9892bb8d | ||
|
|
0a9e54e5c5 | ||
|
|
0f0d16a104 | ||
|
|
7d7ee6ca6a | ||
|
|
0d96cd3fe8 | ||
|
|
596244543c | ||
|
|
237d6b9414 | ||
|
|
a7c42779f8 | ||
|
|
c49e5f2054 | ||
|
|
6ca6037aa0 | ||
|
|
cc7f360c04 | ||
|
|
75991bffea | ||
|
|
afd1b1968c | ||
|
|
746e9d2a6d | ||
|
|
36b5b5d0f3 | ||
|
|
dd603cfcc8 | ||
|
|
cefce9913a | ||
|
|
0b29f27fcd | ||
|
|
f3f3e27bfe | ||
|
|
519c44a72a | ||
|
|
7e28da0530 | ||
|
|
af70d98b50 | ||
|
|
3f0b84ea22 | ||
|
|
e58abd45ec | ||
|
|
abeee263f0 | ||
|
|
30f68bd7b9 | ||
|
|
f13394d27f | ||
|
|
88e0617429 | ||
|
|
ab94ffc055 | ||
|
|
265ab99cc7 | ||
|
|
738adbe38e | ||
|
|
4e6862cef9 | ||
|
|
7aff60b24d | ||
|
|
8819e38073 | ||
|
|
2bbac91436 | ||
|
|
cf4dea432b | ||
|
|
bd70df1f05 | ||
|
|
b2638c1fac | ||
|
|
4aa9409f5d | ||
|
|
dfa863a54a | ||
|
|
626c04df48 | ||
|
|
6026fa57f0 | ||
|
|
0db28fb5e2 | ||
|
|
c9bf8ced99 | ||
|
|
18a55db245 | ||
|
|
efb9664761 | ||
|
|
3ee412c7a5 | ||
|
|
5afc00a502 | ||
|
|
e5ca0e6415 | ||
|
|
98f121258c | ||
|
|
b2474c51fd | ||
|
|
1bd6ebdb41 | ||
|
|
20ef99326d | ||
|
|
171a1b9ae3 | ||
|
|
cd2c9d151a | ||
|
|
8fbfe9a76a | ||
|
|
63cf0d4f97 | ||
|
|
faa98126f8 | ||
|
|
0dd70249b9 | ||
|
|
aadd99cac3 | ||
|
|
5bede842ba | ||
|
|
1f2ac77b5e | ||
|
|
3f4f35c6d1 | ||
|
|
9575ddbdb4 | ||
|
|
3ed918d98d | ||
|
|
89d5af3372 | ||
|
|
fa8f40eee3 | ||
|
|
8f06035500 | ||
|
|
8f7d969099 | ||
|
|
5422785feb | ||
|
|
911f82c00b | ||
|
|
fbff8c991b | ||
|
|
f1b139d55d | ||
|
|
9c7f196e20 | ||
|
|
4d8a37006e | ||
|
|
bc9d3d561f | ||
|
|
fd9e80bdf5 | ||
|
|
aee2f71170 | ||
|
|
102160d651 | ||
|
|
e33f26d33c | ||
|
|
57113fa02f | ||
|
|
2be7575f98 | ||
|
|
c5647b46e1 | ||
|
|
3450cff92f | ||
|
|
13a11411a9 | ||
|
|
6e7eb9dec4 | ||
|
|
c74eed7c0e | ||
|
|
2fc6811495 | ||
|
|
57e0dac45b | ||
|
|
537e31000e | ||
|
|
e0872f4536 | ||
|
|
9dddf6dd2e | ||
|
|
7a0c5feed3 | ||
|
|
42d154f0b7 | ||
|
|
44b0ab2203 | ||
|
|
4af59b50ad | ||
|
|
b309099f0b | ||
|
|
affea99cb1 | ||
|
|
9bc35a3026 | ||
|
|
57e9d499fb | ||
|
|
d53cf598a4 | ||
|
|
bdaca2bd37 | ||
|
|
53e3af9b30 | ||
|
|
9cfef895f8 | ||
|
|
56fa9644a5 | ||
|
|
8eff51a96b | ||
|
|
d130a1d44a | ||
|
|
98316fd282 | ||
|
|
59f1ea3073 | ||
|
|
270757f3bd | ||
|
|
843c6b36a8 | ||
|
|
f71a2a8fc2 | ||
|
|
63fc763958 | ||
|
|
9bc0aac63d | ||
|
|
ff529da874 | ||
|
|
ff40944f00 | ||
|
|
1d0ac46c7e | ||
|
|
d62bb1e5b6 | ||
|
|
89f91e46b7 | ||
|
|
bc2daa5f8b | ||
|
|
0fbf240a58 | ||
|
|
83d57b33f7 | ||
|
|
59ae23e315 | ||
|
|
df26adfe89 | ||
|
|
d1a92aeb36 | ||
|
|
c09f1b2f1c | ||
|
|
a70a1e6290 | ||
|
|
dde5258b59 | ||
|
|
dc438e6eb7 | ||
|
|
37a9a97f4f | ||
|
|
4fb6e3fe7b | ||
|
|
7884848c78 | ||
|
|
2ece328e50 | ||
|
|
b8ac3cd22f | ||
|
|
9b4bd7a3f0 | ||
|
|
facefc5c58 | ||
|
|
5d6a6b1af7 | ||
|
|
916e0ead99 | ||
|
|
aff7b07f33 | ||
|
|
8267b429ca | ||
|
|
5759bee1df | ||
|
|
f2648ec85c | ||
|
|
e083722f0b | ||
|
|
f03e63fa54 | ||
|
|
f33c3e30eb | ||
|
|
c9ee0b0fcb | ||
|
|
7aaa8036bc | ||
|
|
00f2410d2d | ||
|
|
be77a494db | ||
|
|
787a95bdd2 | ||
|
|
720ce591b7 | ||
|
|
af92797ba0 | ||
|
|
6625365586 | ||
|
|
3e88e33397 | ||
|
|
f155a9d4a4 | ||
|
|
6ae118dcb5 | ||
|
|
b8c4f1a09a | ||
|
|
f0602aa6e4 | ||
|
|
61ac750dd7 | ||
|
|
23b660df6e | ||
|
|
a5d1c6860c | ||
|
|
50fe3f8db0 | ||
|
|
875f878b42 | ||
|
|
76e6f6633f | ||
|
|
caf5adc42d | ||
|
|
b70609c047 | ||
|
|
80a2b1685a | ||
|
|
de5c0be2c7 | ||
|
|
a12623dda8 | ||
|
|
1b3146d69f | ||
|
|
b39c89d069 | ||
|
|
ed31e0952e | ||
|
|
28a58433de | ||
|
|
670b79269d | ||
|
|
ad8abbc213 | ||
|
|
8e2ebc84c8 | ||
|
|
c9fddf2907 | ||
|
|
207deebfd5 | ||
|
|
641371cd74 | ||
|
|
1bf76279c7 | ||
|
|
907a252f74 | ||
|
|
4ef5c181db | ||
|
|
ae584703e4 | ||
|
|
b38250a37e | ||
|
|
60e00196be | ||
|
|
84d3969ec3 | ||
|
|
8ebdc0bd8c | ||
|
|
c79d99d791 | ||
|
|
a31da69771 | ||
|
|
50589edcd2 | ||
|
|
b227a0f388 | ||
|
|
277d2e86a9 | ||
|
|
467d4321f4 | ||
|
|
24bb90d279 | ||
|
|
4bfc78b984 | ||
|
|
ee1005c363 | ||
|
|
3e4237275c | ||
|
|
fa4a041773 | ||
|
|
040c42705d | ||
|
|
f3f5a0bfa2 | ||
|
|
875462b619 | ||
|
|
7253cb111b | ||
|
|
d318844304 | ||
|
|
a59ea3175b | ||
|
|
15456f2c2d | ||
|
|
cc824a96e0 | ||
|
|
18257e0835 | ||
|
|
f669a0ed60 | ||
|
|
e148a057f7 | ||
|
|
799eae65d2 | ||
|
|
f1dc4108f8 | ||
|
|
08b0b9eb92 | ||
|
|
85ba545107 | ||
|
|
2604e467e8 | ||
|
|
bd2c54a38d | ||
|
|
5c83c939d3 | ||
|
|
f7a7589107 | ||
|
|
2418c56212 | ||
|
|
6e89584263 | ||
|
|
0b696409fc | ||
|
|
6602d13c95 | ||
|
|
f19e637780 | ||
|
|
7da33b1f93 | ||
|
|
453b5e565c | ||
|
|
1827a2487b | ||
|
|
edc01c3e2b | ||
|
|
720215ea05 | ||
|
|
d5d40857f4 | ||
|
|
94c2d50f70 | ||
|
|
1be50ebe59 | ||
|
|
80404b3148 | ||
|
|
0f3c4d7363 | ||
|
|
939a5ab7c0 | ||
|
|
722161b6cd | ||
|
|
7c8dc6e0d9 | ||
|
|
efdd06b3e9 | ||
|
|
ee689c231c | ||
|
|
9e3db377fb | ||
|
|
6cb4c93e89 | ||
|
|
15dcb274c3 | ||
|
|
d721476986 | ||
|
|
850a762905 | ||
|
|
7c1e4f8e41 | ||
|
|
37182f07fb | ||
|
|
30519d92fc | ||
|
|
a1cbd76985 | ||
|
|
6df8d7a30a | ||
|
|
09568274b3 | ||
|
|
fa6e02ee8e | ||
|
|
56346a3a64 | ||
|
|
fb1d9842d4 | ||
|
|
4642ff0cdb | ||
|
|
508ca1dbc0 | ||
|
|
997ed57188 | ||
|
|
8630c712bb | ||
|
|
8b7134a650 | ||
|
|
1850f47c13 | ||
|
|
2fb44827b0 | ||
|
|
f0683e101b | ||
|
|
e2fda89ab3 | ||
|
|
a8b3f721c9 | ||
|
|
5948d6cf33 | ||
|
|
17b71bc7fb | ||
|
|
dd5fe708ae | ||
|
|
d705ccbe80 | ||
|
|
b53f46e0a0 | ||
|
|
b497d1ab9a | ||
|
|
2f86292503 | ||
|
|
e804b157c8 | ||
|
|
0ac801d00d | ||
|
|
df6b9341ac | ||
|
|
749d18308b | ||
|
|
d16166c27a | ||
|
|
edbd1e7e8f | ||
|
|
8855ce9414 | ||
|
|
61b2a57925 | ||
|
|
f8d6286d2a | ||
|
|
389e8b6a48 | ||
|
|
ae39e754b0 | ||
|
|
3b398a7643 | ||
|
|
9f8784e66a | ||
|
|
7b2d56a4c7 | ||
|
|
f093dbef7a | ||
|
|
950f62de85 | ||
|
|
de159153f5 | ||
|
|
814960c5f0 | ||
|
|
ab66abb348 | ||
|
|
a5d139a820 | ||
|
|
2c4c34afcf | ||
|
|
cbe4840ce2 | ||
|
|
cdf378ff45 | ||
|
|
7919961b8a | ||
|
|
8d3ddb6ac5 | ||
|
|
cf6f850586 | ||
|
|
4dbf00810f | ||
|
|
96fc743f6a | ||
|
|
06ac19915c | ||
|
|
c3b3ba4a9e | ||
|
|
727aac9811 | ||
|
|
6699c58d73 | ||
|
|
d7a0147191 | ||
|
|
a7697d4479 | ||
|
|
d603ad76b8 | ||
|
|
231dcea37e | ||
|
|
d579c4d167 | ||
|
|
88e739606b | ||
|
|
a922da5868 | ||
|
|
5c329803aa | ||
|
|
0cb81d0642 | ||
|
|
b1b13f9b8b | ||
|
|
cbfff5feea | ||
|
|
dbe8e64b9e | ||
|
|
dad46c06fb | ||
|
|
add7cafcb5 | ||
|
|
e3d229104a | ||
|
|
ccf2ffb2d5 | ||
|
|
d239b07900 | ||
|
|
fe062f0b93 | ||
|
|
ab4103561f | ||
|
|
8e447397b2 | ||
|
|
7a746b6779 | ||
|
|
8e29b2a481 | ||
|
|
5434df4868 | ||
|
|
780c7b6400 | ||
|
|
ccca7f05c6 | ||
|
|
489ca46fdf | ||
|
|
01e8e64505 | ||
|
|
25998f1b0e | ||
|
|
97686dc14f | ||
|
|
6fbe450dcc | ||
|
|
5f63b0c935 | ||
|
|
04fa3c3115 | ||
|
|
36e2f28d51 | ||
|
|
5c77f9d9b3 | ||
|
|
a99a51e0d4 | ||
|
|
2d091568f3 | ||
|
|
a449b4b001 | ||
|
|
aa71dc1103 | ||
|
|
0b0ff448d5 | ||
|
|
b08e01f1c3 | ||
|
|
fe0439382e | ||
|
|
bf89714926 | ||
|
|
25a9f9823d | ||
|
|
bfb23b1dd5 | ||
|
|
f814f4cd8f | ||
|
|
83ada782b8 | ||
|
|
09ae033196 | ||
|
|
ad1a8c302c | ||
|
|
c2f861146b | ||
|
|
48ac57281c | ||
|
|
88f557f829 | ||
|
|
8e0dae4d51 | ||
|
|
bfdd8e840d | ||
|
|
e1039f52e5 | ||
|
|
a3e7076073 | ||
|
|
3e2bded3c3 | ||
|
|
a2a851d17f | ||
|
|
580e25de6d | ||
|
|
658544c305 | ||
|
|
a872a2d2ee | ||
|
|
b1c5a28241 | ||
|
|
df8046287e | ||
|
|
627f998d25 | ||
|
|
52a30f5804 | ||
|
|
b910574cdb | ||
|
|
de6d642b0d | ||
|
|
7eb9dddf22 | ||
|
|
47b5f25dcf | ||
|
|
f5bb7d3cbe | ||
|
|
b4a1d72013 | ||
|
|
2faf951592 | ||
|
|
063b2bcc5f | ||
|
|
fc2f527b15 | ||
|
|
47fa33a2a2 | ||
|
|
6731d7be59 | ||
|
|
91ce734d32 | ||
|
|
b89394c819 | ||
|
|
2732edce09 | ||
|
|
68f1b9234b | ||
|
|
81497e19ce | ||
|
|
a0d3f8aa86 | ||
|
|
4074ae3271 | ||
|
|
7b197d93bc | ||
|
|
6b5cca4bb9 | ||
|
|
62971c0fd0 | ||
|
|
8007d5a5d6 | ||
|
|
eb9421255d | ||
|
|
8e97097dfd | ||
|
|
b6972db5a7 | ||
|
|
755d904136 | ||
|
|
1052208d9e | ||
|
|
e259f624ad | ||
|
|
a77483ee31 | ||
|
|
201181ee6f | ||
|
|
2968b3c30e | ||
|
|
49a9fc7682 | ||
|
|
0ff0c63903 | ||
|
|
0af084b4eb | ||
|
|
d75278b52b | ||
|
|
cf94729830 | ||
|
|
15d3ad5a96 | ||
|
|
bb231c7416 | ||
|
|
a75b93699c | ||
|
|
bf3e08868b | ||
|
|
db9db1981c | ||
|
|
4dff051927 | ||
|
|
bb92059343 | ||
|
|
1e72304c25 | ||
|
|
692ed8fce8 | ||
|
|
6a12224b21 | ||
|
|
e8c7155c69 | ||
|
|
a2e7e917d1 | ||
|
|
c73baaa3d2 | ||
|
|
09924a0f9c | ||
|
|
2a9a28cdb4 | ||
|
|
db6256c720 | ||
|
|
440a80c552 | ||
|
|
a0e5eae244 | ||
|
|
6346ad96e4 | ||
|
|
886803112d | ||
|
|
f277b8c534 | ||
|
|
a8d8db3a10 | ||
|
|
21549ab842 | ||
|
|
e8e2adcee0 |
No files matched your search
@@ -10,17 +10,14 @@ srcdist/
|
|||||||
# Generated email templates
|
# Generated email templates
|
||||||
email/*.tmpl
|
email/*.tmpl
|
||||||
|
|
||||||
# Romanian ro.po is generated from ro.px, due to mapping to latin-1
|
|
||||||
ro.po
|
|
||||||
|
|
||||||
# Build results
|
# Build results
|
||||||
SABnzbd*.zip
|
SABnzbd*.zip
|
||||||
SABnzbd*.exe
|
SABnzbd*.exe
|
||||||
SABnzbd*.gz
|
SABnzbd*.gz
|
||||||
SABnzbd*.dmg
|
SABnzbd*.dmg
|
||||||
|
|
||||||
# WingIDE project file
|
# WingIDE project files
|
||||||
*.wpr
|
*.wp[ru]
|
||||||
|
|
||||||
# General junk
|
# General junk
|
||||||
*.keep
|
*.keep
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
*******************************************
|
*******************************************
|
||||||
*** This is SABnzbd 1.1.x ***
|
*** This is SABnzbd 2.2.0 ***
|
||||||
*******************************************
|
*******************************************
|
||||||
SABnzbd is an open-source cross-platform binary newsreader.
|
SABnzbd is an open-source cross-platform binary newsreader.
|
||||||
It simplifies the process of downloading from Usenet dramatically,
|
It simplifies the process of downloading from Usenet dramatically,
|
||||||
@@ -12,9 +12,6 @@ and offers a complete API for third-party applications to hook into.
|
|||||||
There is an extensive Wiki on the use of SABnzbd.
|
There is an extensive Wiki on the use of SABnzbd.
|
||||||
https://sabnzbd.org/wiki/
|
https://sabnzbd.org/wiki/
|
||||||
|
|
||||||
IMPORTANT INFORMATION about release 1.0.0:
|
|
||||||
https://sabnzbd.org/wiki/introducing-1-0
|
|
||||||
|
|
||||||
Please also read the file "ISSUES.txt"
|
Please also read the file "ISSUES.txt"
|
||||||
|
|
||||||
The organization of the download queue is different from 0.7.x (and older).
|
The organization of the download queue is different from 0.7.x (and older).
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
|
|
||||||
(c) Copyright 2007-2016 by "The SABnzbd-team" <team@sabnzbd.org>
|
(c) Copyright 2007-2017 by "The SABnzbd-team" <team@sabnzbd.org>
|
||||||
|
|
||||||
The SABnzbd-team is:
|
The SABnzbd-team is:
|
||||||
|
|
||||||
|
|||||||
@@ -1,18 +0,0 @@
|
|||||||
FROM ubuntu:14.04
|
|
||||||
|
|
||||||
MAINTAINER Johannes 'fish' Ziemke <docker@freigeist.org> @discordianfish
|
|
||||||
|
|
||||||
RUN echo deb http://archive.ubuntu.com/ubuntu/ trusty multiverse >> \
|
|
||||||
/etc/apt/sources.list
|
|
||||||
RUN apt-get -qy update && apt-get -qy install python python-cheetah unrar \
|
|
||||||
unzip python-yenc par2
|
|
||||||
|
|
||||||
RUN useradd sabnzbd -d /sab -m && chown -R sabnzbd:sabnzbd /sab
|
|
||||||
VOLUME /sab
|
|
||||||
ADD . /sabnzbd
|
|
||||||
|
|
||||||
EXPOSE 8080
|
|
||||||
USER sabnzbd
|
|
||||||
ENV HOME /sab
|
|
||||||
|
|
||||||
ENTRYPOINT [ "python", "/sabnzbd/SABnzbd.py", "-s", "0.0.0.0:8080" ]
|
|
||||||
@@ -1,10 +1,10 @@
|
|||||||
SABnzbd 1.1.0
|
SABnzbd 2.3.0
|
||||||
|
|
||||||
-------------------------------------------------------------------------------
|
-------------------------------------------------------------------------------
|
||||||
0) LICENSE
|
0) LICENSE
|
||||||
-------------------------------------------------------------------------------
|
-------------------------------------------------------------------------------
|
||||||
|
|
||||||
(c) Copyright 2007-2016 by "The SABnzbd-team" <team@sabnzbd.org>
|
(c) Copyright 2007-2017 by "The SABnzbd-team" <team@sabnzbd.org>
|
||||||
|
|
||||||
This program is free software; you can redistribute it and/or
|
This program is free software; you can redistribute it and/or
|
||||||
modify it under the terms of the GNU General Public License
|
modify it under the terms of the GNU General Public License
|
||||||
@@ -21,85 +21,80 @@ along with this program; if not, write to the Free Software
|
|||||||
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||||
|
|
||||||
-------------------------------------------------------------------------------
|
-------------------------------------------------------------------------------
|
||||||
1) INSTALL with the Win32 installer
|
1) INSTALL with the Windows installer
|
||||||
-------------------------------------------------------------------------------
|
-------------------------------------------------------------------------------
|
||||||
Just run the downloaded EXE file and the installer will start.
|
Just run the downloaded EXE file and the installer will start.
|
||||||
It's just a simple standard installer.
|
It's just a simple standard installer.
|
||||||
After installation, find the SABnzbd program in the Start menu and start it.
|
After installation, find the SABnzbd program in the Start menu and start it.
|
||||||
|
|
||||||
Within 5-10 seconds your web browser will start and show the user interface.
|
Within a few seconds your web browser will start and show the user interface.
|
||||||
Use the "Help" button in the web-interface to be directed to the Help Wiki.
|
Use the "Help" button in the web-interface to be directed to the Help Wiki.
|
||||||
|
|
||||||
|
|
||||||
-------------------------------------------------------------------------------
|
-------------------------------------------------------------------------------
|
||||||
2) INSTALL pre-built Win32 binaries
|
2) INSTALL pre-built Windows binaries
|
||||||
-------------------------------------------------------------------------------
|
-------------------------------------------------------------------------------
|
||||||
Unzip pre-built version to any folder of your liking.
|
Unzip pre-built version to any folder of your liking.
|
||||||
Start the SABnzbd.exe program.
|
Start the SABnzbd.exe program.
|
||||||
Within 5-10 seconds your web browser will start and show the user interface.
|
Within a few seconds your web browser will start and show the user interface.
|
||||||
Use the "Help" button in the web-interface to be directed to the Help Wiki.
|
Use the "Help" button in the web-interface to be directed to the Help Wiki.
|
||||||
|
|
||||||
-------------------------------------------------------------------------------
|
-------------------------------------------------------------------------------
|
||||||
3) INSTALL pre-built OSX binaries
|
3) INSTALL pre-built macOS binaries
|
||||||
-------------------------------------------------------------------------------
|
-------------------------------------------------------------------------------
|
||||||
Download the DMG file, mount and drag the SABnzbd icon to Programs.
|
Download the DMG file, mount and drag the SABnzbd icon to Programs.
|
||||||
Just like you do with so many apps.
|
Just like you do with so many apps.
|
||||||
Make sure you pick the right folder, depending on your OSX version.
|
|
||||||
|
|
||||||
|
|
||||||
-------------------------------------------------------------------------------
|
-------------------------------------------------------------------------------
|
||||||
4) INSTALL with only sources
|
4) INSTALL with only sources
|
||||||
-------------------------------------------------------------------------------
|
-------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
Specific guides to install from source are available for Windows and macOS:
|
||||||
|
https://sabnzbd.org/wiki/installation/install-macos
|
||||||
|
https://sabnzbd.org/wiki/installation/install-from-source-windows
|
||||||
|
|
||||||
You need to have Python installed plus some non-standard Python modules
|
You need to have Python installed plus some non-standard Python modules
|
||||||
and a few tools.
|
and a few tools.
|
||||||
|
|
||||||
Unix/Linux/OSX
|
All platforms
|
||||||
Python-2.7 http://www.python.org (2.7.9+ recommended)
|
Python-2.7.latest http://www.python.org (2.7.9+ recommended)
|
||||||
|
|
||||||
OSX Mavericks or newer
|
|
||||||
Apple Python 2.7 Included in OSX (default)
|
|
||||||
|
|
||||||
Windows
|
Windows
|
||||||
Python-2.7.latest http://www.python.com
|
|
||||||
Most versions will work: 2.7.10+ recommended
|
|
||||||
PyWin32 use "pip install pypiwin32"
|
PyWin32 use "pip install pypiwin32"
|
||||||
|
|
||||||
Essential modules
|
Essential modules
|
||||||
cheetah-2.0.1+ use "pip install cheetah"
|
cheetah-2.0.1+ use "pip install cheetah"
|
||||||
par2cmdline >= 0.4 http://parchive.sourceforge.net/
|
par2cmdline >= 0.4 https://github.com/Parchive/par2cmdline/releases
|
||||||
|
See also: https://sabnzbd.org/wiki/installation/multicore-par2
|
||||||
unrar >= 5.00+ http://www.rarlab.com/rar_add.htm
|
unrar >= 5.00+ http://www.rarlab.com/rar_add.htm
|
||||||
|
openssl >= 1.0.0 http://www.openssl.org/
|
||||||
|
|
||||||
Optional modules
|
Optional modules
|
||||||
unzip >= 6.00 http://www.info-zip.org/
|
unzip >= 6.00 http://www.info-zip.org/
|
||||||
7zip >= 9.20 http://www.7zip.org/
|
7zip >= 9.20 http://www.7zip.org/
|
||||||
yenc module >= 0.3 http://sabnzbd.sourceforge.net/yenc-0.3.tar.gz
|
sabyenc == 3.0.2 use "pip install sabyenc"
|
||||||
http://sabnzbd.sourceforge.net/yenc-0.3-w32fixed.zip (Win32-only)
|
More information: https://sabnzbd.org/sabyenc
|
||||||
|
cryptography >= 1.0 use "pip install cryptography"
|
||||||
|
Enables certificate generation and detection of encrypted RAR-files
|
||||||
|
|
||||||
Optional modules Windows
|
Optional modules Linux
|
||||||
pyopenssl >= 0.13 "pip install pyopenssl"
|
|
||||||
or http://www.egenix.com/products/python/pyOpenSSL/
|
|
||||||
(Binaries, including the OpenSSL libraries)
|
|
||||||
|
|
||||||
Optional modules Unix/Linux/OSX
|
|
||||||
pyopenssl >= 0.15 http://pypi.python.org/pypi/pyOpenSSL
|
|
||||||
openssl => ? http://www.openssl.org/
|
|
||||||
Make sure the OpenSSL libraries match with PyOpenSSL
|
|
||||||
pynotify Should be part of GTK for Python support on Debian/Ubuntu
|
pynotify Should be part of GTK for Python support on Debian/Ubuntu
|
||||||
If not, you cannot use the NotifyOSD feature.
|
If not, you cannot use the NotifyOSD feature.
|
||||||
|
python-dbus Enable option to Shutdown/Restart/Standby PC on queue finish.
|
||||||
|
|
||||||
Embedded modules (preferably use the included version)
|
Embedded modules (preferably use the included version)
|
||||||
CherryPy-6.0.2 with patches http://www.cherrypy.org
|
CherryPy-8.1.2 with patches http://www.cherrypy.org
|
||||||
|
|
||||||
|
|
||||||
Unpack the ZIP-file containing the SABnzbd sources to any folder of your liking.
|
Unpack the ZIP-file containing the SABnzbd sources to any folder of your liking.
|
||||||
|
|
||||||
If you want multiple languages, you need to compile the translations.
|
If you want multiple languages, you need to compile the translations.
|
||||||
Start this from a shell terminal (or command prompt):
|
Start this from a shell terminal (or command prompt):
|
||||||
python tools/make_mo.py
|
python tools/make_mo.py
|
||||||
|
|
||||||
Start this from a shell terminal (or command prompt):
|
Start this from a shell terminal (or command prompt):
|
||||||
python SABnzbd.py
|
python SABnzbd.py
|
||||||
Within 5-10 seconds your web browser will start and show the user interface.
|
|
||||||
|
Within a few seconds your web browser will start and show the user interface.
|
||||||
Use the "Help" button in the web-interface to be directed to the Help Wiki.
|
Use the "Help" button in the web-interface to be directed to the Help Wiki.
|
||||||
|
|
||||||
|
|
||||||
@@ -117,7 +112,7 @@ or
|
|||||||
|
|
||||||
You may of course try other port numbers too.
|
You may of course try other port numbers too.
|
||||||
|
|
||||||
For troubleshooting you can use the program SABnzbd-console.exe.
|
For troubleshooting on Windows you can use the program SABnzbd-console.exe.
|
||||||
This will show a black window where logging information will be shown. This
|
This will show a black window where logging information will be shown. This
|
||||||
may help you solve problems easier.
|
may help you solve problems easier.
|
||||||
|
|
||||||
@@ -125,7 +120,7 @@ may help you solve problems easier.
|
|||||||
6) MORE INFORMATION
|
6) MORE INFORMATION
|
||||||
-------------------------------------------------------------------------------
|
-------------------------------------------------------------------------------
|
||||||
|
|
||||||
Visit the WIKI site:
|
Visit our wiki:
|
||||||
https://sabnzbd.org/wiki/
|
https://sabnzbd.org/wiki/
|
||||||
|
|
||||||
|
|
||||||
@@ -135,4 +130,4 @@ Visit the WIKI site:
|
|||||||
|
|
||||||
Several parts of SABnzbd were built by other people, illustrating the
|
Several parts of SABnzbd were built by other people, illustrating the
|
||||||
wonderful world of Free Open Source Software.
|
wonderful world of Free Open Source Software.
|
||||||
See the licenses folder of the main program and of the skin folders.
|
See the licenses folder of the main program and of the skin folders.
|
||||||
@@ -12,7 +12,7 @@
|
|||||||
Windows-only:
|
Windows-only:
|
||||||
If you keep having trouble with par2 multicore you can disable it
|
If you keep having trouble with par2 multicore you can disable it
|
||||||
in Config->Switches.
|
in Config->Switches.
|
||||||
This will force the use of the old and tried, but slower par2-classic program.
|
This will force the use of the old and tried, but slower par2cmdline program.
|
||||||
|
|
||||||
- A bug in Windows 7 may cause severe memory leaks when you use SABnzbd in
|
- A bug in Windows 7 may cause severe memory leaks when you use SABnzbd in
|
||||||
combination with some virus scanners and firewalls.
|
combination with some virus scanners and firewalls.
|
||||||
@@ -24,13 +24,13 @@
|
|||||||
For these the server blocking method is not very favourable.
|
For these the server blocking method is not very favourable.
|
||||||
There is an INI-only option that will limit blocks to 1 minute.
|
There is an INI-only option that will limit blocks to 1 minute.
|
||||||
no_penalties = 1
|
no_penalties = 1
|
||||||
See: https://sabnzbd.org/wiki/configuration/1.0/special
|
See: https://sabnzbd.org/wiki/configuration/2.2/special
|
||||||
|
|
||||||
- Some third-party utilties try to probe SABnzbd API in such a way that you will
|
- Some third-party utilties try to probe SABnzbd API in such a way that you will
|
||||||
often see warnings about unauthenticated access.
|
often see warnings about unauthenticated access.
|
||||||
If you are sure these probes are harmless, you can suppress the warnings by
|
If you are sure these probes are harmless, you can suppress the warnings by
|
||||||
setting the option "api_warnings" to 0.
|
setting the option "api_warnings" to 0.
|
||||||
See: https://sabnzbd.org/wiki/configuration/1.0/special
|
See: https://sabnzbd.org/wiki/configuration/2.2/special
|
||||||
|
|
||||||
- On OSX you may encounter downloaded files with foreign characters.
|
- On OSX you may encounter downloaded files with foreign characters.
|
||||||
The par2 repair may fail when the files were created on a Windows system.
|
The par2 repair may fail when the files were created on a Windows system.
|
||||||
@@ -41,7 +41,7 @@
|
|||||||
You will see this only when downloaded files contain accented characters.
|
You will see this only when downloaded files contain accented characters.
|
||||||
You need to fix it yourself by running the convmv utility (available for most Linux platforms).
|
You need to fix it yourself by running the convmv utility (available for most Linux platforms).
|
||||||
Possible the file system override setting 'fsys_type' might be solve things:
|
Possible the file system override setting 'fsys_type' might be solve things:
|
||||||
See: https://sabnzbd.org/wiki/configuration/1.0/special
|
See: https://sabnzbd.org/wiki/configuration/2.2/special
|
||||||
|
|
||||||
- The "Watched Folder" sometimes fails to delete the NZB files it has
|
- The "Watched Folder" sometimes fails to delete the NZB files it has
|
||||||
processed. This happens when other software still accesses these files.
|
processed. This happens when other software still accesses these files.
|
||||||
@@ -81,4 +81,4 @@
|
|||||||
- Squeeze Linux
|
- Squeeze Linux
|
||||||
There is a "special" option that will allow you to select an alternative library.
|
There is a "special" option that will allow you to select an alternative library.
|
||||||
use_pickle = 1
|
use_pickle = 1
|
||||||
See: https://sabnzbd.org/wiki/configuration/1.0/special
|
See: https://sabnzbd.org/wiki/configuration/2.2/special
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
(c) Copyright 2007-2016 by "The SABnzbd-team" <team@sabnzbd.org>
|
(c) Copyright 2007-2017 by "The SABnzbd-team" <team@sabnzbd.org>
|
||||||
|
|
||||||
This program is free software; you can redistribute it and/or
|
This program is free software; you can redistribute it and/or
|
||||||
modify it under the terms of the GNU General Public License
|
modify it under the terms of the GNU General Public License
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
Metadata-Version: 1.0
|
Metadata-Version: 1.0
|
||||||
Name: SABnzbd
|
Name: SABnzbd
|
||||||
Version: 1.2.0
|
Version: 2.3.0Alpha2
|
||||||
Summary: SABnzbd-1.2.0
|
Summary: SABnzbd-2.3.0Alpha1
|
||||||
Home-page: http://sabnzbd.org
|
Home-page: https://sabnzbd.org
|
||||||
Author: The SABnzbd Team
|
Author: The SABnzbd Team
|
||||||
Author-email: team@sabnzbd.org
|
Author-email: team@sabnzbd.org
|
||||||
License: GNU General Public License 2 (GPL2 or later)
|
License: GNU General Public License 2 (GPL2 or later)
|
||||||
|
|||||||
@@ -1,37 +1,28 @@
|
|||||||
SABnzbd - The automated Usenet download tool
|
SABnzbd - The automated Usenet download tool
|
||||||
============================================
|
============================================
|
||||||
|
|
||||||
This Unicode release is not compatible with 0.7.x queues!
|
|
||||||
|
|
||||||
There is also an issue with upgrading of the "sabnzbd.ini" file.
|
|
||||||
Make sure that you have a backup!
|
|
||||||
|
|
||||||
Saved queues may not be compatible after updates.
|
|
||||||
|
|
||||||
----
|
|
||||||
|
|
||||||
SABnzbd is an Open Source Binary Newsreader written in Python.
|
SABnzbd is an Open Source Binary Newsreader written in Python.
|
||||||
|
|
||||||
It's totally free, incredibly easy to use, and works practically everywhere.
|
It's totally free, incredibly easy to use, and works practically everywhere.
|
||||||
|
SABnzbd makes Usenet as simple and streamlined as possible by automating everything we can. All you have to do is add an `.nzb`. SABnzbd takes over from there, where it will be automatically downloaded, verified, repaired, extracted and filed away with zero human interaction.
|
||||||
SABnzbd makes Usenet as simple and streamlined as possible by automating everything we can. All you have to do is add an .nzb. SABnzbd takes over from there, where it will be automatically downloaded, verified, repaired, extracted and filed away with zero human interaction.
|
If you want to know more you can head over to our website: https://sabnzbd.org.
|
||||||
|
|
||||||
If you want to know more you can head over to our website: http://sabnzbd.org.
|
|
||||||
|
|
||||||
## Resolving Dependencies
|
## Resolving Dependencies
|
||||||
|
|
||||||
SABnzbd has a good deal of dependencies you'll need before you can get running. If you've previously run SABnzbd from one of the various Linux packages floating around (Ubuntu, Debian, Fedora, etc), then you likely already have all the needed dependencies. If not, here's what you're looking for:
|
SABnzbd has a good deal of dependencies you'll need before you can get running. If you've previously run SABnzbd from one of the various Linux packages, then you likely already have all the needed dependencies. If not, here's what you're looking for:
|
||||||
|
|
||||||
- `python` (only 2.7.9 and higher, but not 3.x.x)
|
- `python` (only 2.7.x and higher, but not 3.x.x)
|
||||||
- `python-cheetah`
|
- `python-cheetah`
|
||||||
- `python-dbus`
|
|
||||||
- `python-openssl`
|
|
||||||
- `python-support`
|
- `python-support`
|
||||||
- `python-yenc`
|
- `par2` (Multi-threaded par2 installation guide can be found [here](https://sabnzbd.org/wiki/installation/multicore-par2))
|
||||||
- `par2` (Multi-threaded par2 installation guide can be found [here](https://forums.sabnzbd.org/viewtopic.php?f=16&t=18793#p99702) )
|
|
||||||
- `unrar` (Make sure you get the "official" non-free version of unrar)
|
- `unrar` (Make sure you get the "official" non-free version of unrar)
|
||||||
- `unzip`
|
- `sabyenc` (installation guide can be found [here](https://sabnzbd.org/sabyenc))
|
||||||
|
|
||||||
|
Optional:
|
||||||
|
- `python-cryptography` (enables certificate generation and detection of encrypted RAR-files during download)
|
||||||
|
- `python-dbus` (enable option to Shutdown/Restart/Standby PC on queue finish)
|
||||||
- `7zip`
|
- `7zip`
|
||||||
|
- `unzip`
|
||||||
|
|
||||||
Your package manager should supply these. If not, we've got links in our more in-depth [installation guide](https://github.com/sabnzbd/sabnzbd/blob/master/INSTALL.txt).
|
Your package manager should supply these. If not, we've got links in our more in-depth [installation guide](https://github.com/sabnzbd/sabnzbd/blob/master/INSTALL.txt).
|
||||||
|
|
||||||
@@ -40,13 +31,13 @@ Your package manager should supply these. If not, we've got links in our more in
|
|||||||
Once you've sorted out all the dependencies, simply run:
|
Once you've sorted out all the dependencies, simply run:
|
||||||
|
|
||||||
```
|
```
|
||||||
python SABnzbd.py
|
python -OO SABnzbd.py
|
||||||
```
|
```
|
||||||
|
|
||||||
Or, if you want to run in the background:
|
Or, if you want to run in the background:
|
||||||
|
|
||||||
```
|
```
|
||||||
python SABnzbd.py -d -f /path/to/sabnzbd.ini
|
python -OO SABnzbd.py -d -f /path/to/sabnzbd.ini
|
||||||
```
|
```
|
||||||
|
|
||||||
If you want multi-language support, run:
|
If you want multi-language support, run:
|
||||||
@@ -61,18 +52,17 @@ Our many other command line options are explained in depth [here](https://sabnzb
|
|||||||
|
|
||||||
The workflow we use, is a simplified form of "GitFlow".
|
The workflow we use, is a simplified form of "GitFlow".
|
||||||
Basically:
|
Basically:
|
||||||
- "master" contains only stable releases (which have been merged to "master")
|
- `master` contains only stable releases (which have been merged to `master`) and is intended for end-users.
|
||||||
- "develop" is the target for integration
|
- `develop` is the target for integration and is **not** intended for end-users.
|
||||||
- "1.0.x" is a release and maintenance branch for 1.0.x: 1.0.0 -> 1.0.1 -> 1.0.2
|
- `1.1.x` is a release and maintenance branch for 1.1.x (1.1.0 -> 1.1.1 -> 1.1.2) and is **not** intended for end-users.
|
||||||
- "1.1.x" is a release and maintenance branch for 1.1.x: 1.1.0 -> 1.1.1 -> 1.1.2
|
- `feature/my_feature` is a temporary feature branch based on `develop`.
|
||||||
- "feature/my_feature" is a temporary feature branch
|
- `bugfix/my_bugfix` is an optional temporary branch for bugfix(es) based on `develop`.
|
||||||
- "hotfix/my_hotfix is an optional temporary branch for bugfix(es)
|
|
||||||
|
|
||||||
Condtions:
|
Conditions:
|
||||||
- Merging of a stable release into "master" will be simple: the release branch is always right.
|
- Merging of a stable release into `master` will be simple: the release branch is always right.
|
||||||
- "master" is not merged back to "develop"
|
- `master` is not merged back to `develop`.
|
||||||
- "develop" is not re-based on "master".
|
- `develop` is not re-based on `master`.
|
||||||
- Release branches branch from "develop" only.
|
- Release branches branch from `develop` only.
|
||||||
- Bugfixes created specifically for a release branch are done there (because they are specific, they're not cherry-picked to "develop").
|
- Bugfixes created specifically for a release branch are done there (because they are specific, they're not cherry-picked to `develop`).
|
||||||
- Bugfixes done on "develop" may be cherry-picked to a release branch.
|
- Bugfixes done on `develop` may be cherry-picked to a release branch.
|
||||||
- We will not release a 1.0.2 if a 1.1.0 has already been released.
|
- We will not release a 1.0.2 if a 1.1.0 has already been released.
|
||||||
@@ -1,40 +1,67 @@
|
|||||||
Release Notes - SABnzbd 1.2.0
|
Release Notes - SABnzbd 2.3.0 Alpha 2
|
||||||
===============================
|
=========================================================
|
||||||
|
|
||||||
## What's new in 1.2.0
|
## Changes and bugfixes since 2.3.0 Alpha 1
|
||||||
|
- Specials Config page could not be loaded
|
||||||
|
- Crash when adding new jobs
|
||||||
|
- Further stalling-detection improvements
|
||||||
|
- Crash when a job was set to only Download
|
||||||
|
- Display of download progress and missing data improved
|
||||||
|
- Retried jobs would show incorrect download progress
|
||||||
|
- Different icon for downloads with Force priority
|
||||||
|
- Show progress during verification of extra files
|
||||||
|
- API: 'missing' field removed, use 'mbmissing'
|
||||||
|
|
||||||
|
|
||||||
## Changes:
|
## Changes since 2.2.1
|
||||||
|
- Option to limit Servers to specific Categories removed
|
||||||
|
- Improved par2 handling and obfuscated files detection
|
||||||
|
- Duplicate filenames in NZB's no longer rejected by default
|
||||||
|
- Set custom URL instead of /sabnzbd/ (in Config > Specials)
|
||||||
|
- Article-state (which servers are tried) is preserved after restart
|
||||||
|
- Auto disconnect (if enabled) only after verification of last item
|
||||||
|
- Slight performance improvement when fetching RSS-feeds
|
||||||
|
- RSS-feed title is shown for URLs being grabbed
|
||||||
|
- URL grabbing can now be individually paused
|
||||||
|
- Scheduler can pause/unpause jobs in specific category
|
||||||
|
- Series Duplicate Checker can allow PROPER/REAL/REPACK versions
|
||||||
|
- Refresh-icon in Glitter when refresh rate is above 2 seconds
|
||||||
|
- macOS: Bundled new OpenSSL version with support for TLSv1.2
|
||||||
|
- macOS: No longer linked to system certificate store
|
||||||
|
- macOS and Windows: Installers include Mozilla CA certificates
|
||||||
|
|
||||||
|
## Bugfixes since 2.2.1
|
||||||
|
- Reduce CPU usage with multiple servers
|
||||||
|
- Fix yet another potential stalling issue
|
||||||
|
- Remove Timeout tracebacks
|
||||||
|
- Only warn if number of actual passwords is larger than 30
|
||||||
|
- Unexpected behavior when diskspace becomes critically low
|
||||||
|
- MacOS: Direct Unpack could hang in case of special charters in names
|
||||||
|
- API: Correct listing of downloaded and queued files in get_files
|
||||||
|
- API: Jobs with Force priority should always have status 'Downloading'
|
||||||
|
|
||||||
## Bug fixes
|
## Upgrading from 2.1.x and older
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
## About
|
|
||||||
SABnzbd is an open-source cross-platform binary newsreader.
|
|
||||||
It simplifies the process of downloading from Usenet dramatically,
|
|
||||||
thanks to its web-based user interface and advanced
|
|
||||||
built-in post-processing options that automatically verify, repair,
|
|
||||||
extract and clean up posts downloaded from Usenet.
|
|
||||||
|
|
||||||
(c) Copyright 2007-2016 by "The SABnzbd-team" \<team@sabnzbd.org\>
|
|
||||||
|
|
||||||
|
|
||||||
### IMPORTANT INFORMATION about release 1.0.0+
|
|
||||||
<https://sabnzbd.org/wiki/introducing-1-0>
|
|
||||||
|
|
||||||
### Known problems and solutions
|
|
||||||
- Read the file "ISSUES.txt"
|
|
||||||
|
|
||||||
### Upgrading from 0.7.x and older
|
|
||||||
- Finish queue
|
- Finish queue
|
||||||
- Stop SABnzbd
|
- Stop SABnzbd
|
||||||
- Install new version
|
- Install new version
|
||||||
- Start SABnzbd
|
- Start SABnzbd
|
||||||
|
|
||||||
The organization of the download queue is different from older versions.
|
## Upgrade notices
|
||||||
1.0.x will not see the existing queue, but you can go to
|
- When upgrading from 2.1.0 or older the queue will be converted. Job order,
|
||||||
Status->QueueRepair and "Repair" the old queue.
|
settings and data will be preserved, but all jobs will be unpaused and
|
||||||
Also, your sabnzbd.ini file will be upgraded, making it
|
URL's that did not finish fetching before the upgrade will be lost.
|
||||||
incompatible with releases older than 0.7.9
|
- The organization of the download queue is different from 0.7.x releases.
|
||||||
|
This version will not see the 0.7.x queue, but you can restore the jobs
|
||||||
|
by going to Status page and using Queue Repair.
|
||||||
|
|
||||||
|
## Known problems and solutions
|
||||||
|
- Read the file "ISSUES.txt"
|
||||||
|
|
||||||
|
## About
|
||||||
|
SABnzbd is an open-source cross-platform binary newsreader.
|
||||||
|
It simplifies the process of downloading from Usenet dramatically, thanks
|
||||||
|
to its web-based user interface and advanced built-in post-processing options
|
||||||
|
that automatically verify, repair, extract and clean up posts downloaded
|
||||||
|
from Usenet.
|
||||||
|
|
||||||
|
(c) Copyright 2007-2017 by "The SABnzbd-team" \<team@sabnzbd.org\>
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
#!/usr/bin/python -OO
|
#!/usr/bin/python -OO
|
||||||
# Copyright 2008-2015 The SABnzbd-Team <team@sabnzbd.org>
|
# Copyright 2008-2017 The SABnzbd-Team <team@sabnzbd.org>
|
||||||
#
|
#
|
||||||
# This program is free software; you can redistribute it and/or
|
# This program is free software; you can redistribute it and/or
|
||||||
# modify it under the terms of the GNU General Public License
|
# modify it under the terms of the GNU General Public License
|
||||||
|
|||||||
@@ -1,25 +0,0 @@
|
|||||||
Copyright (c) 2004-2016, CherryPy Team (team@cherrypy.org)
|
|
||||||
All rights reserved.
|
|
||||||
|
|
||||||
Redistribution and use in source and binary forms, with or without modification,
|
|
||||||
are permitted provided that the following conditions are met:
|
|
||||||
|
|
||||||
* Redistributions of source code must retain the above copyright notice,
|
|
||||||
this list of conditions and the following disclaimer.
|
|
||||||
* Redistributions in binary form must reproduce the above copyright notice,
|
|
||||||
this list of conditions and the following disclaimer in the documentation
|
|
||||||
and/or other materials provided with the distribution.
|
|
||||||
* Neither the name of the CherryPy Team nor the names of its contributors
|
|
||||||
may be used to endorse or promote products derived from this software
|
|
||||||
without specific prior written permission.
|
|
||||||
|
|
||||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
|
|
||||||
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
|
||||||
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
|
||||||
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE
|
|
||||||
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
|
||||||
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
|
||||||
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
|
||||||
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
|
||||||
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
|
||||||
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|
||||||
@@ -1,4 +1,5 @@
|
|||||||
CherryPy 6.0.2 Official distribution: https://pypi.python.org/packages/source/C/CherryPy/CherryPy-6.0.2.tar.gz
|
CherryPy 8.1.2
|
||||||
|
Official distribution: https://github.com/cherrypy/cherrypy/releases
|
||||||
The folders 'tutorial', 'test' and 'scaffold' have been removed.
|
The folders 'tutorial', 'test' and 'scaffold' have been removed.
|
||||||
This file has been added.
|
This file has been added.
|
||||||
|
|
||||||
@@ -61,23 +61,26 @@ try:
|
|||||||
except ImportError:
|
except ImportError:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
from cherrypy._cperror import HTTPError, HTTPRedirect, InternalRedirect
|
from threading import local as _local
|
||||||
from cherrypy._cperror import NotFound, CherryPyException, TimeoutError
|
|
||||||
|
|
||||||
from cherrypy import _cpdispatch as dispatch
|
from cherrypy._cperror import HTTPError, HTTPRedirect, InternalRedirect # noqa
|
||||||
|
from cherrypy._cperror import NotFound, CherryPyException, TimeoutError # noqa
|
||||||
|
|
||||||
|
from cherrypy import _cplogging
|
||||||
|
|
||||||
|
from cherrypy import _cpdispatch as dispatch # noqa
|
||||||
|
|
||||||
from cherrypy import _cptools
|
from cherrypy import _cptools
|
||||||
tools = _cptools.default_toolbox
|
from cherrypy._cptools import default_toolbox as tools, Tool
|
||||||
Tool = _cptools.Tool
|
|
||||||
|
|
||||||
from cherrypy import _cprequest
|
from cherrypy import _cprequest
|
||||||
from cherrypy.lib import httputil as _httputil
|
from cherrypy.lib import httputil as _httputil
|
||||||
|
|
||||||
from cherrypy import _cptree
|
from cherrypy import _cptree
|
||||||
tree = _cptree.Tree()
|
from cherrypy._cptree import Application # noqa
|
||||||
from cherrypy._cptree import Application
|
from cherrypy import _cpwsgi as wsgi # noqa
|
||||||
from cherrypy import _cpwsgi as wsgi
|
|
||||||
|
|
||||||
|
from cherrypy import _cpserver
|
||||||
from cherrypy import process
|
from cherrypy import process
|
||||||
try:
|
try:
|
||||||
from cherrypy.process import win32
|
from cherrypy.process import win32
|
||||||
@@ -88,11 +91,11 @@ except ImportError:
|
|||||||
engine = process.bus
|
engine = process.bus
|
||||||
|
|
||||||
|
|
||||||
try:
|
tree = _cptree.Tree()
|
||||||
__version__ = pkg_resources.require('cherrypy')[0].version
|
|
||||||
except Exception:
|
|
||||||
__version__ = 'unknown'
|
__version__ = '8.1.2'
|
||||||
__version__ = '6.0.2'
|
|
||||||
|
|
||||||
# Timeout monitor. We add two channels to the engine
|
# Timeout monitor. We add two channels to the engine
|
||||||
# to which cherrypy.Application will publish.
|
# to which cherrypy.Application will publish.
|
||||||
@@ -141,20 +144,19 @@ class _HandleSignalsPlugin(object):
|
|||||||
|
|
||||||
def subscribe(self):
|
def subscribe(self):
|
||||||
"""Add the handlers based on the platform"""
|
"""Add the handlers based on the platform"""
|
||||||
if hasattr(self.bus, "signal_handler"):
|
if hasattr(self.bus, 'signal_handler'):
|
||||||
self.bus.signal_handler.subscribe()
|
self.bus.signal_handler.subscribe()
|
||||||
if hasattr(self.bus, "console_control_handler"):
|
if hasattr(self.bus, 'console_control_handler'):
|
||||||
self.bus.console_control_handler.subscribe()
|
self.bus.console_control_handler.subscribe()
|
||||||
|
|
||||||
engine.signals = _HandleSignalsPlugin(engine)
|
engine.signals = _HandleSignalsPlugin(engine)
|
||||||
|
|
||||||
|
|
||||||
from cherrypy import _cpserver
|
|
||||||
server = _cpserver.Server()
|
server = _cpserver.Server()
|
||||||
server.subscribe()
|
server.subscribe()
|
||||||
|
|
||||||
|
|
||||||
def quickstart(root=None, script_name="", config=None):
|
def quickstart(root=None, script_name='', config=None):
|
||||||
"""Mount the given root, start the builtin server (and engine), then block.
|
"""Mount the given root, start the builtin server (and engine), then block.
|
||||||
|
|
||||||
root: an instance of a "controller class" (a collection of page handler
|
root: an instance of a "controller class" (a collection of page handler
|
||||||
@@ -181,9 +183,6 @@ def quickstart(root=None, script_name="", config=None):
|
|||||||
engine.block()
|
engine.block()
|
||||||
|
|
||||||
|
|
||||||
from cherrypy._cpcompat import threadlocal as _local
|
|
||||||
|
|
||||||
|
|
||||||
class _Serving(_local):
|
class _Serving(_local):
|
||||||
|
|
||||||
"""An interface for registering request and response objects.
|
"""An interface for registering request and response objects.
|
||||||
@@ -196,8 +195,8 @@ class _Serving(_local):
|
|||||||
thread-safe way.
|
thread-safe way.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
request = _cprequest.Request(_httputil.Host("127.0.0.1", 80),
|
request = _cprequest.Request(_httputil.Host('127.0.0.1', 80),
|
||||||
_httputil.Host("127.0.0.1", 1111))
|
_httputil.Host('127.0.0.1', 1111))
|
||||||
"""
|
"""
|
||||||
The request object for the current thread. In the main thread,
|
The request object for the current thread. In the main thread,
|
||||||
and any threads which are not receiving HTTP requests, this is None."""
|
and any threads which are not receiving HTTP requests, this is None."""
|
||||||
@@ -230,7 +229,7 @@ class _ThreadLocalProxy(object):
|
|||||||
return getattr(child, name)
|
return getattr(child, name)
|
||||||
|
|
||||||
def __setattr__(self, name, value):
|
def __setattr__(self, name, value):
|
||||||
if name in ("__attrname__", ):
|
if name in ('__attrname__', ):
|
||||||
object.__setattr__(self, name, value)
|
object.__setattr__(self, name, value)
|
||||||
else:
|
else:
|
||||||
child = getattr(serving, self.__attrname__)
|
child = getattr(serving, self.__attrname__)
|
||||||
@@ -306,9 +305,6 @@ except ImportError:
|
|||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
from cherrypy import _cplogging
|
|
||||||
|
|
||||||
|
|
||||||
class _GlobalLogManager(_cplogging.LogManager):
|
class _GlobalLogManager(_cplogging.LogManager):
|
||||||
|
|
||||||
"""A site-wide LogManager; routes to app.log or global log as appropriate.
|
"""A site-wide LogManager; routes to app.log or global log as appropriate.
|
||||||
@@ -352,10 +348,10 @@ def _buslog(msg, level):
|
|||||||
log.error(msg, 'ENGINE', severity=level)
|
log.error(msg, 'ENGINE', severity=level)
|
||||||
engine.subscribe('log', _buslog)
|
engine.subscribe('log', _buslog)
|
||||||
|
|
||||||
from cherrypy._helper import expose, popargs, url
|
from cherrypy._helper import expose, popargs, url # noqa
|
||||||
|
|
||||||
# import _cpconfig last so it can reference other top-level objects
|
# import _cpconfig last so it can reference other top-level objects
|
||||||
from cherrypy import _cpconfig
|
from cherrypy import _cpconfig # noqa
|
||||||
# Use _global_conf_alias so quickstart can use 'config' as an arg
|
# Use _global_conf_alias so quickstart can use 'config' as an arg
|
||||||
# without shadowing cherrypy.config.
|
# without shadowing cherrypy.config.
|
||||||
config = _global_conf_alias = _cpconfig.Config()
|
config = _global_conf_alias = _cpconfig.Config()
|
||||||
@@ -365,11 +361,11 @@ config.defaults = {
|
|||||||
'tools.trailing_slash.on': True,
|
'tools.trailing_slash.on': True,
|
||||||
'tools.encode.on': True
|
'tools.encode.on': True
|
||||||
}
|
}
|
||||||
config.namespaces["log"] = lambda k, v: setattr(log, k, v)
|
config.namespaces['log'] = lambda k, v: setattr(log, k, v)
|
||||||
config.namespaces["checker"] = lambda k, v: setattr(checker, k, v)
|
config.namespaces['checker'] = lambda k, v: setattr(checker, k, v)
|
||||||
# Must reset to get our defaults applied.
|
# Must reset to get our defaults applied.
|
||||||
config.reset()
|
config.reset()
|
||||||
|
|
||||||
from cherrypy import _cpchecker
|
from cherrypy import _cpchecker # noqa
|
||||||
checker = _cpchecker.Checker()
|
checker = _cpchecker.Checker()
|
||||||
engine.subscribe('start', checker)
|
engine.subscribe('start', checker)
|
||||||
@@ -33,7 +33,7 @@ class Checker(object):
|
|||||||
warnings.formatwarning = self.formatwarning
|
warnings.formatwarning = self.formatwarning
|
||||||
try:
|
try:
|
||||||
for name in dir(self):
|
for name in dir(self):
|
||||||
if name.startswith("check_"):
|
if name.startswith('check_'):
|
||||||
method = getattr(self, name)
|
method = getattr(self, name)
|
||||||
if method and hasattr(method, '__call__'):
|
if method and hasattr(method, '__call__'):
|
||||||
method()
|
method()
|
||||||
@@ -42,7 +42,7 @@ class Checker(object):
|
|||||||
|
|
||||||
def formatwarning(self, message, category, filename, lineno, line=None):
|
def formatwarning(self, message, category, filename, lineno, line=None):
|
||||||
"""Function to format a warning."""
|
"""Function to format a warning."""
|
||||||
return "CherryPy Checker:\n%s\n\n" % message
|
return 'CherryPy Checker:\n%s\n\n' % message
|
||||||
|
|
||||||
# This value should be set inside _cpconfig.
|
# This value should be set inside _cpconfig.
|
||||||
global_config_contained_paths = False
|
global_config_contained_paths = False
|
||||||
@@ -57,13 +57,13 @@ class Checker(object):
|
|||||||
continue
|
continue
|
||||||
if sn == '':
|
if sn == '':
|
||||||
continue
|
continue
|
||||||
sn_atoms = sn.strip("/").split("/")
|
sn_atoms = sn.strip('/').split('/')
|
||||||
for key in app.config.keys():
|
for key in app.config.keys():
|
||||||
key_atoms = key.strip("/").split("/")
|
key_atoms = key.strip('/').split('/')
|
||||||
if key_atoms[:len(sn_atoms)] == sn_atoms:
|
if key_atoms[:len(sn_atoms)] == sn_atoms:
|
||||||
warnings.warn(
|
warnings.warn(
|
||||||
"The application mounted at %r has config "
|
'The application mounted at %r has config '
|
||||||
"entries that start with its script name: %r" % (sn,
|
'entries that start with its script name: %r' % (sn,
|
||||||
key))
|
key))
|
||||||
|
|
||||||
def check_site_config_entries_in_app_config(self):
|
def check_site_config_entries_in_app_config(self):
|
||||||
@@ -76,17 +76,17 @@ class Checker(object):
|
|||||||
for section, entries in iteritems(app.config):
|
for section, entries in iteritems(app.config):
|
||||||
if section.startswith('/'):
|
if section.startswith('/'):
|
||||||
for key, value in iteritems(entries):
|
for key, value in iteritems(entries):
|
||||||
for n in ("engine.", "server.", "tree.", "checker."):
|
for n in ('engine.', 'server.', 'tree.', 'checker.'):
|
||||||
if key.startswith(n):
|
if key.startswith(n):
|
||||||
msg.append("[%s] %s = %s" %
|
msg.append('[%s] %s = %s' %
|
||||||
(section, key, value))
|
(section, key, value))
|
||||||
if msg:
|
if msg:
|
||||||
msg.insert(0,
|
msg.insert(0,
|
||||||
"The application mounted at %r contains the "
|
'The application mounted at %r contains the '
|
||||||
"following config entries, which are only allowed "
|
'following config entries, which are only allowed '
|
||||||
"in site-wide config. Move them to a [global] "
|
'in site-wide config. Move them to a [global] '
|
||||||
"section and pass them to cherrypy.config.update() "
|
'section and pass them to cherrypy.config.update() '
|
||||||
"instead of tree.mount()." % sn)
|
'instead of tree.mount().' % sn)
|
||||||
warnings.warn(os.linesep.join(msg))
|
warnings.warn(os.linesep.join(msg))
|
||||||
|
|
||||||
def check_skipped_app_config(self):
|
def check_skipped_app_config(self):
|
||||||
@@ -95,13 +95,13 @@ class Checker(object):
|
|||||||
if not isinstance(app, cherrypy.Application):
|
if not isinstance(app, cherrypy.Application):
|
||||||
continue
|
continue
|
||||||
if not app.config:
|
if not app.config:
|
||||||
msg = "The Application mounted at %r has an empty config." % sn
|
msg = 'The Application mounted at %r has an empty config.' % sn
|
||||||
if self.global_config_contained_paths:
|
if self.global_config_contained_paths:
|
||||||
msg += (" It looks like the config you passed to "
|
msg += (' It looks like the config you passed to '
|
||||||
"cherrypy.config.update() contains application-"
|
'cherrypy.config.update() contains application-'
|
||||||
"specific sections. You must explicitly pass "
|
'specific sections. You must explicitly pass '
|
||||||
"application config via "
|
'application config via '
|
||||||
"cherrypy.tree.mount(..., config=app_config)")
|
'cherrypy.tree.mount(..., config=app_config)')
|
||||||
warnings.warn(msg)
|
warnings.warn(msg)
|
||||||
return
|
return
|
||||||
|
|
||||||
@@ -115,12 +115,12 @@ class Checker(object):
|
|||||||
if not app.config:
|
if not app.config:
|
||||||
continue
|
continue
|
||||||
for key in app.config.keys():
|
for key in app.config.keys():
|
||||||
if key.startswith("[") or key.endswith("]"):
|
if key.startswith('[') or key.endswith(']'):
|
||||||
warnings.warn(
|
warnings.warn(
|
||||||
"The application mounted at %r has config "
|
'The application mounted at %r has config '
|
||||||
"section names with extraneous brackets: %r. "
|
'section names with extraneous brackets: %r. '
|
||||||
"Config *files* need brackets; config *dicts* "
|
'Config *files* need brackets; config *dicts* '
|
||||||
"(e.g. passed to tree.mount) do not." % (sn, key))
|
'(e.g. passed to tree.mount) do not.' % (sn, key))
|
||||||
|
|
||||||
def check_static_paths(self):
|
def check_static_paths(self):
|
||||||
"""Check Application config for incorrect static paths."""
|
"""Check Application config for incorrect static paths."""
|
||||||
@@ -132,47 +132,47 @@ class Checker(object):
|
|||||||
request.app = app
|
request.app = app
|
||||||
for section in app.config:
|
for section in app.config:
|
||||||
# get_resource will populate request.config
|
# get_resource will populate request.config
|
||||||
request.get_resource(section + "/dummy.html")
|
request.get_resource(section + '/dummy.html')
|
||||||
conf = request.config.get
|
conf = request.config.get
|
||||||
|
|
||||||
if conf("tools.staticdir.on", False):
|
if conf('tools.staticdir.on', False):
|
||||||
msg = ""
|
msg = ''
|
||||||
root = conf("tools.staticdir.root")
|
root = conf('tools.staticdir.root')
|
||||||
dir = conf("tools.staticdir.dir")
|
dir = conf('tools.staticdir.dir')
|
||||||
if dir is None:
|
if dir is None:
|
||||||
msg = "tools.staticdir.dir is not set."
|
msg = 'tools.staticdir.dir is not set.'
|
||||||
else:
|
else:
|
||||||
fulldir = ""
|
fulldir = ''
|
||||||
if os.path.isabs(dir):
|
if os.path.isabs(dir):
|
||||||
fulldir = dir
|
fulldir = dir
|
||||||
if root:
|
if root:
|
||||||
msg = ("dir is an absolute path, even "
|
msg = ('dir is an absolute path, even '
|
||||||
"though a root is provided.")
|
'though a root is provided.')
|
||||||
testdir = os.path.join(root, dir[1:])
|
testdir = os.path.join(root, dir[1:])
|
||||||
if os.path.exists(testdir):
|
if os.path.exists(testdir):
|
||||||
msg += (
|
msg += (
|
||||||
"\nIf you meant to serve the "
|
'\nIf you meant to serve the '
|
||||||
"filesystem folder at %r, remove the "
|
'filesystem folder at %r, remove the '
|
||||||
"leading slash from dir." % (testdir,))
|
'leading slash from dir.' % (testdir,))
|
||||||
else:
|
else:
|
||||||
if not root:
|
if not root:
|
||||||
msg = (
|
msg = (
|
||||||
"dir is a relative path and "
|
'dir is a relative path and '
|
||||||
"no root provided.")
|
'no root provided.')
|
||||||
else:
|
else:
|
||||||
fulldir = os.path.join(root, dir)
|
fulldir = os.path.join(root, dir)
|
||||||
if not os.path.isabs(fulldir):
|
if not os.path.isabs(fulldir):
|
||||||
msg = ("%r is not an absolute path." % (
|
msg = ('%r is not an absolute path.' % (
|
||||||
fulldir,))
|
fulldir,))
|
||||||
|
|
||||||
if fulldir and not os.path.exists(fulldir):
|
if fulldir and not os.path.exists(fulldir):
|
||||||
if msg:
|
if msg:
|
||||||
msg += "\n"
|
msg += '\n'
|
||||||
msg += ("%r (root + dir) is not an existing "
|
msg += ('%r (root + dir) is not an existing '
|
||||||
"filesystem path." % fulldir)
|
'filesystem path.' % fulldir)
|
||||||
|
|
||||||
if msg:
|
if msg:
|
||||||
warnings.warn("%s\nsection: [%s]\nroot: %r\ndir: %r"
|
warnings.warn('%s\nsection: [%s]\nroot: %r\ndir: %r'
|
||||||
% (msg, section, root, dir))
|
% (msg, section, root, dir))
|
||||||
|
|
||||||
# -------------------------- Compatibility -------------------------- #
|
# -------------------------- Compatibility -------------------------- #
|
||||||
@@ -198,19 +198,19 @@ class Checker(object):
|
|||||||
if isinstance(conf, dict):
|
if isinstance(conf, dict):
|
||||||
for k, v in conf.items():
|
for k, v in conf.items():
|
||||||
if k in self.obsolete:
|
if k in self.obsolete:
|
||||||
warnings.warn("%r is obsolete. Use %r instead.\n"
|
warnings.warn('%r is obsolete. Use %r instead.\n'
|
||||||
"section: [%s]" %
|
'section: [%s]' %
|
||||||
(k, self.obsolete[k], section))
|
(k, self.obsolete[k], section))
|
||||||
elif k in self.deprecated:
|
elif k in self.deprecated:
|
||||||
warnings.warn("%r is deprecated. Use %r instead.\n"
|
warnings.warn('%r is deprecated. Use %r instead.\n'
|
||||||
"section: [%s]" %
|
'section: [%s]' %
|
||||||
(k, self.deprecated[k], section))
|
(k, self.deprecated[k], section))
|
||||||
else:
|
else:
|
||||||
if section in self.obsolete:
|
if section in self.obsolete:
|
||||||
warnings.warn("%r is obsolete. Use %r instead."
|
warnings.warn('%r is obsolete. Use %r instead.'
|
||||||
% (section, self.obsolete[section]))
|
% (section, self.obsolete[section]))
|
||||||
elif section in self.deprecated:
|
elif section in self.deprecated:
|
||||||
warnings.warn("%r is deprecated. Use %r instead."
|
warnings.warn('%r is deprecated. Use %r instead.'
|
||||||
% (section, self.deprecated[section]))
|
% (section, self.deprecated[section]))
|
||||||
|
|
||||||
def check_compatibility(self):
|
def check_compatibility(self):
|
||||||
@@ -225,7 +225,7 @@ class Checker(object):
|
|||||||
extra_config_namespaces = []
|
extra_config_namespaces = []
|
||||||
|
|
||||||
def _known_ns(self, app):
|
def _known_ns(self, app):
|
||||||
ns = ["wsgi"]
|
ns = ['wsgi']
|
||||||
ns.extend(copykeys(app.toolboxes))
|
ns.extend(copykeys(app.toolboxes))
|
||||||
ns.extend(copykeys(app.namespaces))
|
ns.extend(copykeys(app.namespaces))
|
||||||
ns.extend(copykeys(app.request_class.namespaces))
|
ns.extend(copykeys(app.request_class.namespaces))
|
||||||
@@ -233,32 +233,32 @@ class Checker(object):
|
|||||||
ns += self.extra_config_namespaces
|
ns += self.extra_config_namespaces
|
||||||
|
|
||||||
for section, conf in app.config.items():
|
for section, conf in app.config.items():
|
||||||
is_path_section = section.startswith("/")
|
is_path_section = section.startswith('/')
|
||||||
if is_path_section and isinstance(conf, dict):
|
if is_path_section and isinstance(conf, dict):
|
||||||
for k, v in conf.items():
|
for k, v in conf.items():
|
||||||
atoms = k.split(".")
|
atoms = k.split('.')
|
||||||
if len(atoms) > 1:
|
if len(atoms) > 1:
|
||||||
if atoms[0] not in ns:
|
if atoms[0] not in ns:
|
||||||
# Spit out a special warning if a known
|
# Spit out a special warning if a known
|
||||||
# namespace is preceded by "cherrypy."
|
# namespace is preceded by "cherrypy."
|
||||||
if atoms[0] == "cherrypy" and atoms[1] in ns:
|
if atoms[0] == 'cherrypy' and atoms[1] in ns:
|
||||||
msg = (
|
msg = (
|
||||||
"The config entry %r is invalid; "
|
'The config entry %r is invalid; '
|
||||||
"try %r instead.\nsection: [%s]"
|
'try %r instead.\nsection: [%s]'
|
||||||
% (k, ".".join(atoms[1:]), section))
|
% (k, '.'.join(atoms[1:]), section))
|
||||||
else:
|
else:
|
||||||
msg = (
|
msg = (
|
||||||
"The config entry %r is invalid, "
|
'The config entry %r is invalid, '
|
||||||
"because the %r config namespace "
|
'because the %r config namespace '
|
||||||
"is unknown.\n"
|
'is unknown.\n'
|
||||||
"section: [%s]" % (k, atoms[0], section))
|
'section: [%s]' % (k, atoms[0], section))
|
||||||
warnings.warn(msg)
|
warnings.warn(msg)
|
||||||
elif atoms[0] == "tools":
|
elif atoms[0] == 'tools':
|
||||||
if atoms[1] not in dir(cherrypy.tools):
|
if atoms[1] not in dir(cherrypy.tools):
|
||||||
msg = (
|
msg = (
|
||||||
"The config entry %r may be invalid, "
|
'The config entry %r may be invalid, '
|
||||||
"because the %r tool was not found.\n"
|
'because the %r tool was not found.\n'
|
||||||
"section: [%s]" % (k, atoms[1], section))
|
'section: [%s]' % (k, atoms[1], section))
|
||||||
warnings.warn(msg)
|
warnings.warn(msg)
|
||||||
|
|
||||||
def check_config_namespaces(self):
|
def check_config_namespaces(self):
|
||||||
@@ -282,17 +282,17 @@ class Checker(object):
|
|||||||
continue
|
continue
|
||||||
vtype = type(getattr(obj, name, None))
|
vtype = type(getattr(obj, name, None))
|
||||||
if vtype in b:
|
if vtype in b:
|
||||||
self.known_config_types[namespace + "." + name] = vtype
|
self.known_config_types[namespace + '.' + name] = vtype
|
||||||
|
|
||||||
traverse(cherrypy.request, "request")
|
traverse(cherrypy.request, 'request')
|
||||||
traverse(cherrypy.response, "response")
|
traverse(cherrypy.response, 'response')
|
||||||
traverse(cherrypy.server, "server")
|
traverse(cherrypy.server, 'server')
|
||||||
traverse(cherrypy.engine, "engine")
|
traverse(cherrypy.engine, 'engine')
|
||||||
traverse(cherrypy.log, "log")
|
traverse(cherrypy.log, 'log')
|
||||||
|
|
||||||
def _known_types(self, config):
|
def _known_types(self, config):
|
||||||
msg = ("The config entry %r in section %r is of type %r, "
|
msg = ('The config entry %r in section %r is of type %r, '
|
||||||
"which does not match the expected type %r.")
|
'which does not match the expected type %r.')
|
||||||
|
|
||||||
for section, conf in config.items():
|
for section, conf in config.items():
|
||||||
if isinstance(conf, dict):
|
if isinstance(conf, dict):
|
||||||
@@ -326,7 +326,7 @@ class Checker(object):
|
|||||||
for k, v in cherrypy.config.items():
|
for k, v in cherrypy.config.items():
|
||||||
if k == 'server.socket_host' and v == 'localhost':
|
if k == 'server.socket_host' and v == 'localhost':
|
||||||
warnings.warn("The use of 'localhost' as a socket host can "
|
warnings.warn("The use of 'localhost' as a socket host can "
|
||||||
"cause problems on newer systems, since "
|
'cause problems on newer systems, since '
|
||||||
"'localhost' can map to either an IPv4 or an "
|
"'localhost' can map to either an IPv4 or an "
|
||||||
"IPv6 address. You should use '127.0.0.1' "
|
"IPv6 address. You should use '127.0.0.1' "
|
||||||
"or '[::1]' instead.")
|
"or '[::1]' instead.")
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
"""Compatibility code for using CherryPy with various versions of Python.
|
"""Compatibility code for using CherryPy with various versions of Python.
|
||||||
|
|
||||||
CherryPy 3.2 is compatible with Python versions 2.3+. This module provides a
|
CherryPy 3.2 is compatible with Python versions 2.6+. This module provides a
|
||||||
useful abstraction over the differences between Python versions, sometimes by
|
useful abstraction over the differences between Python versions, sometimes by
|
||||||
preferring a newer idiom, sometimes an older one, and sometimes a custom one.
|
preferring a newer idiom, sometimes an older one, and sometimes a custom one.
|
||||||
|
|
||||||
@@ -15,6 +15,8 @@ specifically with bytes, and a 'StringIO' name for dealing with native strings.
|
|||||||
It also provides a 'base64_decode' function with native strings as input and
|
It also provides a 'base64_decode' function with native strings as input and
|
||||||
output.
|
output.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
import binascii
|
||||||
import os
|
import os
|
||||||
import re
|
import re
|
||||||
import sys
|
import sys
|
||||||
@@ -23,8 +25,6 @@ import threading
|
|||||||
import six
|
import six
|
||||||
|
|
||||||
if six.PY3:
|
if six.PY3:
|
||||||
basestring = (bytes, str)
|
|
||||||
|
|
||||||
def ntob(n, encoding='ISO-8859-1'):
|
def ntob(n, encoding='ISO-8859-1'):
|
||||||
"""Return the given native string as a byte string in the given
|
"""Return the given native string as a byte string in the given
|
||||||
encoding.
|
encoding.
|
||||||
@@ -49,8 +49,6 @@ if six.PY3:
|
|||||||
return n
|
return n
|
||||||
else:
|
else:
|
||||||
# Python 2
|
# Python 2
|
||||||
basestring = basestring
|
|
||||||
|
|
||||||
def ntob(n, encoding='ISO-8859-1'):
|
def ntob(n, encoding='ISO-8859-1'):
|
||||||
"""Return the given native string as a byte string in the given
|
"""Return the given native string as a byte string in the given
|
||||||
encoding.
|
encoding.
|
||||||
@@ -90,7 +88,7 @@ else:
|
|||||||
|
|
||||||
def assert_native(n):
|
def assert_native(n):
|
||||||
if not isinstance(n, str):
|
if not isinstance(n, str):
|
||||||
raise TypeError("n must be a native str (got %s)" % type(n).__name__)
|
raise TypeError('n must be a native str (got %s)' % type(n).__name__)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
# Python 3.1+
|
# Python 3.1+
|
||||||
@@ -140,16 +138,11 @@ try:
|
|||||||
from urllib.request import parse_http_list, parse_keqv_list
|
from urllib.request import parse_http_list, parse_keqv_list
|
||||||
except ImportError:
|
except ImportError:
|
||||||
# Python 2
|
# Python 2
|
||||||
from urlparse import urljoin
|
from urlparse import urljoin # noqa
|
||||||
from urllib import urlencode, urlopen
|
from urllib import urlencode, urlopen # noqa
|
||||||
from urllib import quote, quote_plus
|
from urllib import quote, quote_plus # noqa
|
||||||
from urllib import unquote
|
from urllib import unquote # noqa
|
||||||
from urllib2 import parse_http_list, parse_keqv_list
|
from urllib2 import parse_http_list, parse_keqv_list # noqa
|
||||||
|
|
||||||
try:
|
|
||||||
from threading import local as threadlocal
|
|
||||||
except ImportError:
|
|
||||||
from cherrypy._cpthreadinglocal import local as threadlocal
|
|
||||||
|
|
||||||
try:
|
try:
|
||||||
dict.iteritems
|
dict.iteritems
|
||||||
@@ -186,7 +179,7 @@ try:
|
|||||||
import builtins
|
import builtins
|
||||||
except ImportError:
|
except ImportError:
|
||||||
# Python 2
|
# Python 2
|
||||||
import __builtin__ as builtins
|
import __builtin__ as builtins # noqa
|
||||||
|
|
||||||
try:
|
try:
|
||||||
# Python 2. We try Python 2 first clients on Python 2
|
# Python 2. We try Python 2 first clients on Python 2
|
||||||
@@ -197,10 +190,10 @@ try:
|
|||||||
from BaseHTTPServer import BaseHTTPRequestHandler
|
from BaseHTTPServer import BaseHTTPRequestHandler
|
||||||
except ImportError:
|
except ImportError:
|
||||||
# Python 3
|
# Python 3
|
||||||
from http.cookies import SimpleCookie, CookieError
|
from http.cookies import SimpleCookie, CookieError # noqa
|
||||||
from http.client import BadStatusLine, HTTPConnection, IncompleteRead
|
from http.client import BadStatusLine, HTTPConnection, IncompleteRead # noqa
|
||||||
from http.client import NotConnected
|
from http.client import NotConnected # noqa
|
||||||
from http.server import BaseHTTPRequestHandler
|
from http.server import BaseHTTPRequestHandler # noqa
|
||||||
|
|
||||||
# Some platforms don't expose HTTPSConnection, so handle it separately
|
# Some platforms don't expose HTTPSConnection, so handle it separately
|
||||||
if six.PY3:
|
if six.PY3:
|
||||||
@@ -222,29 +215,6 @@ except NameError:
|
|||||||
# Python 3
|
# Python 3
|
||||||
xrange = range
|
xrange = range
|
||||||
|
|
||||||
import threading
|
|
||||||
if hasattr(threading.Thread, "daemon"):
|
|
||||||
# Python 2.6+
|
|
||||||
def get_daemon(t):
|
|
||||||
return t.daemon
|
|
||||||
|
|
||||||
def set_daemon(t, val):
|
|
||||||
t.daemon = val
|
|
||||||
else:
|
|
||||||
def get_daemon(t):
|
|
||||||
return t.isDaemon()
|
|
||||||
|
|
||||||
def set_daemon(t, val):
|
|
||||||
t.setDaemon(val)
|
|
||||||
|
|
||||||
try:
|
|
||||||
from email.utils import formatdate
|
|
||||||
|
|
||||||
def HTTPDate(timeval=None):
|
|
||||||
return formatdate(timeval, usegmt=True)
|
|
||||||
except ImportError:
|
|
||||||
from rfc822 import formatdate as HTTPDate
|
|
||||||
|
|
||||||
try:
|
try:
|
||||||
# Python 3
|
# Python 3
|
||||||
from urllib.parse import unquote as parse_unquote
|
from urllib.parse import unquote as parse_unquote
|
||||||
@@ -291,15 +261,14 @@ finally:
|
|||||||
else:
|
else:
|
||||||
json_encode = _json_encode
|
json_encode = _json_encode
|
||||||
|
|
||||||
|
text_or_bytes = six.text_type, six.binary_type
|
||||||
|
|
||||||
try:
|
try:
|
||||||
import cPickle as pickle
|
import cPickle as pickle
|
||||||
except ImportError:
|
except ImportError:
|
||||||
# In Python 2, pickle is a Python version.
|
# In Python 2, pickle is a Python version.
|
||||||
# In Python 3, pickle is the sped-up C version.
|
# In Python 3, pickle is the sped-up C version.
|
||||||
import pickle
|
import pickle # noqa
|
||||||
|
|
||||||
import binascii
|
|
||||||
|
|
||||||
def random20():
|
def random20():
|
||||||
return binascii.hexlify(os.urandom(20)).decode('ascii')
|
return binascii.hexlify(os.urandom(20)).decode('ascii')
|
||||||
@@ -307,7 +276,7 @@ def random20():
|
|||||||
try:
|
try:
|
||||||
from _thread import get_ident as get_thread_ident
|
from _thread import get_ident as get_thread_ident
|
||||||
except ImportError:
|
except ImportError:
|
||||||
from thread import get_ident as get_thread_ident
|
from thread import get_ident as get_thread_ident # noqa
|
||||||
|
|
||||||
try:
|
try:
|
||||||
# Python 3
|
# Python 3
|
||||||
@@ -325,17 +294,41 @@ else:
|
|||||||
Timer = threading._Timer
|
Timer = threading._Timer
|
||||||
Event = threading._Event
|
Event = threading._Event
|
||||||
|
|
||||||
# Prior to Python 2.6, the Thread class did not have a .daemon property.
|
try:
|
||||||
# This mix-in adds that property.
|
# Python 2.7+
|
||||||
|
from subprocess import _args_from_interpreter_flags
|
||||||
|
except ImportError:
|
||||||
|
def _args_from_interpreter_flags():
|
||||||
|
"""Tries to reconstruct original interpreter args from sys.flags for Python 2.6
|
||||||
|
|
||||||
|
Backported from Python 3.5. Aims to return a list of
|
||||||
|
command-line arguments reproducing the current
|
||||||
|
settings in sys.flags and sys.warnoptions.
|
||||||
|
"""
|
||||||
|
flag_opt_map = {
|
||||||
|
'debug': 'd',
|
||||||
|
# 'inspect': 'i',
|
||||||
|
# 'interactive': 'i',
|
||||||
|
'optimize': 'O',
|
||||||
|
'dont_write_bytecode': 'B',
|
||||||
|
'no_user_site': 's',
|
||||||
|
'no_site': 'S',
|
||||||
|
'ignore_environment': 'E',
|
||||||
|
'verbose': 'v',
|
||||||
|
'bytes_warning': 'b',
|
||||||
|
'quiet': 'q',
|
||||||
|
'hash_randomization': 'R',
|
||||||
|
'py3k_warning': '3',
|
||||||
|
}
|
||||||
|
|
||||||
class SetDaemonProperty:
|
args = []
|
||||||
|
for flag, opt in flag_opt_map.items():
|
||||||
|
v = getattr(sys.flags, flag)
|
||||||
|
if v > 0:
|
||||||
|
if flag == 'hash_randomization':
|
||||||
|
v = 1 # Handle specification of an exact seed
|
||||||
|
args.append('-' + opt * v)
|
||||||
|
for opt in sys.warnoptions:
|
||||||
|
args.append('-W' + opt)
|
||||||
|
|
||||||
def __get_daemon(self):
|
return args
|
||||||
return self.isDaemon()
|
|
||||||
|
|
||||||
def __set_daemon(self, daemon):
|
|
||||||
self.setDaemon(daemon)
|
|
||||||
|
|
||||||
if sys.version_info < (2, 6):
|
|
||||||
daemon = property(__get_daemon, __set_daemon)
|
|
||||||
@@ -119,7 +119,7 @@ style) context manager.
|
|||||||
"""
|
"""
|
||||||
|
|
||||||
import cherrypy
|
import cherrypy
|
||||||
from cherrypy._cpcompat import basestring
|
from cherrypy._cpcompat import text_or_bytes
|
||||||
from cherrypy.lib import reprconf
|
from cherrypy.lib import reprconf
|
||||||
|
|
||||||
# Deprecated in CherryPy 3.2--remove in 3.3
|
# Deprecated in CherryPy 3.2--remove in 3.3
|
||||||
@@ -132,16 +132,16 @@ def merge(base, other):
|
|||||||
If the given config is a filename, it will be appended to
|
If the given config is a filename, it will be appended to
|
||||||
the list of files to monitor for "autoreload" changes.
|
the list of files to monitor for "autoreload" changes.
|
||||||
"""
|
"""
|
||||||
if isinstance(other, basestring):
|
if isinstance(other, text_or_bytes):
|
||||||
cherrypy.engine.autoreload.files.add(other)
|
cherrypy.engine.autoreload.files.add(other)
|
||||||
|
|
||||||
# Load other into base
|
# Load other into base
|
||||||
for section, value_map in reprconf.as_dict(other).items():
|
for section, value_map in reprconf.as_dict(other).items():
|
||||||
if not isinstance(value_map, dict):
|
if not isinstance(value_map, dict):
|
||||||
raise ValueError(
|
raise ValueError(
|
||||||
"Application config must include section headers, but the "
|
'Application config must include section headers, but the '
|
||||||
"config you tried to merge doesn't have any sections. "
|
"config you tried to merge doesn't have any sections. "
|
||||||
"Wrap your config in another dict with paths as section "
|
'Wrap your config in another dict with paths as section '
|
||||||
"headers, for example: {'/': config}.")
|
"headers, for example: {'/': config}.")
|
||||||
base.setdefault(section, {}).update(value_map)
|
base.setdefault(section, {}).update(value_map)
|
||||||
|
|
||||||
@@ -152,19 +152,19 @@ class Config(reprconf.Config):
|
|||||||
|
|
||||||
def update(self, config):
|
def update(self, config):
|
||||||
"""Update self from a dict, file or filename."""
|
"""Update self from a dict, file or filename."""
|
||||||
if isinstance(config, basestring):
|
if isinstance(config, text_or_bytes):
|
||||||
# Filename
|
# Filename
|
||||||
cherrypy.engine.autoreload.files.add(config)
|
cherrypy.engine.autoreload.files.add(config)
|
||||||
reprconf.Config.update(self, config)
|
reprconf.Config.update(self, config)
|
||||||
|
|
||||||
def _apply(self, config):
|
def _apply(self, config):
|
||||||
"""Update self from a dict."""
|
"""Update self from a dict."""
|
||||||
if isinstance(config.get("global"), dict):
|
if isinstance(config.get('global'), dict):
|
||||||
if len(config) > 1:
|
if len(config) > 1:
|
||||||
cherrypy.checker.global_config_contained_paths = True
|
cherrypy.checker.global_config_contained_paths = True
|
||||||
config = config["global"]
|
config = config['global']
|
||||||
if 'tools.staticdir.dir' in config:
|
if 'tools.staticdir.dir' in config:
|
||||||
config['tools.staticdir.section'] = "global"
|
config['tools.staticdir.section'] = 'global'
|
||||||
reprconf.Config._apply(self, config)
|
reprconf.Config._apply(self, config)
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
@@ -172,8 +172,8 @@ class Config(reprconf.Config):
|
|||||||
"""Decorator for page handlers to set _cp_config."""
|
"""Decorator for page handlers to set _cp_config."""
|
||||||
if args:
|
if args:
|
||||||
raise TypeError(
|
raise TypeError(
|
||||||
"The cherrypy.config decorator does not accept positional "
|
'The cherrypy.config decorator does not accept positional '
|
||||||
"arguments; you must use keyword arguments.")
|
'arguments; you must use keyword arguments.')
|
||||||
|
|
||||||
def tool_decorator(f):
|
def tool_decorator(f):
|
||||||
_Vars(f).setdefault('_cp_config', {}).update(kwargs)
|
_Vars(f).setdefault('_cp_config', {}).update(kwargs)
|
||||||
@@ -197,14 +197,14 @@ class _Vars(object):
|
|||||||
|
|
||||||
# Sphinx begin config.environments
|
# Sphinx begin config.environments
|
||||||
Config.environments = environments = {
|
Config.environments = environments = {
|
||||||
"staging": {
|
'staging': {
|
||||||
'engine.autoreload.on': False,
|
'engine.autoreload.on': False,
|
||||||
'checker.on': False,
|
'checker.on': False,
|
||||||
'tools.log_headers.on': False,
|
'tools.log_headers.on': False,
|
||||||
'request.show_tracebacks': False,
|
'request.show_tracebacks': False,
|
||||||
'request.show_mismatched_params': False,
|
'request.show_mismatched_params': False,
|
||||||
},
|
},
|
||||||
"production": {
|
'production': {
|
||||||
'engine.autoreload.on': False,
|
'engine.autoreload.on': False,
|
||||||
'checker.on': False,
|
'checker.on': False,
|
||||||
'tools.log_headers.on': False,
|
'tools.log_headers.on': False,
|
||||||
@@ -212,7 +212,7 @@ Config.environments = environments = {
|
|||||||
'request.show_mismatched_params': False,
|
'request.show_mismatched_params': False,
|
||||||
'log.screen': False,
|
'log.screen': False,
|
||||||
},
|
},
|
||||||
"embedded": {
|
'embedded': {
|
||||||
# For use with CherryPy embedded in another deployment stack.
|
# For use with CherryPy embedded in another deployment stack.
|
||||||
'engine.autoreload.on': False,
|
'engine.autoreload.on': False,
|
||||||
'checker.on': False,
|
'checker.on': False,
|
||||||
@@ -223,7 +223,7 @@ Config.environments = environments = {
|
|||||||
'engine.SIGHUP': None,
|
'engine.SIGHUP': None,
|
||||||
'engine.SIGTERM': None,
|
'engine.SIGTERM': None,
|
||||||
},
|
},
|
||||||
"test_suite": {
|
'test_suite': {
|
||||||
'engine.autoreload.on': False,
|
'engine.autoreload.on': False,
|
||||||
'checker.on': False,
|
'checker.on': False,
|
||||||
'tools.log_headers.on': False,
|
'tools.log_headers.on': False,
|
||||||
@@ -237,11 +237,11 @@ Config.environments = environments = {
|
|||||||
|
|
||||||
def _server_namespace_handler(k, v):
|
def _server_namespace_handler(k, v):
|
||||||
"""Config handler for the "server" namespace."""
|
"""Config handler for the "server" namespace."""
|
||||||
atoms = k.split(".", 1)
|
atoms = k.split('.', 1)
|
||||||
if len(atoms) > 1:
|
if len(atoms) > 1:
|
||||||
# Special-case config keys of the form 'server.servername.socket_port'
|
# Special-case config keys of the form 'server.servername.socket_port'
|
||||||
# to configure additional HTTP servers.
|
# to configure additional HTTP servers.
|
||||||
if not hasattr(cherrypy, "servers"):
|
if not hasattr(cherrypy, 'servers'):
|
||||||
cherrypy.servers = {}
|
cherrypy.servers = {}
|
||||||
|
|
||||||
servername, k = atoms
|
servername, k = atoms
|
||||||
@@ -260,45 +260,19 @@ def _server_namespace_handler(k, v):
|
|||||||
setattr(cherrypy.servers[servername], k, v)
|
setattr(cherrypy.servers[servername], k, v)
|
||||||
else:
|
else:
|
||||||
setattr(cherrypy.server, k, v)
|
setattr(cherrypy.server, k, v)
|
||||||
Config.namespaces["server"] = _server_namespace_handler
|
Config.namespaces['server'] = _server_namespace_handler
|
||||||
|
|
||||||
|
|
||||||
def _engine_namespace_handler(k, v):
|
def _engine_namespace_handler(k, v):
|
||||||
"""Backward compatibility handler for the "engine" namespace."""
|
"""Config handler for the "engine" namespace."""
|
||||||
engine = cherrypy.engine
|
engine = cherrypy.engine
|
||||||
|
|
||||||
deprecated = {
|
if k == 'SIGHUP':
|
||||||
'autoreload_on': 'autoreload.on',
|
engine.subscribe('SIGHUP', v)
|
||||||
'autoreload_frequency': 'autoreload.frequency',
|
|
||||||
'autoreload_match': 'autoreload.match',
|
|
||||||
'reload_files': 'autoreload.files',
|
|
||||||
'deadlock_poll_freq': 'timeout_monitor.frequency'
|
|
||||||
}
|
|
||||||
|
|
||||||
if k in deprecated:
|
|
||||||
engine.log(
|
|
||||||
'WARNING: Use of engine.%s is deprecated and will be removed in a '
|
|
||||||
'future version. Use engine.%s instead.' % (k, deprecated[k]))
|
|
||||||
|
|
||||||
if k == 'autoreload_on':
|
|
||||||
if v:
|
|
||||||
engine.autoreload.subscribe()
|
|
||||||
else:
|
|
||||||
engine.autoreload.unsubscribe()
|
|
||||||
elif k == 'autoreload_frequency':
|
|
||||||
engine.autoreload.frequency = v
|
|
||||||
elif k == 'autoreload_match':
|
|
||||||
engine.autoreload.match = v
|
|
||||||
elif k == 'reload_files':
|
|
||||||
engine.autoreload.files = set(v)
|
|
||||||
elif k == 'deadlock_poll_freq':
|
|
||||||
engine.timeout_monitor.frequency = v
|
|
||||||
elif k == 'SIGHUP':
|
|
||||||
engine.listeners['SIGHUP'] = set([v])
|
|
||||||
elif k == 'SIGTERM':
|
elif k == 'SIGTERM':
|
||||||
engine.listeners['SIGTERM'] = set([v])
|
engine.subscribe('SIGTERM', v)
|
||||||
elif "." in k:
|
elif '.' in k:
|
||||||
plugin, attrname = k.split(".", 1)
|
plugin, attrname = k.split('.', 1)
|
||||||
plugin = getattr(engine, plugin)
|
plugin = getattr(engine, plugin)
|
||||||
if attrname == 'on':
|
if attrname == 'on':
|
||||||
if v and hasattr(getattr(plugin, 'subscribe', None), '__call__'):
|
if v and hasattr(getattr(plugin, 'subscribe', None), '__call__'):
|
||||||
@@ -313,7 +287,7 @@ def _engine_namespace_handler(k, v):
|
|||||||
setattr(plugin, attrname, v)
|
setattr(plugin, attrname, v)
|
||||||
else:
|
else:
|
||||||
setattr(engine, k, v)
|
setattr(engine, k, v)
|
||||||
Config.namespaces["engine"] = _engine_namespace_handler
|
Config.namespaces['engine'] = _engine_namespace_handler
|
||||||
|
|
||||||
|
|
||||||
def _tree_namespace_handler(k, v):
|
def _tree_namespace_handler(k, v):
|
||||||
@@ -321,9 +295,9 @@ def _tree_namespace_handler(k, v):
|
|||||||
if isinstance(v, dict):
|
if isinstance(v, dict):
|
||||||
for script_name, app in v.items():
|
for script_name, app in v.items():
|
||||||
cherrypy.tree.graft(app, script_name)
|
cherrypy.tree.graft(app, script_name)
|
||||||
msg = "Mounted: %s on %s" % (app, script_name or "/")
|
msg = 'Mounted: %s on %s' % (app, script_name or '/')
|
||||||
cherrypy.engine.log(msg)
|
cherrypy.engine.log(msg)
|
||||||
else:
|
else:
|
||||||
cherrypy.tree.graft(v, v.script_name)
|
cherrypy.tree.graft(v, v.script_name)
|
||||||
cherrypy.engine.log("Mounted: %s on %s" % (v, v.script_name or "/"))
|
cherrypy.engine.log('Mounted: %s on %s' % (v, v.script_name or '/'))
|
||||||
Config.namespaces["tree"] = _tree_namespace_handler
|
Config.namespaces['tree'] = _tree_namespace_handler
|
||||||
@@ -39,7 +39,7 @@ class PageHandler(object):
|
|||||||
args = property(
|
args = property(
|
||||||
get_args,
|
get_args,
|
||||||
set_args,
|
set_args,
|
||||||
doc="The ordered args should be accessible from post dispatch hooks"
|
doc='The ordered args should be accessible from post dispatch hooks'
|
||||||
)
|
)
|
||||||
|
|
||||||
def get_kwargs(self):
|
def get_kwargs(self):
|
||||||
@@ -52,7 +52,7 @@ class PageHandler(object):
|
|||||||
kwargs = property(
|
kwargs = property(
|
||||||
get_kwargs,
|
get_kwargs,
|
||||||
set_kwargs,
|
set_kwargs,
|
||||||
doc="The named kwargs should be accessible from post dispatch hooks"
|
doc='The named kwargs should be accessible from post dispatch hooks'
|
||||||
)
|
)
|
||||||
|
|
||||||
def __call__(self):
|
def __call__(self):
|
||||||
@@ -153,7 +153,7 @@ def test_callable_spec(callable, callable_args, callable_kwargs):
|
|||||||
# arguments it's definitely a 404.
|
# arguments it's definitely a 404.
|
||||||
message = None
|
message = None
|
||||||
if show_mismatched_params:
|
if show_mismatched_params:
|
||||||
message = "Missing parameters: %s" % ",".join(missing_args)
|
message = 'Missing parameters: %s' % ','.join(missing_args)
|
||||||
raise cherrypy.HTTPError(404, message=message)
|
raise cherrypy.HTTPError(404, message=message)
|
||||||
|
|
||||||
# the extra positional arguments come from the path - 404 Not Found
|
# the extra positional arguments come from the path - 404 Not Found
|
||||||
@@ -175,8 +175,8 @@ def test_callable_spec(callable, callable_args, callable_kwargs):
|
|||||||
|
|
||||||
message = None
|
message = None
|
||||||
if show_mismatched_params:
|
if show_mismatched_params:
|
||||||
message = "Multiple values for parameters: "\
|
message = 'Multiple values for parameters: '\
|
||||||
"%s" % ",".join(multiple_args)
|
'%s' % ','.join(multiple_args)
|
||||||
raise cherrypy.HTTPError(error, message=message)
|
raise cherrypy.HTTPError(error, message=message)
|
||||||
|
|
||||||
if not varkw and varkw_usage > 0:
|
if not varkw and varkw_usage > 0:
|
||||||
@@ -186,8 +186,8 @@ def test_callable_spec(callable, callable_args, callable_kwargs):
|
|||||||
if extra_qs_params:
|
if extra_qs_params:
|
||||||
message = None
|
message = None
|
||||||
if show_mismatched_params:
|
if show_mismatched_params:
|
||||||
message = "Unexpected query string "\
|
message = 'Unexpected query string '\
|
||||||
"parameters: %s" % ", ".join(extra_qs_params)
|
'parameters: %s' % ', '.join(extra_qs_params)
|
||||||
raise cherrypy.HTTPError(404, message=message)
|
raise cherrypy.HTTPError(404, message=message)
|
||||||
|
|
||||||
# If there were any extra body parameters, it's a 400 Not Found
|
# If there were any extra body parameters, it's a 400 Not Found
|
||||||
@@ -195,8 +195,8 @@ def test_callable_spec(callable, callable_args, callable_kwargs):
|
|||||||
if extra_body_params:
|
if extra_body_params:
|
||||||
message = None
|
message = None
|
||||||
if show_mismatched_params:
|
if show_mismatched_params:
|
||||||
message = "Unexpected body parameters: "\
|
message = 'Unexpected body parameters: '\
|
||||||
"%s" % ", ".join(extra_body_params)
|
'%s' % ', '.join(extra_body_params)
|
||||||
raise cherrypy.HTTPError(400, message=message)
|
raise cherrypy.HTTPError(400, message=message)
|
||||||
|
|
||||||
|
|
||||||
@@ -244,14 +244,14 @@ if sys.version_info < (3, 0):
|
|||||||
def validate_translator(t):
|
def validate_translator(t):
|
||||||
if not isinstance(t, str) or len(t) != 256:
|
if not isinstance(t, str) or len(t) != 256:
|
||||||
raise ValueError(
|
raise ValueError(
|
||||||
"The translate argument must be a str of len 256.")
|
'The translate argument must be a str of len 256.')
|
||||||
else:
|
else:
|
||||||
punctuation_to_underscores = str.maketrans(
|
punctuation_to_underscores = str.maketrans(
|
||||||
string.punctuation, '_' * len(string.punctuation))
|
string.punctuation, '_' * len(string.punctuation))
|
||||||
|
|
||||||
def validate_translator(t):
|
def validate_translator(t):
|
||||||
if not isinstance(t, dict):
|
if not isinstance(t, dict):
|
||||||
raise ValueError("The translate argument must be a dict.")
|
raise ValueError('The translate argument must be a dict.')
|
||||||
|
|
||||||
|
|
||||||
class Dispatcher(object):
|
class Dispatcher(object):
|
||||||
@@ -289,7 +289,7 @@ class Dispatcher(object):
|
|||||||
|
|
||||||
if func:
|
if func:
|
||||||
# Decode any leftover %2F in the virtual_path atoms.
|
# Decode any leftover %2F in the virtual_path atoms.
|
||||||
vpath = [x.replace("%2F", "/") for x in vpath]
|
vpath = [x.replace('%2F', '/') for x in vpath]
|
||||||
request.handler = LateParamPageHandler(func, *vpath)
|
request.handler = LateParamPageHandler(func, *vpath)
|
||||||
else:
|
else:
|
||||||
request.handler = cherrypy.NotFound()
|
request.handler = cherrypy.NotFound()
|
||||||
@@ -323,10 +323,10 @@ class Dispatcher(object):
|
|||||||
fullpath_len = len(fullpath)
|
fullpath_len = len(fullpath)
|
||||||
segleft = fullpath_len
|
segleft = fullpath_len
|
||||||
nodeconf = {}
|
nodeconf = {}
|
||||||
if hasattr(root, "_cp_config"):
|
if hasattr(root, '_cp_config'):
|
||||||
nodeconf.update(root._cp_config)
|
nodeconf.update(root._cp_config)
|
||||||
if "/" in app.config:
|
if '/' in app.config:
|
||||||
nodeconf.update(app.config["/"])
|
nodeconf.update(app.config['/'])
|
||||||
object_trail = [['root', root, nodeconf, segleft]]
|
object_trail = [['root', root, nodeconf, segleft]]
|
||||||
|
|
||||||
node = root
|
node = root
|
||||||
@@ -361,9 +361,9 @@ class Dispatcher(object):
|
|||||||
if segleft > pre_len:
|
if segleft > pre_len:
|
||||||
# No path segment was removed. Raise an error.
|
# No path segment was removed. Raise an error.
|
||||||
raise cherrypy.CherryPyException(
|
raise cherrypy.CherryPyException(
|
||||||
"A vpath segment was added. Custom dispatchers may only "
|
'A vpath segment was added. Custom dispatchers may only '
|
||||||
+ "remove elements. While trying to process "
|
+ 'remove elements. While trying to process '
|
||||||
+ "{0} in {1}".format(name, fullpath)
|
+ '{0} in {1}'.format(name, fullpath)
|
||||||
)
|
)
|
||||||
elif segleft == pre_len:
|
elif segleft == pre_len:
|
||||||
# Assume that the handler used the current path segment, but
|
# Assume that the handler used the current path segment, but
|
||||||
@@ -375,7 +375,7 @@ class Dispatcher(object):
|
|||||||
|
|
||||||
if node is not None:
|
if node is not None:
|
||||||
# Get _cp_config attached to this node.
|
# Get _cp_config attached to this node.
|
||||||
if hasattr(node, "_cp_config"):
|
if hasattr(node, '_cp_config'):
|
||||||
nodeconf.update(node._cp_config)
|
nodeconf.update(node._cp_config)
|
||||||
|
|
||||||
# Mix in values from app.config for this path.
|
# Mix in values from app.config for this path.
|
||||||
@@ -414,16 +414,16 @@ class Dispatcher(object):
|
|||||||
continue
|
continue
|
||||||
|
|
||||||
# Try a "default" method on the current leaf.
|
# Try a "default" method on the current leaf.
|
||||||
if hasattr(candidate, "default"):
|
if hasattr(candidate, 'default'):
|
||||||
defhandler = candidate.default
|
defhandler = candidate.default
|
||||||
if getattr(defhandler, 'exposed', False):
|
if getattr(defhandler, 'exposed', False):
|
||||||
# Insert any extra _cp_config from the default handler.
|
# Insert any extra _cp_config from the default handler.
|
||||||
conf = getattr(defhandler, "_cp_config", {})
|
conf = getattr(defhandler, '_cp_config', {})
|
||||||
object_trail.insert(
|
object_trail.insert(
|
||||||
i + 1, ["default", defhandler, conf, segleft])
|
i + 1, ['default', defhandler, conf, segleft])
|
||||||
request.config = set_conf()
|
request.config = set_conf()
|
||||||
# See https://github.com/cherrypy/cherrypy/issues/613
|
# See https://github.com/cherrypy/cherrypy/issues/613
|
||||||
request.is_index = path.endswith("/")
|
request.is_index = path.endswith('/')
|
||||||
return defhandler, fullpath[fullpath_len - segleft:-1]
|
return defhandler, fullpath[fullpath_len - segleft:-1]
|
||||||
|
|
||||||
# Uncomment the next line to restrict positional params to
|
# Uncomment the next line to restrict positional params to
|
||||||
@@ -470,23 +470,23 @@ class MethodDispatcher(Dispatcher):
|
|||||||
if resource:
|
if resource:
|
||||||
# Set Allow header
|
# Set Allow header
|
||||||
avail = [m for m in dir(resource) if m.isupper()]
|
avail = [m for m in dir(resource) if m.isupper()]
|
||||||
if "GET" in avail and "HEAD" not in avail:
|
if 'GET' in avail and 'HEAD' not in avail:
|
||||||
avail.append("HEAD")
|
avail.append('HEAD')
|
||||||
avail.sort()
|
avail.sort()
|
||||||
cherrypy.serving.response.headers['Allow'] = ", ".join(avail)
|
cherrypy.serving.response.headers['Allow'] = ', '.join(avail)
|
||||||
|
|
||||||
# Find the subhandler
|
# Find the subhandler
|
||||||
meth = request.method.upper()
|
meth = request.method.upper()
|
||||||
func = getattr(resource, meth, None)
|
func = getattr(resource, meth, None)
|
||||||
if func is None and meth == "HEAD":
|
if func is None and meth == 'HEAD':
|
||||||
func = getattr(resource, "GET", None)
|
func = getattr(resource, 'GET', None)
|
||||||
if func:
|
if func:
|
||||||
# Grab any _cp_config on the subhandler.
|
# Grab any _cp_config on the subhandler.
|
||||||
if hasattr(func, "_cp_config"):
|
if hasattr(func, '_cp_config'):
|
||||||
request.config.update(func._cp_config)
|
request.config.update(func._cp_config)
|
||||||
|
|
||||||
# Decode any leftover %2F in the virtual_path atoms.
|
# Decode any leftover %2F in the virtual_path atoms.
|
||||||
vpath = [x.replace("%2F", "/") for x in vpath]
|
vpath = [x.replace('%2F', '/') for x in vpath]
|
||||||
request.handler = LateParamPageHandler(func, *vpath)
|
request.handler = LateParamPageHandler(func, *vpath)
|
||||||
else:
|
else:
|
||||||
request.handler = cherrypy.HTTPError(405)
|
request.handler = cherrypy.HTTPError(405)
|
||||||
@@ -554,28 +554,28 @@ class RoutesDispatcher(object):
|
|||||||
|
|
||||||
# Get config for the root object/path.
|
# Get config for the root object/path.
|
||||||
request.config = base = cherrypy.config.copy()
|
request.config = base = cherrypy.config.copy()
|
||||||
curpath = ""
|
curpath = ''
|
||||||
|
|
||||||
def merge(nodeconf):
|
def merge(nodeconf):
|
||||||
if 'tools.staticdir.dir' in nodeconf:
|
if 'tools.staticdir.dir' in nodeconf:
|
||||||
nodeconf['tools.staticdir.section'] = curpath or "/"
|
nodeconf['tools.staticdir.section'] = curpath or '/'
|
||||||
base.update(nodeconf)
|
base.update(nodeconf)
|
||||||
|
|
||||||
app = request.app
|
app = request.app
|
||||||
root = app.root
|
root = app.root
|
||||||
if hasattr(root, "_cp_config"):
|
if hasattr(root, '_cp_config'):
|
||||||
merge(root._cp_config)
|
merge(root._cp_config)
|
||||||
if "/" in app.config:
|
if '/' in app.config:
|
||||||
merge(app.config["/"])
|
merge(app.config['/'])
|
||||||
|
|
||||||
# Mix in values from app.config.
|
# Mix in values from app.config.
|
||||||
atoms = [x for x in path_info.split("/") if x]
|
atoms = [x for x in path_info.split('/') if x]
|
||||||
if atoms:
|
if atoms:
|
||||||
last = atoms.pop()
|
last = atoms.pop()
|
||||||
else:
|
else:
|
||||||
last = None
|
last = None
|
||||||
for atom in atoms:
|
for atom in atoms:
|
||||||
curpath = "/".join((curpath, atom))
|
curpath = '/'.join((curpath, atom))
|
||||||
if curpath in app.config:
|
if curpath in app.config:
|
||||||
merge(app.config[curpath])
|
merge(app.config[curpath])
|
||||||
|
|
||||||
@@ -587,14 +587,14 @@ class RoutesDispatcher(object):
|
|||||||
if isinstance(controller, classtype):
|
if isinstance(controller, classtype):
|
||||||
controller = controller()
|
controller = controller()
|
||||||
# Get config from the controller.
|
# Get config from the controller.
|
||||||
if hasattr(controller, "_cp_config"):
|
if hasattr(controller, '_cp_config'):
|
||||||
merge(controller._cp_config)
|
merge(controller._cp_config)
|
||||||
|
|
||||||
action = result.get('action')
|
action = result.get('action')
|
||||||
if action is not None:
|
if action is not None:
|
||||||
handler = getattr(controller, action, None)
|
handler = getattr(controller, action, None)
|
||||||
# Get config from the handler
|
# Get config from the handler
|
||||||
if hasattr(handler, "_cp_config"):
|
if hasattr(handler, '_cp_config'):
|
||||||
merge(handler._cp_config)
|
merge(handler._cp_config)
|
||||||
else:
|
else:
|
||||||
handler = controller
|
handler = controller
|
||||||
@@ -602,7 +602,7 @@ class RoutesDispatcher(object):
|
|||||||
# Do the last path atom here so it can
|
# Do the last path atom here so it can
|
||||||
# override the controller's _cp_config.
|
# override the controller's _cp_config.
|
||||||
if last:
|
if last:
|
||||||
curpath = "/".join((curpath, last))
|
curpath = '/'.join((curpath, last))
|
||||||
if curpath in app.config:
|
if curpath in app.config:
|
||||||
merge(app.config[curpath])
|
merge(app.config[curpath])
|
||||||
|
|
||||||
@@ -666,9 +666,9 @@ def VirtualHost(next_dispatcher=Dispatcher(), use_x_forwarded_host=True,
|
|||||||
|
|
||||||
domain = header('Host', '')
|
domain = header('Host', '')
|
||||||
if use_x_forwarded_host:
|
if use_x_forwarded_host:
|
||||||
domain = header("X-Forwarded-Host", domain)
|
domain = header('X-Forwarded-Host', domain)
|
||||||
|
|
||||||
prefix = domains.get(domain, "")
|
prefix = domains.get(domain, '')
|
||||||
if prefix:
|
if prefix:
|
||||||
path_info = httputil.urljoin(prefix, path_info)
|
path_info = httputil.urljoin(prefix, path_info)
|
||||||
|
|
||||||
|
|||||||
@@ -115,13 +115,15 @@ Note that you have to explicitly set
|
|||||||
and not simply return an error message as a result.
|
and not simply return an error message as a result.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
import contextlib
|
||||||
from cgi import escape as _escape
|
from cgi import escape as _escape
|
||||||
from sys import exc_info as _exc_info
|
from sys import exc_info as _exc_info
|
||||||
from traceback import format_exception as _format_exception
|
from traceback import format_exception as _format_exception
|
||||||
|
from xml.sax import saxutils
|
||||||
|
|
||||||
import six
|
import six
|
||||||
|
|
||||||
from cherrypy._cpcompat import basestring, iteritems, ntob
|
from cherrypy._cpcompat import text_or_bytes, iteritems, ntob
|
||||||
from cherrypy._cpcompat import tonative, urljoin as _urljoin
|
from cherrypy._cpcompat import tonative, urljoin as _urljoin
|
||||||
from cherrypy.lib import httputil as _httputil
|
from cherrypy.lib import httputil as _httputil
|
||||||
|
|
||||||
@@ -148,14 +150,14 @@ class InternalRedirect(CherryPyException):
|
|||||||
URL.
|
URL.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self, path, query_string=""):
|
def __init__(self, path, query_string=''):
|
||||||
import cherrypy
|
import cherrypy
|
||||||
self.request = cherrypy.serving.request
|
self.request = cherrypy.serving.request
|
||||||
|
|
||||||
self.query_string = query_string
|
self.query_string = query_string
|
||||||
if "?" in path:
|
if '?' in path:
|
||||||
# Separate any params included in the path
|
# Separate any params included in the path
|
||||||
path, self.query_string = path.split("?", 1)
|
path, self.query_string = path.split('?', 1)
|
||||||
|
|
||||||
# Note that urljoin will "do the right thing" whether url is:
|
# Note that urljoin will "do the right thing" whether url is:
|
||||||
# 1. a URL relative to root (e.g. "/dummy")
|
# 1. a URL relative to root (e.g. "/dummy")
|
||||||
@@ -209,21 +211,10 @@ class HTTPRedirect(CherryPyException):
|
|||||||
import cherrypy
|
import cherrypy
|
||||||
request = cherrypy.serving.request
|
request = cherrypy.serving.request
|
||||||
|
|
||||||
if isinstance(urls, basestring):
|
if isinstance(urls, text_or_bytes):
|
||||||
urls = [urls]
|
urls = [urls]
|
||||||
|
|
||||||
abs_urls = []
|
self.urls = [tonative(url, encoding or self.encoding) for url in urls]
|
||||||
for url in urls:
|
|
||||||
url = tonative(url, encoding or self.encoding)
|
|
||||||
|
|
||||||
# Note that urljoin will "do the right thing" whether url is:
|
|
||||||
# 1. a complete URL with host (e.g. "http://www.example.com/test")
|
|
||||||
# 2. a URL relative to root (e.g. "/dummy")
|
|
||||||
# 3. a URL relative to the current path
|
|
||||||
# Note that any query string in cherrypy.request is discarded.
|
|
||||||
url = _urljoin(cherrypy.url(), url)
|
|
||||||
abs_urls.append(url)
|
|
||||||
self.urls = abs_urls
|
|
||||||
|
|
||||||
# RFC 2616 indicates a 301 response code fits our goal; however,
|
# RFC 2616 indicates a 301 response code fits our goal; however,
|
||||||
# browser support for 301 is quite messy. Do 302/303 instead. See
|
# browser support for 301 is quite messy. Do 302/303 instead. See
|
||||||
@@ -236,10 +227,10 @@ class HTTPRedirect(CherryPyException):
|
|||||||
else:
|
else:
|
||||||
status = int(status)
|
status = int(status)
|
||||||
if status < 300 or status > 399:
|
if status < 300 or status > 399:
|
||||||
raise ValueError("status must be between 300 and 399.")
|
raise ValueError('status must be between 300 and 399.')
|
||||||
|
|
||||||
self.status = status
|
self.status = status
|
||||||
CherryPyException.__init__(self, abs_urls, status)
|
CherryPyException.__init__(self, self.urls, status)
|
||||||
|
|
||||||
def set_response(self):
|
def set_response(self):
|
||||||
"""Modify cherrypy.response status, headers, and body to represent
|
"""Modify cherrypy.response status, headers, and body to represent
|
||||||
@@ -253,7 +244,7 @@ class HTTPRedirect(CherryPyException):
|
|||||||
response.status = status = self.status
|
response.status = status = self.status
|
||||||
|
|
||||||
if status in (300, 301, 302, 303, 307):
|
if status in (300, 301, 302, 303, 307):
|
||||||
response.headers['Content-Type'] = "text/html;charset=utf-8"
|
response.headers['Content-Type'] = 'text/html;charset=utf-8'
|
||||||
# "The ... URI SHOULD be given by the Location field
|
# "The ... URI SHOULD be given by the Location field
|
||||||
# in the response."
|
# in the response."
|
||||||
response.headers['Location'] = self.urls[0]
|
response.headers['Location'] = self.urls[0]
|
||||||
@@ -262,16 +253,15 @@ class HTTPRedirect(CherryPyException):
|
|||||||
# SHOULD contain a short hypertext note with a hyperlink to the
|
# SHOULD contain a short hypertext note with a hyperlink to the
|
||||||
# new URI(s)."
|
# new URI(s)."
|
||||||
msg = {
|
msg = {
|
||||||
300: "This resource can be found at ",
|
300: 'This resource can be found at ',
|
||||||
301: "This resource has permanently moved to ",
|
301: 'This resource has permanently moved to ',
|
||||||
302: "This resource resides temporarily at ",
|
302: 'This resource resides temporarily at ',
|
||||||
303: "This resource can be found at ",
|
303: 'This resource can be found at ',
|
||||||
307: "This resource has moved temporarily to ",
|
307: 'This resource has moved temporarily to ',
|
||||||
}[status]
|
}[status]
|
||||||
msg += '<a href=%s>%s</a>.'
|
msg += '<a href=%s>%s</a>.'
|
||||||
from xml.sax import saxutils
|
|
||||||
msgs = [msg % (saxutils.quoteattr(u), u) for u in self.urls]
|
msgs = [msg % (saxutils.quoteattr(u), u) for u in self.urls]
|
||||||
response.body = ntob("<br />\n".join(msgs), 'utf-8')
|
response.body = ntob('<br />\n'.join(msgs), 'utf-8')
|
||||||
# Previous code may have set C-L, so we have to reset it
|
# Previous code may have set C-L, so we have to reset it
|
||||||
# (allow finalize to set it).
|
# (allow finalize to set it).
|
||||||
response.headers.pop('Content-Length', None)
|
response.headers.pop('Content-Length', None)
|
||||||
@@ -301,7 +291,7 @@ class HTTPRedirect(CherryPyException):
|
|||||||
# Previous code may have set C-L, so we have to reset it.
|
# Previous code may have set C-L, so we have to reset it.
|
||||||
response.headers.pop('Content-Length', None)
|
response.headers.pop('Content-Length', None)
|
||||||
else:
|
else:
|
||||||
raise ValueError("The %s status code is unknown." % status)
|
raise ValueError('The %s status code is unknown.' % status)
|
||||||
|
|
||||||
def __call__(self):
|
def __call__(self):
|
||||||
"""Use this exception as a request.handler (raise self)."""
|
"""Use this exception as a request.handler (raise self)."""
|
||||||
@@ -317,9 +307,9 @@ def clean_headers(status):
|
|||||||
# Remove headers which applied to the original content,
|
# Remove headers which applied to the original content,
|
||||||
# but do not apply to the error page.
|
# but do not apply to the error page.
|
||||||
respheaders = response.headers
|
respheaders = response.headers
|
||||||
for key in ["Accept-Ranges", "Age", "ETag", "Location", "Retry-After",
|
for key in ['Accept-Ranges', 'Age', 'ETag', 'Location', 'Retry-After',
|
||||||
"Vary", "Content-Encoding", "Content-Length", "Expires",
|
'Vary', 'Content-Encoding', 'Content-Length', 'Expires',
|
||||||
"Content-Location", "Content-MD5", "Last-Modified"]:
|
'Content-Location', 'Content-MD5', 'Last-Modified']:
|
||||||
if key in respheaders:
|
if key in respheaders:
|
||||||
del respheaders[key]
|
del respheaders[key]
|
||||||
|
|
||||||
@@ -330,8 +320,8 @@ def clean_headers(status):
|
|||||||
# specifies the current length of the selected resource.
|
# specifies the current length of the selected resource.
|
||||||
# A response with status code 206 (Partial Content) MUST NOT
|
# A response with status code 206 (Partial Content) MUST NOT
|
||||||
# include a Content-Range field with a byte-range- resp-spec of "*".
|
# include a Content-Range field with a byte-range- resp-spec of "*".
|
||||||
if "Content-Range" in respheaders:
|
if 'Content-Range' in respheaders:
|
||||||
del respheaders["Content-Range"]
|
del respheaders['Content-Range']
|
||||||
|
|
||||||
|
|
||||||
class HTTPError(CherryPyException):
|
class HTTPError(CherryPyException):
|
||||||
@@ -371,7 +361,7 @@ class HTTPError(CherryPyException):
|
|||||||
raise self.__class__(500, _exc_info()[1].args[0])
|
raise self.__class__(500, _exc_info()[1].args[0])
|
||||||
|
|
||||||
if self.code < 400 or self.code > 599:
|
if self.code < 400 or self.code > 599:
|
||||||
raise ValueError("status must be between 400 and 599.")
|
raise ValueError('status must be between 400 and 599.')
|
||||||
|
|
||||||
# See http://www.python.org/dev/peps/pep-0352/
|
# See http://www.python.org/dev/peps/pep-0352/
|
||||||
# self.message = message
|
# self.message = message
|
||||||
@@ -413,6 +403,15 @@ class HTTPError(CherryPyException):
|
|||||||
"""Use this exception as a request.handler (raise self)."""
|
"""Use this exception as a request.handler (raise self)."""
|
||||||
raise self
|
raise self
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
@contextlib.contextmanager
|
||||||
|
def handle(cls, exception, status=500, message=''):
|
||||||
|
"""Translate exception into an HTTPError."""
|
||||||
|
try:
|
||||||
|
yield
|
||||||
|
except exception as exc:
|
||||||
|
raise cls(status, message or str(exc))
|
||||||
|
|
||||||
|
|
||||||
class NotFound(HTTPError):
|
class NotFound(HTTPError):
|
||||||
|
|
||||||
@@ -480,7 +479,7 @@ def get_error_page(status, **kwargs):
|
|||||||
# We can't use setdefault here, because some
|
# We can't use setdefault here, because some
|
||||||
# callers send None for kwarg values.
|
# callers send None for kwarg values.
|
||||||
if kwargs.get('status') is None:
|
if kwargs.get('status') is None:
|
||||||
kwargs['status'] = "%s %s" % (code, reason)
|
kwargs['status'] = '%s %s' % (code, reason)
|
||||||
if kwargs.get('message') is None:
|
if kwargs.get('message') is None:
|
||||||
kwargs['message'] = message
|
kwargs['message'] = message
|
||||||
if kwargs.get('traceback') is None:
|
if kwargs.get('traceback') is None:
|
||||||
@@ -490,7 +489,7 @@ def get_error_page(status, **kwargs):
|
|||||||
|
|
||||||
for k, v in iteritems(kwargs):
|
for k, v in iteritems(kwargs):
|
||||||
if v is None:
|
if v is None:
|
||||||
kwargs[k] = ""
|
kwargs[k] = ''
|
||||||
else:
|
else:
|
||||||
kwargs[k] = _escape(kwargs[k])
|
kwargs[k] = _escape(kwargs[k])
|
||||||
|
|
||||||
@@ -528,12 +527,12 @@ def get_error_page(status, **kwargs):
|
|||||||
e = _format_exception(*_exc_info())[-1]
|
e = _format_exception(*_exc_info())[-1]
|
||||||
m = kwargs['message']
|
m = kwargs['message']
|
||||||
if m:
|
if m:
|
||||||
m += "<br />"
|
m += '<br />'
|
||||||
m += "In addition, the custom error page failed:\n<br />%s" % e
|
m += 'In addition, the custom error page failed:\n<br />%s' % e
|
||||||
kwargs['message'] = m
|
kwargs['message'] = m
|
||||||
|
|
||||||
response = cherrypy.serving.response
|
response = cherrypy.serving.response
|
||||||
response.headers['Content-Type'] = "text/html;charset=utf-8"
|
response.headers['Content-Type'] = 'text/html;charset=utf-8'
|
||||||
result = template % kwargs
|
result = template % kwargs
|
||||||
return result.encode('utf-8')
|
return result.encode('utf-8')
|
||||||
|
|
||||||
@@ -565,7 +564,7 @@ def _be_ie_unfriendly(status):
|
|||||||
if l and l < s:
|
if l and l < s:
|
||||||
# IN ADDITION: the response must be written to IE
|
# IN ADDITION: the response must be written to IE
|
||||||
# in one chunk or it will still get replaced! Bah.
|
# in one chunk or it will still get replaced! Bah.
|
||||||
content = content + (ntob(" ") * (s - l))
|
content = content + (ntob(' ') * (s - l))
|
||||||
response.body = content
|
response.body = content
|
||||||
response.headers['Content-Length'] = str(len(content))
|
response.headers['Content-Length'] = str(len(content))
|
||||||
|
|
||||||
@@ -576,9 +575,9 @@ def format_exc(exc=None):
|
|||||||
if exc is None:
|
if exc is None:
|
||||||
exc = _exc_info()
|
exc = _exc_info()
|
||||||
if exc == (None, None, None):
|
if exc == (None, None, None):
|
||||||
return ""
|
return ''
|
||||||
import traceback
|
import traceback
|
||||||
return "".join(traceback.format_exception(*exc))
|
return ''.join(traceback.format_exception(*exc))
|
||||||
finally:
|
finally:
|
||||||
del exc
|
del exc
|
||||||
|
|
||||||
@@ -600,13 +599,13 @@ def bare_error(extrabody=None):
|
|||||||
# it cannot be allowed to fail. Therefore, don't add to it!
|
# it cannot be allowed to fail. Therefore, don't add to it!
|
||||||
# In particular, don't call any other CP functions.
|
# In particular, don't call any other CP functions.
|
||||||
|
|
||||||
body = ntob("Unrecoverable error in the server.")
|
body = ntob('Unrecoverable error in the server.')
|
||||||
if extrabody is not None:
|
if extrabody is not None:
|
||||||
if not isinstance(extrabody, bytes):
|
if not isinstance(extrabody, bytes):
|
||||||
extrabody = extrabody.encode('utf-8')
|
extrabody = extrabody.encode('utf-8')
|
||||||
body += ntob("\n") + extrabody
|
body += ntob('\n') + extrabody
|
||||||
|
|
||||||
return (ntob("500 Internal Server Error"),
|
return (ntob('500 Internal Server Error'),
|
||||||
[(ntob('Content-Type'), ntob('text/plain')),
|
[(ntob('Content-Type'), ntob('text/plain')),
|
||||||
(ntob('Content-Length'), ntob(str(len(body)), 'ISO-8859-1'))],
|
(ntob('Content-Length'), ntob(str(len(body)), 'ISO-8859-1'))],
|
||||||
[body])
|
[body])
|
||||||
@@ -109,9 +109,6 @@ the "log.error_file" config entry, for example).
|
|||||||
|
|
||||||
import datetime
|
import datetime
|
||||||
import logging
|
import logging
|
||||||
# Silence the no-handlers "warning" (stderr write!) in stdlib logging
|
|
||||||
logging.Logger.manager.emittedNoHandlerWarning = 1
|
|
||||||
logfmt = logging.Formatter("%(message)s")
|
|
||||||
import os
|
import os
|
||||||
import sys
|
import sys
|
||||||
|
|
||||||
@@ -122,6 +119,11 @@ from cherrypy import _cperror
|
|||||||
from cherrypy._cpcompat import ntob
|
from cherrypy._cpcompat import ntob
|
||||||
|
|
||||||
|
|
||||||
|
# Silence the no-handlers "warning" (stderr write!) in stdlib logging
|
||||||
|
logging.Logger.manager.emittedNoHandlerWarning = 1
|
||||||
|
logfmt = logging.Formatter('%(message)s')
|
||||||
|
|
||||||
|
|
||||||
class NullHandler(logging.Handler):
|
class NullHandler(logging.Handler):
|
||||||
|
|
||||||
"""A no-op logging handler to silence the logging.lastResort handler."""
|
"""A no-op logging handler to silence the logging.lastResort handler."""
|
||||||
@@ -170,17 +172,17 @@ class LogManager(object):
|
|||||||
cherrypy.access.<appid>
|
cherrypy.access.<appid>
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self, appid=None, logger_root="cherrypy"):
|
def __init__(self, appid=None, logger_root='cherrypy'):
|
||||||
self.logger_root = logger_root
|
self.logger_root = logger_root
|
||||||
self.appid = appid
|
self.appid = appid
|
||||||
if appid is None:
|
if appid is None:
|
||||||
self.error_log = logging.getLogger("%s.error" % logger_root)
|
self.error_log = logging.getLogger('%s.error' % logger_root)
|
||||||
self.access_log = logging.getLogger("%s.access" % logger_root)
|
self.access_log = logging.getLogger('%s.access' % logger_root)
|
||||||
else:
|
else:
|
||||||
self.error_log = logging.getLogger(
|
self.error_log = logging.getLogger(
|
||||||
"%s.error.%s" % (logger_root, appid))
|
'%s.error.%s' % (logger_root, appid))
|
||||||
self.access_log = logging.getLogger(
|
self.access_log = logging.getLogger(
|
||||||
"%s.access.%s" % (logger_root, appid))
|
'%s.access.%s' % (logger_root, appid))
|
||||||
self.error_log.setLevel(logging.INFO)
|
self.error_log.setLevel(logging.INFO)
|
||||||
self.access_log.setLevel(logging.INFO)
|
self.access_log.setLevel(logging.INFO)
|
||||||
|
|
||||||
@@ -244,19 +246,19 @@ class LogManager(object):
|
|||||||
outheaders = response.headers
|
outheaders = response.headers
|
||||||
inheaders = request.headers
|
inheaders = request.headers
|
||||||
if response.output_status is None:
|
if response.output_status is None:
|
||||||
status = "-"
|
status = '-'
|
||||||
else:
|
else:
|
||||||
status = response.output_status.split(ntob(" "), 1)[0]
|
status = response.output_status.split(ntob(' '), 1)[0]
|
||||||
if six.PY3:
|
if six.PY3:
|
||||||
status = status.decode('ISO-8859-1')
|
status = status.decode('ISO-8859-1')
|
||||||
|
|
||||||
atoms = {'h': remote.name or remote.ip,
|
atoms = {'h': remote.name or remote.ip,
|
||||||
'l': '-',
|
'l': '-',
|
||||||
'u': getattr(request, "login", None) or "-",
|
'u': getattr(request, 'login', None) or '-',
|
||||||
't': self.time(),
|
't': self.time(),
|
||||||
'r': request.request_line,
|
'r': request.request_line,
|
||||||
's': status,
|
's': status,
|
||||||
'b': dict.get(outheaders, 'Content-Length', '') or "-",
|
'b': dict.get(outheaders, 'Content-Length', '') or '-',
|
||||||
'f': dict.get(inheaders, 'Referer', ''),
|
'f': dict.get(inheaders, 'Referer', ''),
|
||||||
'a': dict.get(inheaders, 'User-Agent', ''),
|
'a': dict.get(inheaders, 'User-Agent', ''),
|
||||||
'o': dict.get(inheaders, 'Host', '-'),
|
'o': dict.get(inheaders, 'Host', '-'),
|
||||||
@@ -312,26 +314,26 @@ class LogManager(object):
|
|||||||
|
|
||||||
def _get_builtin_handler(self, log, key):
|
def _get_builtin_handler(self, log, key):
|
||||||
for h in log.handlers:
|
for h in log.handlers:
|
||||||
if getattr(h, "_cpbuiltin", None) == key:
|
if getattr(h, '_cpbuiltin', None) == key:
|
||||||
return h
|
return h
|
||||||
|
|
||||||
# ------------------------- Screen handlers ------------------------- #
|
# ------------------------- Screen handlers ------------------------- #
|
||||||
def _set_screen_handler(self, log, enable, stream=None):
|
def _set_screen_handler(self, log, enable, stream=None):
|
||||||
h = self._get_builtin_handler(log, "screen")
|
h = self._get_builtin_handler(log, 'screen')
|
||||||
if enable:
|
if enable:
|
||||||
if not h:
|
if not h:
|
||||||
if stream is None:
|
if stream is None:
|
||||||
stream = sys.stderr
|
stream = sys.stderr
|
||||||
h = logging.StreamHandler(stream)
|
h = logging.StreamHandler(stream)
|
||||||
h.setFormatter(logfmt)
|
h.setFormatter(logfmt)
|
||||||
h._cpbuiltin = "screen"
|
h._cpbuiltin = 'screen'
|
||||||
log.addHandler(h)
|
log.addHandler(h)
|
||||||
elif h:
|
elif h:
|
||||||
log.handlers.remove(h)
|
log.handlers.remove(h)
|
||||||
|
|
||||||
def _get_screen(self):
|
def _get_screen(self):
|
||||||
h = self._get_builtin_handler
|
h = self._get_builtin_handler
|
||||||
has_h = h(self.error_log, "screen") or h(self.access_log, "screen")
|
has_h = h(self.error_log, 'screen') or h(self.access_log, 'screen')
|
||||||
return bool(has_h)
|
return bool(has_h)
|
||||||
|
|
||||||
def _set_screen(self, newvalue):
|
def _set_screen(self, newvalue):
|
||||||
@@ -349,11 +351,11 @@ class LogManager(object):
|
|||||||
def _add_builtin_file_handler(self, log, fname):
|
def _add_builtin_file_handler(self, log, fname):
|
||||||
h = logging.FileHandler(fname)
|
h = logging.FileHandler(fname)
|
||||||
h.setFormatter(logfmt)
|
h.setFormatter(logfmt)
|
||||||
h._cpbuiltin = "file"
|
h._cpbuiltin = 'file'
|
||||||
log.addHandler(h)
|
log.addHandler(h)
|
||||||
|
|
||||||
def _set_file_handler(self, log, filename):
|
def _set_file_handler(self, log, filename):
|
||||||
h = self._get_builtin_handler(log, "file")
|
h = self._get_builtin_handler(log, 'file')
|
||||||
if filename:
|
if filename:
|
||||||
if h:
|
if h:
|
||||||
if h.baseFilename != os.path.abspath(filename):
|
if h.baseFilename != os.path.abspath(filename):
|
||||||
@@ -368,7 +370,7 @@ class LogManager(object):
|
|||||||
log.handlers.remove(h)
|
log.handlers.remove(h)
|
||||||
|
|
||||||
def _get_error_file(self):
|
def _get_error_file(self):
|
||||||
h = self._get_builtin_handler(self.error_log, "file")
|
h = self._get_builtin_handler(self.error_log, 'file')
|
||||||
if h:
|
if h:
|
||||||
return h.baseFilename
|
return h.baseFilename
|
||||||
return ''
|
return ''
|
||||||
@@ -383,7 +385,7 @@ class LogManager(object):
|
|||||||
""")
|
""")
|
||||||
|
|
||||||
def _get_access_file(self):
|
def _get_access_file(self):
|
||||||
h = self._get_builtin_handler(self.access_log, "file")
|
h = self._get_builtin_handler(self.access_log, 'file')
|
||||||
if h:
|
if h:
|
||||||
return h.baseFilename
|
return h.baseFilename
|
||||||
return ''
|
return ''
|
||||||
@@ -400,18 +402,18 @@ class LogManager(object):
|
|||||||
# ------------------------- WSGI handlers ------------------------- #
|
# ------------------------- WSGI handlers ------------------------- #
|
||||||
|
|
||||||
def _set_wsgi_handler(self, log, enable):
|
def _set_wsgi_handler(self, log, enable):
|
||||||
h = self._get_builtin_handler(log, "wsgi")
|
h = self._get_builtin_handler(log, 'wsgi')
|
||||||
if enable:
|
if enable:
|
||||||
if not h:
|
if not h:
|
||||||
h = WSGIErrorHandler()
|
h = WSGIErrorHandler()
|
||||||
h.setFormatter(logfmt)
|
h.setFormatter(logfmt)
|
||||||
h._cpbuiltin = "wsgi"
|
h._cpbuiltin = 'wsgi'
|
||||||
log.addHandler(h)
|
log.addHandler(h)
|
||||||
elif h:
|
elif h:
|
||||||
log.handlers.remove(h)
|
log.handlers.remove(h)
|
||||||
|
|
||||||
def _get_wsgi(self):
|
def _get_wsgi(self):
|
||||||
return bool(self._get_builtin_handler(self.error_log, "wsgi"))
|
return bool(self._get_builtin_handler(self.error_log, 'wsgi'))
|
||||||
|
|
||||||
def _set_wsgi(self, newvalue):
|
def _set_wsgi(self, newvalue):
|
||||||
self._set_wsgi_handler(self.error_log, newvalue)
|
self._set_wsgi_handler(self.error_log, newvalue)
|
||||||
@@ -447,16 +449,16 @@ class WSGIErrorHandler(logging.Handler):
|
|||||||
else:
|
else:
|
||||||
try:
|
try:
|
||||||
msg = self.format(record)
|
msg = self.format(record)
|
||||||
fs = "%s\n"
|
fs = '%s\n'
|
||||||
import types
|
import types
|
||||||
# if no unicode support...
|
# if no unicode support...
|
||||||
if not hasattr(types, "UnicodeType"):
|
if not hasattr(types, 'UnicodeType'):
|
||||||
stream.write(fs % msg)
|
stream.write(fs % msg)
|
||||||
else:
|
else:
|
||||||
try:
|
try:
|
||||||
stream.write(fs % msg)
|
stream.write(fs % msg)
|
||||||
except UnicodeError:
|
except UnicodeError:
|
||||||
stream.write(fs % msg.encode("UTF-8"))
|
stream.write(fs % msg.encode('UTF-8'))
|
||||||
self.flush()
|
self.flush()
|
||||||
except:
|
except:
|
||||||
self.handleError(record)
|
self.handleError(record)
|
||||||
@@ -55,9 +55,11 @@ resides in the global site-package this won't be needed.
|
|||||||
Then restart apache2 and access http://127.0.0.1:8080
|
Then restart apache2 and access http://127.0.0.1:8080
|
||||||
"""
|
"""
|
||||||
|
|
||||||
import logging
|
|
||||||
import sys
|
|
||||||
import io
|
import io
|
||||||
|
import logging
|
||||||
|
import os
|
||||||
|
import re
|
||||||
|
import sys
|
||||||
|
|
||||||
import cherrypy
|
import cherrypy
|
||||||
from cherrypy._cpcompat import copyitems, ntob
|
from cherrypy._cpcompat import copyitems, ntob
|
||||||
@@ -86,14 +88,14 @@ def setup(req):
|
|||||||
func()
|
func()
|
||||||
|
|
||||||
cherrypy.config.update({'log.screen': False,
|
cherrypy.config.update({'log.screen': False,
|
||||||
"tools.ignore_headers.on": True,
|
'tools.ignore_headers.on': True,
|
||||||
"tools.ignore_headers.headers": ['Range'],
|
'tools.ignore_headers.headers': ['Range'],
|
||||||
})
|
})
|
||||||
|
|
||||||
engine = cherrypy.engine
|
engine = cherrypy.engine
|
||||||
if hasattr(engine, "signal_handler"):
|
if hasattr(engine, 'signal_handler'):
|
||||||
engine.signal_handler.unsubscribe()
|
engine.signal_handler.unsubscribe()
|
||||||
if hasattr(engine, "console_control_handler"):
|
if hasattr(engine, 'console_control_handler'):
|
||||||
engine.console_control_handler.unsubscribe()
|
engine.console_control_handler.unsubscribe()
|
||||||
engine.autoreload.unsubscribe()
|
engine.autoreload.unsubscribe()
|
||||||
cherrypy.server.unsubscribe()
|
cherrypy.server.unsubscribe()
|
||||||
@@ -147,10 +149,10 @@ def handler(req):
|
|||||||
# Obtain a Request object from CherryPy
|
# Obtain a Request object from CherryPy
|
||||||
local = req.connection.local_addr
|
local = req.connection.local_addr
|
||||||
local = httputil.Host(
|
local = httputil.Host(
|
||||||
local[0], local[1], req.connection.local_host or "")
|
local[0], local[1], req.connection.local_host or '')
|
||||||
remote = req.connection.remote_addr
|
remote = req.connection.remote_addr
|
||||||
remote = httputil.Host(
|
remote = httputil.Host(
|
||||||
remote[0], remote[1], req.connection.remote_host or "")
|
remote[0], remote[1], req.connection.remote_host or '')
|
||||||
|
|
||||||
scheme = req.parsed_uri[0] or 'http'
|
scheme = req.parsed_uri[0] or 'http'
|
||||||
req.get_basic_auth_pw()
|
req.get_basic_auth_pw()
|
||||||
@@ -163,7 +165,7 @@ def handler(req):
|
|||||||
except AttributeError:
|
except AttributeError:
|
||||||
bad_value = ("You must provide a PythonOption '%s', "
|
bad_value = ("You must provide a PythonOption '%s', "
|
||||||
"either 'on' or 'off', when running a version "
|
"either 'on' or 'off', when running a version "
|
||||||
"of mod_python < 3.1")
|
'of mod_python < 3.1')
|
||||||
|
|
||||||
threaded = options.get('multithread', '').lower()
|
threaded = options.get('multithread', '').lower()
|
||||||
if threaded == 'on':
|
if threaded == 'on':
|
||||||
@@ -171,7 +173,7 @@ def handler(req):
|
|||||||
elif threaded == 'off':
|
elif threaded == 'off':
|
||||||
threaded = False
|
threaded = False
|
||||||
else:
|
else:
|
||||||
raise ValueError(bad_value % "multithread")
|
raise ValueError(bad_value % 'multithread')
|
||||||
|
|
||||||
forked = options.get('multiprocess', '').lower()
|
forked = options.get('multiprocess', '').lower()
|
||||||
if forked == 'on':
|
if forked == 'on':
|
||||||
@@ -179,16 +181,16 @@ def handler(req):
|
|||||||
elif forked == 'off':
|
elif forked == 'off':
|
||||||
forked = False
|
forked = False
|
||||||
else:
|
else:
|
||||||
raise ValueError(bad_value % "multiprocess")
|
raise ValueError(bad_value % 'multiprocess')
|
||||||
|
|
||||||
sn = cherrypy.tree.script_name(req.uri or "/")
|
sn = cherrypy.tree.script_name(req.uri or '/')
|
||||||
if sn is None:
|
if sn is None:
|
||||||
send_response(req, '404 Not Found', [], '')
|
send_response(req, '404 Not Found', [], '')
|
||||||
else:
|
else:
|
||||||
app = cherrypy.tree.apps[sn]
|
app = cherrypy.tree.apps[sn]
|
||||||
method = req.method
|
method = req.method
|
||||||
path = req.uri
|
path = req.uri
|
||||||
qs = req.args or ""
|
qs = req.args or ''
|
||||||
reqproto = req.protocol
|
reqproto = req.protocol
|
||||||
headers = copyitems(req.headers_in)
|
headers = copyitems(req.headers_in)
|
||||||
rfile = _ReadOnlyRequest(req)
|
rfile = _ReadOnlyRequest(req)
|
||||||
@@ -198,7 +200,7 @@ def handler(req):
|
|||||||
redirections = []
|
redirections = []
|
||||||
while True:
|
while True:
|
||||||
request, response = app.get_serving(local, remote, scheme,
|
request, response = app.get_serving(local, remote, scheme,
|
||||||
"HTTP/1.1")
|
'HTTP/1.1')
|
||||||
request.login = req.user
|
request.login = req.user
|
||||||
request.multithread = bool(threaded)
|
request.multithread = bool(threaded)
|
||||||
request.multiprocess = bool(forked)
|
request.multiprocess = bool(forked)
|
||||||
@@ -217,17 +219,17 @@ def handler(req):
|
|||||||
if not recursive:
|
if not recursive:
|
||||||
if ir.path in redirections:
|
if ir.path in redirections:
|
||||||
raise RuntimeError(
|
raise RuntimeError(
|
||||||
"InternalRedirector visited the same URL "
|
'InternalRedirector visited the same URL '
|
||||||
"twice: %r" % ir.path)
|
'twice: %r' % ir.path)
|
||||||
else:
|
else:
|
||||||
# Add the *previous* path_info + qs to
|
# Add the *previous* path_info + qs to
|
||||||
# redirections.
|
# redirections.
|
||||||
if qs:
|
if qs:
|
||||||
qs = "?" + qs
|
qs = '?' + qs
|
||||||
redirections.append(sn + path + qs)
|
redirections.append(sn + path + qs)
|
||||||
|
|
||||||
# Munge environment and try again.
|
# Munge environment and try again.
|
||||||
method = "GET"
|
method = 'GET'
|
||||||
path = ir.path
|
path = ir.path
|
||||||
qs = ir.query_string
|
qs = ir.query_string
|
||||||
rfile = io.BytesIO()
|
rfile = io.BytesIO()
|
||||||
@@ -250,7 +252,7 @@ def send_response(req, status, headers, body, stream=False):
|
|||||||
req.status = int(status[:3])
|
req.status = int(status[:3])
|
||||||
|
|
||||||
# Set response headers
|
# Set response headers
|
||||||
req.content_type = "text/plain"
|
req.content_type = 'text/plain'
|
||||||
for header, value in headers:
|
for header, value in headers:
|
||||||
if header.lower() == 'content-type':
|
if header.lower() == 'content-type':
|
||||||
req.content_type = value
|
req.content_type = value
|
||||||
@@ -262,7 +264,7 @@ def send_response(req, status, headers, body, stream=False):
|
|||||||
req.flush()
|
req.flush()
|
||||||
|
|
||||||
# Set response body
|
# Set response body
|
||||||
if isinstance(body, basestring):
|
if isinstance(body, text_or_bytes):
|
||||||
req.write(body)
|
req.write(body)
|
||||||
else:
|
else:
|
||||||
for seg in body:
|
for seg in body:
|
||||||
@@ -270,8 +272,6 @@ def send_response(req, status, headers, body, stream=False):
|
|||||||
|
|
||||||
|
|
||||||
# --------------- Startup tools for CherryPy + mod_python --------------- #
|
# --------------- Startup tools for CherryPy + mod_python --------------- #
|
||||||
import os
|
|
||||||
import re
|
|
||||||
try:
|
try:
|
||||||
import subprocess
|
import subprocess
|
||||||
|
|
||||||
@@ -286,13 +286,13 @@ except ImportError:
|
|||||||
return pipeout
|
return pipeout
|
||||||
|
|
||||||
|
|
||||||
def read_process(cmd, args=""):
|
def read_process(cmd, args=''):
|
||||||
fullcmd = "%s %s" % (cmd, args)
|
fullcmd = '%s %s' % (cmd, args)
|
||||||
pipeout = popen(fullcmd)
|
pipeout = popen(fullcmd)
|
||||||
try:
|
try:
|
||||||
firstline = pipeout.readline()
|
firstline = pipeout.readline()
|
||||||
cmd_not_found = re.search(
|
cmd_not_found = re.search(
|
||||||
ntob("(not recognized|No such file|not found)"),
|
ntob('(not recognized|No such file|not found)'),
|
||||||
firstline,
|
firstline,
|
||||||
re.IGNORECASE
|
re.IGNORECASE
|
||||||
)
|
)
|
||||||
@@ -321,8 +321,8 @@ LoadModule python_module modules/mod_python.so
|
|||||||
</Location>
|
</Location>
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self, loc="/", port=80, opts=None, apache_path="apache",
|
def __init__(self, loc='/', port=80, opts=None, apache_path='apache',
|
||||||
handler="cherrypy._cpmodpy::handler"):
|
handler='cherrypy._cpmodpy::handler'):
|
||||||
self.loc = loc
|
self.loc = loc
|
||||||
self.port = port
|
self.port = port
|
||||||
self.opts = opts
|
self.opts = opts
|
||||||
@@ -330,25 +330,25 @@ LoadModule python_module modules/mod_python.so
|
|||||||
self.handler = handler
|
self.handler = handler
|
||||||
|
|
||||||
def start(self):
|
def start(self):
|
||||||
opts = "".join([" PythonOption %s %s\n" % (k, v)
|
opts = ''.join([' PythonOption %s %s\n' % (k, v)
|
||||||
for k, v in self.opts])
|
for k, v in self.opts])
|
||||||
conf_data = self.template % {"port": self.port,
|
conf_data = self.template % {'port': self.port,
|
||||||
"loc": self.loc,
|
'loc': self.loc,
|
||||||
"opts": opts,
|
'opts': opts,
|
||||||
"handler": self.handler,
|
'handler': self.handler,
|
||||||
}
|
}
|
||||||
|
|
||||||
mpconf = os.path.join(os.path.dirname(__file__), "cpmodpy.conf")
|
mpconf = os.path.join(os.path.dirname(__file__), 'cpmodpy.conf')
|
||||||
f = open(mpconf, 'wb')
|
f = open(mpconf, 'wb')
|
||||||
try:
|
try:
|
||||||
f.write(conf_data)
|
f.write(conf_data)
|
||||||
finally:
|
finally:
|
||||||
f.close()
|
f.close()
|
||||||
|
|
||||||
response = read_process(self.apache_path, "-k start -f %s" % mpconf)
|
response = read_process(self.apache_path, '-k start -f %s' % mpconf)
|
||||||
self.ready = True
|
self.ready = True
|
||||||
return response
|
return response
|
||||||
|
|
||||||
def stop(self):
|
def stop(self):
|
||||||
os.popen("apache -k stop")
|
os.popen('apache -k stop')
|
||||||
self.ready = False
|
self.ready = False
|
||||||
@@ -19,19 +19,19 @@ class NativeGateway(wsgiserver.Gateway):
|
|||||||
try:
|
try:
|
||||||
# Obtain a Request object from CherryPy
|
# Obtain a Request object from CherryPy
|
||||||
local = req.server.bind_addr
|
local = req.server.bind_addr
|
||||||
local = httputil.Host(local[0], local[1], "")
|
local = httputil.Host(local[0], local[1], '')
|
||||||
remote = req.conn.remote_addr, req.conn.remote_port
|
remote = req.conn.remote_addr, req.conn.remote_port
|
||||||
remote = httputil.Host(remote[0], remote[1], "")
|
remote = httputil.Host(remote[0], remote[1], '')
|
||||||
|
|
||||||
scheme = req.scheme
|
scheme = req.scheme
|
||||||
sn = cherrypy.tree.script_name(req.uri or "/")
|
sn = cherrypy.tree.script_name(req.uri or '/')
|
||||||
if sn is None:
|
if sn is None:
|
||||||
self.send_response('404 Not Found', [], [''])
|
self.send_response('404 Not Found', [], [''])
|
||||||
else:
|
else:
|
||||||
app = cherrypy.tree.apps[sn]
|
app = cherrypy.tree.apps[sn]
|
||||||
method = req.method
|
method = req.method
|
||||||
path = req.path
|
path = req.path
|
||||||
qs = req.qs or ""
|
qs = req.qs or ''
|
||||||
headers = req.inheaders.items()
|
headers = req.inheaders.items()
|
||||||
rfile = req.rfile
|
rfile = req.rfile
|
||||||
prev = None
|
prev = None
|
||||||
@@ -40,7 +40,7 @@ class NativeGateway(wsgiserver.Gateway):
|
|||||||
redirections = []
|
redirections = []
|
||||||
while True:
|
while True:
|
||||||
request, response = app.get_serving(
|
request, response = app.get_serving(
|
||||||
local, remote, scheme, "HTTP/1.1")
|
local, remote, scheme, 'HTTP/1.1')
|
||||||
request.multithread = True
|
request.multithread = True
|
||||||
request.multiprocess = False
|
request.multiprocess = False
|
||||||
request.app = app
|
request.app = app
|
||||||
@@ -60,17 +60,17 @@ class NativeGateway(wsgiserver.Gateway):
|
|||||||
if not self.recursive:
|
if not self.recursive:
|
||||||
if ir.path in redirections:
|
if ir.path in redirections:
|
||||||
raise RuntimeError(
|
raise RuntimeError(
|
||||||
"InternalRedirector visited the same "
|
'InternalRedirector visited the same '
|
||||||
"URL twice: %r" % ir.path)
|
'URL twice: %r' % ir.path)
|
||||||
else:
|
else:
|
||||||
# Add the *previous* path_info + qs to
|
# Add the *previous* path_info + qs to
|
||||||
# redirections.
|
# redirections.
|
||||||
if qs:
|
if qs:
|
||||||
qs = "?" + qs
|
qs = '?' + qs
|
||||||
redirections.append(sn + path + qs)
|
redirections.append(sn + path + qs)
|
||||||
|
|
||||||
# Munge environment and try again.
|
# Munge environment and try again.
|
||||||
method = "GET"
|
method = 'GET'
|
||||||
path = ir.path
|
path = ir.path
|
||||||
qs = ir.query_string
|
qs = ir.query_string
|
||||||
rfile = io.BytesIO()
|
rfile = io.BytesIO()
|
||||||
@@ -91,7 +91,7 @@ class NativeGateway(wsgiserver.Gateway):
|
|||||||
req = self.req
|
req = self.req
|
||||||
|
|
||||||
# Set response status
|
# Set response status
|
||||||
req.status = str(status or "500 Server Error")
|
req.status = str(status or '500 Server Error')
|
||||||
|
|
||||||
# Set response headers
|
# Set response headers
|
||||||
for header, value in headers:
|
for header, value in headers:
|
||||||
|
|||||||
@@ -132,7 +132,7 @@ except ImportError:
|
|||||||
return ntob('').join(atoms)
|
return ntob('').join(atoms)
|
||||||
|
|
||||||
import cherrypy
|
import cherrypy
|
||||||
from cherrypy._cpcompat import basestring, ntob, ntou
|
from cherrypy._cpcompat import text_or_bytes, ntob, ntou
|
||||||
from cherrypy.lib import httputil
|
from cherrypy.lib import httputil
|
||||||
|
|
||||||
|
|
||||||
@@ -169,8 +169,8 @@ def process_urlencoded(entity):
|
|||||||
break
|
break
|
||||||
else:
|
else:
|
||||||
raise cherrypy.HTTPError(
|
raise cherrypy.HTTPError(
|
||||||
400, "The request entity could not be decoded. The following "
|
400, 'The request entity could not be decoded. The following '
|
||||||
"charsets were attempted: %s" % repr(entity.attempt_charsets))
|
'charsets were attempted: %s' % repr(entity.attempt_charsets))
|
||||||
|
|
||||||
# Now that all values have been successfully parsed and decoded,
|
# Now that all values have been successfully parsed and decoded,
|
||||||
# apply them to the entity.params dict.
|
# apply them to the entity.params dict.
|
||||||
@@ -185,7 +185,7 @@ def process_urlencoded(entity):
|
|||||||
|
|
||||||
def process_multipart(entity):
|
def process_multipart(entity):
|
||||||
"""Read all multipart parts into entity.parts."""
|
"""Read all multipart parts into entity.parts."""
|
||||||
ib = ""
|
ib = ''
|
||||||
if 'boundary' in entity.content_type.params:
|
if 'boundary' in entity.content_type.params:
|
||||||
# http://tools.ietf.org/html/rfc2046#section-5.1.1
|
# http://tools.ietf.org/html/rfc2046#section-5.1.1
|
||||||
# "The grammar for parameters on the Content-type field is such that it
|
# "The grammar for parameters on the Content-type field is such that it
|
||||||
@@ -193,7 +193,7 @@ def process_multipart(entity):
|
|||||||
# on the Content-type line"
|
# on the Content-type line"
|
||||||
ib = entity.content_type.params['boundary'].strip('"')
|
ib = entity.content_type.params['boundary'].strip('"')
|
||||||
|
|
||||||
if not re.match("^[ -~]{0,200}[!-~]$", ib):
|
if not re.match('^[ -~]{0,200}[!-~]$', ib):
|
||||||
raise ValueError('Invalid boundary in multipart form: %r' % (ib,))
|
raise ValueError('Invalid boundary in multipart form: %r' % (ib,))
|
||||||
|
|
||||||
ib = ('--' + ib).encode('ascii')
|
ib = ('--' + ib).encode('ascii')
|
||||||
@@ -428,7 +428,7 @@ class Entity(object):
|
|||||||
|
|
||||||
# Copy the class 'attempt_charsets', prepending any Content-Type
|
# Copy the class 'attempt_charsets', prepending any Content-Type
|
||||||
# charset
|
# charset
|
||||||
dec = self.content_type.params.get("charset", None)
|
dec = self.content_type.params.get('charset', None)
|
||||||
if dec:
|
if dec:
|
||||||
self.attempt_charsets = [dec] + [c for c in self.attempt_charsets
|
self.attempt_charsets = [dec] + [c for c in self.attempt_charsets
|
||||||
if c != dec]
|
if c != dec]
|
||||||
@@ -469,8 +469,8 @@ class Entity(object):
|
|||||||
# The 'type' attribute is deprecated in 3.2; remove it in 3.3.
|
# The 'type' attribute is deprecated in 3.2; remove it in 3.3.
|
||||||
type = property(
|
type = property(
|
||||||
lambda self: self.content_type,
|
lambda self: self.content_type,
|
||||||
doc="A deprecated alias for "
|
doc='A deprecated alias for '
|
||||||
":attr:`content_type<cherrypy._cpreqbody.Entity.content_type>`."
|
':attr:`content_type<cherrypy._cpreqbody.Entity.content_type>`.'
|
||||||
)
|
)
|
||||||
|
|
||||||
def read(self, size=None, fp_out=None):
|
def read(self, size=None, fp_out=None):
|
||||||
@@ -536,8 +536,8 @@ class Entity(object):
|
|||||||
else:
|
else:
|
||||||
raise cherrypy.HTTPError(
|
raise cherrypy.HTTPError(
|
||||||
400,
|
400,
|
||||||
"The request entity could not be decoded. The following "
|
'The request entity could not be decoded. The following '
|
||||||
"charsets were attempted: %s" % repr(self.attempt_charsets)
|
'charsets were attempted: %s' % repr(self.attempt_charsets)
|
||||||
)
|
)
|
||||||
|
|
||||||
def process(self):
|
def process(self):
|
||||||
@@ -613,18 +613,19 @@ class Part(Entity):
|
|||||||
self.file = None
|
self.file = None
|
||||||
self.value = None
|
self.value = None
|
||||||
|
|
||||||
|
@classmethod
|
||||||
def from_fp(cls, fp, boundary):
|
def from_fp(cls, fp, boundary):
|
||||||
headers = cls.read_headers(fp)
|
headers = cls.read_headers(fp)
|
||||||
return cls(fp, headers, boundary)
|
return cls(fp, headers, boundary)
|
||||||
from_fp = classmethod(from_fp)
|
|
||||||
|
|
||||||
|
@classmethod
|
||||||
def read_headers(cls, fp):
|
def read_headers(cls, fp):
|
||||||
headers = httputil.HeaderMap()
|
headers = httputil.HeaderMap()
|
||||||
while True:
|
while True:
|
||||||
line = fp.readline()
|
line = fp.readline()
|
||||||
if not line:
|
if not line:
|
||||||
# No more data--illegal end of headers
|
# No more data--illegal end of headers
|
||||||
raise EOFError("Illegal end of headers.")
|
raise EOFError('Illegal end of headers.')
|
||||||
|
|
||||||
if line == ntob('\r\n') or line == ntob('\n'):
|
if line == ntob('\r\n') or line == ntob('\n'):
|
||||||
# Normal end of headers
|
# Normal end of headers
|
||||||
@@ -636,17 +637,16 @@ class Part(Entity):
|
|||||||
# It's a continuation line.
|
# It's a continuation line.
|
||||||
v = line.strip().decode('ISO-8859-1')
|
v = line.strip().decode('ISO-8859-1')
|
||||||
else:
|
else:
|
||||||
k, v = line.split(ntob(":"), 1)
|
k, v = line.split(ntob(':'), 1)
|
||||||
k = k.strip().decode('ISO-8859-1')
|
k = k.strip().decode('ISO-8859-1')
|
||||||
v = v.strip().decode('ISO-8859-1')
|
v = v.strip().decode('ISO-8859-1')
|
||||||
|
|
||||||
existing = headers.get(k)
|
existing = headers.get(k)
|
||||||
if existing:
|
if existing:
|
||||||
v = ", ".join((existing, v))
|
v = ', '.join((existing, v))
|
||||||
headers[k] = v
|
headers[k] = v
|
||||||
|
|
||||||
return headers
|
return headers
|
||||||
read_headers = classmethod(read_headers)
|
|
||||||
|
|
||||||
def read_lines_to_boundary(self, fp_out=None):
|
def read_lines_to_boundary(self, fp_out=None):
|
||||||
"""Read bytes from self.fp and return or write them to a file.
|
"""Read bytes from self.fp and return or write them to a file.
|
||||||
@@ -658,16 +658,16 @@ class Part(Entity):
|
|||||||
object that supports the 'write' method; all bytes read will be
|
object that supports the 'write' method; all bytes read will be
|
||||||
written to the fp, and that fp is returned.
|
written to the fp, and that fp is returned.
|
||||||
"""
|
"""
|
||||||
endmarker = self.boundary + ntob("--")
|
endmarker = self.boundary + ntob('--')
|
||||||
delim = ntob("")
|
delim = ntob('')
|
||||||
prev_lf = True
|
prev_lf = True
|
||||||
lines = []
|
lines = []
|
||||||
seen = 0
|
seen = 0
|
||||||
while True:
|
while True:
|
||||||
line = self.fp.readline(1 << 16)
|
line = self.fp.readline(1 << 16)
|
||||||
if not line:
|
if not line:
|
||||||
raise EOFError("Illegal end of multipart body.")
|
raise EOFError('Illegal end of multipart body.')
|
||||||
if line.startswith(ntob("--")) and prev_lf:
|
if line.startswith(ntob('--')) and prev_lf:
|
||||||
strippedline = line.strip()
|
strippedline = line.strip()
|
||||||
if strippedline == self.boundary:
|
if strippedline == self.boundary:
|
||||||
break
|
break
|
||||||
@@ -677,16 +677,16 @@ class Part(Entity):
|
|||||||
|
|
||||||
line = delim + line
|
line = delim + line
|
||||||
|
|
||||||
if line.endswith(ntob("\r\n")):
|
if line.endswith(ntob('\r\n')):
|
||||||
delim = ntob("\r\n")
|
delim = ntob('\r\n')
|
||||||
line = line[:-2]
|
line = line[:-2]
|
||||||
prev_lf = True
|
prev_lf = True
|
||||||
elif line.endswith(ntob("\n")):
|
elif line.endswith(ntob('\n')):
|
||||||
delim = ntob("\n")
|
delim = ntob('\n')
|
||||||
line = line[:-1]
|
line = line[:-1]
|
||||||
prev_lf = True
|
prev_lf = True
|
||||||
else:
|
else:
|
||||||
delim = ntob("")
|
delim = ntob('')
|
||||||
prev_lf = False
|
prev_lf = False
|
||||||
|
|
||||||
if fp_out is None:
|
if fp_out is None:
|
||||||
@@ -715,7 +715,7 @@ class Part(Entity):
|
|||||||
self.file = self.read_into_file()
|
self.file = self.read_into_file()
|
||||||
else:
|
else:
|
||||||
result = self.read_lines_to_boundary()
|
result = self.read_lines_to_boundary()
|
||||||
if isinstance(result, basestring):
|
if isinstance(result, text_or_bytes):
|
||||||
self.value = result
|
self.value = result
|
||||||
else:
|
else:
|
||||||
self.file = result
|
self.file = result
|
||||||
@@ -732,19 +732,7 @@ class Part(Entity):
|
|||||||
|
|
||||||
Entity.part_class = Part
|
Entity.part_class = Part
|
||||||
|
|
||||||
try:
|
inf = float('inf')
|
||||||
inf = float('inf')
|
|
||||||
except ValueError:
|
|
||||||
# Python 2.4 and lower
|
|
||||||
class Infinity(object):
|
|
||||||
|
|
||||||
def __cmp__(self, other):
|
|
||||||
return 1
|
|
||||||
|
|
||||||
def __sub__(self, other):
|
|
||||||
return self
|
|
||||||
inf = Infinity()
|
|
||||||
|
|
||||||
|
|
||||||
comma_separated_headers = [
|
comma_separated_headers = [
|
||||||
'Accept', 'Accept-Charset', 'Accept-Encoding',
|
'Accept', 'Accept-Charset', 'Accept-Encoding',
|
||||||
@@ -839,7 +827,7 @@ class SizedReader:
|
|||||||
if e.__class__.__name__ == 'MaxSizeExceeded':
|
if e.__class__.__name__ == 'MaxSizeExceeded':
|
||||||
# Post data is too big
|
# Post data is too big
|
||||||
raise cherrypy.HTTPError(
|
raise cherrypy.HTTPError(
|
||||||
413, "Maximum request length: %r" % e.args[1])
|
413, 'Maximum request length: %r' % e.args[1])
|
||||||
else:
|
else:
|
||||||
raise
|
raise
|
||||||
if not data:
|
if not data:
|
||||||
@@ -915,23 +903,23 @@ class SizedReader:
|
|||||||
v = line.strip()
|
v = line.strip()
|
||||||
else:
|
else:
|
||||||
try:
|
try:
|
||||||
k, v = line.split(ntob(":"), 1)
|
k, v = line.split(ntob(':'), 1)
|
||||||
except ValueError:
|
except ValueError:
|
||||||
raise ValueError("Illegal header line.")
|
raise ValueError('Illegal header line.')
|
||||||
k = k.strip().title()
|
k = k.strip().title()
|
||||||
v = v.strip()
|
v = v.strip()
|
||||||
|
|
||||||
if k in comma_separated_headers:
|
if k in comma_separated_headers:
|
||||||
existing = self.trailers.get(envname)
|
existing = self.trailers.get(envname)
|
||||||
if existing:
|
if existing:
|
||||||
v = ntob(", ").join((existing, v))
|
v = ntob(', ').join((existing, v))
|
||||||
self.trailers[k] = v
|
self.trailers[k] = v
|
||||||
except Exception:
|
except Exception:
|
||||||
e = sys.exc_info()[1]
|
e = sys.exc_info()[1]
|
||||||
if e.__class__.__name__ == 'MaxSizeExceeded':
|
if e.__class__.__name__ == 'MaxSizeExceeded':
|
||||||
# Post data is too big
|
# Post data is too big
|
||||||
raise cherrypy.HTTPError(
|
raise cherrypy.HTTPError(
|
||||||
413, "Maximum request length: %r" % e.args[1])
|
413, 'Maximum request length: %r' % e.args[1])
|
||||||
else:
|
else:
|
||||||
raise
|
raise
|
||||||
|
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ import warnings
|
|||||||
import six
|
import six
|
||||||
|
|
||||||
import cherrypy
|
import cherrypy
|
||||||
from cherrypy._cpcompat import basestring, copykeys, ntob
|
from cherrypy._cpcompat import text_or_bytes, copykeys, ntob
|
||||||
from cherrypy._cpcompat import SimpleCookie, CookieError
|
from cherrypy._cpcompat import SimpleCookie, CookieError
|
||||||
from cherrypy import _cpreqbody, _cpconfig
|
from cherrypy import _cpreqbody, _cpconfig
|
||||||
from cherrypy._cperror import format_exc, bare_error
|
from cherrypy._cperror import format_exc, bare_error
|
||||||
@@ -41,11 +41,11 @@ class Hook(object):
|
|||||||
self.callback = callback
|
self.callback = callback
|
||||||
|
|
||||||
if failsafe is None:
|
if failsafe is None:
|
||||||
failsafe = getattr(callback, "failsafe", False)
|
failsafe = getattr(callback, 'failsafe', False)
|
||||||
self.failsafe = failsafe
|
self.failsafe = failsafe
|
||||||
|
|
||||||
if priority is None:
|
if priority is None:
|
||||||
priority = getattr(callback, "priority", 50)
|
priority = getattr(callback, 'priority', 50)
|
||||||
self.priority = priority
|
self.priority = priority
|
||||||
|
|
||||||
self.kwargs = kwargs
|
self.kwargs = kwargs
|
||||||
@@ -64,10 +64,10 @@ class Hook(object):
|
|||||||
|
|
||||||
def __repr__(self):
|
def __repr__(self):
|
||||||
cls = self.__class__
|
cls = self.__class__
|
||||||
return ("%s.%s(callback=%r, failsafe=%r, priority=%r, %s)"
|
return ('%s.%s(callback=%r, failsafe=%r, priority=%r, %s)'
|
||||||
% (cls.__module__, cls.__name__, self.callback,
|
% (cls.__module__, cls.__name__, self.callback,
|
||||||
self.failsafe, self.priority,
|
self.failsafe, self.priority,
|
||||||
", ".join(['%s=%r' % (k, v)
|
', '.join(['%s=%r' % (k, v)
|
||||||
for k, v in self.kwargs.items()])))
|
for k, v in self.kwargs.items()])))
|
||||||
|
|
||||||
|
|
||||||
@@ -124,7 +124,7 @@ class HookMap(dict):
|
|||||||
|
|
||||||
def __repr__(self):
|
def __repr__(self):
|
||||||
cls = self.__class__
|
cls = self.__class__
|
||||||
return "%s.%s(points=%r)" % (
|
return '%s.%s(points=%r)' % (
|
||||||
cls.__module__,
|
cls.__module__,
|
||||||
cls.__name__,
|
cls.__name__,
|
||||||
copykeys(self)
|
copykeys(self)
|
||||||
@@ -138,8 +138,8 @@ def hooks_namespace(k, v):
|
|||||||
# Use split again to allow multiple hooks for a single
|
# Use split again to allow multiple hooks for a single
|
||||||
# hookpoint per path (e.g. "hooks.before_handler.1").
|
# hookpoint per path (e.g. "hooks.before_handler.1").
|
||||||
# Little-known fact you only get from reading source ;)
|
# Little-known fact you only get from reading source ;)
|
||||||
hookpoint = k.split(".", 1)[0]
|
hookpoint = k.split('.', 1)[0]
|
||||||
if isinstance(v, basestring):
|
if isinstance(v, text_or_bytes):
|
||||||
v = cherrypy.lib.attributes(v)
|
v = cherrypy.lib.attributes(v)
|
||||||
if not isinstance(v, Hook):
|
if not isinstance(v, Hook):
|
||||||
v = Hook(v)
|
v = Hook(v)
|
||||||
@@ -199,23 +199,23 @@ class Request(object):
|
|||||||
unless we are processing an InternalRedirect."""
|
unless we are processing an InternalRedirect."""
|
||||||
|
|
||||||
# Conversation/connection attributes
|
# Conversation/connection attributes
|
||||||
local = httputil.Host("127.0.0.1", 80)
|
local = httputil.Host('127.0.0.1', 80)
|
||||||
"An httputil.Host(ip, port, hostname) object for the server socket."
|
'An httputil.Host(ip, port, hostname) object for the server socket.'
|
||||||
|
|
||||||
remote = httputil.Host("127.0.0.1", 1111)
|
remote = httputil.Host('127.0.0.1', 1111)
|
||||||
"An httputil.Host(ip, port, hostname) object for the client socket."
|
'An httputil.Host(ip, port, hostname) object for the client socket.'
|
||||||
|
|
||||||
scheme = "http"
|
scheme = 'http'
|
||||||
"""
|
"""
|
||||||
The protocol used between client and server. In most cases,
|
The protocol used between client and server. In most cases,
|
||||||
this will be either 'http' or 'https'."""
|
this will be either 'http' or 'https'."""
|
||||||
|
|
||||||
server_protocol = "HTTP/1.1"
|
server_protocol = 'HTTP/1.1'
|
||||||
"""
|
"""
|
||||||
The HTTP version for which the HTTP server is at least
|
The HTTP version for which the HTTP server is at least
|
||||||
conditionally compliant."""
|
conditionally compliant."""
|
||||||
|
|
||||||
base = ""
|
base = ''
|
||||||
"""The (scheme://host) portion of the requested URL.
|
"""The (scheme://host) portion of the requested URL.
|
||||||
In some cases (e.g. when proxying via mod_rewrite), this may contain
|
In some cases (e.g. when proxying via mod_rewrite), this may contain
|
||||||
path segments which cherrypy.url uses when constructing url's, but
|
path segments which cherrypy.url uses when constructing url's, but
|
||||||
@@ -223,13 +223,13 @@ class Request(object):
|
|||||||
MUST NOT end in a slash."""
|
MUST NOT end in a slash."""
|
||||||
|
|
||||||
# Request-Line attributes
|
# Request-Line attributes
|
||||||
request_line = ""
|
request_line = ''
|
||||||
"""
|
"""
|
||||||
The complete Request-Line received from the client. This is a
|
The complete Request-Line received from the client. This is a
|
||||||
single string consisting of the request method, URI, and protocol
|
single string consisting of the request method, URI, and protocol
|
||||||
version (joined by spaces). Any final CRLF is removed."""
|
version (joined by spaces). Any final CRLF is removed."""
|
||||||
|
|
||||||
method = "GET"
|
method = 'GET'
|
||||||
"""
|
"""
|
||||||
Indicates the HTTP method to be performed on the resource identified
|
Indicates the HTTP method to be performed on the resource identified
|
||||||
by the Request-URI. Common methods include GET, HEAD, POST, PUT, and
|
by the Request-URI. Common methods include GET, HEAD, POST, PUT, and
|
||||||
@@ -237,7 +237,7 @@ class Request(object):
|
|||||||
servers and gateways may restrict the set of allowable methods.
|
servers and gateways may restrict the set of allowable methods.
|
||||||
CherryPy applications SHOULD restrict the set (on a per-URI basis)."""
|
CherryPy applications SHOULD restrict the set (on a per-URI basis)."""
|
||||||
|
|
||||||
query_string = ""
|
query_string = ''
|
||||||
"""
|
"""
|
||||||
The query component of the Request-URI, a string of information to be
|
The query component of the Request-URI, a string of information to be
|
||||||
interpreted by the resource. The query portion of a URI follows the
|
interpreted by the resource. The query portion of a URI follows the
|
||||||
@@ -312,7 +312,7 @@ class Request(object):
|
|||||||
If True, the rfile (if any) is automatically read and parsed,
|
If True, the rfile (if any) is automatically read and parsed,
|
||||||
and the result placed into request.params or request.body."""
|
and the result placed into request.params or request.body."""
|
||||||
|
|
||||||
methods_with_bodies = ("POST", "PUT")
|
methods_with_bodies = ('POST', 'PUT')
|
||||||
"""
|
"""
|
||||||
A sequence of HTTP methods for which CherryPy will automatically
|
A sequence of HTTP methods for which CherryPy will automatically
|
||||||
attempt to read a body from the rfile. If you are going to change
|
attempt to read a body from the rfile. If you are going to change
|
||||||
@@ -341,7 +341,7 @@ class Request(object):
|
|||||||
to a hierarchical arrangement of objects, starting at request.app.root.
|
to a hierarchical arrangement of objects, starting at request.app.root.
|
||||||
See help(cherrypy.dispatch) for more information."""
|
See help(cherrypy.dispatch) for more information."""
|
||||||
|
|
||||||
script_name = ""
|
script_name = ''
|
||||||
"""
|
"""
|
||||||
The 'mount point' of the application which is handling this request.
|
The 'mount point' of the application which is handling this request.
|
||||||
|
|
||||||
@@ -349,7 +349,7 @@ class Request(object):
|
|||||||
the root of the URI, it MUST be an empty string (not "/").
|
the root of the URI, it MUST be an empty string (not "/").
|
||||||
"""
|
"""
|
||||||
|
|
||||||
path_info = "/"
|
path_info = '/'
|
||||||
"""
|
"""
|
||||||
The 'relative path' portion of the Request-URI. This is relative
|
The 'relative path' portion of the Request-URI. This is relative
|
||||||
to the script_name ('mount point') of the application which is
|
to the script_name ('mount point') of the application which is
|
||||||
@@ -468,15 +468,15 @@ class Request(object):
|
|||||||
This is useful when debugging a live server with hung requests."""
|
This is useful when debugging a live server with hung requests."""
|
||||||
|
|
||||||
namespaces = _cpconfig.NamespaceSet(
|
namespaces = _cpconfig.NamespaceSet(
|
||||||
**{"hooks": hooks_namespace,
|
**{'hooks': hooks_namespace,
|
||||||
"request": request_namespace,
|
'request': request_namespace,
|
||||||
"response": response_namespace,
|
'response': response_namespace,
|
||||||
"error_page": error_page_namespace,
|
'error_page': error_page_namespace,
|
||||||
"tools": cherrypy.tools,
|
'tools': cherrypy.tools,
|
||||||
})
|
})
|
||||||
|
|
||||||
def __init__(self, local_host, remote_host, scheme="http",
|
def __init__(self, local_host, remote_host, scheme='http',
|
||||||
server_protocol="HTTP/1.1"):
|
server_protocol='HTTP/1.1'):
|
||||||
"""Populate a new Request object.
|
"""Populate a new Request object.
|
||||||
|
|
||||||
local_host should be an httputil.Host object with the server info.
|
local_host should be an httputil.Host object with the server info.
|
||||||
@@ -544,7 +544,7 @@ class Request(object):
|
|||||||
self.error_response = cherrypy.HTTPError(500).set_response
|
self.error_response = cherrypy.HTTPError(500).set_response
|
||||||
|
|
||||||
self.method = method
|
self.method = method
|
||||||
path = path or "/"
|
path = path or '/'
|
||||||
self.query_string = query_string or ''
|
self.query_string = query_string or ''
|
||||||
self.params = {}
|
self.params = {}
|
||||||
|
|
||||||
@@ -600,11 +600,11 @@ class Request(object):
|
|||||||
if self.show_tracebacks:
|
if self.show_tracebacks:
|
||||||
body = format_exc()
|
body = format_exc()
|
||||||
else:
|
else:
|
||||||
body = ""
|
body = ''
|
||||||
r = bare_error(body)
|
r = bare_error(body)
|
||||||
response.output_status, response.header_list, response.body = r
|
response.output_status, response.header_list, response.body = r
|
||||||
|
|
||||||
if self.method == "HEAD":
|
if self.method == 'HEAD':
|
||||||
# HEAD requests MUST NOT return a message-body in the response.
|
# HEAD requests MUST NOT return a message-body in the response.
|
||||||
response.body = []
|
response.body = []
|
||||||
|
|
||||||
@@ -696,8 +696,8 @@ class Request(object):
|
|||||||
self.query_string, encoding=self.query_string_encoding)
|
self.query_string, encoding=self.query_string_encoding)
|
||||||
except UnicodeDecodeError:
|
except UnicodeDecodeError:
|
||||||
raise cherrypy.HTTPError(
|
raise cherrypy.HTTPError(
|
||||||
404, "The given query string could not be processed. Query "
|
404, 'The given query string could not be processed. Query '
|
||||||
"strings for this resource must be encoded with %r." %
|
'strings for this resource must be encoded with %r.' %
|
||||||
self.query_string_encoding)
|
self.query_string_encoding)
|
||||||
|
|
||||||
# Python 2 only: keyword arguments must be byte strings (type 'str').
|
# Python 2 only: keyword arguments must be byte strings (type 'str').
|
||||||
@@ -722,7 +722,7 @@ class Request(object):
|
|||||||
# (AFAIK, only Konqueror does that), only the last one will
|
# (AFAIK, only Konqueror does that), only the last one will
|
||||||
# remain in headers (but they will be correctly stored in
|
# remain in headers (but they will be correctly stored in
|
||||||
# request.cookie).
|
# request.cookie).
|
||||||
if "=?" in value:
|
if '=?' in value:
|
||||||
dict.__setitem__(headers, name, httputil.decode_TEXT(value))
|
dict.__setitem__(headers, name, httputil.decode_TEXT(value))
|
||||||
else:
|
else:
|
||||||
dict.__setitem__(headers, name, value)
|
dict.__setitem__(headers, name, value)
|
||||||
@@ -733,7 +733,7 @@ class Request(object):
|
|||||||
try:
|
try:
|
||||||
self.cookie.load(value)
|
self.cookie.load(value)
|
||||||
except CookieError:
|
except CookieError:
|
||||||
msg = "Illegal cookie name %s" % value.split('=')[0]
|
msg = 'Illegal cookie name %s' % value.split('=')[0]
|
||||||
raise cherrypy.HTTPError(400, msg)
|
raise cherrypy.HTTPError(400, msg)
|
||||||
|
|
||||||
if not dict.__contains__(headers, 'Host'):
|
if not dict.__contains__(headers, 'Host'):
|
||||||
@@ -746,7 +746,7 @@ class Request(object):
|
|||||||
host = dict.get(headers, 'Host')
|
host = dict.get(headers, 'Host')
|
||||||
if not host:
|
if not host:
|
||||||
host = self.local.name or self.local.ip
|
host = self.local.name or self.local.ip
|
||||||
self.base = "%s://%s" % (self.scheme, host)
|
self.base = '%s://%s' % (self.scheme, host)
|
||||||
|
|
||||||
def get_resource(self, path):
|
def get_resource(self, path):
|
||||||
"""Call a dispatcher (which sets self.handler and .config). (Core)"""
|
"""Call a dispatcher (which sets self.handler and .config). (Core)"""
|
||||||
@@ -754,7 +754,7 @@ class Request(object):
|
|||||||
# dispatchers can only be specified in app.config, not in _cp_config
|
# dispatchers can only be specified in app.config, not in _cp_config
|
||||||
# (since custom dispatchers may not even have an app.root).
|
# (since custom dispatchers may not even have an app.root).
|
||||||
dispatch = self.app.find_config(
|
dispatch = self.app.find_config(
|
||||||
path, "request.dispatch", self.dispatch)
|
path, 'request.dispatch', self.dispatch)
|
||||||
|
|
||||||
# dispatch() should set self.handler and self.config
|
# dispatch() should set self.handler and self.config
|
||||||
dispatch(path)
|
dispatch(path)
|
||||||
@@ -762,10 +762,10 @@ class Request(object):
|
|||||||
def handle_error(self):
|
def handle_error(self):
|
||||||
"""Handle the last unanticipated exception. (Core)"""
|
"""Handle the last unanticipated exception. (Core)"""
|
||||||
try:
|
try:
|
||||||
self.hooks.run("before_error_response")
|
self.hooks.run('before_error_response')
|
||||||
if self.error_response:
|
if self.error_response:
|
||||||
self.error_response()
|
self.error_response()
|
||||||
self.hooks.run("after_error_response")
|
self.hooks.run('after_error_response')
|
||||||
cherrypy.serving.response.finalize()
|
cherrypy.serving.response.finalize()
|
||||||
except cherrypy.HTTPRedirect:
|
except cherrypy.HTTPRedirect:
|
||||||
inst = sys.exc_info()[1]
|
inst = sys.exc_info()[1]
|
||||||
@@ -776,8 +776,8 @@ class Request(object):
|
|||||||
|
|
||||||
def _get_body_params(self):
|
def _get_body_params(self):
|
||||||
warnings.warn(
|
warnings.warn(
|
||||||
"body_params is deprecated in CherryPy 3.2, will be removed in "
|
'body_params is deprecated in CherryPy 3.2, will be removed in '
|
||||||
"CherryPy 3.3.",
|
'CherryPy 3.3.',
|
||||||
DeprecationWarning
|
DeprecationWarning
|
||||||
)
|
)
|
||||||
return self.body.params
|
return self.body.params
|
||||||
@@ -800,8 +800,8 @@ class ResponseBody(object):
|
|||||||
"""The body of the HTTP response (the response entity)."""
|
"""The body of the HTTP response (the response entity)."""
|
||||||
|
|
||||||
if six.PY3:
|
if six.PY3:
|
||||||
unicode_err = ("Page handlers MUST return bytes. Use tools.encode "
|
unicode_err = ('Page handlers MUST return bytes. Use tools.encode '
|
||||||
"if you wish to return unicode.")
|
'if you wish to return unicode.')
|
||||||
|
|
||||||
def __get__(self, obj, objclass=None):
|
def __get__(self, obj, objclass=None):
|
||||||
if obj is None:
|
if obj is None:
|
||||||
@@ -815,7 +815,7 @@ class ResponseBody(object):
|
|||||||
if six.PY3 and isinstance(value, str):
|
if six.PY3 and isinstance(value, str):
|
||||||
raise ValueError(self.unicode_err)
|
raise ValueError(self.unicode_err)
|
||||||
|
|
||||||
if isinstance(value, basestring):
|
if isinstance(value, text_or_bytes):
|
||||||
# strings get wrapped in a list because iterating over a single
|
# strings get wrapped in a list because iterating over a single
|
||||||
# item list is much faster than iterating over every character
|
# item list is much faster than iterating over every character
|
||||||
# in a long string.
|
# in a long string.
|
||||||
@@ -842,7 +842,7 @@ class Response(object):
|
|||||||
|
|
||||||
"""An HTTP Response, including status, headers, and body."""
|
"""An HTTP Response, including status, headers, and body."""
|
||||||
|
|
||||||
status = ""
|
status = ''
|
||||||
"""The HTTP Status-Code and Reason-Phrase."""
|
"""The HTTP Status-Code and Reason-Phrase."""
|
||||||
|
|
||||||
header_list = []
|
header_list = []
|
||||||
@@ -893,15 +893,15 @@ class Response(object):
|
|||||||
# Since we know all our keys are titled strings, we can
|
# Since we know all our keys are titled strings, we can
|
||||||
# bypass HeaderMap.update and get a big speed boost.
|
# bypass HeaderMap.update and get a big speed boost.
|
||||||
dict.update(self.headers, {
|
dict.update(self.headers, {
|
||||||
"Content-Type": 'text/html',
|
'Content-Type': 'text/html',
|
||||||
"Server": "CherryPy/" + cherrypy.__version__,
|
'Server': 'CherryPy/' + cherrypy.__version__,
|
||||||
"Date": httputil.HTTPDate(self.time),
|
'Date': httputil.HTTPDate(self.time),
|
||||||
})
|
})
|
||||||
self.cookie = SimpleCookie()
|
self.cookie = SimpleCookie()
|
||||||
|
|
||||||
def collapse_body(self):
|
def collapse_body(self):
|
||||||
"""Collapse self.body to a single string; replace it and return it."""
|
"""Collapse self.body to a single string; replace it and return it."""
|
||||||
if isinstance(self.body, basestring):
|
if isinstance(self.body, text_or_bytes):
|
||||||
return self.body
|
return self.body
|
||||||
|
|
||||||
newbody = []
|
newbody = []
|
||||||
@@ -924,9 +924,9 @@ class Response(object):
|
|||||||
|
|
||||||
headers = self.headers
|
headers = self.headers
|
||||||
|
|
||||||
self.status = "%s %s" % (code, reason)
|
self.status = '%s %s' % (code, reason)
|
||||||
self.output_status = ntob(str(code), 'ascii') + \
|
self.output_status = ntob(str(code), 'ascii') + \
|
||||||
ntob(" ") + headers.encode(reason)
|
ntob(' ') + headers.encode(reason)
|
||||||
|
|
||||||
if self.stream:
|
if self.stream:
|
||||||
# The upshot: wsgiserver will chunk the response if
|
# The upshot: wsgiserver will chunk the response if
|
||||||
@@ -939,7 +939,7 @@ class Response(object):
|
|||||||
# and 304 (not modified) responses MUST NOT
|
# and 304 (not modified) responses MUST NOT
|
||||||
# include a message-body."
|
# include a message-body."
|
||||||
dict.pop(headers, 'Content-Length', None)
|
dict.pop(headers, 'Content-Length', None)
|
||||||
self.body = ntob("")
|
self.body = ntob('')
|
||||||
else:
|
else:
|
||||||
# Responses which are not streamed should have a Content-Length,
|
# Responses which are not streamed should have a Content-Length,
|
||||||
# but allow user code to set Content-Length if desired.
|
# but allow user code to set Content-Length if desired.
|
||||||
@@ -952,13 +952,10 @@ class Response(object):
|
|||||||
|
|
||||||
cookie = self.cookie.output()
|
cookie = self.cookie.output()
|
||||||
if cookie:
|
if cookie:
|
||||||
for line in cookie.split("\n"):
|
for line in cookie.split('\r\n'):
|
||||||
if line.endswith("\r"):
|
name, value = line.split(': ', 1)
|
||||||
# Python 2.4 emits cookies joined by LF but 2.5+ by CRLF.
|
|
||||||
line = line[:-1]
|
|
||||||
name, value = line.split(": ", 1)
|
|
||||||
if isinstance(name, six.text_type):
|
if isinstance(name, six.text_type):
|
||||||
name = name.encode("ISO-8859-1")
|
name = name.encode('ISO-8859-1')
|
||||||
if isinstance(value, six.text_type):
|
if isinstance(value, six.text_type):
|
||||||
value = headers.encode(value)
|
value = headers.encode(value)
|
||||||
h.append((name, value))
|
h.append((name, value))
|
||||||
|
|||||||
@@ -3,8 +3,8 @@
|
|||||||
import six
|
import six
|
||||||
|
|
||||||
import cherrypy
|
import cherrypy
|
||||||
from cherrypy.lib import attributes
|
from cherrypy.lib.reprconf import attributes
|
||||||
from cherrypy._cpcompat import basestring
|
from cherrypy._cpcompat import text_or_bytes
|
||||||
|
|
||||||
# We import * because we want to export check_port
|
# We import * because we want to export check_port
|
||||||
# et al as attributes of this module.
|
# et al as attributes of this module.
|
||||||
@@ -35,7 +35,7 @@ class Server(ServerAdapter):
|
|||||||
if value == '':
|
if value == '':
|
||||||
raise ValueError("The empty string ('') is not an allowed value. "
|
raise ValueError("The empty string ('') is not an allowed value. "
|
||||||
"Use '0.0.0.0' instead to listen on all active "
|
"Use '0.0.0.0' instead to listen on all active "
|
||||||
"interfaces (INADDR_ANY).")
|
'interfaces (INADDR_ANY).')
|
||||||
self._socket_host = value
|
self._socket_host = value
|
||||||
socket_host = property(
|
socket_host = property(
|
||||||
_get_socket_host,
|
_get_socket_host,
|
||||||
@@ -156,7 +156,7 @@ class Server(ServerAdapter):
|
|||||||
if httpserver is None:
|
if httpserver is None:
|
||||||
from cherrypy import _cpwsgi_server
|
from cherrypy import _cpwsgi_server
|
||||||
httpserver = _cpwsgi_server.CPWSGIServer(self)
|
httpserver = _cpwsgi_server.CPWSGIServer(self)
|
||||||
if isinstance(httpserver, basestring):
|
if isinstance(httpserver, text_or_bytes):
|
||||||
# Is anyone using this? Can I add an arg?
|
# Is anyone using this? Can I add an arg?
|
||||||
httpserver = attributes(httpserver)(self)
|
httpserver = attributes(httpserver)(self)
|
||||||
return httpserver, self.bind_addr
|
return httpserver, self.bind_addr
|
||||||
@@ -180,7 +180,7 @@ class Server(ServerAdapter):
|
|||||||
self.socket_file = None
|
self.socket_file = None
|
||||||
self.socket_host = None
|
self.socket_host = None
|
||||||
self.socket_port = None
|
self.socket_port = None
|
||||||
elif isinstance(value, basestring):
|
elif isinstance(value, text_or_bytes):
|
||||||
self.socket_file = value
|
self.socket_file = value
|
||||||
self.socket_host = None
|
self.socket_host = None
|
||||||
self.socket_port = None
|
self.socket_port = None
|
||||||
@@ -189,9 +189,9 @@ class Server(ServerAdapter):
|
|||||||
self.socket_host, self.socket_port = value
|
self.socket_host, self.socket_port = value
|
||||||
self.socket_file = None
|
self.socket_file = None
|
||||||
except ValueError:
|
except ValueError:
|
||||||
raise ValueError("bind_addr must be a (host, port) tuple "
|
raise ValueError('bind_addr must be a (host, port) tuple '
|
||||||
"(for TCP sockets) or a string (for Unix "
|
'(for TCP sockets) or a string (for Unix '
|
||||||
"domain sockets), not %r" % value)
|
'domain sockets), not %r' % value)
|
||||||
bind_addr = property(
|
bind_addr = property(
|
||||||
_get_bind_addr,
|
_get_bind_addr,
|
||||||
_set_bind_addr,
|
_set_bind_addr,
|
||||||
@@ -215,12 +215,12 @@ class Server(ServerAdapter):
|
|||||||
port = self.socket_port
|
port = self.socket_port
|
||||||
|
|
||||||
if self.ssl_certificate:
|
if self.ssl_certificate:
|
||||||
scheme = "https"
|
scheme = 'https'
|
||||||
if port != 443:
|
if port != 443:
|
||||||
host += ":%s" % port
|
host += ':%s' % port
|
||||||
else:
|
else:
|
||||||
scheme = "http"
|
scheme = 'http'
|
||||||
if port != 80:
|
if port != 80:
|
||||||
host += ":%s" % port
|
host += ':%s' % port
|
||||||
|
|
||||||
return "%s://%s" % (scheme, host)
|
return '%s://%s' % (scheme, host)
|
||||||
@@ -1,241 +0,0 @@
|
|||||||
# This is a backport of Python-2.4's threading.local() implementation
|
|
||||||
|
|
||||||
"""Thread-local objects
|
|
||||||
|
|
||||||
(Note that this module provides a Python version of thread
|
|
||||||
threading.local class. Depending on the version of Python you're
|
|
||||||
using, there may be a faster one available. You should always import
|
|
||||||
the local class from threading.)
|
|
||||||
|
|
||||||
Thread-local objects support the management of thread-local data.
|
|
||||||
If you have data that you want to be local to a thread, simply create
|
|
||||||
a thread-local object and use its attributes:
|
|
||||||
|
|
||||||
>>> mydata = local()
|
|
||||||
>>> mydata.number = 42
|
|
||||||
>>> mydata.number
|
|
||||||
42
|
|
||||||
|
|
||||||
You can also access the local-object's dictionary:
|
|
||||||
|
|
||||||
>>> mydata.__dict__
|
|
||||||
{'number': 42}
|
|
||||||
>>> mydata.__dict__.setdefault('widgets', [])
|
|
||||||
[]
|
|
||||||
>>> mydata.widgets
|
|
||||||
[]
|
|
||||||
|
|
||||||
What's important about thread-local objects is that their data are
|
|
||||||
local to a thread. If we access the data in a different thread:
|
|
||||||
|
|
||||||
>>> log = []
|
|
||||||
>>> def f():
|
|
||||||
... items = mydata.__dict__.items()
|
|
||||||
... items.sort()
|
|
||||||
... log.append(items)
|
|
||||||
... mydata.number = 11
|
|
||||||
... log.append(mydata.number)
|
|
||||||
|
|
||||||
>>> import threading
|
|
||||||
>>> thread = threading.Thread(target=f)
|
|
||||||
>>> thread.start()
|
|
||||||
>>> thread.join()
|
|
||||||
>>> log
|
|
||||||
[[], 11]
|
|
||||||
|
|
||||||
we get different data. Furthermore, changes made in the other thread
|
|
||||||
don't affect data seen in this thread:
|
|
||||||
|
|
||||||
>>> mydata.number
|
|
||||||
42
|
|
||||||
|
|
||||||
Of course, values you get from a local object, including a __dict__
|
|
||||||
attribute, are for whatever thread was current at the time the
|
|
||||||
attribute was read. For that reason, you generally don't want to save
|
|
||||||
these values across threads, as they apply only to the thread they
|
|
||||||
came from.
|
|
||||||
|
|
||||||
You can create custom local objects by subclassing the local class:
|
|
||||||
|
|
||||||
>>> class MyLocal(local):
|
|
||||||
... number = 2
|
|
||||||
... initialized = False
|
|
||||||
... def __init__(self, **kw):
|
|
||||||
... if self.initialized:
|
|
||||||
... raise SystemError('__init__ called too many times')
|
|
||||||
... self.initialized = True
|
|
||||||
... self.__dict__.update(kw)
|
|
||||||
... def squared(self):
|
|
||||||
... return self.number ** 2
|
|
||||||
|
|
||||||
This can be useful to support default values, methods and
|
|
||||||
initialization. Note that if you define an __init__ method, it will be
|
|
||||||
called each time the local object is used in a separate thread. This
|
|
||||||
is necessary to initialize each thread's dictionary.
|
|
||||||
|
|
||||||
Now if we create a local object:
|
|
||||||
|
|
||||||
>>> mydata = MyLocal(color='red')
|
|
||||||
|
|
||||||
Now we have a default number:
|
|
||||||
|
|
||||||
>>> mydata.number
|
|
||||||
2
|
|
||||||
|
|
||||||
an initial color:
|
|
||||||
|
|
||||||
>>> mydata.color
|
|
||||||
'red'
|
|
||||||
>>> del mydata.color
|
|
||||||
|
|
||||||
And a method that operates on the data:
|
|
||||||
|
|
||||||
>>> mydata.squared()
|
|
||||||
4
|
|
||||||
|
|
||||||
As before, we can access the data in a separate thread:
|
|
||||||
|
|
||||||
>>> log = []
|
|
||||||
>>> thread = threading.Thread(target=f)
|
|
||||||
>>> thread.start()
|
|
||||||
>>> thread.join()
|
|
||||||
>>> log
|
|
||||||
[[('color', 'red'), ('initialized', True)], 11]
|
|
||||||
|
|
||||||
without affecting this thread's data:
|
|
||||||
|
|
||||||
>>> mydata.number
|
|
||||||
2
|
|
||||||
>>> mydata.color
|
|
||||||
Traceback (most recent call last):
|
|
||||||
...
|
|
||||||
AttributeError: 'MyLocal' object has no attribute 'color'
|
|
||||||
|
|
||||||
Note that subclasses can define slots, but they are not thread
|
|
||||||
local. They are shared across threads:
|
|
||||||
|
|
||||||
>>> class MyLocal(local):
|
|
||||||
... __slots__ = 'number'
|
|
||||||
|
|
||||||
>>> mydata = MyLocal()
|
|
||||||
>>> mydata.number = 42
|
|
||||||
>>> mydata.color = 'red'
|
|
||||||
|
|
||||||
So, the separate thread:
|
|
||||||
|
|
||||||
>>> thread = threading.Thread(target=f)
|
|
||||||
>>> thread.start()
|
|
||||||
>>> thread.join()
|
|
||||||
|
|
||||||
affects what we see:
|
|
||||||
|
|
||||||
>>> mydata.number
|
|
||||||
11
|
|
||||||
|
|
||||||
>>> del mydata
|
|
||||||
"""
|
|
||||||
|
|
||||||
# Threading import is at end
|
|
||||||
|
|
||||||
|
|
||||||
class _localbase(object):
|
|
||||||
__slots__ = '_local__key', '_local__args', '_local__lock'
|
|
||||||
|
|
||||||
def __new__(cls, *args, **kw):
|
|
||||||
self = object.__new__(cls)
|
|
||||||
key = 'thread.local.' + str(id(self))
|
|
||||||
object.__setattr__(self, '_local__key', key)
|
|
||||||
object.__setattr__(self, '_local__args', (args, kw))
|
|
||||||
object.__setattr__(self, '_local__lock', RLock())
|
|
||||||
|
|
||||||
if args or kw and (cls.__init__ is object.__init__):
|
|
||||||
raise TypeError("Initialization arguments are not supported")
|
|
||||||
|
|
||||||
# We need to create the thread dict in anticipation of
|
|
||||||
# __init__ being called, to make sure we don't call it
|
|
||||||
# again ourselves.
|
|
||||||
dict = object.__getattribute__(self, '__dict__')
|
|
||||||
currentThread().__dict__[key] = dict
|
|
||||||
|
|
||||||
return self
|
|
||||||
|
|
||||||
|
|
||||||
def _patch(self):
|
|
||||||
key = object.__getattribute__(self, '_local__key')
|
|
||||||
d = currentThread().__dict__.get(key)
|
|
||||||
if d is None:
|
|
||||||
d = {}
|
|
||||||
currentThread().__dict__[key] = d
|
|
||||||
object.__setattr__(self, '__dict__', d)
|
|
||||||
|
|
||||||
# we have a new instance dict, so call out __init__ if we have
|
|
||||||
# one
|
|
||||||
cls = type(self)
|
|
||||||
if cls.__init__ is not object.__init__:
|
|
||||||
args, kw = object.__getattribute__(self, '_local__args')
|
|
||||||
cls.__init__(self, *args, **kw)
|
|
||||||
else:
|
|
||||||
object.__setattr__(self, '__dict__', d)
|
|
||||||
|
|
||||||
|
|
||||||
class local(_localbase):
|
|
||||||
|
|
||||||
def __getattribute__(self, name):
|
|
||||||
lock = object.__getattribute__(self, '_local__lock')
|
|
||||||
lock.acquire()
|
|
||||||
try:
|
|
||||||
_patch(self)
|
|
||||||
return object.__getattribute__(self, name)
|
|
||||||
finally:
|
|
||||||
lock.release()
|
|
||||||
|
|
||||||
def __setattr__(self, name, value):
|
|
||||||
lock = object.__getattribute__(self, '_local__lock')
|
|
||||||
lock.acquire()
|
|
||||||
try:
|
|
||||||
_patch(self)
|
|
||||||
return object.__setattr__(self, name, value)
|
|
||||||
finally:
|
|
||||||
lock.release()
|
|
||||||
|
|
||||||
def __delattr__(self, name):
|
|
||||||
lock = object.__getattribute__(self, '_local__lock')
|
|
||||||
lock.acquire()
|
|
||||||
try:
|
|
||||||
_patch(self)
|
|
||||||
return object.__delattr__(self, name)
|
|
||||||
finally:
|
|
||||||
lock.release()
|
|
||||||
|
|
||||||
def __del__():
|
|
||||||
threading_enumerate = enumerate
|
|
||||||
__getattribute__ = object.__getattribute__
|
|
||||||
|
|
||||||
def __del__(self):
|
|
||||||
key = __getattribute__(self, '_local__key')
|
|
||||||
|
|
||||||
try:
|
|
||||||
threads = list(threading_enumerate())
|
|
||||||
except:
|
|
||||||
# if enumerate fails, as it seems to do during
|
|
||||||
# shutdown, we'll skip cleanup under the assumption
|
|
||||||
# that there is nothing to clean up
|
|
||||||
return
|
|
||||||
|
|
||||||
for thread in threads:
|
|
||||||
try:
|
|
||||||
__dict__ = thread.__dict__
|
|
||||||
except AttributeError:
|
|
||||||
# Thread is dying, rest in peace
|
|
||||||
continue
|
|
||||||
|
|
||||||
if key in __dict__:
|
|
||||||
try:
|
|
||||||
del __dict__[key]
|
|
||||||
except KeyError:
|
|
||||||
pass # didn't have anything in this thread
|
|
||||||
|
|
||||||
return __del__
|
|
||||||
__del__ = __del__()
|
|
||||||
|
|
||||||
from threading import currentThread, enumerate, RLock
|
|
||||||
@@ -28,6 +28,11 @@ import warnings
|
|||||||
import cherrypy
|
import cherrypy
|
||||||
from cherrypy._helper import expose
|
from cherrypy._helper import expose
|
||||||
|
|
||||||
|
from cherrypy.lib import cptools, encoding, auth, static, jsontools
|
||||||
|
from cherrypy.lib import sessions as _sessions, xmlrpcutil as _xmlrpc
|
||||||
|
from cherrypy.lib import caching as _caching
|
||||||
|
from cherrypy.lib import auth_basic, auth_digest
|
||||||
|
|
||||||
|
|
||||||
def _getargs(func):
|
def _getargs(func):
|
||||||
"""Return the names of all static arguments to the given function."""
|
"""Return the names of all static arguments to the given function."""
|
||||||
@@ -45,8 +50,8 @@ def _getargs(func):
|
|||||||
|
|
||||||
|
|
||||||
_attr_error = (
|
_attr_error = (
|
||||||
"CherryPy Tools cannot be turned on directly. Instead, turn them "
|
'CherryPy Tools cannot be turned on directly. Instead, turn them '
|
||||||
"on via config, or use them as decorators on your page handlers."
|
'on via config, or use them as decorators on your page handlers.'
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@@ -57,7 +62,7 @@ class Tool(object):
|
|||||||
help(tool.callable) should give you more information about this Tool.
|
help(tool.callable) should give you more information about this Tool.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
namespace = "tools"
|
namespace = 'tools'
|
||||||
|
|
||||||
def __init__(self, point, callable, name=None, priority=50):
|
def __init__(self, point, callable, name=None, priority=50):
|
||||||
self._point = point
|
self._point = point
|
||||||
@@ -80,7 +85,7 @@ class Tool(object):
|
|||||||
for arg in _getargs(self.callable):
|
for arg in _getargs(self.callable):
|
||||||
setattr(self, arg, None)
|
setattr(self, arg, None)
|
||||||
except (TypeError, AttributeError):
|
except (TypeError, AttributeError):
|
||||||
if hasattr(self.callable, "__call__"):
|
if hasattr(self.callable, '__call__'):
|
||||||
for arg in _getargs(self.callable.__call__):
|
for arg in _getargs(self.callable.__call__):
|
||||||
setattr(self, arg, None)
|
setattr(self, arg, None)
|
||||||
# IronPython 1.0 raises NotImplementedError because
|
# IronPython 1.0 raises NotImplementedError because
|
||||||
@@ -104,8 +109,8 @@ class Tool(object):
|
|||||||
if self._name in tm:
|
if self._name in tm:
|
||||||
conf.update(tm[self._name])
|
conf.update(tm[self._name])
|
||||||
|
|
||||||
if "on" in conf:
|
if 'on' in conf:
|
||||||
del conf["on"]
|
del conf['on']
|
||||||
|
|
||||||
return conf
|
return conf
|
||||||
|
|
||||||
@@ -120,15 +125,15 @@ class Tool(object):
|
|||||||
return cherrypy.request.base
|
return cherrypy.request.base
|
||||||
"""
|
"""
|
||||||
if args:
|
if args:
|
||||||
raise TypeError("The %r Tool does not accept positional "
|
raise TypeError('The %r Tool does not accept positional '
|
||||||
"arguments; you must use keyword arguments."
|
'arguments; you must use keyword arguments.'
|
||||||
% self._name)
|
% self._name)
|
||||||
|
|
||||||
def tool_decorator(f):
|
def tool_decorator(f):
|
||||||
if not hasattr(f, "_cp_config"):
|
if not hasattr(f, '_cp_config'):
|
||||||
f._cp_config = {}
|
f._cp_config = {}
|
||||||
subspace = self.namespace + "." + self._name + "."
|
subspace = self.namespace + '.' + self._name + '.'
|
||||||
f._cp_config[subspace + "on"] = True
|
f._cp_config[subspace + 'on'] = True
|
||||||
for k, v in kwargs.items():
|
for k, v in kwargs.items():
|
||||||
f._cp_config[subspace + k] = v
|
f._cp_config[subspace + k] = v
|
||||||
return f
|
return f
|
||||||
@@ -141,9 +146,9 @@ class Tool(object):
|
|||||||
method when the tool is "turned on" in config.
|
method when the tool is "turned on" in config.
|
||||||
"""
|
"""
|
||||||
conf = self._merged_args()
|
conf = self._merged_args()
|
||||||
p = conf.pop("priority", None)
|
p = conf.pop('priority', None)
|
||||||
if p is None:
|
if p is None:
|
||||||
p = getattr(self.callable, "priority", self._priority)
|
p = getattr(self.callable, 'priority', self._priority)
|
||||||
cherrypy.serving.request.hooks.attach(self._point, self.callable,
|
cherrypy.serving.request.hooks.attach(self._point, self.callable,
|
||||||
priority=p, **conf)
|
priority=p, **conf)
|
||||||
|
|
||||||
@@ -191,9 +196,9 @@ class HandlerTool(Tool):
|
|||||||
method when the tool is "turned on" in config.
|
method when the tool is "turned on" in config.
|
||||||
"""
|
"""
|
||||||
conf = self._merged_args()
|
conf = self._merged_args()
|
||||||
p = conf.pop("priority", None)
|
p = conf.pop('priority', None)
|
||||||
if p is None:
|
if p is None:
|
||||||
p = getattr(self.callable, "priority", self._priority)
|
p = getattr(self.callable, 'priority', self._priority)
|
||||||
cherrypy.serving.request.hooks.attach(self._point, self._wrapper,
|
cherrypy.serving.request.hooks.attach(self._point, self._wrapper,
|
||||||
priority=p, **conf)
|
priority=p, **conf)
|
||||||
|
|
||||||
@@ -254,11 +259,6 @@ class ErrorTool(Tool):
|
|||||||
|
|
||||||
# Builtin tools #
|
# Builtin tools #
|
||||||
|
|
||||||
from cherrypy.lib import cptools, encoding, auth, static, jsontools
|
|
||||||
from cherrypy.lib import sessions as _sessions, xmlrpcutil as _xmlrpc
|
|
||||||
from cherrypy.lib import caching as _caching
|
|
||||||
from cherrypy.lib import auth_basic, auth_digest
|
|
||||||
|
|
||||||
|
|
||||||
class SessionTool(Tool):
|
class SessionTool(Tool):
|
||||||
|
|
||||||
@@ -296,9 +296,9 @@ class SessionTool(Tool):
|
|||||||
|
|
||||||
conf = self._merged_args()
|
conf = self._merged_args()
|
||||||
|
|
||||||
p = conf.pop("priority", None)
|
p = conf.pop('priority', None)
|
||||||
if p is None:
|
if p is None:
|
||||||
p = getattr(self.callable, "priority", self._priority)
|
p = getattr(self.callable, 'priority', self._priority)
|
||||||
|
|
||||||
hooks.attach(self._point, self.callable, priority=p, **conf)
|
hooks.attach(self._point, self.callable, priority=p, **conf)
|
||||||
|
|
||||||
@@ -374,7 +374,7 @@ class XMLRPCController(object):
|
|||||||
for attr in str(rpcmethod).split('.'):
|
for attr in str(rpcmethod).split('.'):
|
||||||
subhandler = getattr(subhandler, attr, None)
|
subhandler = getattr(subhandler, attr, None)
|
||||||
|
|
||||||
if subhandler and getattr(subhandler, "exposed", False):
|
if subhandler and getattr(subhandler, 'exposed', False):
|
||||||
body = subhandler(*(vpath + rpcparams), **params)
|
body = subhandler(*(vpath + rpcparams), **params)
|
||||||
|
|
||||||
else:
|
else:
|
||||||
@@ -384,7 +384,7 @@ class XMLRPCController(object):
|
|||||||
# cherrypy.lib.xmlrpcutil.on_error
|
# cherrypy.lib.xmlrpcutil.on_error
|
||||||
raise Exception('method "%s" is not supported' % attr)
|
raise Exception('method "%s" is not supported' % attr)
|
||||||
|
|
||||||
conf = cherrypy.serving.request.toolmaps['tools'].get("xmlrpc", {})
|
conf = cherrypy.serving.request.toolmaps['tools'].get('xmlrpc', {})
|
||||||
_xmlrpc.respond(body,
|
_xmlrpc.respond(body,
|
||||||
conf.get('encoding', 'utf-8'),
|
conf.get('encoding', 'utf-8'),
|
||||||
conf.get('allow_none', 0))
|
conf.get('allow_none', 0))
|
||||||
@@ -395,7 +395,7 @@ class SessionAuthTool(HandlerTool):
|
|||||||
|
|
||||||
def _setargs(self):
|
def _setargs(self):
|
||||||
for name in dir(cptools.SessionAuth):
|
for name in dir(cptools.SessionAuth):
|
||||||
if not name.startswith("__"):
|
if not name.startswith('__'):
|
||||||
setattr(self, name, None)
|
setattr(self, name, None)
|
||||||
|
|
||||||
|
|
||||||
@@ -418,7 +418,7 @@ class CachingTool(Tool):
|
|||||||
"""Hook caching into cherrypy.request."""
|
"""Hook caching into cherrypy.request."""
|
||||||
conf = self._merged_args()
|
conf = self._merged_args()
|
||||||
|
|
||||||
p = conf.pop("priority", None)
|
p = conf.pop('priority', None)
|
||||||
cherrypy.serving.request.hooks.attach('before_handler', self._wrapper,
|
cherrypy.serving.request.hooks.attach('before_handler', self._wrapper,
|
||||||
priority=p, **conf)
|
priority=p, **conf)
|
||||||
|
|
||||||
@@ -447,7 +447,7 @@ class Toolbox(object):
|
|||||||
cherrypy.serving.request.toolmaps[self.namespace] = map = {}
|
cherrypy.serving.request.toolmaps[self.namespace] = map = {}
|
||||||
|
|
||||||
def populate(k, v):
|
def populate(k, v):
|
||||||
toolname, arg = k.split(".", 1)
|
toolname, arg = k.split('.', 1)
|
||||||
bucket = map.setdefault(toolname, {})
|
bucket = map.setdefault(toolname, {})
|
||||||
bucket[arg] = v
|
bucket[arg] = v
|
||||||
return populate
|
return populate
|
||||||
@@ -457,7 +457,7 @@ class Toolbox(object):
|
|||||||
map = cherrypy.serving.request.toolmaps.get(self.namespace)
|
map = cherrypy.serving.request.toolmaps.get(self.namespace)
|
||||||
if map:
|
if map:
|
||||||
for name, settings in map.items():
|
for name, settings in map.items():
|
||||||
if settings.get("on", False):
|
if settings.get('on', False):
|
||||||
tool = getattr(self, name)
|
tool = getattr(self, name)
|
||||||
tool._setup()
|
tool._setup()
|
||||||
|
|
||||||
@@ -472,7 +472,7 @@ class Toolbox(object):
|
|||||||
class DeprecatedTool(Tool):
|
class DeprecatedTool(Tool):
|
||||||
|
|
||||||
_name = None
|
_name = None
|
||||||
warnmsg = "This Tool is deprecated."
|
warnmsg = 'This Tool is deprecated.'
|
||||||
|
|
||||||
def __init__(self, point, warnmsg=None):
|
def __init__(self, point, warnmsg=None):
|
||||||
self.point = point
|
self.point = point
|
||||||
@@ -490,7 +490,7 @@ class DeprecatedTool(Tool):
|
|||||||
warnings.warn(self.warnmsg)
|
warnings.warn(self.warnmsg)
|
||||||
|
|
||||||
|
|
||||||
default_toolbox = _d = Toolbox("tools")
|
default_toolbox = _d = Toolbox('tools')
|
||||||
_d.session_auth = SessionAuthTool(cptools.session_auth)
|
_d.session_auth = SessionAuthTool(cptools.session_auth)
|
||||||
_d.allow = Tool('on_start_resource', cptools.allow)
|
_d.allow = Tool('on_start_resource', cptools.allow)
|
||||||
_d.proxy = Tool('before_request_body', cptools.proxy, priority=30)
|
_d.proxy = Tool('before_request_body', cptools.proxy, priority=30)
|
||||||
@@ -512,14 +512,14 @@ _d.caching = CachingTool('before_handler', _caching.get, 'caching')
|
|||||||
_d.expires = Tool('before_finalize', _caching.expires)
|
_d.expires = Tool('before_finalize', _caching.expires)
|
||||||
_d.tidy = DeprecatedTool(
|
_d.tidy = DeprecatedTool(
|
||||||
'before_finalize',
|
'before_finalize',
|
||||||
"The tidy tool has been removed from the standard distribution of "
|
'The tidy tool has been removed from the standard distribution of '
|
||||||
"CherryPy. The most recent version can be found at "
|
'CherryPy. The most recent version can be found at '
|
||||||
"http://tools.cherrypy.org/browser.")
|
'http://tools.cherrypy.org/browser.')
|
||||||
_d.nsgmls = DeprecatedTool(
|
_d.nsgmls = DeprecatedTool(
|
||||||
'before_finalize',
|
'before_finalize',
|
||||||
"The nsgmls tool has been removed from the standard distribution of "
|
'The nsgmls tool has been removed from the standard distribution of '
|
||||||
"CherryPy. The most recent version can be found at "
|
'CherryPy. The most recent version can be found at '
|
||||||
"http://tools.cherrypy.org/browser.")
|
'http://tools.cherrypy.org/browser.')
|
||||||
_d.ignore_headers = Tool('before_request_body', cptools.ignore_headers)
|
_d.ignore_headers = Tool('before_request_body', cptools.ignore_headers)
|
||||||
_d.referer = Tool('before_request_body', cptools.referer)
|
_d.referer = Tool('before_request_body', cptools.referer)
|
||||||
_d.basic_auth = Tool('on_start_resource', auth.basic_auth)
|
_d.basic_auth = Tool('on_start_resource', auth.basic_auth)
|
||||||
@@ -533,5 +533,6 @@ _d.json_in = Tool('before_request_body', jsontools.json_in, priority=30)
|
|||||||
_d.json_out = Tool('before_handler', jsontools.json_out, priority=30)
|
_d.json_out = Tool('before_handler', jsontools.json_out, priority=30)
|
||||||
_d.auth_basic = Tool('before_handler', auth_basic.basic_auth, priority=1)
|
_d.auth_basic = Tool('before_handler', auth_basic.basic_auth, priority=1)
|
||||||
_d.auth_digest = Tool('before_handler', auth_digest.digest_auth, priority=1)
|
_d.auth_digest = Tool('before_handler', auth_digest.digest_auth, priority=1)
|
||||||
|
_d.params = Tool('before_handler', cptools.convert_params)
|
||||||
|
|
||||||
del _d, cptools, encoding, auth, static
|
del _d, cptools, encoding, auth, static
|
||||||
@@ -46,22 +46,22 @@ class Application(object):
|
|||||||
|
|
||||||
relative_urls = False
|
relative_urls = False
|
||||||
|
|
||||||
def __init__(self, root, script_name="", config=None):
|
def __init__(self, root, script_name='', config=None):
|
||||||
self.log = _cplogging.LogManager(id(self), cherrypy.log.logger_root)
|
self.log = _cplogging.LogManager(id(self), cherrypy.log.logger_root)
|
||||||
self.root = root
|
self.root = root
|
||||||
self.script_name = script_name
|
self.script_name = script_name
|
||||||
self.wsgiapp = _cpwsgi.CPWSGIApp(self)
|
self.wsgiapp = _cpwsgi.CPWSGIApp(self)
|
||||||
|
|
||||||
self.namespaces = self.namespaces.copy()
|
self.namespaces = self.namespaces.copy()
|
||||||
self.namespaces["log"] = lambda k, v: setattr(self.log, k, v)
|
self.namespaces['log'] = lambda k, v: setattr(self.log, k, v)
|
||||||
self.namespaces["wsgi"] = self.wsgiapp.namespace_handler
|
self.namespaces['wsgi'] = self.wsgiapp.namespace_handler
|
||||||
|
|
||||||
self.config = self.__class__.config.copy()
|
self.config = self.__class__.config.copy()
|
||||||
if config:
|
if config:
|
||||||
self.merge(config)
|
self.merge(config)
|
||||||
|
|
||||||
def __repr__(self):
|
def __repr__(self):
|
||||||
return "%s.%s(%r, %r)" % (self.__module__, self.__class__.__name__,
|
return '%s.%s(%r, %r)' % (self.__module__, self.__class__.__name__,
|
||||||
self.root, self.script_name)
|
self.root, self.script_name)
|
||||||
|
|
||||||
script_name_doc = """The URI "mount point" for this app. A mount point
|
script_name_doc = """The URI "mount point" for this app. A mount point
|
||||||
@@ -86,11 +86,11 @@ class Application(object):
|
|||||||
|
|
||||||
# A `_script_name` with a value of None signals that the script name
|
# A `_script_name` with a value of None signals that the script name
|
||||||
# should be pulled from WSGI environ.
|
# should be pulled from WSGI environ.
|
||||||
return cherrypy.serving.request.wsgi_environ['SCRIPT_NAME'].rstrip("/")
|
return cherrypy.serving.request.wsgi_environ['SCRIPT_NAME'].rstrip('/')
|
||||||
|
|
||||||
def _set_script_name(self, value):
|
def _set_script_name(self, value):
|
||||||
if value:
|
if value:
|
||||||
value = value.rstrip("/")
|
value = value.rstrip('/')
|
||||||
self._script_name = value
|
self._script_name = value
|
||||||
script_name = property(fget=_get_script_name, fset=_set_script_name,
|
script_name = property(fget=_get_script_name, fset=_set_script_name,
|
||||||
doc=script_name_doc)
|
doc=script_name_doc)
|
||||||
@@ -100,22 +100,22 @@ class Application(object):
|
|||||||
_cpconfig.merge(self.config, config)
|
_cpconfig.merge(self.config, config)
|
||||||
|
|
||||||
# Handle namespaces specified in config.
|
# Handle namespaces specified in config.
|
||||||
self.namespaces(self.config.get("/", {}))
|
self.namespaces(self.config.get('/', {}))
|
||||||
|
|
||||||
def find_config(self, path, key, default=None):
|
def find_config(self, path, key, default=None):
|
||||||
"""Return the most-specific value for key along path, or default."""
|
"""Return the most-specific value for key along path, or default."""
|
||||||
trail = path or "/"
|
trail = path or '/'
|
||||||
while trail:
|
while trail:
|
||||||
nodeconf = self.config.get(trail, {})
|
nodeconf = self.config.get(trail, {})
|
||||||
|
|
||||||
if key in nodeconf:
|
if key in nodeconf:
|
||||||
return nodeconf[key]
|
return nodeconf[key]
|
||||||
|
|
||||||
lastslash = trail.rfind("/")
|
lastslash = trail.rfind('/')
|
||||||
if lastslash == -1:
|
if lastslash == -1:
|
||||||
break
|
break
|
||||||
elif lastslash == 0 and trail != "/":
|
elif lastslash == 0 and trail != '/':
|
||||||
trail = "/"
|
trail = '/'
|
||||||
else:
|
else:
|
||||||
trail = trail[:lastslash]
|
trail = trail[:lastslash]
|
||||||
|
|
||||||
@@ -172,7 +172,7 @@ class Tree(object):
|
|||||||
def __init__(self):
|
def __init__(self):
|
||||||
self.apps = {}
|
self.apps = {}
|
||||||
|
|
||||||
def mount(self, root, script_name="", config=None):
|
def mount(self, root, script_name='', config=None):
|
||||||
"""Mount a new app from a root object, script_name, and config.
|
"""Mount a new app from a root object, script_name, and config.
|
||||||
|
|
||||||
root
|
root
|
||||||
@@ -197,29 +197,29 @@ class Tree(object):
|
|||||||
if script_name is None:
|
if script_name is None:
|
||||||
raise TypeError(
|
raise TypeError(
|
||||||
"The 'script_name' argument may not be None. Application "
|
"The 'script_name' argument may not be None. Application "
|
||||||
"objects may, however, possess a script_name of None (in "
|
'objects may, however, possess a script_name of None (in '
|
||||||
"order to inpect the WSGI environ for SCRIPT_NAME upon each "
|
'order to inpect the WSGI environ for SCRIPT_NAME upon each '
|
||||||
"request). You cannot mount such Applications on this Tree; "
|
'request). You cannot mount such Applications on this Tree; '
|
||||||
"you must pass them to a WSGI server interface directly.")
|
'you must pass them to a WSGI server interface directly.')
|
||||||
|
|
||||||
# Next line both 1) strips trailing slash and 2) maps "/" -> "".
|
# Next line both 1) strips trailing slash and 2) maps "/" -> "".
|
||||||
script_name = script_name.rstrip("/")
|
script_name = script_name.rstrip('/')
|
||||||
|
|
||||||
if isinstance(root, Application):
|
if isinstance(root, Application):
|
||||||
app = root
|
app = root
|
||||||
if script_name != "" and script_name != app.script_name:
|
if script_name != '' and script_name != app.script_name:
|
||||||
raise ValueError(
|
raise ValueError(
|
||||||
"Cannot specify a different script name and pass an "
|
'Cannot specify a different script name and pass an '
|
||||||
"Application instance to cherrypy.mount")
|
'Application instance to cherrypy.mount')
|
||||||
script_name = app.script_name
|
script_name = app.script_name
|
||||||
else:
|
else:
|
||||||
app = Application(root, script_name)
|
app = Application(root, script_name)
|
||||||
|
|
||||||
# If mounted at "", add favicon.ico
|
# If mounted at "", add favicon.ico
|
||||||
if (script_name == "" and root is not None
|
if (script_name == '' and root is not None
|
||||||
and not hasattr(root, "favicon_ico")):
|
and not hasattr(root, 'favicon_ico')):
|
||||||
favicon = os.path.join(os.getcwd(), os.path.dirname(__file__),
|
favicon = os.path.join(os.getcwd(), os.path.dirname(__file__),
|
||||||
"favicon.ico")
|
'favicon.ico')
|
||||||
root.favicon_ico = tools.staticfile.handler(favicon)
|
root.favicon_ico = tools.staticfile.handler(favicon)
|
||||||
|
|
||||||
if config:
|
if config:
|
||||||
@@ -229,10 +229,10 @@ class Tree(object):
|
|||||||
|
|
||||||
return app
|
return app
|
||||||
|
|
||||||
def graft(self, wsgi_callable, script_name=""):
|
def graft(self, wsgi_callable, script_name=''):
|
||||||
"""Mount a wsgi callable at the given script_name."""
|
"""Mount a wsgi callable at the given script_name."""
|
||||||
# Next line both 1) strips trailing slash and 2) maps "/" -> "".
|
# Next line both 1) strips trailing slash and 2) maps "/" -> "".
|
||||||
script_name = script_name.rstrip("/")
|
script_name = script_name.rstrip('/')
|
||||||
self.apps[script_name] = wsgi_callable
|
self.apps[script_name] = wsgi_callable
|
||||||
|
|
||||||
def script_name(self, path=None):
|
def script_name(self, path=None):
|
||||||
@@ -252,11 +252,11 @@ class Tree(object):
|
|||||||
if path in self.apps:
|
if path in self.apps:
|
||||||
return path
|
return path
|
||||||
|
|
||||||
if path == "":
|
if path == '':
|
||||||
return None
|
return None
|
||||||
|
|
||||||
# Move one node up the tree and try again.
|
# Move one node up the tree and try again.
|
||||||
path = path[:path.rfind("/")]
|
path = path[:path.rfind('/')]
|
||||||
|
|
||||||
def __call__(self, environ, start_response):
|
def __call__(self, environ, start_response):
|
||||||
# If you're calling this, then you're probably setting SCRIPT_NAME
|
# If you're calling this, then you're probably setting SCRIPT_NAME
|
||||||
@@ -267,7 +267,7 @@ class Tree(object):
|
|||||||
env1x = _cpwsgi.downgrade_wsgi_ux_to_1x(environ)
|
env1x = _cpwsgi.downgrade_wsgi_ux_to_1x(environ)
|
||||||
path = httputil.urljoin(env1x.get('SCRIPT_NAME', ''),
|
path = httputil.urljoin(env1x.get('SCRIPT_NAME', ''),
|
||||||
env1x.get('PATH_INFO', ''))
|
env1x.get('PATH_INFO', ''))
|
||||||
sn = self.script_name(path or "/")
|
sn = self.script_name(path or '/')
|
||||||
if sn is None:
|
if sn is None:
|
||||||
start_response('404 Not Found', [])
|
start_response('404 Not Found', [])
|
||||||
return []
|
return []
|
||||||
@@ -280,8 +280,8 @@ class Tree(object):
|
|||||||
# Python 2/WSGI u.0: all strings MUST be of type unicode
|
# Python 2/WSGI u.0: all strings MUST be of type unicode
|
||||||
enc = environ[ntou('wsgi.url_encoding')]
|
enc = environ[ntou('wsgi.url_encoding')]
|
||||||
environ[ntou('SCRIPT_NAME')] = sn.decode(enc)
|
environ[ntou('SCRIPT_NAME')] = sn.decode(enc)
|
||||||
environ[ntou('PATH_INFO')] = path[len(sn.rstrip("/")):].decode(enc)
|
environ[ntou('PATH_INFO')] = path[len(sn.rstrip('/')):].decode(enc)
|
||||||
else:
|
else:
|
||||||
environ['SCRIPT_NAME'] = sn
|
environ['SCRIPT_NAME'] = sn
|
||||||
environ['PATH_INFO'] = path[len(sn.rstrip("/")):]
|
environ['PATH_INFO'] = path[len(sn.rstrip('/')):]
|
||||||
return app(environ, start_response)
|
return app(environ, start_response)
|
||||||
@@ -46,10 +46,13 @@ class VirtualHost(object):
|
|||||||
Domain2App = cherrypy.Application(root)
|
Domain2App = cherrypy.Application(root)
|
||||||
SecureApp = cherrypy.Application(Secure())
|
SecureApp = cherrypy.Application(Secure())
|
||||||
|
|
||||||
vhost = cherrypy._cpwsgi.VirtualHost(RootApp,
|
vhost = cherrypy._cpwsgi.VirtualHost(
|
||||||
domains={'www.domain2.example': Domain2App,
|
RootApp,
|
||||||
'www.domain2.example:443': SecureApp,
|
domains={
|
||||||
})
|
'www.domain2.example': Domain2App,
|
||||||
|
'www.domain2.example:443': SecureApp,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
cherrypy.tree.graft(vhost)
|
cherrypy.tree.graft(vhost)
|
||||||
"""
|
"""
|
||||||
@@ -78,7 +81,7 @@ class VirtualHost(object):
|
|||||||
def __call__(self, environ, start_response):
|
def __call__(self, environ, start_response):
|
||||||
domain = environ.get('HTTP_HOST', '')
|
domain = environ.get('HTTP_HOST', '')
|
||||||
if self.use_x_forwarded_host:
|
if self.use_x_forwarded_host:
|
||||||
domain = environ.get("HTTP_X_FORWARDED_HOST", domain)
|
domain = environ.get('HTTP_X_FORWARDED_HOST', domain)
|
||||||
|
|
||||||
nextapp = self.domains.get(domain)
|
nextapp = self.domains.get(domain)
|
||||||
if nextapp is None:
|
if nextapp is None:
|
||||||
@@ -109,7 +112,7 @@ class InternalRedirector(object):
|
|||||||
# Add the *previous* path_info + qs to redirections.
|
# Add the *previous* path_info + qs to redirections.
|
||||||
old_uri = sn + path
|
old_uri = sn + path
|
||||||
if qs:
|
if qs:
|
||||||
old_uri += "?" + qs
|
old_uri += '?' + qs
|
||||||
redirections.append(old_uri)
|
redirections.append(old_uri)
|
||||||
|
|
||||||
if not self.recursive:
|
if not self.recursive:
|
||||||
@@ -117,18 +120,20 @@ class InternalRedirector(object):
|
|||||||
# already
|
# already
|
||||||
new_uri = sn + ir.path
|
new_uri = sn + ir.path
|
||||||
if ir.query_string:
|
if ir.query_string:
|
||||||
new_uri += "?" + ir.query_string
|
new_uri += '?' + ir.query_string
|
||||||
if new_uri in redirections:
|
if new_uri in redirections:
|
||||||
ir.request.close()
|
ir.request.close()
|
||||||
raise RuntimeError("InternalRedirector visited the "
|
tmpl = (
|
||||||
"same URL twice: %r" % new_uri)
|
'InternalRedirector visited the same URL twice: %r'
|
||||||
|
)
|
||||||
|
raise RuntimeError(tmpl % new_uri)
|
||||||
|
|
||||||
# Munge the environment and try again.
|
# Munge the environment and try again.
|
||||||
environ['REQUEST_METHOD'] = "GET"
|
environ['REQUEST_METHOD'] = 'GET'
|
||||||
environ['PATH_INFO'] = ir.path
|
environ['PATH_INFO'] = ir.path
|
||||||
environ['QUERY_STRING'] = ir.query_string
|
environ['QUERY_STRING'] = ir.query_string
|
||||||
environ['wsgi.input'] = io.BytesIO()
|
environ['wsgi.input'] = io.BytesIO()
|
||||||
environ['CONTENT_LENGTH'] = "0"
|
environ['CONTENT_LENGTH'] = '0'
|
||||||
environ['cherrypy.previous_request'] = ir.request
|
environ['cherrypy.previous_request'] = ir.request
|
||||||
|
|
||||||
|
|
||||||
@@ -160,7 +165,8 @@ class _TrappedResponse(object):
|
|||||||
self.throws = throws
|
self.throws = throws
|
||||||
self.started_response = False
|
self.started_response = False
|
||||||
self.response = self.trap(
|
self.response = self.trap(
|
||||||
self.nextapp, self.environ, self.start_response)
|
self.nextapp, self.environ, self.start_response,
|
||||||
|
)
|
||||||
self.iter_response = iter(self.response)
|
self.iter_response = iter(self.response)
|
||||||
|
|
||||||
def __iter__(self):
|
def __iter__(self):
|
||||||
@@ -187,16 +193,17 @@ class _TrappedResponse(object):
|
|||||||
raise
|
raise
|
||||||
except:
|
except:
|
||||||
tb = _cperror.format_exc()
|
tb = _cperror.format_exc()
|
||||||
#print('trapped (started %s):' % self.started_response, tb)
|
|
||||||
_cherrypy.log(tb, severity=40)
|
_cherrypy.log(tb, severity=40)
|
||||||
if not _cherrypy.request.show_tracebacks:
|
if not _cherrypy.request.show_tracebacks:
|
||||||
tb = ""
|
tb = ''
|
||||||
s, h, b = _cperror.bare_error(tb)
|
s, h, b = _cperror.bare_error(tb)
|
||||||
if six.PY3:
|
if six.PY3:
|
||||||
# What fun.
|
# What fun.
|
||||||
s = s.decode('ISO-8859-1')
|
s = s.decode('ISO-8859-1')
|
||||||
h = [(k.decode('ISO-8859-1'), v.decode('ISO-8859-1'))
|
h = [
|
||||||
for k, v in h]
|
(k.decode('ISO-8859-1'), v.decode('ISO-8859-1'))
|
||||||
|
for k, v in h
|
||||||
|
]
|
||||||
if self.started_response:
|
if self.started_response:
|
||||||
# Empty our iterable (so future calls raise StopIteration)
|
# Empty our iterable (so future calls raise StopIteration)
|
||||||
self.iter_response = iter([])
|
self.iter_response = iter([])
|
||||||
@@ -215,7 +222,7 @@ class _TrappedResponse(object):
|
|||||||
raise
|
raise
|
||||||
|
|
||||||
if self.started_response:
|
if self.started_response:
|
||||||
return ntob("").join(b)
|
return ntob('').join(b)
|
||||||
else:
|
else:
|
||||||
return b
|
return b
|
||||||
|
|
||||||
@@ -240,18 +247,18 @@ class AppResponse(object):
|
|||||||
|
|
||||||
outstatus = r.output_status
|
outstatus = r.output_status
|
||||||
if not isinstance(outstatus, bytes):
|
if not isinstance(outstatus, bytes):
|
||||||
raise TypeError("response.output_status is not a byte string.")
|
raise TypeError('response.output_status is not a byte string.')
|
||||||
|
|
||||||
outheaders = []
|
outheaders = []
|
||||||
for k, v in r.header_list:
|
for k, v in r.header_list:
|
||||||
if not isinstance(k, bytes):
|
if not isinstance(k, bytes):
|
||||||
raise TypeError(
|
tmpl = 'response.header_list key %r is not a byte string.'
|
||||||
"response.header_list key %r is not a byte string." %
|
raise TypeError(tmpl % k)
|
||||||
k)
|
|
||||||
if not isinstance(v, bytes):
|
if not isinstance(v, bytes):
|
||||||
raise TypeError(
|
tmpl = (
|
||||||
"response.header_list value %r is not a byte string." %
|
'response.header_list value %r is not a byte string.'
|
||||||
v)
|
)
|
||||||
|
raise TypeError(tmpl % v)
|
||||||
outheaders.append((k, v))
|
outheaders.append((k, v))
|
||||||
|
|
||||||
if six.PY3:
|
if six.PY3:
|
||||||
@@ -260,8 +267,10 @@ class AppResponse(object):
|
|||||||
# that is, they must be of type "str" but are restricted to
|
# that is, they must be of type "str" but are restricted to
|
||||||
# code points in the "latin-1" set.
|
# code points in the "latin-1" set.
|
||||||
outstatus = outstatus.decode('ISO-8859-1')
|
outstatus = outstatus.decode('ISO-8859-1')
|
||||||
outheaders = [(k.decode('ISO-8859-1'), v.decode('ISO-8859-1'))
|
outheaders = [
|
||||||
for k, v in outheaders]
|
(k.decode('ISO-8859-1'), v.decode('ISO-8859-1'))
|
||||||
|
for k, v in outheaders
|
||||||
|
]
|
||||||
|
|
||||||
self.iter_response = iter(r.body)
|
self.iter_response = iter(r.body)
|
||||||
self.write = start_response(outstatus, outheaders)
|
self.write = start_response(outstatus, outheaders)
|
||||||
@@ -299,14 +308,18 @@ class AppResponse(object):
|
|||||||
"""Create a Request object using environ."""
|
"""Create a Request object using environ."""
|
||||||
env = self.environ.get
|
env = self.environ.get
|
||||||
|
|
||||||
local = httputil.Host('',
|
local = httputil.Host(
|
||||||
int(env('SERVER_PORT', 80) or -1),
|
'',
|
||||||
env('SERVER_NAME', ''))
|
int(env('SERVER_PORT', 80) or -1),
|
||||||
remote = httputil.Host(env('REMOTE_ADDR', ''),
|
env('SERVER_NAME', ''),
|
||||||
int(env('REMOTE_PORT', -1) or -1),
|
)
|
||||||
env('REMOTE_HOST', ''))
|
remote = httputil.Host(
|
||||||
|
env('REMOTE_ADDR', ''),
|
||||||
|
int(env('REMOTE_PORT', -1) or -1),
|
||||||
|
env('REMOTE_HOST', ''),
|
||||||
|
)
|
||||||
scheme = env('wsgi.url_scheme')
|
scheme = env('wsgi.url_scheme')
|
||||||
sproto = env('ACTUAL_SERVER_PROTOCOL', "HTTP/1.1")
|
sproto = env('ACTUAL_SERVER_PROTOCOL', 'HTTP/1.1')
|
||||||
request, resp = self.cpapp.get_serving(local, remote, scheme, sproto)
|
request, resp = self.cpapp.get_serving(local, remote, scheme, sproto)
|
||||||
|
|
||||||
# LOGON_USER is served by IIS, and is the name of the
|
# LOGON_USER is served by IIS, and is the name of the
|
||||||
@@ -320,44 +333,54 @@ class AppResponse(object):
|
|||||||
|
|
||||||
meth = self.environ['REQUEST_METHOD']
|
meth = self.environ['REQUEST_METHOD']
|
||||||
|
|
||||||
path = httputil.urljoin(self.environ.get('SCRIPT_NAME', ''),
|
path = httputil.urljoin(
|
||||||
self.environ.get('PATH_INFO', ''))
|
self.environ.get('SCRIPT_NAME', ''),
|
||||||
|
self.environ.get('PATH_INFO', ''),
|
||||||
|
)
|
||||||
qs = self.environ.get('QUERY_STRING', '')
|
qs = self.environ.get('QUERY_STRING', '')
|
||||||
|
|
||||||
if six.PY3:
|
path, qs = self.recode_path_qs(path, qs) or (path, qs)
|
||||||
# This isn't perfect; if the given PATH_INFO is in the
|
|
||||||
# wrong encoding, it may fail to match the appropriate config
|
|
||||||
# section URI. But meh.
|
|
||||||
old_enc = self.environ.get('wsgi.url_encoding', 'ISO-8859-1')
|
|
||||||
new_enc = self.cpapp.find_config(self.environ.get('PATH_INFO', ''),
|
|
||||||
"request.uri_encoding", 'utf-8')
|
|
||||||
if new_enc.lower() != old_enc.lower():
|
|
||||||
# Even though the path and qs are unicode, the WSGI server
|
|
||||||
# is required by PEP 3333 to coerce them to ISO-8859-1
|
|
||||||
# masquerading as unicode. So we have to encode back to
|
|
||||||
# bytes and then decode again using the "correct" encoding.
|
|
||||||
try:
|
|
||||||
u_path = path.encode(old_enc).decode(new_enc)
|
|
||||||
u_qs = qs.encode(old_enc).decode(new_enc)
|
|
||||||
except (UnicodeEncodeError, UnicodeDecodeError):
|
|
||||||
# Just pass them through without transcoding and hope.
|
|
||||||
pass
|
|
||||||
else:
|
|
||||||
# Only set transcoded values if they both succeed.
|
|
||||||
path = u_path
|
|
||||||
qs = u_qs
|
|
||||||
|
|
||||||
rproto = self.environ.get('SERVER_PROTOCOL')
|
rproto = self.environ.get('SERVER_PROTOCOL')
|
||||||
headers = self.translate_headers(self.environ)
|
headers = self.translate_headers(self.environ)
|
||||||
rfile = self.environ['wsgi.input']
|
rfile = self.environ['wsgi.input']
|
||||||
request.run(meth, path, qs, rproto, headers, rfile)
|
request.run(meth, path, qs, rproto, headers, rfile)
|
||||||
|
|
||||||
headerNames = {'HTTP_CGI_AUTHORIZATION': 'Authorization',
|
headerNames = {
|
||||||
'CONTENT_LENGTH': 'Content-Length',
|
'HTTP_CGI_AUTHORIZATION': 'Authorization',
|
||||||
'CONTENT_TYPE': 'Content-Type',
|
'CONTENT_LENGTH': 'Content-Length',
|
||||||
'REMOTE_HOST': 'Remote-Host',
|
'CONTENT_TYPE': 'Content-Type',
|
||||||
'REMOTE_ADDR': 'Remote-Addr',
|
'REMOTE_HOST': 'Remote-Host',
|
||||||
}
|
'REMOTE_ADDR': 'Remote-Addr',
|
||||||
|
}
|
||||||
|
|
||||||
|
def recode_path_qs(self, path, qs):
|
||||||
|
if not six.PY3:
|
||||||
|
return
|
||||||
|
|
||||||
|
# This isn't perfect; if the given PATH_INFO is in the
|
||||||
|
# wrong encoding, it may fail to match the appropriate config
|
||||||
|
# section URI. But meh.
|
||||||
|
old_enc = self.environ.get('wsgi.url_encoding', 'ISO-8859-1')
|
||||||
|
new_enc = self.cpapp.find_config(
|
||||||
|
self.environ.get('PATH_INFO', ''),
|
||||||
|
'request.uri_encoding', 'utf-8',
|
||||||
|
)
|
||||||
|
if new_enc.lower() == old_enc.lower():
|
||||||
|
return
|
||||||
|
|
||||||
|
# Even though the path and qs are unicode, the WSGI server
|
||||||
|
# is required by PEP 3333 to coerce them to ISO-8859-1
|
||||||
|
# masquerading as unicode. So we have to encode back to
|
||||||
|
# bytes and then decode again using the "correct" encoding.
|
||||||
|
try:
|
||||||
|
return (
|
||||||
|
path.encode(old_enc).decode(new_enc),
|
||||||
|
qs.encode(old_enc).decode(new_enc),
|
||||||
|
)
|
||||||
|
except (UnicodeEncodeError, UnicodeDecodeError):
|
||||||
|
# Just pass them through without transcoding and hope.
|
||||||
|
pass
|
||||||
|
|
||||||
def translate_headers(self, environ):
|
def translate_headers(self, environ):
|
||||||
"""Translate CGI-environ header names to HTTP header names."""
|
"""Translate CGI-environ header names to HTTP header names."""
|
||||||
@@ -365,9 +388,9 @@ class AppResponse(object):
|
|||||||
# We assume all incoming header keys are uppercase already.
|
# We assume all incoming header keys are uppercase already.
|
||||||
if cgiName in self.headerNames:
|
if cgiName in self.headerNames:
|
||||||
yield self.headerNames[cgiName], environ[cgiName]
|
yield self.headerNames[cgiName], environ[cgiName]
|
||||||
elif cgiName[:5] == "HTTP_":
|
elif cgiName[:5] == 'HTTP_':
|
||||||
# Hackish attempt at recovering original header names.
|
# Hackish attempt at recovering original header names.
|
||||||
translatedHeader = cgiName[5:].replace("_", "-")
|
translatedHeader = cgiName[5:].replace('_', '-')
|
||||||
yield translatedHeader, environ[cgiName]
|
yield translatedHeader, environ[cgiName]
|
||||||
|
|
||||||
|
|
||||||
@@ -375,9 +398,10 @@ class CPWSGIApp(object):
|
|||||||
|
|
||||||
"""A WSGI application object for a CherryPy Application."""
|
"""A WSGI application object for a CherryPy Application."""
|
||||||
|
|
||||||
pipeline = [('ExceptionTrapper', ExceptionTrapper),
|
pipeline = [
|
||||||
('InternalRedirector', InternalRedirector),
|
('ExceptionTrapper', ExceptionTrapper),
|
||||||
]
|
('InternalRedirector', InternalRedirector),
|
||||||
|
]
|
||||||
"""A list of (name, wsgiapp) pairs. Each 'wsgiapp' MUST be a
|
"""A list of (name, wsgiapp) pairs. Each 'wsgiapp' MUST be a
|
||||||
constructor that takes an initial, positional 'nextapp' argument,
|
constructor that takes an initial, positional 'nextapp' argument,
|
||||||
plus optional keyword arguments, and returns a WSGI application
|
plus optional keyword arguments, and returns a WSGI application
|
||||||
@@ -427,16 +451,16 @@ class CPWSGIApp(object):
|
|||||||
|
|
||||||
def namespace_handler(self, k, v):
|
def namespace_handler(self, k, v):
|
||||||
"""Config handler for the 'wsgi' namespace."""
|
"""Config handler for the 'wsgi' namespace."""
|
||||||
if k == "pipeline":
|
if k == 'pipeline':
|
||||||
# Note this allows multiple 'wsgi.pipeline' config entries
|
# Note this allows multiple 'wsgi.pipeline' config entries
|
||||||
# (but each entry will be processed in a 'random' order).
|
# (but each entry will be processed in a 'random' order).
|
||||||
# It should also allow developers to set default middleware
|
# It should also allow developers to set default middleware
|
||||||
# in code (passed to self.__init__) that deployers can add to
|
# in code (passed to self.__init__) that deployers can add to
|
||||||
# (but not remove) via config.
|
# (but not remove) via config.
|
||||||
self.pipeline.extend(v)
|
self.pipeline.extend(v)
|
||||||
elif k == "response_class":
|
elif k == 'response_class':
|
||||||
self.response_class = v
|
self.response_class = v
|
||||||
else:
|
else:
|
||||||
name, arg = k.split(".", 1)
|
name, arg = k.split('.', 1)
|
||||||
bucket = self.config.setdefault(name, {})
|
bucket = self.config.setdefault(name, {})
|
||||||
bucket[arg] = v
|
bucket[arg] = v
|
||||||
@@ -66,5 +66,5 @@ class CPWSGIServer(wsgiserver.CherryPyWSGIServer):
|
|||||||
self.stats['Enabled'] = getattr(
|
self.stats['Enabled'] = getattr(
|
||||||
self.server_adapter, 'statistics', False)
|
self.server_adapter, 'statistics', False)
|
||||||
|
|
||||||
def error_log(self, msg="", level=20, traceback=False):
|
def error_log(self, msg='', level=20, traceback=False):
|
||||||
cherrypy.engine.log(msg, level, traceback)
|
cherrypy.engine.log(msg, level, traceback)
|
||||||
@@ -5,7 +5,7 @@ Helper functions for CP apps
|
|||||||
import six
|
import six
|
||||||
|
|
||||||
from cherrypy._cpcompat import urljoin as _urljoin, urlencode as _urlencode
|
from cherrypy._cpcompat import urljoin as _urljoin, urlencode as _urlencode
|
||||||
from cherrypy._cpcompat import basestring
|
from cherrypy._cpcompat import text_or_bytes
|
||||||
|
|
||||||
import cherrypy
|
import cherrypy
|
||||||
|
|
||||||
@@ -17,11 +17,11 @@ def expose(func=None, alias=None):
|
|||||||
def expose_(func):
|
def expose_(func):
|
||||||
func.exposed = True
|
func.exposed = True
|
||||||
if alias is not None:
|
if alias is not None:
|
||||||
if isinstance(alias, basestring):
|
if isinstance(alias, text_or_bytes):
|
||||||
parents[alias.replace(".", "_")] = func
|
parents[alias.replace('.', '_')] = func
|
||||||
else:
|
else:
|
||||||
for a in alias:
|
for a in alias:
|
||||||
parents[a.replace(".", "_")] = func
|
parents[a.replace('.', '_')] = func
|
||||||
return func
|
return func
|
||||||
|
|
||||||
import sys
|
import sys
|
||||||
@@ -191,7 +191,7 @@ def popargs(*args, **kwargs):
|
|||||||
return decorated
|
return decorated
|
||||||
|
|
||||||
|
|
||||||
def url(path="", qs="", script_name=None, base=None, relative=None):
|
def url(path='', qs='', script_name=None, base=None, relative=None):
|
||||||
"""Create an absolute URL for the given path.
|
"""Create an absolute URL for the given path.
|
||||||
|
|
||||||
If 'path' starts with a slash ('/'), this will return
|
If 'path' starts with a slash ('/'), this will return
|
||||||
@@ -224,7 +224,7 @@ def url(path="", qs="", script_name=None, base=None, relative=None):
|
|||||||
qs = '?' + qs
|
qs = '?' + qs
|
||||||
|
|
||||||
if cherrypy.request.app:
|
if cherrypy.request.app:
|
||||||
if not path.startswith("/"):
|
if not path.startswith('/'):
|
||||||
# Append/remove trailing slash from path_info as needed
|
# Append/remove trailing slash from path_info as needed
|
||||||
# (this is to support mistyped URL's without redirecting;
|
# (this is to support mistyped URL's without redirecting;
|
||||||
# if you want to redirect, use tools.trailing_slash).
|
# if you want to redirect, use tools.trailing_slash).
|
||||||
@@ -236,7 +236,7 @@ def url(path="", qs="", script_name=None, base=None, relative=None):
|
|||||||
if pi.endswith('/') and pi != '/':
|
if pi.endswith('/') and pi != '/':
|
||||||
pi = pi[:-1]
|
pi = pi[:-1]
|
||||||
|
|
||||||
if path == "":
|
if path == '':
|
||||||
path = pi
|
path = pi
|
||||||
else:
|
else:
|
||||||
path = _urljoin(pi, path)
|
path = _urljoin(pi, path)
|
||||||
@@ -255,7 +255,7 @@ def url(path="", qs="", script_name=None, base=None, relative=None):
|
|||||||
if base is None:
|
if base is None:
|
||||||
base = cherrypy.server.base()
|
base = cherrypy.server.base()
|
||||||
|
|
||||||
path = (script_name or "") + path
|
path = (script_name or '') + path
|
||||||
newurl = base + path + qs
|
newurl = base + path + qs
|
||||||
|
|
||||||
if './' in newurl:
|
if './' in newurl:
|
||||||
@@ -273,7 +273,7 @@ def url(path="", qs="", script_name=None, base=None, relative=None):
|
|||||||
# At this point, we should have a fully-qualified absolute URL.
|
# At this point, we should have a fully-qualified absolute URL.
|
||||||
|
|
||||||
if relative is None:
|
if relative is None:
|
||||||
relative = getattr(cherrypy.request.app, "relative_urls", False)
|
relative = getattr(cherrypy.request.app, 'relative_urls', False)
|
||||||
|
|
||||||
# See http://www.ietf.org/rfc/rfc2396.txt
|
# See http://www.ietf.org/rfc/rfc2396.txt
|
||||||
if relative == 'server':
|
if relative == 'server':
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ def start(configfiles=None, daemonize=False, environment=None,
|
|||||||
"""Subscribe all engine plugins and start the engine."""
|
"""Subscribe all engine plugins and start the engine."""
|
||||||
sys.path = [''] + sys.path
|
sys.path = [''] + sys.path
|
||||||
for i in imports or []:
|
for i in imports or []:
|
||||||
exec("import %s" % i)
|
exec('import %s' % i)
|
||||||
|
|
||||||
for c in configfiles or []:
|
for c in configfiles or []:
|
||||||
cherrypy.config.update(c)
|
cherrypy.config.update(c)
|
||||||
@@ -37,18 +37,18 @@ def start(configfiles=None, daemonize=False, environment=None,
|
|||||||
if pidfile:
|
if pidfile:
|
||||||
plugins.PIDFile(engine, pidfile).subscribe()
|
plugins.PIDFile(engine, pidfile).subscribe()
|
||||||
|
|
||||||
if hasattr(engine, "signal_handler"):
|
if hasattr(engine, 'signal_handler'):
|
||||||
engine.signal_handler.subscribe()
|
engine.signal_handler.subscribe()
|
||||||
if hasattr(engine, "console_control_handler"):
|
if hasattr(engine, 'console_control_handler'):
|
||||||
engine.console_control_handler.subscribe()
|
engine.console_control_handler.subscribe()
|
||||||
|
|
||||||
if (fastcgi and (scgi or cgi)) or (scgi and cgi):
|
if (fastcgi and (scgi or cgi)) or (scgi and cgi):
|
||||||
cherrypy.log.error("You may only specify one of the cgi, fastcgi, and "
|
cherrypy.log.error('You may only specify one of the cgi, fastcgi, and '
|
||||||
"scgi options.", 'ENGINE')
|
'scgi options.', 'ENGINE')
|
||||||
sys.exit(1)
|
sys.exit(1)
|
||||||
elif fastcgi or scgi or cgi:
|
elif fastcgi or scgi or cgi:
|
||||||
# Turn off autoreload when using *cgi.
|
# Turn off autoreload when using *cgi.
|
||||||
cherrypy.config.update({'engine.autoreload_on': False})
|
cherrypy.config.update({'engine.autoreload.on': False})
|
||||||
# Turn off the default HTTP server (which is subscribed by default).
|
# Turn off the default HTTP server (which is subscribed by default).
|
||||||
cherrypy.server.unsubscribe()
|
cherrypy.server.unsubscribe()
|
||||||
|
|
||||||
@@ -76,25 +76,25 @@ def run():
|
|||||||
from optparse import OptionParser
|
from optparse import OptionParser
|
||||||
|
|
||||||
p = OptionParser()
|
p = OptionParser()
|
||||||
p.add_option('-c', '--config', action="append", dest='config',
|
p.add_option('-c', '--config', action='append', dest='config',
|
||||||
help="specify config file(s)")
|
help='specify config file(s)')
|
||||||
p.add_option('-d', action="store_true", dest='daemonize',
|
p.add_option('-d', action='store_true', dest='daemonize',
|
||||||
help="run the server as a daemon")
|
help='run the server as a daemon')
|
||||||
p.add_option('-e', '--environment', dest='environment', default=None,
|
p.add_option('-e', '--environment', dest='environment', default=None,
|
||||||
help="apply the given config environment")
|
help='apply the given config environment')
|
||||||
p.add_option('-f', action="store_true", dest='fastcgi',
|
p.add_option('-f', action='store_true', dest='fastcgi',
|
||||||
help="start a fastcgi server instead of the default HTTP "
|
help='start a fastcgi server instead of the default HTTP '
|
||||||
"server")
|
'server')
|
||||||
p.add_option('-s', action="store_true", dest='scgi',
|
p.add_option('-s', action='store_true', dest='scgi',
|
||||||
help="start a scgi server instead of the default HTTP server")
|
help='start a scgi server instead of the default HTTP server')
|
||||||
p.add_option('-x', action="store_true", dest='cgi',
|
p.add_option('-x', action='store_true', dest='cgi',
|
||||||
help="start a cgi server instead of the default HTTP server")
|
help='start a cgi server instead of the default HTTP server')
|
||||||
p.add_option('-i', '--import', action="append", dest='imports',
|
p.add_option('-i', '--import', action='append', dest='imports',
|
||||||
help="specify modules to import")
|
help='specify modules to import')
|
||||||
p.add_option('-p', '--pidfile', dest='pidfile', default=None,
|
p.add_option('-p', '--pidfile', dest='pidfile', default=None,
|
||||||
help="store the process id in the given file")
|
help='store the process id in the given file')
|
||||||
p.add_option('-P', '--Path', action="append", dest='Path',
|
p.add_option('-P', '--Path', action='append', dest='Path',
|
||||||
help="add the given paths to sys.path")
|
help='add the given paths to sys.path')
|
||||||
options, args = p.parse_args()
|
options, args = p.parse_args()
|
||||||
|
|
||||||
if options.Path:
|
if options.Path:
|
||||||
|
|||||||
@@ -1,7 +1,5 @@
|
|||||||
"""CherryPy Library"""
|
"""CherryPy Library"""
|
||||||
|
|
||||||
# Deprecated in CherryPy 3.2 -- remove in CherryPy 3.3
|
|
||||||
from cherrypy.lib.reprconf import unrepr, modules, attributes
|
|
||||||
|
|
||||||
def is_iterator(obj):
|
def is_iterator(obj):
|
||||||
'''Returns a boolean indicating if the object provided implements
|
'''Returns a boolean indicating if the object provided implements
|
||||||
@@ -16,22 +14,23 @@ def is_iterator(obj):
|
|||||||
# Types which implement the protocol must return themselves when
|
# Types which implement the protocol must return themselves when
|
||||||
# invoking 'iter' upon them.
|
# invoking 'iter' upon them.
|
||||||
return iter(obj) is obj
|
return iter(obj) is obj
|
||||||
|
|
||||||
|
|
||||||
def is_closable_iterator(obj):
|
def is_closable_iterator(obj):
|
||||||
|
|
||||||
# Not an iterator.
|
# Not an iterator.
|
||||||
if not is_iterator(obj):
|
if not is_iterator(obj):
|
||||||
return False
|
return False
|
||||||
|
|
||||||
# A generator - the easiest thing to deal with.
|
# A generator - the easiest thing to deal with.
|
||||||
import inspect
|
import inspect
|
||||||
if inspect.isgenerator(obj):
|
if inspect.isgenerator(obj):
|
||||||
return True
|
return True
|
||||||
|
|
||||||
# A custom iterator. Look for a close method...
|
# A custom iterator. Look for a close method...
|
||||||
if not (hasattr(obj, 'close') and callable(obj.close)):
|
if not (hasattr(obj, 'close') and callable(obj.close)):
|
||||||
return False
|
return False
|
||||||
|
|
||||||
# ... which doesn't require any arguments.
|
# ... which doesn't require any arguments.
|
||||||
try:
|
try:
|
||||||
inspect.getcallargs(obj.close)
|
inspect.getcallargs(obj.close)
|
||||||
@@ -40,6 +39,7 @@ def is_closable_iterator(obj):
|
|||||||
else:
|
else:
|
||||||
return True
|
return True
|
||||||
|
|
||||||
|
|
||||||
class file_generator(object):
|
class file_generator(object):
|
||||||
|
|
||||||
"""Yield the given input (a file object) in chunks (default 64k). (Core)"""
|
"""Yield the given input (a file object) in chunks (default 64k). (Core)"""
|
||||||
@@ -77,9 +77,9 @@ def file_generator_limited(fileobj, count, chunk_size=65536):
|
|||||||
|
|
||||||
|
|
||||||
def set_vary_header(response, header_name):
|
def set_vary_header(response, header_name):
|
||||||
"Add a Vary header to a response"
|
'Add a Vary header to a response'
|
||||||
varies = response.headers.get("Vary", "")
|
varies = response.headers.get('Vary', '')
|
||||||
varies = [x.strip() for x in varies.split(",") if x.strip()]
|
varies = [x.strip() for x in varies.split(',') if x.strip()]
|
||||||
if header_name not in varies:
|
if header_name not in varies:
|
||||||
varies.append(header_name)
|
varies.append(header_name)
|
||||||
response.headers['Vary'] = ", ".join(varies)
|
response.headers['Vary'] = ', '.join(varies)
|
||||||
@@ -22,25 +22,25 @@ def check_auth(users, encrypt=None, realm=None):
|
|||||||
|
|
||||||
if not isinstance(users, dict):
|
if not isinstance(users, dict):
|
||||||
raise ValueError(
|
raise ValueError(
|
||||||
"Authentication users must be a dictionary")
|
'Authentication users must be a dictionary')
|
||||||
|
|
||||||
# fetch the user password
|
# fetch the user password
|
||||||
password = users.get(ah["username"], None)
|
password = users.get(ah['username'], None)
|
||||||
except TypeError:
|
except TypeError:
|
||||||
# returns a password (encrypted or clear text)
|
# returns a password (encrypted or clear text)
|
||||||
password = users(ah["username"])
|
password = users(ah['username'])
|
||||||
else:
|
else:
|
||||||
if not isinstance(users, dict):
|
if not isinstance(users, dict):
|
||||||
raise ValueError("Authentication users must be a dictionary")
|
raise ValueError('Authentication users must be a dictionary')
|
||||||
|
|
||||||
# fetch the user password
|
# fetch the user password
|
||||||
password = users.get(ah["username"], None)
|
password = users.get(ah['username'], None)
|
||||||
|
|
||||||
# validate the authorization by re-computing it here
|
# validate the authorization by re-computing it here
|
||||||
# and compare it with what the user-agent provided
|
# and compare it with what the user-agent provided
|
||||||
if httpauth.checkResponse(ah, password, method=request.method,
|
if httpauth.checkResponse(ah, password, method=request.method,
|
||||||
encrypt=encrypt, realm=realm):
|
encrypt=encrypt, realm=realm):
|
||||||
request.login = ah["username"]
|
request.login = ah['username']
|
||||||
return True
|
return True
|
||||||
|
|
||||||
request.login = False
|
request.login = False
|
||||||
@@ -72,7 +72,7 @@ def basic_auth(realm, users, encrypt=None, debug=False):
|
|||||||
'www-authenticate'] = httpauth.basicAuth(realm)
|
'www-authenticate'] = httpauth.basicAuth(realm)
|
||||||
|
|
||||||
raise cherrypy.HTTPError(
|
raise cherrypy.HTTPError(
|
||||||
401, "You are not authorized to access that resource")
|
401, 'You are not authorized to access that resource')
|
||||||
|
|
||||||
|
|
||||||
def digest_auth(realm, users, debug=False):
|
def digest_auth(realm, users, debug=False):
|
||||||
@@ -94,4 +94,4 @@ def digest_auth(realm, users, debug=False):
|
|||||||
'www-authenticate'] = httpauth.digestAuth(realm)
|
'www-authenticate'] = httpauth.digestAuth(realm)
|
||||||
|
|
||||||
raise cherrypy.HTTPError(
|
raise cherrypy.HTTPError(
|
||||||
401, "You are not authorized to access that resource")
|
401, 'You are not authorized to access that resource')
|
||||||
@@ -2,6 +2,12 @@
|
|||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
# vim:ts=4:sw=4:expandtab:fileencoding=utf-8
|
# vim:ts=4:sw=4:expandtab:fileencoding=utf-8
|
||||||
|
|
||||||
|
import binascii
|
||||||
|
|
||||||
|
import cherrypy
|
||||||
|
from cherrypy._cpcompat import base64_decode
|
||||||
|
|
||||||
|
|
||||||
__doc__ = """This module provides a CherryPy 3.x tool which implements
|
__doc__ = """This module provides a CherryPy 3.x tool which implements
|
||||||
the server-side of HTTP Basic Access Authentication, as described in
|
the server-side of HTTP Basic Access Authentication, as described in
|
||||||
:rfc:`2617`.
|
:rfc:`2617`.
|
||||||
@@ -22,10 +28,6 @@ as the credentials store::
|
|||||||
__author__ = 'visteya'
|
__author__ = 'visteya'
|
||||||
__date__ = 'April 2009'
|
__date__ = 'April 2009'
|
||||||
|
|
||||||
import binascii
|
|
||||||
from cherrypy._cpcompat import base64_decode
|
|
||||||
import cherrypy
|
|
||||||
|
|
||||||
|
|
||||||
def checkpassword_dict(user_password_dict):
|
def checkpassword_dict(user_password_dict):
|
||||||
"""Returns a checkpassword function which checks credentials
|
"""Returns a checkpassword function which checks credentials
|
||||||
@@ -70,7 +72,8 @@ def basic_auth(realm, checkpassword, debug=False):
|
|||||||
|
|
||||||
auth_header = request.headers.get('authorization')
|
auth_header = request.headers.get('authorization')
|
||||||
if auth_header is not None:
|
if auth_header is not None:
|
||||||
try:
|
# split() error, base64.decodestring() error
|
||||||
|
with cherrypy.HTTPError.handle((ValueError, binascii.Error), 400, 'Bad Request'):
|
||||||
scheme, params = auth_header.split(' ', 1)
|
scheme, params = auth_header.split(' ', 1)
|
||||||
if scheme.lower() == 'basic':
|
if scheme.lower() == 'basic':
|
||||||
username, password = base64_decode(params).split(':', 1)
|
username, password = base64_decode(params).split(':', 1)
|
||||||
@@ -79,12 +82,9 @@ def basic_auth(realm, checkpassword, debug=False):
|
|||||||
cherrypy.log('Auth succeeded', 'TOOLS.AUTH_BASIC')
|
cherrypy.log('Auth succeeded', 'TOOLS.AUTH_BASIC')
|
||||||
request.login = username
|
request.login = username
|
||||||
return # successful authentication
|
return # successful authentication
|
||||||
# split() error, base64.decodestring() error
|
|
||||||
except (ValueError, binascii.Error):
|
|
||||||
raise cherrypy.HTTPError(400, 'Bad Request')
|
|
||||||
|
|
||||||
# Respond with 401 status and a WWW-Authenticate header
|
# Respond with 401 status and a WWW-Authenticate header
|
||||||
cherrypy.serving.response.headers[
|
cherrypy.serving.response.headers[
|
||||||
'www-authenticate'] = 'Basic realm="%s"' % realm
|
'www-authenticate'] = 'Basic realm="%s"' % realm
|
||||||
raise cherrypy.HTTPError(
|
raise cherrypy.HTTPError(
|
||||||
401, "You are not authorized to access that resource")
|
401, 'You are not authorized to access that resource')
|
||||||
@@ -2,6 +2,13 @@
|
|||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
# vim:ts=4:sw=4:expandtab:fileencoding=utf-8
|
# vim:ts=4:sw=4:expandtab:fileencoding=utf-8
|
||||||
|
|
||||||
|
import time
|
||||||
|
from hashlib import md5
|
||||||
|
|
||||||
|
import cherrypy
|
||||||
|
from cherrypy._cpcompat import ntob, parse_http_list, parse_keqv_list
|
||||||
|
|
||||||
|
|
||||||
__doc__ = """An implementation of the server-side of HTTP Digest Access
|
__doc__ = """An implementation of the server-side of HTTP Digest Access
|
||||||
Authentication, which is described in :rfc:`2617`.
|
Authentication, which is described in :rfc:`2617`.
|
||||||
|
|
||||||
@@ -22,12 +29,6 @@ __author__ = 'visteya'
|
|||||||
__date__ = 'April 2009'
|
__date__ = 'April 2009'
|
||||||
|
|
||||||
|
|
||||||
import time
|
|
||||||
from hashlib import md5
|
|
||||||
from cherrypy._cpcompat import parse_http_list, parse_keqv_list
|
|
||||||
|
|
||||||
import cherrypy
|
|
||||||
from cherrypy._cpcompat import ntob
|
|
||||||
md5_hex = lambda s: md5(ntob(s)).hexdigest()
|
md5_hex = lambda s: md5(ntob(s)).hexdigest()
|
||||||
|
|
||||||
qop_auth = 'auth'
|
qop_auth = 'auth'
|
||||||
@@ -142,7 +143,7 @@ class HttpDigestAuthorization (object):
|
|||||||
def __init__(self, auth_header, http_method, debug=False):
|
def __init__(self, auth_header, http_method, debug=False):
|
||||||
self.http_method = http_method
|
self.http_method = http_method
|
||||||
self.debug = debug
|
self.debug = debug
|
||||||
scheme, params = auth_header.split(" ", 1)
|
scheme, params = auth_header.split(' ', 1)
|
||||||
self.scheme = scheme.lower()
|
self.scheme = scheme.lower()
|
||||||
if self.scheme != 'digest':
|
if self.scheme != 'digest':
|
||||||
raise ValueError('Authorization scheme is not "Digest"')
|
raise ValueError('Authorization scheme is not "Digest"')
|
||||||
@@ -180,7 +181,7 @@ class HttpDigestAuthorization (object):
|
|||||||
)
|
)
|
||||||
if not has_reqd:
|
if not has_reqd:
|
||||||
raise ValueError(
|
raise ValueError(
|
||||||
self.errmsg("Not all required parameters are present."))
|
self.errmsg('Not all required parameters are present.'))
|
||||||
|
|
||||||
if self.qop:
|
if self.qop:
|
||||||
if self.qop not in valid_qops:
|
if self.qop not in valid_qops:
|
||||||
@@ -188,13 +189,13 @@ class HttpDigestAuthorization (object):
|
|||||||
self.errmsg("Unsupported value for qop: '%s'" % self.qop))
|
self.errmsg("Unsupported value for qop: '%s'" % self.qop))
|
||||||
if not (self.cnonce and self.nc):
|
if not (self.cnonce and self.nc):
|
||||||
raise ValueError(
|
raise ValueError(
|
||||||
self.errmsg("If qop is sent then "
|
self.errmsg('If qop is sent then '
|
||||||
"cnonce and nc MUST be present"))
|
'cnonce and nc MUST be present'))
|
||||||
else:
|
else:
|
||||||
if self.cnonce or self.nc:
|
if self.cnonce or self.nc:
|
||||||
raise ValueError(
|
raise ValueError(
|
||||||
self.errmsg("If qop is not sent, "
|
self.errmsg('If qop is not sent, '
|
||||||
"neither cnonce nor nc can be present"))
|
'neither cnonce nor nc can be present'))
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
return 'authorization : %s' % self.auth_header
|
return 'authorization : %s' % self.auth_header
|
||||||
@@ -239,7 +240,7 @@ class HttpDigestAuthorization (object):
|
|||||||
except ValueError: # int() error
|
except ValueError: # int() error
|
||||||
pass
|
pass
|
||||||
if self.debug:
|
if self.debug:
|
||||||
TRACE("nonce is stale")
|
TRACE('nonce is stale')
|
||||||
return True
|
return True
|
||||||
|
|
||||||
def HA2(self, entity_body=''):
|
def HA2(self, entity_body=''):
|
||||||
@@ -251,14 +252,14 @@ class HttpDigestAuthorization (object):
|
|||||||
#
|
#
|
||||||
# If the "qop" value is "auth-int", then A2 is:
|
# If the "qop" value is "auth-int", then A2 is:
|
||||||
# A2 = method ":" digest-uri-value ":" H(entity-body)
|
# A2 = method ":" digest-uri-value ":" H(entity-body)
|
||||||
if self.qop is None or self.qop == "auth":
|
if self.qop is None or self.qop == 'auth':
|
||||||
a2 = '%s:%s' % (self.http_method, self.uri)
|
a2 = '%s:%s' % (self.http_method, self.uri)
|
||||||
elif self.qop == "auth-int":
|
elif self.qop == 'auth-int':
|
||||||
a2 = "%s:%s:%s" % (self.http_method, self.uri, H(entity_body))
|
a2 = '%s:%s:%s' % (self.http_method, self.uri, H(entity_body))
|
||||||
else:
|
else:
|
||||||
# in theory, this should never happen, since I validate qop in
|
# in theory, this should never happen, since I validate qop in
|
||||||
# __init__()
|
# __init__()
|
||||||
raise ValueError(self.errmsg("Unrecognized value for qop!"))
|
raise ValueError(self.errmsg('Unrecognized value for qop!'))
|
||||||
return H(a2)
|
return H(a2)
|
||||||
|
|
||||||
def request_digest(self, ha1, entity_body=''):
|
def request_digest(self, ha1, entity_body=''):
|
||||||
@@ -279,10 +280,10 @@ class HttpDigestAuthorization (object):
|
|||||||
ha2 = self.HA2(entity_body)
|
ha2 = self.HA2(entity_body)
|
||||||
# Request-Digest -- RFC 2617 3.2.2.1
|
# Request-Digest -- RFC 2617 3.2.2.1
|
||||||
if self.qop:
|
if self.qop:
|
||||||
req = "%s:%s:%s:%s:%s" % (
|
req = '%s:%s:%s:%s:%s' % (
|
||||||
self.nonce, self.nc, self.cnonce, self.qop, ha2)
|
self.nonce, self.nc, self.cnonce, self.qop, ha2)
|
||||||
else:
|
else:
|
||||||
req = "%s:%s" % (self.nonce, ha2)
|
req = '%s:%s' % (self.nonce, ha2)
|
||||||
|
|
||||||
# RFC 2617 3.2.2.2
|
# RFC 2617 3.2.2.2
|
||||||
#
|
#
|
||||||
@@ -351,12 +352,10 @@ def digest_auth(realm, get_ha1, key, debug=False):
|
|||||||
auth_header = request.headers.get('authorization')
|
auth_header = request.headers.get('authorization')
|
||||||
nonce_is_stale = False
|
nonce_is_stale = False
|
||||||
if auth_header is not None:
|
if auth_header is not None:
|
||||||
try:
|
with cherrypy.HTTPError.handle(ValueError, 400,
|
||||||
|
'The Authorization header could not be parsed.'):
|
||||||
auth = HttpDigestAuthorization(
|
auth = HttpDigestAuthorization(
|
||||||
auth_header, request.method, debug=debug)
|
auth_header, request.method, debug=debug)
|
||||||
except ValueError:
|
|
||||||
raise cherrypy.HTTPError(
|
|
||||||
400, "The Authorization header could not be parsed.")
|
|
||||||
|
|
||||||
if debug:
|
if debug:
|
||||||
TRACE(str(auth))
|
TRACE(str(auth))
|
||||||
@@ -370,7 +369,7 @@ def digest_auth(realm, get_ha1, key, debug=False):
|
|||||||
digest = auth.request_digest(ha1, entity_body=request.body)
|
digest = auth.request_digest(ha1, entity_body=request.body)
|
||||||
if digest == auth.response: # authenticated
|
if digest == auth.response: # authenticated
|
||||||
if debug:
|
if debug:
|
||||||
TRACE("digest matches auth.response")
|
TRACE('digest matches auth.response')
|
||||||
# Now check if nonce is stale.
|
# Now check if nonce is stale.
|
||||||
# The choice of ten minutes' lifetime for nonce is somewhat
|
# The choice of ten minutes' lifetime for nonce is somewhat
|
||||||
# arbitrary
|
# arbitrary
|
||||||
@@ -378,7 +377,7 @@ def digest_auth(realm, get_ha1, key, debug=False):
|
|||||||
if not nonce_is_stale:
|
if not nonce_is_stale:
|
||||||
request.login = auth.username
|
request.login = auth.username
|
||||||
if debug:
|
if debug:
|
||||||
TRACE("authentication of %s successful" %
|
TRACE('authentication of %s successful' %
|
||||||
auth.username)
|
auth.username)
|
||||||
return
|
return
|
||||||
|
|
||||||
@@ -388,4 +387,4 @@ def digest_auth(realm, get_ha1, key, debug=False):
|
|||||||
TRACE(header)
|
TRACE(header)
|
||||||
cherrypy.serving.response.headers['WWW-Authenticate'] = header
|
cherrypy.serving.response.headers['WWW-Authenticate'] = header
|
||||||
raise cherrypy.HTTPError(
|
raise cherrypy.HTTPError(
|
||||||
401, "You are not authorized to access that resource")
|
401, 'You are not authorized to access that resource')
|
||||||
@@ -39,7 +39,7 @@ import time
|
|||||||
|
|
||||||
import cherrypy
|
import cherrypy
|
||||||
from cherrypy.lib import cptools, httputil
|
from cherrypy.lib import cptools, httputil
|
||||||
from cherrypy._cpcompat import copyitems, ntob, set_daemon, sorted, Event
|
from cherrypy._cpcompat import copyitems, ntob, sorted, Event
|
||||||
|
|
||||||
|
|
||||||
class Cache(object):
|
class Cache(object):
|
||||||
@@ -170,7 +170,7 @@ class MemoryCache(Cache):
|
|||||||
# Run self.expire_cache in a separate daemon thread.
|
# Run self.expire_cache in a separate daemon thread.
|
||||||
t = threading.Thread(target=self.expire_cache, name='expire_cache')
|
t = threading.Thread(target=self.expire_cache, name='expire_cache')
|
||||||
self.expiration_thread = t
|
self.expiration_thread = t
|
||||||
set_daemon(t, True)
|
t.daemon = True
|
||||||
t.start()
|
t.start()
|
||||||
|
|
||||||
def clear(self):
|
def clear(self):
|
||||||
@@ -265,7 +265,7 @@ class MemoryCache(Cache):
|
|||||||
self.store.pop(uri, None)
|
self.store.pop(uri, None)
|
||||||
|
|
||||||
|
|
||||||
def get(invalid_methods=("POST", "PUT", "DELETE"), debug=False, **kwargs):
|
def get(invalid_methods=('POST', 'PUT', 'DELETE'), debug=False, **kwargs):
|
||||||
"""Try to obtain cached output. If fresh enough, raise HTTPError(304).
|
"""Try to obtain cached output. If fresh enough, raise HTTPError(304).
|
||||||
|
|
||||||
If POST, PUT, or DELETE:
|
If POST, PUT, or DELETE:
|
||||||
@@ -291,9 +291,9 @@ def get(invalid_methods=("POST", "PUT", "DELETE"), debug=False, **kwargs):
|
|||||||
request = cherrypy.serving.request
|
request = cherrypy.serving.request
|
||||||
response = cherrypy.serving.response
|
response = cherrypy.serving.response
|
||||||
|
|
||||||
if not hasattr(cherrypy, "_cache"):
|
if not hasattr(cherrypy, '_cache'):
|
||||||
# Make a process-wide Cache object.
|
# Make a process-wide Cache object.
|
||||||
cherrypy._cache = kwargs.pop("cache_class", MemoryCache)()
|
cherrypy._cache = kwargs.pop('cache_class', MemoryCache)()
|
||||||
|
|
||||||
# Take all remaining kwargs and set them on the Cache object.
|
# Take all remaining kwargs and set them on the Cache object.
|
||||||
for k, v in kwargs.items():
|
for k, v in kwargs.items():
|
||||||
@@ -328,7 +328,7 @@ def get(invalid_methods=("POST", "PUT", "DELETE"), debug=False, **kwargs):
|
|||||||
if directive == 'max-age':
|
if directive == 'max-age':
|
||||||
if len(atoms) != 1 or not atoms[0].isdigit():
|
if len(atoms) != 1 or not atoms[0].isdigit():
|
||||||
raise cherrypy.HTTPError(
|
raise cherrypy.HTTPError(
|
||||||
400, "Invalid Cache-Control header")
|
400, 'Invalid Cache-Control header')
|
||||||
max_age = int(atoms[0])
|
max_age = int(atoms[0])
|
||||||
break
|
break
|
||||||
elif directive == 'no-cache':
|
elif directive == 'no-cache':
|
||||||
@@ -359,7 +359,7 @@ def get(invalid_methods=("POST", "PUT", "DELETE"), debug=False, **kwargs):
|
|||||||
dict.__setitem__(rh, k, dict.__getitem__(h, k))
|
dict.__setitem__(rh, k, dict.__getitem__(h, k))
|
||||||
|
|
||||||
# Add the required Age header
|
# Add the required Age header
|
||||||
response.headers["Age"] = str(age)
|
response.headers['Age'] = str(age)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
# Note that validate_since depends on a Last-Modified header;
|
# Note that validate_since depends on a Last-Modified header;
|
||||||
@@ -457,14 +457,14 @@ def expires(secs=0, force=False, debug=False):
|
|||||||
secs = (86400 * secs.days) + secs.seconds
|
secs = (86400 * secs.days) + secs.seconds
|
||||||
|
|
||||||
if secs == 0:
|
if secs == 0:
|
||||||
if force or ("Pragma" not in headers):
|
if force or ('Pragma' not in headers):
|
||||||
headers["Pragma"] = "no-cache"
|
headers['Pragma'] = 'no-cache'
|
||||||
if cherrypy.serving.request.protocol >= (1, 1):
|
if cherrypy.serving.request.protocol >= (1, 1):
|
||||||
if force or "Cache-Control" not in headers:
|
if force or 'Cache-Control' not in headers:
|
||||||
headers["Cache-Control"] = "no-cache, must-revalidate"
|
headers['Cache-Control'] = 'no-cache, must-revalidate'
|
||||||
# Set an explicit Expires date in the past.
|
# Set an explicit Expires date in the past.
|
||||||
expiry = httputil.HTTPDate(1169942400.0)
|
expiry = httputil.HTTPDate(1169942400.0)
|
||||||
else:
|
else:
|
||||||
expiry = httputil.HTTPDate(response.time + secs)
|
expiry = httputil.HTTPDate(response.time + secs)
|
||||||
if force or "Expires" not in headers:
|
if force or 'Expires' not in headers:
|
||||||
headers["Expires"] = expiry
|
headers['Expires'] = expiry
|
||||||
@@ -25,11 +25,12 @@ import sys
|
|||||||
import cgi
|
import cgi
|
||||||
import os
|
import os
|
||||||
import os.path
|
import os.path
|
||||||
localFile = os.path.join(os.path.dirname(__file__), "coverage.cache")
|
|
||||||
|
|
||||||
from cherrypy._cpcompat import quote_plus
|
|
||||||
|
|
||||||
import cherrypy
|
import cherrypy
|
||||||
|
from cherrypy._cpcompat import quote_plus
|
||||||
|
|
||||||
|
|
||||||
|
localFile = os.path.join(os.path.dirname(__file__), 'coverage.cache')
|
||||||
|
|
||||||
the_coverage = None
|
the_coverage = None
|
||||||
try:
|
try:
|
||||||
@@ -45,8 +46,8 @@ except ImportError:
|
|||||||
|
|
||||||
import warnings
|
import warnings
|
||||||
warnings.warn(
|
warnings.warn(
|
||||||
"No code coverage will be performed; "
|
'No code coverage will be performed; '
|
||||||
"coverage.py could not be imported.")
|
'coverage.py could not be imported.')
|
||||||
|
|
||||||
def start():
|
def start():
|
||||||
pass
|
pass
|
||||||
@@ -196,7 +197,7 @@ def _percent(statements, missing):
|
|||||||
return 0
|
return 0
|
||||||
|
|
||||||
|
|
||||||
def _show_branch(root, base, path, pct=0, showpct=False, exclude="",
|
def _show_branch(root, base, path, pct=0, showpct=False, exclude='',
|
||||||
coverage=the_coverage):
|
coverage=the_coverage):
|
||||||
|
|
||||||
# Show the directory name and any of our children
|
# Show the directory name and any of our children
|
||||||
@@ -207,7 +208,7 @@ def _show_branch(root, base, path, pct=0, showpct=False, exclude="",
|
|||||||
|
|
||||||
if newpath.lower().startswith(base):
|
if newpath.lower().startswith(base):
|
||||||
relpath = newpath[len(base):]
|
relpath = newpath[len(base):]
|
||||||
yield "| " * relpath.count(os.sep)
|
yield '| ' * relpath.count(os.sep)
|
||||||
yield (
|
yield (
|
||||||
"<a class='directory' "
|
"<a class='directory' "
|
||||||
"href='menu?base=%s&exclude=%s'>%s</a>\n" %
|
"href='menu?base=%s&exclude=%s'>%s</a>\n" %
|
||||||
@@ -228,7 +229,7 @@ def _show_branch(root, base, path, pct=0, showpct=False, exclude="",
|
|||||||
for name in files:
|
for name in files:
|
||||||
newpath = os.path.join(path, name)
|
newpath = os.path.join(path, name)
|
||||||
|
|
||||||
pc_str = ""
|
pc_str = ''
|
||||||
if showpct:
|
if showpct:
|
||||||
try:
|
try:
|
||||||
_, statements, _, missing, _ = coverage.analysis2(newpath)
|
_, statements, _, missing, _ = coverage.analysis2(newpath)
|
||||||
@@ -237,13 +238,13 @@ def _show_branch(root, base, path, pct=0, showpct=False, exclude="",
|
|||||||
pass
|
pass
|
||||||
else:
|
else:
|
||||||
pc = _percent(statements, missing)
|
pc = _percent(statements, missing)
|
||||||
pc_str = ("%3d%% " % pc).replace(' ', ' ')
|
pc_str = ('%3d%% ' % pc).replace(' ', ' ')
|
||||||
if pc < float(pct) or pc == -1:
|
if pc < float(pct) or pc == -1:
|
||||||
pc_str = "<span class='fail'>%s</span>" % pc_str
|
pc_str = "<span class='fail'>%s</span>" % pc_str
|
||||||
else:
|
else:
|
||||||
pc_str = "<span class='pass'>%s</span>" % pc_str
|
pc_str = "<span class='pass'>%s</span>" % pc_str
|
||||||
|
|
||||||
yield TEMPLATE_ITEM % ("| " * (relpath.count(os.sep) + 1),
|
yield TEMPLATE_ITEM % ('| ' * (relpath.count(os.sep) + 1),
|
||||||
pc_str, newpath, name)
|
pc_str, newpath, name)
|
||||||
|
|
||||||
|
|
||||||
@@ -263,8 +264,8 @@ def _graft(path, tree):
|
|||||||
break
|
break
|
||||||
atoms.append(tail)
|
atoms.append(tail)
|
||||||
atoms.append(p)
|
atoms.append(p)
|
||||||
if p != "/":
|
if p != '/':
|
||||||
atoms.append("/")
|
atoms.append('/')
|
||||||
|
|
||||||
atoms.reverse()
|
atoms.reverse()
|
||||||
for node in atoms:
|
for node in atoms:
|
||||||
@@ -298,7 +299,7 @@ class CoverStats(object):
|
|||||||
return TEMPLATE_FRAMESET % self.root.lower()
|
return TEMPLATE_FRAMESET % self.root.lower()
|
||||||
|
|
||||||
@cherrypy.expose
|
@cherrypy.expose
|
||||||
def menu(self, base="/", pct="50", showpct="",
|
def menu(self, base='/', pct='50', showpct='',
|
||||||
exclude=r'python\d\.\d|test|tut\d|tutorial'):
|
exclude=r'python\d\.\d|test|tut\d|tutorial'):
|
||||||
|
|
||||||
# The coverage module uses all-lower-case names.
|
# The coverage module uses all-lower-case names.
|
||||||
@@ -309,36 +310,36 @@ class CoverStats(object):
|
|||||||
|
|
||||||
# Start by showing links for parent paths
|
# Start by showing links for parent paths
|
||||||
yield "<div id='crumbs'>"
|
yield "<div id='crumbs'>"
|
||||||
path = ""
|
path = ''
|
||||||
atoms = base.split(os.sep)
|
atoms = base.split(os.sep)
|
||||||
atoms.pop()
|
atoms.pop()
|
||||||
for atom in atoms:
|
for atom in atoms:
|
||||||
path += atom + os.sep
|
path += atom + os.sep
|
||||||
yield ("<a href='menu?base=%s&exclude=%s'>%s</a> %s"
|
yield ("<a href='menu?base=%s&exclude=%s'>%s</a> %s"
|
||||||
% (path, quote_plus(exclude), atom, os.sep))
|
% (path, quote_plus(exclude), atom, os.sep))
|
||||||
yield "</div>"
|
yield '</div>'
|
||||||
|
|
||||||
yield "<div id='tree'>"
|
yield "<div id='tree'>"
|
||||||
|
|
||||||
# Then display the tree
|
# Then display the tree
|
||||||
tree = get_tree(base, exclude, self.coverage)
|
tree = get_tree(base, exclude, self.coverage)
|
||||||
if not tree:
|
if not tree:
|
||||||
yield "<p>No modules covered.</p>"
|
yield '<p>No modules covered.</p>'
|
||||||
else:
|
else:
|
||||||
for chunk in _show_branch(tree, base, "/", pct,
|
for chunk in _show_branch(tree, base, '/', pct,
|
||||||
showpct == 'checked', exclude,
|
showpct == 'checked', exclude,
|
||||||
coverage=self.coverage):
|
coverage=self.coverage):
|
||||||
yield chunk
|
yield chunk
|
||||||
|
|
||||||
yield "</div>"
|
yield '</div>'
|
||||||
yield "</body></html>"
|
yield '</body></html>'
|
||||||
|
|
||||||
def annotated_file(self, filename, statements, excluded, missing):
|
def annotated_file(self, filename, statements, excluded, missing):
|
||||||
source = open(filename, 'r')
|
source = open(filename, 'r')
|
||||||
buffer = []
|
buffer = []
|
||||||
for lineno, line in enumerate(source.readlines()):
|
for lineno, line in enumerate(source.readlines()):
|
||||||
lineno += 1
|
lineno += 1
|
||||||
line = line.strip("\n\r")
|
line = line.strip('\n\r')
|
||||||
empty_the_buffer = True
|
empty_the_buffer = True
|
||||||
if lineno in excluded:
|
if lineno in excluded:
|
||||||
template = TEMPLATE_LOC_EXCLUDED
|
template = TEMPLATE_LOC_EXCLUDED
|
||||||
@@ -374,7 +375,7 @@ class CoverStats(object):
|
|||||||
|
|
||||||
def serve(path=localFile, port=8080, root=None):
|
def serve(path=localFile, port=8080, root=None):
|
||||||
if coverage is None:
|
if coverage is None:
|
||||||
raise ImportError("The coverage module could not be imported.")
|
raise ImportError('The coverage module could not be imported.')
|
||||||
from coverage import coverage
|
from coverage import coverage
|
||||||
cov = coverage(data_file=path)
|
cov = coverage(data_file=path)
|
||||||
cov.load()
|
cov.load()
|
||||||
@@ -382,9 +383,9 @@ def serve(path=localFile, port=8080, root=None):
|
|||||||
import cherrypy
|
import cherrypy
|
||||||
cherrypy.config.update({'server.socket_port': int(port),
|
cherrypy.config.update({'server.socket_port': int(port),
|
||||||
'server.thread_pool': 10,
|
'server.thread_pool': 10,
|
||||||
'environment': "production",
|
'environment': 'production',
|
||||||
})
|
})
|
||||||
cherrypy.quickstart(CoverStats(cov, root))
|
cherrypy.quickstart(CoverStats(cov, root))
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == '__main__':
|
||||||
serve(*tuple(sys.argv[1:]))
|
serve(*tuple(sys.argv[1:]))
|
||||||
@@ -187,9 +187,17 @@ To format statistics reports::
|
|||||||
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
import logging
|
||||||
|
import os
|
||||||
|
import sys
|
||||||
|
import threading
|
||||||
|
import time
|
||||||
|
|
||||||
|
import cherrypy
|
||||||
|
from cherrypy._cpcompat import json
|
||||||
|
|
||||||
# ------------------------------- Statistics -------------------------------- #
|
# ------------------------------- Statistics -------------------------------- #
|
||||||
|
|
||||||
import logging
|
|
||||||
if not hasattr(logging, 'statistics'):
|
if not hasattr(logging, 'statistics'):
|
||||||
logging.statistics = {}
|
logging.statistics = {}
|
||||||
|
|
||||||
@@ -210,12 +218,6 @@ def extrapolate_statistics(scope):
|
|||||||
|
|
||||||
# -------------------- CherryPy Applications Statistics --------------------- #
|
# -------------------- CherryPy Applications Statistics --------------------- #
|
||||||
|
|
||||||
import sys
|
|
||||||
import threading
|
|
||||||
import time
|
|
||||||
|
|
||||||
import cherrypy
|
|
||||||
|
|
||||||
appstats = logging.statistics.setdefault('CherryPy Applications', {})
|
appstats = logging.statistics.setdefault('CherryPy Applications', {})
|
||||||
appstats.update({
|
appstats.update({
|
||||||
'Enabled': True,
|
'Enabled': True,
|
||||||
@@ -390,24 +392,13 @@ class StatsTool(cherrypy.Tool):
|
|||||||
sq.pop(0)
|
sq.pop(0)
|
||||||
|
|
||||||
|
|
||||||
import cherrypy
|
|
||||||
cherrypy.tools.cpstats = StatsTool()
|
cherrypy.tools.cpstats = StatsTool()
|
||||||
|
|
||||||
|
|
||||||
# ---------------------- CherryPy Statistics Reporting ---------------------- #
|
# ---------------------- CherryPy Statistics Reporting ---------------------- #
|
||||||
|
|
||||||
import os
|
|
||||||
thisdir = os.path.abspath(os.path.dirname(__file__))
|
thisdir = os.path.abspath(os.path.dirname(__file__))
|
||||||
|
|
||||||
try:
|
|
||||||
import json
|
|
||||||
except ImportError:
|
|
||||||
try:
|
|
||||||
import simplejson as json
|
|
||||||
except ImportError:
|
|
||||||
json = None
|
|
||||||
|
|
||||||
|
|
||||||
missing = object()
|
missing = object()
|
||||||
|
|
||||||
locale_date = lambda v: time.strftime('%c', time.gmtime(v))
|
locale_date = lambda v: time.strftime('%c', time.gmtime(v))
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ from hashlib import md5
|
|||||||
import six
|
import six
|
||||||
|
|
||||||
import cherrypy
|
import cherrypy
|
||||||
from cherrypy._cpcompat import basestring
|
from cherrypy._cpcompat import text_or_bytes
|
||||||
from cherrypy.lib import httputil as _httputil
|
from cherrypy.lib import httputil as _httputil
|
||||||
from cherrypy.lib import is_iterator
|
from cherrypy.lib import is_iterator
|
||||||
|
|
||||||
@@ -33,7 +33,7 @@ def validate_etags(autotags=False, debug=False):
|
|||||||
response = cherrypy.serving.response
|
response = cherrypy.serving.response
|
||||||
|
|
||||||
# Guard against being run twice.
|
# Guard against being run twice.
|
||||||
if hasattr(response, "ETag"):
|
if hasattr(response, 'ETag'):
|
||||||
return
|
return
|
||||||
|
|
||||||
status, reason, msg = _httputil.valid_status(response.status)
|
status, reason, msg = _httputil.valid_status(response.status)
|
||||||
@@ -72,24 +72,24 @@ def validate_etags(autotags=False, debug=False):
|
|||||||
if debug:
|
if debug:
|
||||||
cherrypy.log('If-Match conditions: %s' % repr(conditions),
|
cherrypy.log('If-Match conditions: %s' % repr(conditions),
|
||||||
'TOOLS.ETAGS')
|
'TOOLS.ETAGS')
|
||||||
if conditions and not (conditions == ["*"] or etag in conditions):
|
if conditions and not (conditions == ['*'] or etag in conditions):
|
||||||
raise cherrypy.HTTPError(412, "If-Match failed: ETag %r did "
|
raise cherrypy.HTTPError(412, 'If-Match failed: ETag %r did '
|
||||||
"not match %r" % (etag, conditions))
|
'not match %r' % (etag, conditions))
|
||||||
|
|
||||||
conditions = request.headers.elements('If-None-Match') or []
|
conditions = request.headers.elements('If-None-Match') or []
|
||||||
conditions = [str(x) for x in conditions]
|
conditions = [str(x) for x in conditions]
|
||||||
if debug:
|
if debug:
|
||||||
cherrypy.log('If-None-Match conditions: %s' % repr(conditions),
|
cherrypy.log('If-None-Match conditions: %s' % repr(conditions),
|
||||||
'TOOLS.ETAGS')
|
'TOOLS.ETAGS')
|
||||||
if conditions == ["*"] or etag in conditions:
|
if conditions == ['*'] or etag in conditions:
|
||||||
if debug:
|
if debug:
|
||||||
cherrypy.log('request.method: %s' %
|
cherrypy.log('request.method: %s' %
|
||||||
request.method, 'TOOLS.ETAGS')
|
request.method, 'TOOLS.ETAGS')
|
||||||
if request.method in ("GET", "HEAD"):
|
if request.method in ('GET', 'HEAD'):
|
||||||
raise cherrypy.HTTPRedirect([], 304)
|
raise cherrypy.HTTPRedirect([], 304)
|
||||||
else:
|
else:
|
||||||
raise cherrypy.HTTPError(412, "If-None-Match failed: ETag %r "
|
raise cherrypy.HTTPError(412, 'If-None-Match failed: ETag %r '
|
||||||
"matched %r" % (etag, conditions))
|
'matched %r' % (etag, conditions))
|
||||||
|
|
||||||
|
|
||||||
def validate_since():
|
def validate_since():
|
||||||
@@ -113,7 +113,7 @@ def validate_since():
|
|||||||
since = request.headers.get('If-Modified-Since')
|
since = request.headers.get('If-Modified-Since')
|
||||||
if since and since == lastmod:
|
if since and since == lastmod:
|
||||||
if (status >= 200 and status <= 299) or status == 304:
|
if (status >= 200 and status <= 299) or status == 304:
|
||||||
if request.method in ("GET", "HEAD"):
|
if request.method in ('GET', 'HEAD'):
|
||||||
raise cherrypy.HTTPRedirect([], 304)
|
raise cherrypy.HTTPRedirect([], 304)
|
||||||
else:
|
else:
|
||||||
raise cherrypy.HTTPError(412)
|
raise cherrypy.HTTPError(412)
|
||||||
@@ -186,7 +186,7 @@ def proxy(base=None, local='X-Forwarded-Host', remote='X-Forwarded-For',
|
|||||||
# This is for lighttpd/pound/Mongrel's 'X-Forwarded-Proto: https'
|
# This is for lighttpd/pound/Mongrel's 'X-Forwarded-Proto: https'
|
||||||
scheme = s
|
scheme = s
|
||||||
if not scheme:
|
if not scheme:
|
||||||
scheme = request.base[:request.base.find("://")]
|
scheme = request.base[:request.base.find('://')]
|
||||||
|
|
||||||
if local:
|
if local:
|
||||||
lbase = request.headers.get(local, None)
|
lbase = request.headers.get(local, None)
|
||||||
@@ -200,9 +200,9 @@ def proxy(base=None, local='X-Forwarded-Host', remote='X-Forwarded-For',
|
|||||||
if port != 80:
|
if port != 80:
|
||||||
base += ':%s' % port
|
base += ':%s' % port
|
||||||
|
|
||||||
if base.find("://") == -1:
|
if base.find('://') == -1:
|
||||||
# add http:// or https:// if needed
|
# add http:// or https:// if needed
|
||||||
base = scheme + "://" + base
|
base = scheme + '://' + base
|
||||||
|
|
||||||
request.base = base
|
request.base = base
|
||||||
|
|
||||||
@@ -212,7 +212,7 @@ def proxy(base=None, local='X-Forwarded-Host', remote='X-Forwarded-For',
|
|||||||
cherrypy.log('Testing remote %r:%r' % (remote, xff), 'TOOLS.PROXY')
|
cherrypy.log('Testing remote %r:%r' % (remote, xff), 'TOOLS.PROXY')
|
||||||
if xff:
|
if xff:
|
||||||
if remote == 'X-Forwarded-For':
|
if remote == 'X-Forwarded-For':
|
||||||
#Bug #1268
|
# Bug #1268
|
||||||
xff = xff.split(',')[0].strip()
|
xff = xff.split(',')[0].strip()
|
||||||
request.remote.ip = xff
|
request.remote.ip = xff
|
||||||
|
|
||||||
@@ -285,7 +285,7 @@ class SessionAuth(object):
|
|||||||
|
|
||||||
"""Assert that the user is logged in."""
|
"""Assert that the user is logged in."""
|
||||||
|
|
||||||
session_key = "username"
|
session_key = 'username'
|
||||||
debug = False
|
debug = False
|
||||||
|
|
||||||
def check_username_and_password(self, username, password):
|
def check_username_and_password(self, username, password):
|
||||||
@@ -317,7 +317,7 @@ Message: %(error_msg)s
|
|||||||
<br />
|
<br />
|
||||||
<input type="submit" />
|
<input type="submit" />
|
||||||
</form>
|
</form>
|
||||||
</body></html>""") % vars()).encode("utf-8")
|
</body></html>""") % vars()).encode('utf-8')
|
||||||
|
|
||||||
def do_login(self, username, password, from_page='..', **kwargs):
|
def do_login(self, username, password, from_page='..', **kwargs):
|
||||||
"""Login. May raise redirect, or return True if request handled."""
|
"""Login. May raise redirect, or return True if request handled."""
|
||||||
@@ -326,15 +326,15 @@ Message: %(error_msg)s
|
|||||||
if error_msg:
|
if error_msg:
|
||||||
body = self.login_screen(from_page, username, error_msg)
|
body = self.login_screen(from_page, username, error_msg)
|
||||||
response.body = body
|
response.body = body
|
||||||
if "Content-Length" in response.headers:
|
if 'Content-Length' in response.headers:
|
||||||
# Delete Content-Length header so finalize() recalcs it.
|
# Delete Content-Length header so finalize() recalcs it.
|
||||||
del response.headers["Content-Length"]
|
del response.headers['Content-Length']
|
||||||
return True
|
return True
|
||||||
else:
|
else:
|
||||||
cherrypy.serving.request.login = username
|
cherrypy.serving.request.login = username
|
||||||
cherrypy.session[self.session_key] = username
|
cherrypy.session[self.session_key] = username
|
||||||
self.on_login(username)
|
self.on_login(username)
|
||||||
raise cherrypy.HTTPRedirect(from_page or "/")
|
raise cherrypy.HTTPRedirect(from_page or '/')
|
||||||
|
|
||||||
def do_logout(self, from_page='..', **kwargs):
|
def do_logout(self, from_page='..', **kwargs):
|
||||||
"""Logout. May raise redirect, or return True if request handled."""
|
"""Logout. May raise redirect, or return True if request handled."""
|
||||||
@@ -364,9 +364,9 @@ Message: %(error_msg)s
|
|||||||
locals(),
|
locals(),
|
||||||
)
|
)
|
||||||
response.body = self.login_screen(url)
|
response.body = self.login_screen(url)
|
||||||
if "Content-Length" in response.headers:
|
if 'Content-Length' in response.headers:
|
||||||
# Delete Content-Length header so finalize() recalcs it.
|
# Delete Content-Length header so finalize() recalcs it.
|
||||||
del response.headers["Content-Length"]
|
del response.headers['Content-Length']
|
||||||
return True
|
return True
|
||||||
self._debug_message('Setting request.login to %(username)r', locals())
|
self._debug_message('Setting request.login to %(username)r', locals())
|
||||||
request.login = username
|
request.login = username
|
||||||
@@ -388,14 +388,14 @@ Message: %(error_msg)s
|
|||||||
return True
|
return True
|
||||||
elif path.endswith('do_login'):
|
elif path.endswith('do_login'):
|
||||||
if request.method != 'POST':
|
if request.method != 'POST':
|
||||||
response.headers['Allow'] = "POST"
|
response.headers['Allow'] = 'POST'
|
||||||
self._debug_message('do_login requires POST')
|
self._debug_message('do_login requires POST')
|
||||||
raise cherrypy.HTTPError(405)
|
raise cherrypy.HTTPError(405)
|
||||||
self._debug_message('routing %(path)r to do_login', locals())
|
self._debug_message('routing %(path)r to do_login', locals())
|
||||||
return self.do_login(**request.params)
|
return self.do_login(**request.params)
|
||||||
elif path.endswith('do_logout'):
|
elif path.endswith('do_logout'):
|
||||||
if request.method != 'POST':
|
if request.method != 'POST':
|
||||||
response.headers['Allow'] = "POST"
|
response.headers['Allow'] = 'POST'
|
||||||
raise cherrypy.HTTPError(405)
|
raise cherrypy.HTTPError(405)
|
||||||
self._debug_message('routing %(path)r to do_logout', locals())
|
self._debug_message('routing %(path)r to do_logout', locals())
|
||||||
return self.do_logout(**request.params)
|
return self.do_logout(**request.params)
|
||||||
@@ -414,19 +414,19 @@ session_auth.__doc__ = """Session authentication hook.
|
|||||||
Any attribute of the SessionAuth class may be overridden via a keyword arg
|
Any attribute of the SessionAuth class may be overridden via a keyword arg
|
||||||
to this function:
|
to this function:
|
||||||
|
|
||||||
""" + "\n".join(["%s: %s" % (k, type(getattr(SessionAuth, k)).__name__)
|
""" + '\n'.join(['%s: %s' % (k, type(getattr(SessionAuth, k)).__name__)
|
||||||
for k in dir(SessionAuth) if not k.startswith("__")])
|
for k in dir(SessionAuth) if not k.startswith('__')])
|
||||||
|
|
||||||
|
|
||||||
def log_traceback(severity=logging.ERROR, debug=False):
|
def log_traceback(severity=logging.ERROR, debug=False):
|
||||||
"""Write the last error's traceback to the cherrypy error log."""
|
"""Write the last error's traceback to the cherrypy error log."""
|
||||||
cherrypy.log("", "HTTP", severity=severity, traceback=True)
|
cherrypy.log('', 'HTTP', severity=severity, traceback=True)
|
||||||
|
|
||||||
|
|
||||||
def log_request_headers(debug=False):
|
def log_request_headers(debug=False):
|
||||||
"""Write request headers to the cherrypy error log."""
|
"""Write request headers to the cherrypy error log."""
|
||||||
h = [" %s: %s" % (k, v) for k, v in cherrypy.serving.request.header_list]
|
h = [' %s: %s' % (k, v) for k, v in cherrypy.serving.request.header_list]
|
||||||
cherrypy.log('\nRequest Headers:\n' + '\n'.join(h), "HTTP")
|
cherrypy.log('\nRequest Headers:\n' + '\n'.join(h), 'HTTP')
|
||||||
|
|
||||||
|
|
||||||
def log_hooks(debug=False):
|
def log_hooks(debug=False):
|
||||||
@@ -442,13 +442,13 @@ def log_hooks(debug=False):
|
|||||||
points.append(k)
|
points.append(k)
|
||||||
|
|
||||||
for k in points:
|
for k in points:
|
||||||
msg.append(" %s:" % k)
|
msg.append(' %s:' % k)
|
||||||
v = request.hooks.get(k, [])
|
v = request.hooks.get(k, [])
|
||||||
v.sort()
|
v.sort()
|
||||||
for h in v:
|
for h in v:
|
||||||
msg.append(" %r" % h)
|
msg.append(' %r' % h)
|
||||||
cherrypy.log('\nRequest Hooks for ' + cherrypy.url() +
|
cherrypy.log('\nRequest Hooks for ' + cherrypy.url() +
|
||||||
':\n' + '\n'.join(msg), "HTTP")
|
':\n' + '\n'.join(msg), 'HTTP')
|
||||||
|
|
||||||
|
|
||||||
def redirect(url='', internal=True, debug=False):
|
def redirect(url='', internal=True, debug=False):
|
||||||
@@ -533,7 +533,7 @@ def accept(media=None, debug=False):
|
|||||||
"""
|
"""
|
||||||
if not media:
|
if not media:
|
||||||
return
|
return
|
||||||
if isinstance(media, basestring):
|
if isinstance(media, text_or_bytes):
|
||||||
media = [media]
|
media = [media]
|
||||||
request = cherrypy.serving.request
|
request = cherrypy.serving.request
|
||||||
|
|
||||||
@@ -549,12 +549,12 @@ def accept(media=None, debug=False):
|
|||||||
# Note that 'ranges' is sorted in order of preference
|
# Note that 'ranges' is sorted in order of preference
|
||||||
for element in ranges:
|
for element in ranges:
|
||||||
if element.qvalue > 0:
|
if element.qvalue > 0:
|
||||||
if element.value == "*/*":
|
if element.value == '*/*':
|
||||||
# Matches any type or subtype
|
# Matches any type or subtype
|
||||||
if debug:
|
if debug:
|
||||||
cherrypy.log('Match due to */*', 'TOOLS.ACCEPT')
|
cherrypy.log('Match due to */*', 'TOOLS.ACCEPT')
|
||||||
return media[0]
|
return media[0]
|
||||||
elif element.value.endswith("/*"):
|
elif element.value.endswith('/*'):
|
||||||
# Matches any subtype
|
# Matches any subtype
|
||||||
mtype = element.value[:-1] # Keep the slash
|
mtype = element.value[:-1] # Keep the slash
|
||||||
for m in media:
|
for m in media:
|
||||||
@@ -574,11 +574,11 @@ def accept(media=None, debug=False):
|
|||||||
# No suitable media-range found.
|
# No suitable media-range found.
|
||||||
ah = request.headers.get('Accept')
|
ah = request.headers.get('Accept')
|
||||||
if ah is None:
|
if ah is None:
|
||||||
msg = "Your client did not send an Accept header."
|
msg = 'Your client did not send an Accept header.'
|
||||||
else:
|
else:
|
||||||
msg = "Your client sent this Accept header: %s." % ah
|
msg = 'Your client sent this Accept header: %s.' % ah
|
||||||
msg += (" But this resource only emits these media types: %s." %
|
msg += (' But this resource only emits these media types: %s.' %
|
||||||
", ".join(media))
|
', '.join(media))
|
||||||
raise cherrypy.HTTPError(406, msg)
|
raise cherrypy.HTTPError(406, msg)
|
||||||
|
|
||||||
|
|
||||||
@@ -630,3 +630,19 @@ def autovary(ignore=None, debug=False):
|
|||||||
v.sort()
|
v.sort()
|
||||||
resp_h['Vary'] = ', '.join(v)
|
resp_h['Vary'] = ', '.join(v)
|
||||||
request.hooks.attach('before_finalize', set_response_header, 95)
|
request.hooks.attach('before_finalize', set_response_header, 95)
|
||||||
|
|
||||||
|
|
||||||
|
def convert_params(exception=ValueError, error=400):
|
||||||
|
"""Convert request params based on function annotations, with error handling.
|
||||||
|
|
||||||
|
exception
|
||||||
|
Exception class to catch.
|
||||||
|
|
||||||
|
status
|
||||||
|
The HTTP error code to return to the client on failure.
|
||||||
|
"""
|
||||||
|
request = cherrypy.serving.request
|
||||||
|
types = request.handler.callable.__annotations__
|
||||||
|
with cherrypy.HTTPError.handle(exception, error):
|
||||||
|
for key in set(types).intersection(request.params):
|
||||||
|
request.params[key] = types[key](request.params[key])
|
||||||
@@ -5,7 +5,7 @@ import io
|
|||||||
import six
|
import six
|
||||||
|
|
||||||
import cherrypy
|
import cherrypy
|
||||||
from cherrypy._cpcompat import basestring, ntob
|
from cherrypy._cpcompat import text_or_bytes, ntob
|
||||||
from cherrypy.lib import file_generator
|
from cherrypy.lib import file_generator
|
||||||
from cherrypy.lib import is_closable_iterator
|
from cherrypy.lib import is_closable_iterator
|
||||||
from cherrypy.lib import set_vary_header
|
from cherrypy.lib import set_vary_header
|
||||||
@@ -66,7 +66,7 @@ class UTF8StreamEncoder:
|
|||||||
class ResponseEncoder:
|
class ResponseEncoder:
|
||||||
|
|
||||||
default_encoding = 'utf-8'
|
default_encoding = 'utf-8'
|
||||||
failmsg = "Response body could not be encoded with %r."
|
failmsg = 'Response body could not be encoded with %r.'
|
||||||
encoding = None
|
encoding = None
|
||||||
errors = 'strict'
|
errors = 'strict'
|
||||||
text_only = True
|
text_only = True
|
||||||
@@ -131,7 +131,7 @@ class ResponseEncoder:
|
|||||||
encoder = self.encode_stream
|
encoder = self.encode_stream
|
||||||
else:
|
else:
|
||||||
encoder = self.encode_string
|
encoder = self.encode_string
|
||||||
if "Content-Length" in response.headers:
|
if 'Content-Length' in response.headers:
|
||||||
# Delete Content-Length header so finalize() recalcs it.
|
# Delete Content-Length header so finalize() recalcs it.
|
||||||
# Encoded strings may be of different lengths from their
|
# Encoded strings may be of different lengths from their
|
||||||
# unicode equivalents, and even from each other. For example:
|
# unicode equivalents, and even from each other. For example:
|
||||||
@@ -142,7 +142,7 @@ class ResponseEncoder:
|
|||||||
# 6
|
# 6
|
||||||
# >>> len(t.encode("utf7"))
|
# >>> len(t.encode("utf7"))
|
||||||
# 8
|
# 8
|
||||||
del response.headers["Content-Length"]
|
del response.headers['Content-Length']
|
||||||
|
|
||||||
# Parse the Accept-Charset request header, and try to provide one
|
# Parse the Accept-Charset request header, and try to provide one
|
||||||
# of the requested charsets (in order of user preference).
|
# of the requested charsets (in order of user preference).
|
||||||
@@ -157,7 +157,7 @@ class ResponseEncoder:
|
|||||||
if self.debug:
|
if self.debug:
|
||||||
cherrypy.log('Specified encoding %r' %
|
cherrypy.log('Specified encoding %r' %
|
||||||
encoding, 'TOOLS.ENCODE')
|
encoding, 'TOOLS.ENCODE')
|
||||||
if (not charsets) or "*" in charsets or encoding in charsets:
|
if (not charsets) or '*' in charsets or encoding in charsets:
|
||||||
if self.debug:
|
if self.debug:
|
||||||
cherrypy.log('Attempting encoding %r' %
|
cherrypy.log('Attempting encoding %r' %
|
||||||
encoding, 'TOOLS.ENCODE')
|
encoding, 'TOOLS.ENCODE')
|
||||||
@@ -177,7 +177,7 @@ class ResponseEncoder:
|
|||||||
else:
|
else:
|
||||||
for element in encs:
|
for element in encs:
|
||||||
if element.qvalue > 0:
|
if element.qvalue > 0:
|
||||||
if element.value == "*":
|
if element.value == '*':
|
||||||
# Matches any charset. Try our default.
|
# Matches any charset. Try our default.
|
||||||
if self.debug:
|
if self.debug:
|
||||||
cherrypy.log('Attempting default encoding due '
|
cherrypy.log('Attempting default encoding due '
|
||||||
@@ -192,7 +192,7 @@ class ResponseEncoder:
|
|||||||
if encoder(encoding):
|
if encoder(encoding):
|
||||||
return encoding
|
return encoding
|
||||||
|
|
||||||
if "*" not in charsets:
|
if '*' not in charsets:
|
||||||
# If no "*" is present in an Accept-Charset field, then all
|
# If no "*" is present in an Accept-Charset field, then all
|
||||||
# character sets not explicitly mentioned get a quality
|
# character sets not explicitly mentioned get a quality
|
||||||
# value of 0, except for ISO-8859-1, which gets a quality
|
# value of 0, except for ISO-8859-1, which gets a quality
|
||||||
@@ -208,18 +208,18 @@ class ResponseEncoder:
|
|||||||
# No suitable encoding found.
|
# No suitable encoding found.
|
||||||
ac = request.headers.get('Accept-Charset')
|
ac = request.headers.get('Accept-Charset')
|
||||||
if ac is None:
|
if ac is None:
|
||||||
msg = "Your client did not send an Accept-Charset header."
|
msg = 'Your client did not send an Accept-Charset header.'
|
||||||
else:
|
else:
|
||||||
msg = "Your client sent this Accept-Charset header: %s." % ac
|
msg = 'Your client sent this Accept-Charset header: %s.' % ac
|
||||||
_charsets = ", ".join(sorted(self.attempted_charsets))
|
_charsets = ', '.join(sorted(self.attempted_charsets))
|
||||||
msg += " We tried these charsets: %s." % (_charsets,)
|
msg += ' We tried these charsets: %s.' % (_charsets,)
|
||||||
raise cherrypy.HTTPError(406, msg)
|
raise cherrypy.HTTPError(406, msg)
|
||||||
|
|
||||||
def __call__(self, *args, **kwargs):
|
def __call__(self, *args, **kwargs):
|
||||||
response = cherrypy.serving.response
|
response = cherrypy.serving.response
|
||||||
self.body = self.oldhandler(*args, **kwargs)
|
self.body = self.oldhandler(*args, **kwargs)
|
||||||
|
|
||||||
if isinstance(self.body, basestring):
|
if isinstance(self.body, text_or_bytes):
|
||||||
# strings get wrapped in a list because iterating over a single
|
# strings get wrapped in a list because iterating over a single
|
||||||
# item list is much faster than iterating over every character
|
# item list is much faster than iterating over every character
|
||||||
# in a long string.
|
# in a long string.
|
||||||
@@ -233,14 +233,14 @@ class ResponseEncoder:
|
|||||||
elif self.body is None:
|
elif self.body is None:
|
||||||
self.body = []
|
self.body = []
|
||||||
|
|
||||||
ct = response.headers.elements("Content-Type")
|
ct = response.headers.elements('Content-Type')
|
||||||
if self.debug:
|
if self.debug:
|
||||||
cherrypy.log('Content-Type: %r' % [str(h)
|
cherrypy.log('Content-Type: %r' % [str(h)
|
||||||
for h in ct], 'TOOLS.ENCODE')
|
for h in ct], 'TOOLS.ENCODE')
|
||||||
if ct and self.add_charset:
|
if ct and self.add_charset:
|
||||||
ct = ct[0]
|
ct = ct[0]
|
||||||
if self.text_only:
|
if self.text_only:
|
||||||
if ct.value.lower().startswith("text/"):
|
if ct.value.lower().startswith('text/'):
|
||||||
if self.debug:
|
if self.debug:
|
||||||
cherrypy.log(
|
cherrypy.log(
|
||||||
'Content-Type %s starts with "text/"' % ct,
|
'Content-Type %s starts with "text/"' % ct,
|
||||||
@@ -264,7 +264,7 @@ class ResponseEncoder:
|
|||||||
if self.debug:
|
if self.debug:
|
||||||
cherrypy.log('Setting Content-Type %s' % ct,
|
cherrypy.log('Setting Content-Type %s' % ct,
|
||||||
'TOOLS.ENCODE')
|
'TOOLS.ENCODE')
|
||||||
response.headers["Content-Type"] = str(ct)
|
response.headers['Content-Type'] = str(ct)
|
||||||
|
|
||||||
return self.body
|
return self.body
|
||||||
|
|
||||||
@@ -280,11 +280,11 @@ def compress(body, compress_level):
|
|||||||
yield ntob('\x08') # CM: compression method
|
yield ntob('\x08') # CM: compression method
|
||||||
yield ntob('\x00') # FLG: none set
|
yield ntob('\x00') # FLG: none set
|
||||||
# MTIME: 4 bytes
|
# MTIME: 4 bytes
|
||||||
yield struct.pack("<L", int(time.time()) & int('FFFFFFFF', 16))
|
yield struct.pack('<L', int(time.time()) & int('FFFFFFFF', 16))
|
||||||
yield ntob('\x02') # XFL: max compression, slowest algo
|
yield ntob('\x02') # XFL: max compression, slowest algo
|
||||||
yield ntob('\xff') # OS: unknown
|
yield ntob('\xff') # OS: unknown
|
||||||
|
|
||||||
crc = zlib.crc32(ntob(""))
|
crc = zlib.crc32(ntob(''))
|
||||||
size = 0
|
size = 0
|
||||||
zobj = zlib.compressobj(compress_level,
|
zobj = zlib.compressobj(compress_level,
|
||||||
zlib.DEFLATED, -zlib.MAX_WBITS,
|
zlib.DEFLATED, -zlib.MAX_WBITS,
|
||||||
@@ -296,9 +296,9 @@ def compress(body, compress_level):
|
|||||||
yield zobj.flush()
|
yield zobj.flush()
|
||||||
|
|
||||||
# CRC32: 4 bytes
|
# CRC32: 4 bytes
|
||||||
yield struct.pack("<L", crc & int('FFFFFFFF', 16))
|
yield struct.pack('<L', crc & int('FFFFFFFF', 16))
|
||||||
# ISIZE: 4 bytes
|
# ISIZE: 4 bytes
|
||||||
yield struct.pack("<L", size & int('FFFFFFFF', 16))
|
yield struct.pack('<L', size & int('FFFFFFFF', 16))
|
||||||
|
|
||||||
|
|
||||||
def decompress(body):
|
def decompress(body):
|
||||||
@@ -335,7 +335,7 @@ def gzip(compress_level=5, mime_types=['text/html', 'text/plain'],
|
|||||||
request = cherrypy.serving.request
|
request = cherrypy.serving.request
|
||||||
response = cherrypy.serving.response
|
response = cherrypy.serving.response
|
||||||
|
|
||||||
set_vary_header(response, "Accept-Encoding")
|
set_vary_header(response, 'Accept-Encoding')
|
||||||
|
|
||||||
if not response.body:
|
if not response.body:
|
||||||
# Response body is empty (might be a 304 for instance)
|
# Response body is empty (might be a 304 for instance)
|
||||||
@@ -345,7 +345,7 @@ def gzip(compress_level=5, mime_types=['text/html', 'text/plain'],
|
|||||||
|
|
||||||
# If returning cached content (which should already have been gzipped),
|
# If returning cached content (which should already have been gzipped),
|
||||||
# don't re-zip.
|
# don't re-zip.
|
||||||
if getattr(request, "cached", False):
|
if getattr(request, 'cached', False):
|
||||||
if debug:
|
if debug:
|
||||||
cherrypy.log('Not gzipping cached response', context='TOOLS.GZIP')
|
cherrypy.log('Not gzipping cached response', context='TOOLS.GZIP')
|
||||||
return
|
return
|
||||||
@@ -413,12 +413,12 @@ def gzip(compress_level=5, mime_types=['text/html', 'text/plain'],
|
|||||||
# Return a generator that compresses the page
|
# Return a generator that compresses the page
|
||||||
response.headers['Content-Encoding'] = 'gzip'
|
response.headers['Content-Encoding'] = 'gzip'
|
||||||
response.body = compress(response.body, compress_level)
|
response.body = compress(response.body, compress_level)
|
||||||
if "Content-Length" in response.headers:
|
if 'Content-Length' in response.headers:
|
||||||
# Delete Content-Length header so finalize() recalcs it.
|
# Delete Content-Length header so finalize() recalcs it.
|
||||||
del response.headers["Content-Length"]
|
del response.headers['Content-Length']
|
||||||
|
|
||||||
return
|
return
|
||||||
|
|
||||||
if debug:
|
if debug:
|
||||||
cherrypy.log('No acceptable encoding found.', context='GZIP')
|
cherrypy.log('No acceptable encoding found.', context='GZIP')
|
||||||
cherrypy.HTTPError(406, "identity, gzip").set_response()
|
cherrypy.HTTPError(406, 'identity, gzip').set_response()
|
||||||
@@ -1,6 +1,5 @@
|
|||||||
import gc
|
import gc
|
||||||
import inspect
|
import inspect
|
||||||
import os
|
|
||||||
import sys
|
import sys
|
||||||
import time
|
import time
|
||||||
|
|
||||||
@@ -36,7 +35,7 @@ class ReferrerTree(object):
|
|||||||
refs = gc.get_referrers(obj)
|
refs = gc.get_referrers(obj)
|
||||||
self.ignore.append(refs)
|
self.ignore.append(refs)
|
||||||
if len(refs) > self.maxparents:
|
if len(refs) > self.maxparents:
|
||||||
return [("[%s referrers]" % len(refs), [])]
|
return [('[%s referrers]' % len(refs), [])]
|
||||||
|
|
||||||
try:
|
try:
|
||||||
ascendcode = self.ascend.__code__
|
ascendcode = self.ascend.__code__
|
||||||
@@ -72,20 +71,20 @@ class ReferrerTree(object):
|
|||||||
return self.peek(repr(obj))
|
return self.peek(repr(obj))
|
||||||
|
|
||||||
if isinstance(obj, dict):
|
if isinstance(obj, dict):
|
||||||
return "{" + ", ".join(["%s: %s" % (self._format(k, descend=False),
|
return '{' + ', '.join(['%s: %s' % (self._format(k, descend=False),
|
||||||
self._format(v, descend=False))
|
self._format(v, descend=False))
|
||||||
for k, v in obj.items()]) + "}"
|
for k, v in obj.items()]) + '}'
|
||||||
elif isinstance(obj, list):
|
elif isinstance(obj, list):
|
||||||
return "[" + ", ".join([self._format(item, descend=False)
|
return '[' + ', '.join([self._format(item, descend=False)
|
||||||
for item in obj]) + "]"
|
for item in obj]) + ']'
|
||||||
elif isinstance(obj, tuple):
|
elif isinstance(obj, tuple):
|
||||||
return "(" + ", ".join([self._format(item, descend=False)
|
return '(' + ', '.join([self._format(item, descend=False)
|
||||||
for item in obj]) + ")"
|
for item in obj]) + ')'
|
||||||
|
|
||||||
r = self.peek(repr(obj))
|
r = self.peek(repr(obj))
|
||||||
if isinstance(obj, (str, int, float)):
|
if isinstance(obj, (str, int, float)):
|
||||||
return r
|
return r
|
||||||
return "%s: %s" % (type(obj), r)
|
return '%s: %s' % (type(obj), r)
|
||||||
|
|
||||||
def format(self, tree):
|
def format(self, tree):
|
||||||
"""Return a list of string reprs from a nested list of referrers."""
|
"""Return a list of string reprs from a nested list of referrers."""
|
||||||
@@ -93,7 +92,7 @@ class ReferrerTree(object):
|
|||||||
|
|
||||||
def ascend(branch, depth=1):
|
def ascend(branch, depth=1):
|
||||||
for parent, grandparents in branch:
|
for parent, grandparents in branch:
|
||||||
output.append((" " * depth) + self._format(parent))
|
output.append((' ' * depth) + self._format(parent))
|
||||||
if grandparents:
|
if grandparents:
|
||||||
ascend(grandparents, depth + 1)
|
ascend(grandparents, depth + 1)
|
||||||
ascend(tree)
|
ascend(tree)
|
||||||
@@ -120,14 +119,14 @@ request_counter.subscribe()
|
|||||||
|
|
||||||
def get_context(obj):
|
def get_context(obj):
|
||||||
if isinstance(obj, _cprequest.Request):
|
if isinstance(obj, _cprequest.Request):
|
||||||
return "path=%s;stage=%s" % (obj.path_info, obj.stage)
|
return 'path=%s;stage=%s' % (obj.path_info, obj.stage)
|
||||||
elif isinstance(obj, _cprequest.Response):
|
elif isinstance(obj, _cprequest.Response):
|
||||||
return "status=%s" % obj.status
|
return 'status=%s' % obj.status
|
||||||
elif isinstance(obj, _cpwsgi.AppResponse):
|
elif isinstance(obj, _cpwsgi.AppResponse):
|
||||||
return "PATH_INFO=%s" % obj.environ.get('PATH_INFO', '')
|
return 'PATH_INFO=%s' % obj.environ.get('PATH_INFO', '')
|
||||||
elif hasattr(obj, "tb_lineno"):
|
elif hasattr(obj, 'tb_lineno'):
|
||||||
return "tb_lineno=%s" % obj.tb_lineno
|
return 'tb_lineno=%s' % obj.tb_lineno
|
||||||
return ""
|
return ''
|
||||||
|
|
||||||
|
|
||||||
class GCRoot(object):
|
class GCRoot(object):
|
||||||
@@ -136,27 +135,27 @@ class GCRoot(object):
|
|||||||
|
|
||||||
classes = [
|
classes = [
|
||||||
(_cprequest.Request, 2, 2,
|
(_cprequest.Request, 2, 2,
|
||||||
"Should be 1 in this request thread and 1 in the main thread."),
|
'Should be 1 in this request thread and 1 in the main thread.'),
|
||||||
(_cprequest.Response, 2, 2,
|
(_cprequest.Response, 2, 2,
|
||||||
"Should be 1 in this request thread and 1 in the main thread."),
|
'Should be 1 in this request thread and 1 in the main thread.'),
|
||||||
(_cpwsgi.AppResponse, 1, 1,
|
(_cpwsgi.AppResponse, 1, 1,
|
||||||
"Should be 1 in this request thread only."),
|
'Should be 1 in this request thread only.'),
|
||||||
]
|
]
|
||||||
|
|
||||||
@cherrypy.expose
|
@cherrypy.expose
|
||||||
def index(self):
|
def index(self):
|
||||||
return "Hello, world!"
|
return 'Hello, world!'
|
||||||
|
|
||||||
@cherrypy.expose
|
@cherrypy.expose
|
||||||
def stats(self):
|
def stats(self):
|
||||||
output = ["Statistics:"]
|
output = ['Statistics:']
|
||||||
|
|
||||||
for trial in range(10):
|
for trial in range(10):
|
||||||
if request_counter.count > 0:
|
if request_counter.count > 0:
|
||||||
break
|
break
|
||||||
time.sleep(0.5)
|
time.sleep(0.5)
|
||||||
else:
|
else:
|
||||||
output.append("\nNot all requests closed properly.")
|
output.append('\nNot all requests closed properly.')
|
||||||
|
|
||||||
# gc_collect isn't perfectly synchronous, because it may
|
# gc_collect isn't perfectly synchronous, because it may
|
||||||
# break reference cycles that then take time to fully
|
# break reference cycles that then take time to fully
|
||||||
@@ -174,11 +173,11 @@ class GCRoot(object):
|
|||||||
for x in gc.garbage:
|
for x in gc.garbage:
|
||||||
trash[type(x)] = trash.get(type(x), 0) + 1
|
trash[type(x)] = trash.get(type(x), 0) + 1
|
||||||
if trash:
|
if trash:
|
||||||
output.insert(0, "\n%s unreachable objects:" % unreachable)
|
output.insert(0, '\n%s unreachable objects:' % unreachable)
|
||||||
trash = [(v, k) for k, v in trash.items()]
|
trash = [(v, k) for k, v in trash.items()]
|
||||||
trash.sort()
|
trash.sort()
|
||||||
for pair in trash:
|
for pair in trash:
|
||||||
output.append(" " + repr(pair))
|
output.append(' ' + repr(pair))
|
||||||
|
|
||||||
# Check declared classes to verify uncollected instances.
|
# Check declared classes to verify uncollected instances.
|
||||||
# These don't have to be part of a cycle; they can be
|
# These don't have to be part of a cycle; they can be
|
||||||
@@ -194,24 +193,24 @@ class GCRoot(object):
|
|||||||
if lenobj < minobj or lenobj > maxobj:
|
if lenobj < minobj or lenobj > maxobj:
|
||||||
if minobj == maxobj:
|
if minobj == maxobj:
|
||||||
output.append(
|
output.append(
|
||||||
"\nExpected %s %r references, got %s." %
|
'\nExpected %s %r references, got %s.' %
|
||||||
(minobj, cls, lenobj))
|
(minobj, cls, lenobj))
|
||||||
else:
|
else:
|
||||||
output.append(
|
output.append(
|
||||||
"\nExpected %s to %s %r references, got %s." %
|
'\nExpected %s to %s %r references, got %s.' %
|
||||||
(minobj, maxobj, cls, lenobj))
|
(minobj, maxobj, cls, lenobj))
|
||||||
|
|
||||||
for obj in objs:
|
for obj in objs:
|
||||||
if objgraph is not None:
|
if objgraph is not None:
|
||||||
ig = [id(objs), id(inspect.currentframe())]
|
ig = [id(objs), id(inspect.currentframe())]
|
||||||
fname = "graph_%s_%s.png" % (cls.__name__, id(obj))
|
fname = 'graph_%s_%s.png' % (cls.__name__, id(obj))
|
||||||
objgraph.show_backrefs(
|
objgraph.show_backrefs(
|
||||||
obj, extra_ignore=ig, max_depth=4, too_many=20,
|
obj, extra_ignore=ig, max_depth=4, too_many=20,
|
||||||
filename=fname, extra_info=get_context)
|
filename=fname, extra_info=get_context)
|
||||||
output.append("\nReferrers for %s (refcount=%s):" %
|
output.append('\nReferrers for %s (refcount=%s):' %
|
||||||
(repr(obj), sys.getrefcount(obj)))
|
(repr(obj), sys.getrefcount(obj)))
|
||||||
t = ReferrerTree(ignore=[objs], maxdepth=3)
|
t = ReferrerTree(ignore=[objs], maxdepth=3)
|
||||||
tree = t.ascend(obj)
|
tree = t.ascend(obj)
|
||||||
output.extend(t.format(tree))
|
output.extend(t.format(tree))
|
||||||
|
|
||||||
return "\n".join(output)
|
return '\n'.join(output)
|
||||||
@@ -1,6 +0,0 @@
|
|||||||
import warnings
|
|
||||||
warnings.warn('cherrypy.lib.http has been deprecated and will be removed '
|
|
||||||
'in CherryPy 3.3 use cherrypy.lib.httputil instead.',
|
|
||||||
DeprecationWarning)
|
|
||||||
|
|
||||||
from cherrypy.lib.httputil import *
|
|
||||||
@@ -20,8 +20,18 @@ Usage:
|
|||||||
SUPPORTED_ALGORITHM - list of supported 'Digest' algorithms
|
SUPPORTED_ALGORITHM - list of supported 'Digest' algorithms
|
||||||
SUPPORTED_QOP - list of supported 'Digest' 'qop'.
|
SUPPORTED_QOP - list of supported 'Digest' 'qop'.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
import time
|
||||||
|
from hashlib import md5
|
||||||
|
|
||||||
|
from cherrypy._cpcompat import (
|
||||||
|
base64_decode, ntob,
|
||||||
|
parse_http_list, parse_keqv_list
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
__version__ = 1, 0, 1
|
__version__ = 1, 0, 1
|
||||||
__author__ = "Tiago Cogumbreiro <cogumbreiro@users.sf.net>"
|
__author__ = 'Tiago Cogumbreiro <cogumbreiro@users.sf.net>'
|
||||||
__credits__ = """
|
__credits__ = """
|
||||||
Peter van Kampen for its recipe which implement most of Digest
|
Peter van Kampen for its recipe which implement most of Digest
|
||||||
authentication:
|
authentication:
|
||||||
@@ -56,21 +66,16 @@ OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
|||||||
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
__all__ = ("digestAuth", "basicAuth", "doAuth", "checkResponse",
|
__all__ = ('digestAuth', 'basicAuth', 'doAuth', 'checkResponse',
|
||||||
"parseAuthorization", "SUPPORTED_ALGORITHM", "md5SessionKey",
|
'parseAuthorization', 'SUPPORTED_ALGORITHM', 'md5SessionKey',
|
||||||
"calculateNonce", "SUPPORTED_QOP")
|
'calculateNonce', 'SUPPORTED_QOP')
|
||||||
|
|
||||||
##########################################################################
|
##########################################################################
|
||||||
import time
|
|
||||||
from hashlib import md5
|
|
||||||
|
|
||||||
from cherrypy._cpcompat import base64_decode, ntob
|
MD5 = 'MD5'
|
||||||
from cherrypy._cpcompat import parse_http_list, parse_keqv_list
|
MD5_SESS = 'MD5-sess'
|
||||||
|
AUTH = 'auth'
|
||||||
MD5 = "MD5"
|
AUTH_INT = 'auth-int'
|
||||||
MD5_SESS = "MD5-sess"
|
|
||||||
AUTH = "auth"
|
|
||||||
AUTH_INT = "auth-int"
|
|
||||||
|
|
||||||
SUPPORTED_ALGORITHM = (MD5, MD5_SESS)
|
SUPPORTED_ALGORITHM = (MD5, MD5_SESS)
|
||||||
SUPPORTED_QOP = (AUTH, AUTH_INT)
|
SUPPORTED_QOP = (AUTH, AUTH_INT)
|
||||||
@@ -95,10 +100,10 @@ def calculateNonce(realm, algorithm=MD5):
|
|||||||
try:
|
try:
|
||||||
encoder = DIGEST_AUTH_ENCODERS[algorithm]
|
encoder = DIGEST_AUTH_ENCODERS[algorithm]
|
||||||
except KeyError:
|
except KeyError:
|
||||||
raise NotImplementedError("The chosen algorithm (%s) does not have "
|
raise NotImplementedError('The chosen algorithm (%s) does not have '
|
||||||
"an implementation yet" % algorithm)
|
'an implementation yet' % algorithm)
|
||||||
|
|
||||||
return encoder("%d:%s" % (time.time(), realm))
|
return encoder('%d:%s' % (time.time(), realm))
|
||||||
|
|
||||||
|
|
||||||
def digestAuth(realm, algorithm=MD5, nonce=None, qop=AUTH):
|
def digestAuth(realm, algorithm=MD5, nonce=None, qop=AUTH):
|
||||||
@@ -129,7 +134,7 @@ def doAuth(realm):
|
|||||||
|
|
||||||
This should be set in the HTTP header under the key 'WWW-Authenticate'."""
|
This should be set in the HTTP header under the key 'WWW-Authenticate'."""
|
||||||
|
|
||||||
return digestAuth(realm) + " " + basicAuth(realm)
|
return digestAuth(realm) + ' ' + basicAuth(realm)
|
||||||
|
|
||||||
|
|
||||||
##########################################################################
|
##########################################################################
|
||||||
@@ -143,31 +148,31 @@ def _parseDigestAuthorization(auth_params):
|
|||||||
# Now validate the params
|
# Now validate the params
|
||||||
|
|
||||||
# Check for required parameters
|
# Check for required parameters
|
||||||
required = ["username", "realm", "nonce", "uri", "response"]
|
required = ['username', 'realm', 'nonce', 'uri', 'response']
|
||||||
for k in required:
|
for k in required:
|
||||||
if k not in params:
|
if k not in params:
|
||||||
return None
|
return None
|
||||||
|
|
||||||
# If qop is sent then cnonce and nc MUST be present
|
# If qop is sent then cnonce and nc MUST be present
|
||||||
if "qop" in params and not ("cnonce" in params
|
if 'qop' in params and not ('cnonce' in params
|
||||||
and "nc" in params):
|
and 'nc' in params):
|
||||||
return None
|
return None
|
||||||
|
|
||||||
# If qop is not sent, neither cnonce nor nc can be present
|
# If qop is not sent, neither cnonce nor nc can be present
|
||||||
if ("cnonce" in params or "nc" in params) and \
|
if ('cnonce' in params or 'nc' in params) and \
|
||||||
"qop" not in params:
|
'qop' not in params:
|
||||||
return None
|
return None
|
||||||
|
|
||||||
return params
|
return params
|
||||||
|
|
||||||
|
|
||||||
def _parseBasicAuthorization(auth_params):
|
def _parseBasicAuthorization(auth_params):
|
||||||
username, password = base64_decode(auth_params).split(":", 1)
|
username, password = base64_decode(auth_params).split(':', 1)
|
||||||
return {"username": username, "password": password}
|
return {'username': username, 'password': password}
|
||||||
|
|
||||||
AUTH_SCHEMES = {
|
AUTH_SCHEMES = {
|
||||||
"basic": _parseBasicAuthorization,
|
'basic': _parseBasicAuthorization,
|
||||||
"digest": _parseDigestAuthorization,
|
'digest': _parseDigestAuthorization,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -178,7 +183,7 @@ def parseAuthorization(credentials):
|
|||||||
|
|
||||||
global AUTH_SCHEMES
|
global AUTH_SCHEMES
|
||||||
|
|
||||||
auth_scheme, auth_params = credentials.split(" ", 1)
|
auth_scheme, auth_params = credentials.split(' ', 1)
|
||||||
auth_scheme = auth_scheme.lower()
|
auth_scheme = auth_scheme.lower()
|
||||||
|
|
||||||
parser = AUTH_SCHEMES[auth_scheme]
|
parser = AUTH_SCHEMES[auth_scheme]
|
||||||
@@ -187,8 +192,8 @@ def parseAuthorization(credentials):
|
|||||||
if params is None:
|
if params is None:
|
||||||
return
|
return
|
||||||
|
|
||||||
assert "auth_scheme" not in params
|
assert 'auth_scheme' not in params
|
||||||
params["auth_scheme"] = auth_scheme
|
params['auth_scheme'] = auth_scheme
|
||||||
return params
|
return params
|
||||||
|
|
||||||
|
|
||||||
@@ -214,50 +219,50 @@ def md5SessionKey(params, password):
|
|||||||
specification.
|
specification.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
keys = ("username", "realm", "nonce", "cnonce")
|
keys = ('username', 'realm', 'nonce', 'cnonce')
|
||||||
params_copy = {}
|
params_copy = {}
|
||||||
for key in keys:
|
for key in keys:
|
||||||
params_copy[key] = params[key]
|
params_copy[key] = params[key]
|
||||||
|
|
||||||
params_copy["algorithm"] = MD5_SESS
|
params_copy['algorithm'] = MD5_SESS
|
||||||
return _A1(params_copy, password)
|
return _A1(params_copy, password)
|
||||||
|
|
||||||
|
|
||||||
def _A1(params, password):
|
def _A1(params, password):
|
||||||
algorithm = params.get("algorithm", MD5)
|
algorithm = params.get('algorithm', MD5)
|
||||||
H = DIGEST_AUTH_ENCODERS[algorithm]
|
H = DIGEST_AUTH_ENCODERS[algorithm]
|
||||||
|
|
||||||
if algorithm == MD5:
|
if algorithm == MD5:
|
||||||
# If the "algorithm" directive's value is "MD5" or is
|
# If the "algorithm" directive's value is "MD5" or is
|
||||||
# unspecified, then A1 is:
|
# unspecified, then A1 is:
|
||||||
# A1 = unq(username-value) ":" unq(realm-value) ":" passwd
|
# A1 = unq(username-value) ":" unq(realm-value) ":" passwd
|
||||||
return "%s:%s:%s" % (params["username"], params["realm"], password)
|
return '%s:%s:%s' % (params['username'], params['realm'], password)
|
||||||
|
|
||||||
elif algorithm == MD5_SESS:
|
elif algorithm == MD5_SESS:
|
||||||
|
|
||||||
# This is A1 if qop is set
|
# This is A1 if qop is set
|
||||||
# A1 = H( unq(username-value) ":" unq(realm-value) ":" passwd )
|
# A1 = H( unq(username-value) ":" unq(realm-value) ":" passwd )
|
||||||
# ":" unq(nonce-value) ":" unq(cnonce-value)
|
# ":" unq(nonce-value) ":" unq(cnonce-value)
|
||||||
h_a1 = H("%s:%s:%s" % (params["username"], params["realm"], password))
|
h_a1 = H('%s:%s:%s' % (params['username'], params['realm'], password))
|
||||||
return "%s:%s:%s" % (h_a1, params["nonce"], params["cnonce"])
|
return '%s:%s:%s' % (h_a1, params['nonce'], params['cnonce'])
|
||||||
|
|
||||||
|
|
||||||
def _A2(params, method, kwargs):
|
def _A2(params, method, kwargs):
|
||||||
# If the "qop" directive's value is "auth" or is unspecified, then A2 is:
|
# If the "qop" directive's value is "auth" or is unspecified, then A2 is:
|
||||||
# A2 = Method ":" digest-uri-value
|
# A2 = Method ":" digest-uri-value
|
||||||
|
|
||||||
qop = params.get("qop", "auth")
|
qop = params.get('qop', 'auth')
|
||||||
if qop == "auth":
|
if qop == 'auth':
|
||||||
return method + ":" + params["uri"]
|
return method + ':' + params['uri']
|
||||||
elif qop == "auth-int":
|
elif qop == 'auth-int':
|
||||||
# If the "qop" value is "auth-int", then A2 is:
|
# If the "qop" value is "auth-int", then A2 is:
|
||||||
# A2 = Method ":" digest-uri-value ":" H(entity-body)
|
# A2 = Method ":" digest-uri-value ":" H(entity-body)
|
||||||
entity_body = kwargs.get("entity_body", "")
|
entity_body = kwargs.get('entity_body', '')
|
||||||
H = kwargs["H"]
|
H = kwargs['H']
|
||||||
|
|
||||||
return "%s:%s:%s" % (
|
return '%s:%s:%s' % (
|
||||||
method,
|
method,
|
||||||
params["uri"],
|
params['uri'],
|
||||||
H(entity_body)
|
H(entity_body)
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -265,19 +270,19 @@ def _A2(params, method, kwargs):
|
|||||||
raise NotImplementedError("The 'qop' method is unknown: %s" % qop)
|
raise NotImplementedError("The 'qop' method is unknown: %s" % qop)
|
||||||
|
|
||||||
|
|
||||||
def _computeDigestResponse(auth_map, password, method="GET", A1=None,
|
def _computeDigestResponse(auth_map, password, method='GET', A1=None,
|
||||||
**kwargs):
|
**kwargs):
|
||||||
"""
|
"""
|
||||||
Generates a response respecting the algorithm defined in RFC 2617
|
Generates a response respecting the algorithm defined in RFC 2617
|
||||||
"""
|
"""
|
||||||
params = auth_map
|
params = auth_map
|
||||||
|
|
||||||
algorithm = params.get("algorithm", MD5)
|
algorithm = params.get('algorithm', MD5)
|
||||||
|
|
||||||
H = DIGEST_AUTH_ENCODERS[algorithm]
|
H = DIGEST_AUTH_ENCODERS[algorithm]
|
||||||
KD = lambda secret, data: H(secret + ":" + data)
|
KD = lambda secret, data: H(secret + ':' + data)
|
||||||
|
|
||||||
qop = params.get("qop", None)
|
qop = params.get('qop', None)
|
||||||
|
|
||||||
H_A2 = H(_A2(params, method, kwargs))
|
H_A2 = H(_A2(params, method, kwargs))
|
||||||
|
|
||||||
@@ -286,7 +291,7 @@ def _computeDigestResponse(auth_map, password, method="GET", A1=None,
|
|||||||
else:
|
else:
|
||||||
H_A1 = H(_A1(params, password))
|
H_A1 = H(_A1(params, password))
|
||||||
|
|
||||||
if qop in ("auth", "auth-int"):
|
if qop in ('auth', 'auth-int'):
|
||||||
# If the "qop" value is "auth" or "auth-int":
|
# If the "qop" value is "auth" or "auth-int":
|
||||||
# request-digest = <"> < KD ( H(A1), unq(nonce-value)
|
# request-digest = <"> < KD ( H(A1), unq(nonce-value)
|
||||||
# ":" nc-value
|
# ":" nc-value
|
||||||
@@ -294,11 +299,11 @@ def _computeDigestResponse(auth_map, password, method="GET", A1=None,
|
|||||||
# ":" unq(qop-value)
|
# ":" unq(qop-value)
|
||||||
# ":" H(A2)
|
# ":" H(A2)
|
||||||
# ) <">
|
# ) <">
|
||||||
request = "%s:%s:%s:%s:%s" % (
|
request = '%s:%s:%s:%s:%s' % (
|
||||||
params["nonce"],
|
params['nonce'],
|
||||||
params["nc"],
|
params['nc'],
|
||||||
params["cnonce"],
|
params['cnonce'],
|
||||||
params["qop"],
|
params['qop'],
|
||||||
H_A2,
|
H_A2,
|
||||||
)
|
)
|
||||||
elif qop is None:
|
elif qop is None:
|
||||||
@@ -306,12 +311,12 @@ def _computeDigestResponse(auth_map, password, method="GET", A1=None,
|
|||||||
# for compatibility with RFC 2069):
|
# for compatibility with RFC 2069):
|
||||||
# request-digest =
|
# request-digest =
|
||||||
# <"> < KD ( H(A1), unq(nonce-value) ":" H(A2) ) > <">
|
# <"> < KD ( H(A1), unq(nonce-value) ":" H(A2) ) > <">
|
||||||
request = "%s:%s" % (params["nonce"], H_A2)
|
request = '%s:%s' % (params['nonce'], H_A2)
|
||||||
|
|
||||||
return KD(H_A1, request)
|
return KD(H_A1, request)
|
||||||
|
|
||||||
|
|
||||||
def _checkDigestResponse(auth_map, password, method="GET", A1=None, **kwargs):
|
def _checkDigestResponse(auth_map, password, method='GET', A1=None, **kwargs):
|
||||||
"""This function is used to verify the response given by the client when
|
"""This function is used to verify the response given by the client when
|
||||||
he tries to authenticate.
|
he tries to authenticate.
|
||||||
Optional arguments:
|
Optional arguments:
|
||||||
@@ -329,7 +334,7 @@ def _checkDigestResponse(auth_map, password, method="GET", A1=None, **kwargs):
|
|||||||
response = _computeDigestResponse(
|
response = _computeDigestResponse(
|
||||||
auth_map, password, method, A1, **kwargs)
|
auth_map, password, method, A1, **kwargs)
|
||||||
|
|
||||||
return response == auth_map["response"]
|
return response == auth_map['response']
|
||||||
|
|
||||||
|
|
||||||
def _checkBasicResponse(auth_map, password, method='GET', encrypt=None,
|
def _checkBasicResponse(auth_map, password, method='GET', encrypt=None,
|
||||||
@@ -339,19 +344,19 @@ def _checkBasicResponse(auth_map, password, method='GET', encrypt=None,
|
|||||||
pass_through = lambda password, username=None: password
|
pass_through = lambda password, username=None: password
|
||||||
encrypt = encrypt or pass_through
|
encrypt = encrypt or pass_through
|
||||||
try:
|
try:
|
||||||
candidate = encrypt(auth_map["password"], auth_map["username"])
|
candidate = encrypt(auth_map['password'], auth_map['username'])
|
||||||
except TypeError:
|
except TypeError:
|
||||||
# if encrypt only takes one parameter, it's the password
|
# if encrypt only takes one parameter, it's the password
|
||||||
candidate = encrypt(auth_map["password"])
|
candidate = encrypt(auth_map['password'])
|
||||||
return candidate == password
|
return candidate == password
|
||||||
|
|
||||||
AUTH_RESPONSES = {
|
AUTH_RESPONSES = {
|
||||||
"basic": _checkBasicResponse,
|
'basic': _checkBasicResponse,
|
||||||
"digest": _checkDigestResponse,
|
'digest': _checkDigestResponse,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
def checkResponse(auth_map, password, method="GET", encrypt=None, **kwargs):
|
def checkResponse(auth_map, password, method='GET', encrypt=None, **kwargs):
|
||||||
"""'checkResponse' compares the auth_map with the password and optionally
|
"""'checkResponse' compares the auth_map with the password and optionally
|
||||||
other arguments that each implementation might need.
|
other arguments that each implementation might need.
|
||||||
|
|
||||||
@@ -368,6 +373,6 @@ def checkResponse(auth_map, password, method="GET", encrypt=None, **kwargs):
|
|||||||
The 'A1' argument is only used in MD5_SESS algorithm based responses.
|
The 'A1' argument is only used in MD5_SESS algorithm based responses.
|
||||||
Check md5SessionKey() for more info.
|
Check md5SessionKey() for more info.
|
||||||
"""
|
"""
|
||||||
checker = AUTH_RESPONSES[auth_map["auth_scheme"]]
|
checker = AUTH_RESPONSES[auth_map['auth_scheme']]
|
||||||
return checker(auth_map, password, method=method, encrypt=encrypt,
|
return checker(auth_map, password, method=method, encrypt=encrypt,
|
||||||
**kwargs)
|
**kwargs)
|
||||||
@@ -7,13 +7,23 @@ FuManChu will personally hang you up by your thumbs and submit you
|
|||||||
to a public caning.
|
to a public caning.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
import functools
|
||||||
|
import email.utils
|
||||||
|
import re
|
||||||
from binascii import b2a_base64
|
from binascii import b2a_base64
|
||||||
|
from cgi import parse_header
|
||||||
|
try:
|
||||||
|
# Python 3
|
||||||
|
from email.header import decode_header
|
||||||
|
except ImportError:
|
||||||
|
from email.Header import decode_header
|
||||||
|
|
||||||
import six
|
import six
|
||||||
|
|
||||||
from cherrypy._cpcompat import BaseHTTPRequestHandler, HTTPDate, ntob, ntou
|
from cherrypy._cpcompat import BaseHTTPRequestHandler, ntob, ntou
|
||||||
from cherrypy._cpcompat import basestring, iteritems
|
from cherrypy._cpcompat import text_or_bytes, iteritems
|
||||||
from cherrypy._cpcompat import reversed, sorted, unquote_qs
|
from cherrypy._cpcompat import reversed, sorted, unquote_qs
|
||||||
|
|
||||||
response_codes = BaseHTTPRequestHandler.responses.copy()
|
response_codes = BaseHTTPRequestHandler.responses.copy()
|
||||||
|
|
||||||
# From https://github.com/cherrypy/cherrypy/issues/361
|
# From https://github.com/cherrypy/cherrypy/issues/361
|
||||||
@@ -25,8 +35,8 @@ response_codes[503] = ('Service Unavailable',
|
|||||||
'request due to a temporary overloading or '
|
'request due to a temporary overloading or '
|
||||||
'maintenance of the server.')
|
'maintenance of the server.')
|
||||||
|
|
||||||
import re
|
|
||||||
from cgi import parse_header
|
HTTPDate = functools.partial(email.utils.formatdate, usegmt=True)
|
||||||
|
|
||||||
|
|
||||||
def urljoin(*atoms):
|
def urljoin(*atoms):
|
||||||
@@ -35,11 +45,11 @@ def urljoin(*atoms):
|
|||||||
This will correctly join a SCRIPT_NAME and PATH_INFO into the
|
This will correctly join a SCRIPT_NAME and PATH_INFO into the
|
||||||
original URL, even if either atom is blank.
|
original URL, even if either atom is blank.
|
||||||
"""
|
"""
|
||||||
url = "/".join([x for x in atoms if x])
|
url = '/'.join([x for x in atoms if x])
|
||||||
while "//" in url:
|
while '//' in url:
|
||||||
url = url.replace("//", "/")
|
url = url.replace('//', '/')
|
||||||
# Special-case the final url of "", and return "/" instead.
|
# Special-case the final url of "", and return "/" instead.
|
||||||
return url or "/"
|
return url or '/'
|
||||||
|
|
||||||
|
|
||||||
def urljoin_bytes(*atoms):
|
def urljoin_bytes(*atoms):
|
||||||
@@ -48,11 +58,11 @@ def urljoin_bytes(*atoms):
|
|||||||
This will correctly join a SCRIPT_NAME and PATH_INFO into the
|
This will correctly join a SCRIPT_NAME and PATH_INFO into the
|
||||||
original URL, even if either atom is blank.
|
original URL, even if either atom is blank.
|
||||||
"""
|
"""
|
||||||
url = ntob("/").join([x for x in atoms if x])
|
url = ntob('/').join([x for x in atoms if x])
|
||||||
while ntob("//") in url:
|
while ntob('//') in url:
|
||||||
url = url.replace(ntob("//"), ntob("/"))
|
url = url.replace(ntob('//'), ntob('/'))
|
||||||
# Special-case the final url of "", and return "/" instead.
|
# Special-case the final url of "", and return "/" instead.
|
||||||
return url or ntob("/")
|
return url or ntob('/')
|
||||||
|
|
||||||
|
|
||||||
def protocol_from_http(protocol_str):
|
def protocol_from_http(protocol_str):
|
||||||
@@ -75,9 +85,9 @@ def get_ranges(headervalue, content_length):
|
|||||||
return None
|
return None
|
||||||
|
|
||||||
result = []
|
result = []
|
||||||
bytesunit, byteranges = headervalue.split("=", 1)
|
bytesunit, byteranges = headervalue.split('=', 1)
|
||||||
for brange in byteranges.split(","):
|
for brange in byteranges.split(','):
|
||||||
start, stop = [x.strip() for x in brange.split("-", 1)]
|
start, stop = [x.strip() for x in brange.split('-', 1)]
|
||||||
if start:
|
if start:
|
||||||
if not stop:
|
if not stop:
|
||||||
stop = content_length - 1
|
stop = content_length - 1
|
||||||
@@ -135,8 +145,8 @@ class HeaderElement(object):
|
|||||||
return self.value < other.value
|
return self.value < other.value
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
p = [";%s=%s" % (k, v) for k, v in iteritems(self.params)]
|
p = [';%s=%s' % (k, v) for k, v in iteritems(self.params)]
|
||||||
return str("%s%s" % (self.value, "".join(p)))
|
return str('%s%s' % (self.value, ''.join(p)))
|
||||||
|
|
||||||
def __bytes__(self):
|
def __bytes__(self):
|
||||||
return ntob(self.__str__())
|
return ntob(self.__str__())
|
||||||
@@ -144,17 +154,17 @@ class HeaderElement(object):
|
|||||||
def __unicode__(self):
|
def __unicode__(self):
|
||||||
return ntou(self.__str__())
|
return ntou(self.__str__())
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
def parse(elementstr):
|
def parse(elementstr):
|
||||||
"""Transform 'token;key=val' to ('token', {'key': 'val'})."""
|
"""Transform 'token;key=val' to ('token', {'key': 'val'})."""
|
||||||
initial_value, params = parse_header(elementstr)
|
initial_value, params = parse_header(elementstr)
|
||||||
return initial_value, params
|
return initial_value, params
|
||||||
parse = staticmethod(parse)
|
|
||||||
|
|
||||||
|
@classmethod
|
||||||
def from_str(cls, elementstr):
|
def from_str(cls, elementstr):
|
||||||
"""Construct an instance from a string of the form 'token;key=val'."""
|
"""Construct an instance from a string of the form 'token;key=val'."""
|
||||||
ival, params = cls.parse(elementstr)
|
ival, params = cls.parse(elementstr)
|
||||||
return cls(ival, params)
|
return cls(ival, params)
|
||||||
from_str = classmethod(from_str)
|
|
||||||
|
|
||||||
|
|
||||||
q_separator = re.compile(r'; *q *=')
|
q_separator = re.compile(r'; *q *=')
|
||||||
@@ -171,6 +181,7 @@ class AcceptElement(HeaderElement):
|
|||||||
have been the other way around, but it's too late to fix now.
|
have been the other way around, but it's too late to fix now.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
@classmethod
|
||||||
def from_str(cls, elementstr):
|
def from_str(cls, elementstr):
|
||||||
qvalue = None
|
qvalue = None
|
||||||
# The first "q" parameter (if any) separates the initial
|
# The first "q" parameter (if any) separates the initial
|
||||||
@@ -184,16 +195,16 @@ class AcceptElement(HeaderElement):
|
|||||||
|
|
||||||
media_type, params = cls.parse(media_range)
|
media_type, params = cls.parse(media_range)
|
||||||
if qvalue is not None:
|
if qvalue is not None:
|
||||||
params["q"] = qvalue
|
params['q'] = qvalue
|
||||||
return cls(media_type, params)
|
return cls(media_type, params)
|
||||||
from_str = classmethod(from_str)
|
|
||||||
|
|
||||||
|
@property
|
||||||
def qvalue(self):
|
def qvalue(self):
|
||||||
val = self.params.get("q", "1")
|
'The qvalue, or priority, of this value.'
|
||||||
|
val = self.params.get('q', '1')
|
||||||
if isinstance(val, HeaderElement):
|
if isinstance(val, HeaderElement):
|
||||||
val = val.value
|
val = val.value
|
||||||
return float(val)
|
return float(val)
|
||||||
qvalue = property(qvalue, doc="The qvalue, or priority, of this value.")
|
|
||||||
|
|
||||||
def __cmp__(self, other):
|
def __cmp__(self, other):
|
||||||
diff = cmp(self.qvalue, other.qvalue)
|
diff = cmp(self.qvalue, other.qvalue)
|
||||||
@@ -216,7 +227,7 @@ def header_elements(fieldname, fieldvalue):
|
|||||||
|
|
||||||
result = []
|
result = []
|
||||||
for element in RE_HEADER_SPLIT.split(fieldvalue):
|
for element in RE_HEADER_SPLIT.split(fieldvalue):
|
||||||
if fieldname.startswith("Accept") or fieldname == 'TE':
|
if fieldname.startswith('Accept') or fieldname == 'TE':
|
||||||
hv = AcceptElement.from_str(element)
|
hv = AcceptElement.from_str(element)
|
||||||
else:
|
else:
|
||||||
hv = HeaderElement.from_str(element)
|
hv = HeaderElement.from_str(element)
|
||||||
@@ -227,13 +238,8 @@ def header_elements(fieldname, fieldvalue):
|
|||||||
|
|
||||||
def decode_TEXT(value):
|
def decode_TEXT(value):
|
||||||
r"""Decode :rfc:`2047` TEXT (e.g. "=?utf-8?q?f=C3=BCr?=" -> "f\xfcr")."""
|
r"""Decode :rfc:`2047` TEXT (e.g. "=?utf-8?q?f=C3=BCr?=" -> "f\xfcr")."""
|
||||||
try:
|
|
||||||
# Python 3
|
|
||||||
from email.header import decode_header
|
|
||||||
except ImportError:
|
|
||||||
from email.Header import decode_header
|
|
||||||
atoms = decode_header(value)
|
atoms = decode_header(value)
|
||||||
decodedvalue = ""
|
decodedvalue = ''
|
||||||
for atom, charset in atoms:
|
for atom, charset in atoms:
|
||||||
if charset is not None:
|
if charset is not None:
|
||||||
atom = atom.decode(charset)
|
atom = atom.decode(charset)
|
||||||
@@ -254,7 +260,7 @@ def valid_status(status):
|
|||||||
status = 200
|
status = 200
|
||||||
|
|
||||||
status = str(status)
|
status = str(status)
|
||||||
parts = status.split(" ", 1)
|
parts = status.split(' ', 1)
|
||||||
if len(parts) == 1:
|
if len(parts) == 1:
|
||||||
# No reason supplied.
|
# No reason supplied.
|
||||||
code, = parts
|
code, = parts
|
||||||
@@ -266,16 +272,16 @@ def valid_status(status):
|
|||||||
try:
|
try:
|
||||||
code = int(code)
|
code = int(code)
|
||||||
except ValueError:
|
except ValueError:
|
||||||
raise ValueError("Illegal response status from server "
|
raise ValueError('Illegal response status from server '
|
||||||
"(%s is non-numeric)." % repr(code))
|
'(%s is non-numeric).' % repr(code))
|
||||||
|
|
||||||
if code < 100 or code > 599:
|
if code < 100 or code > 599:
|
||||||
raise ValueError("Illegal response status from server "
|
raise ValueError('Illegal response status from server '
|
||||||
"(%s is out of range)." % repr(code))
|
'(%s is out of range).' % repr(code))
|
||||||
|
|
||||||
if code not in response_codes:
|
if code not in response_codes:
|
||||||
# code is unknown but not illegal
|
# code is unknown but not illegal
|
||||||
default_reason, message = "", ""
|
default_reason, message = '', ''
|
||||||
else:
|
else:
|
||||||
default_reason, message = response_codes[code]
|
default_reason, message = response_codes[code]
|
||||||
|
|
||||||
@@ -316,7 +322,7 @@ def _parse_qs(qs, keep_blank_values=0, strict_parsing=0, encoding='utf-8'):
|
|||||||
nv = name_value.split('=', 1)
|
nv = name_value.split('=', 1)
|
||||||
if len(nv) != 2:
|
if len(nv) != 2:
|
||||||
if strict_parsing:
|
if strict_parsing:
|
||||||
raise ValueError("bad query field: %r" % (name_value,))
|
raise ValueError('bad query field: %r' % (name_value,))
|
||||||
# Handle case of a control-name with no equal sign
|
# Handle case of a control-name with no equal sign
|
||||||
if keep_blank_values:
|
if keep_blank_values:
|
||||||
nv.append('')
|
nv.append('')
|
||||||
@@ -334,7 +340,7 @@ def _parse_qs(qs, keep_blank_values=0, strict_parsing=0, encoding='utf-8'):
|
|||||||
return d
|
return d
|
||||||
|
|
||||||
|
|
||||||
image_map_pattern = re.compile(r"[0-9]+,[0-9]+")
|
image_map_pattern = re.compile(r'[0-9]+,[0-9]+')
|
||||||
|
|
||||||
|
|
||||||
def parse_query_string(query_string, keep_blank_values=True, encoding='utf-8'):
|
def parse_query_string(query_string, keep_blank_values=True, encoding='utf-8'):
|
||||||
@@ -347,7 +353,7 @@ def parse_query_string(query_string, keep_blank_values=True, encoding='utf-8'):
|
|||||||
if image_map_pattern.match(query_string):
|
if image_map_pattern.match(query_string):
|
||||||
# Server-side image map. Map the coords to 'x' and 'y'
|
# Server-side image map. Map the coords to 'x' and 'y'
|
||||||
# (like CGI::Request does).
|
# (like CGI::Request does).
|
||||||
pm = query_string.split(",")
|
pm = query_string.split(',')
|
||||||
pm = {'x': int(pm[0]), 'y': int(pm[1])}
|
pm = {'x': int(pm[0]), 'y': int(pm[1])}
|
||||||
else:
|
else:
|
||||||
pm = _parse_qs(query_string, keep_blank_values, encoding=encoding)
|
pm = _parse_qs(query_string, keep_blank_values, encoding=encoding)
|
||||||
@@ -384,12 +390,12 @@ class CaseInsensitiveDict(dict):
|
|||||||
for k in E.keys():
|
for k in E.keys():
|
||||||
self[str(k).title()] = E[k]
|
self[str(k).title()] = E[k]
|
||||||
|
|
||||||
|
@classmethod
|
||||||
def fromkeys(cls, seq, value=None):
|
def fromkeys(cls, seq, value=None):
|
||||||
newdict = cls()
|
newdict = cls()
|
||||||
for k in seq:
|
for k in seq:
|
||||||
newdict[str(k).title()] = value
|
newdict[str(k).title()] = value
|
||||||
return newdict
|
return newdict
|
||||||
fromkeys = classmethod(fromkeys)
|
|
||||||
|
|
||||||
def setdefault(self, key, x=None):
|
def setdefault(self, key, x=None):
|
||||||
key = str(key).title()
|
key = str(key).title()
|
||||||
@@ -428,7 +434,7 @@ class HeaderMap(CaseInsensitiveDict):
|
|||||||
"""
|
"""
|
||||||
|
|
||||||
protocol = (1, 1)
|
protocol = (1, 1)
|
||||||
encodings = ["ISO-8859-1"]
|
encodings = ['ISO-8859-1']
|
||||||
|
|
||||||
# Someday, when http-bis is done, this will probably get dropped
|
# Someday, when http-bis is done, this will probably get dropped
|
||||||
# since few servers, clients, or intermediaries do it. But until then,
|
# since few servers, clients, or intermediaries do it. But until then,
|
||||||
@@ -451,6 +457,7 @@ class HeaderMap(CaseInsensitiveDict):
|
|||||||
"""Transform self into a list of (name, value) tuples."""
|
"""Transform self into a list of (name, value) tuples."""
|
||||||
return list(self.encode_header_items(self.items()))
|
return list(self.encode_header_items(self.items()))
|
||||||
|
|
||||||
|
@classmethod
|
||||||
def encode_header_items(cls, header_items):
|
def encode_header_items(cls, header_items):
|
||||||
"""
|
"""
|
||||||
Prepare the sequence of name, value tuples into a form suitable for
|
Prepare the sequence of name, value tuples into a form suitable for
|
||||||
@@ -460,7 +467,7 @@ class HeaderMap(CaseInsensitiveDict):
|
|||||||
if isinstance(k, six.text_type):
|
if isinstance(k, six.text_type):
|
||||||
k = cls.encode(k)
|
k = cls.encode(k)
|
||||||
|
|
||||||
if not isinstance(v, basestring):
|
if not isinstance(v, text_or_bytes):
|
||||||
v = str(v)
|
v = str(v)
|
||||||
|
|
||||||
if isinstance(v, six.text_type):
|
if isinstance(v, six.text_type):
|
||||||
@@ -474,8 +481,8 @@ class HeaderMap(CaseInsensitiveDict):
|
|||||||
header_translate_deletechars)
|
header_translate_deletechars)
|
||||||
|
|
||||||
yield (k, v)
|
yield (k, v)
|
||||||
encode_header_items = classmethod(encode_header_items)
|
|
||||||
|
|
||||||
|
@classmethod
|
||||||
def encode(cls, v):
|
def encode(cls, v):
|
||||||
"""Return the given header name or value, encoded for HTTP output."""
|
"""Return the given header name or value, encoded for HTTP output."""
|
||||||
for enc in cls.encodings:
|
for enc in cls.encodings:
|
||||||
@@ -493,10 +500,9 @@ class HeaderMap(CaseInsensitiveDict):
|
|||||||
v = b2a_base64(v.encode('utf-8'))
|
v = b2a_base64(v.encode('utf-8'))
|
||||||
return (ntob('=?utf-8?b?') + v.strip(ntob('\n')) + ntob('?='))
|
return (ntob('=?utf-8?b?') + v.strip(ntob('\n')) + ntob('?='))
|
||||||
|
|
||||||
raise ValueError("Could not encode header part %r using "
|
raise ValueError('Could not encode header part %r using '
|
||||||
"any of the encodings %r." %
|
'any of the encodings %r.' %
|
||||||
(v, cls.encodings))
|
(v, cls.encodings))
|
||||||
encode = classmethod(encode)
|
|
||||||
|
|
||||||
|
|
||||||
class Host(object):
|
class Host(object):
|
||||||
@@ -509,9 +515,9 @@ class Host(object):
|
|||||||
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
ip = "0.0.0.0"
|
ip = '0.0.0.0'
|
||||||
port = 80
|
port = 80
|
||||||
name = "unknown.tld"
|
name = 'unknown.tld'
|
||||||
|
|
||||||
def __init__(self, ip, port, name=None):
|
def __init__(self, ip, port, name=None):
|
||||||
self.ip = ip
|
self.ip = ip
|
||||||
@@ -521,4 +527,4 @@ class Host(object):
|
|||||||
self.name = name
|
self.name = name
|
||||||
|
|
||||||
def __repr__(self):
|
def __repr__(self):
|
||||||
return "httputil.Host(%r, %r, %r)" % (self.ip, self.port, self.name)
|
return 'httputil.Host(%r, %r, %r)' % (self.ip, self.port, self.name)
|
||||||
@@ -1,17 +1,15 @@
|
|||||||
import cherrypy
|
import cherrypy
|
||||||
from cherrypy._cpcompat import basestring, ntou, json_encode, json_decode
|
from cherrypy._cpcompat import text_or_bytes, ntou, json_encode, json_decode
|
||||||
|
|
||||||
|
|
||||||
def json_processor(entity):
|
def json_processor(entity):
|
||||||
"""Read application/json data into request.json."""
|
"""Read application/json data into request.json."""
|
||||||
if not entity.headers.get(ntou("Content-Length"), ntou("")):
|
if not entity.headers.get(ntou('Content-Length'), ntou('')):
|
||||||
raise cherrypy.HTTPError(411)
|
raise cherrypy.HTTPError(411)
|
||||||
|
|
||||||
body = entity.fp.read()
|
body = entity.fp.read()
|
||||||
try:
|
with cherrypy.HTTPError.handle(ValueError, 400, 'Invalid JSON document'):
|
||||||
cherrypy.serving.request.json = json_decode(body.decode('utf-8'))
|
cherrypy.serving.request.json = json_decode(body.decode('utf-8'))
|
||||||
except ValueError:
|
|
||||||
raise cherrypy.HTTPError(400, 'Invalid JSON document')
|
|
||||||
|
|
||||||
|
|
||||||
def json_in(content_type=[ntou('application/json'), ntou('text/javascript')],
|
def json_in(content_type=[ntou('application/json'), ntou('text/javascript')],
|
||||||
@@ -41,7 +39,7 @@ def json_in(content_type=[ntou('application/json'), ntou('text/javascript')],
|
|||||||
package importable; otherwise, ValueError is raised during processing.
|
package importable; otherwise, ValueError is raised during processing.
|
||||||
"""
|
"""
|
||||||
request = cherrypy.serving.request
|
request = cherrypy.serving.request
|
||||||
if isinstance(content_type, basestring):
|
if isinstance(content_type, text_or_bytes):
|
||||||
content_type = [content_type]
|
content_type = [content_type]
|
||||||
|
|
||||||
if force:
|
if force:
|
||||||
|
|||||||
@@ -1,147 +1,142 @@
|
|||||||
"""
|
"""
|
||||||
Platform-independent file locking. Inspired by and modeled after zc.lockfile.
|
Platform-independent file locking. Inspired by and modeled after zc.lockfile.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
import os
|
import os
|
||||||
|
|
||||||
try:
|
try:
|
||||||
import msvcrt
|
import msvcrt
|
||||||
except ImportError:
|
except ImportError:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
try:
|
try:
|
||||||
import fcntl
|
import fcntl
|
||||||
except ImportError:
|
except ImportError:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
class LockError(Exception):
|
class LockError(Exception):
|
||||||
|
|
||||||
"Could not obtain a lock"
|
'Could not obtain a lock'
|
||||||
|
|
||||||
msg = "Unable to lock %r"
|
msg = 'Unable to lock %r'
|
||||||
|
|
||||||
def __init__(self, path):
|
def __init__(self, path):
|
||||||
super(LockError, self).__init__(self.msg % path)
|
super(LockError, self).__init__(self.msg % path)
|
||||||
|
|
||||||
|
|
||||||
class UnlockError(LockError):
|
class UnlockError(LockError):
|
||||||
|
|
||||||
"Could not release a lock"
|
'Could not release a lock'
|
||||||
|
|
||||||
msg = "Unable to unlock %r"
|
msg = 'Unable to unlock %r'
|
||||||
|
|
||||||
|
|
||||||
# first, a default, naive locking implementation
|
# first, a default, naive locking implementation
|
||||||
class LockFile(object):
|
class LockFile(object):
|
||||||
|
|
||||||
"""
|
"""
|
||||||
A default, naive locking implementation. Always fails if the file
|
A default, naive locking implementation. Always fails if the file
|
||||||
already exists.
|
already exists.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self, path):
|
def __init__(self, path):
|
||||||
self.path = path
|
self.path = path
|
||||||
try:
|
try:
|
||||||
fd = os.open(path, os.O_CREAT | os.O_WRONLY | os.O_EXCL)
|
fd = os.open(path, os.O_CREAT | os.O_WRONLY | os.O_EXCL)
|
||||||
except OSError:
|
except OSError:
|
||||||
raise LockError(self.path)
|
raise LockError(self.path)
|
||||||
os.close(fd)
|
os.close(fd)
|
||||||
|
|
||||||
def release(self):
|
def release(self):
|
||||||
os.remove(self.path)
|
os.remove(self.path)
|
||||||
|
|
||||||
def remove(self):
|
def remove(self):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
class SystemLockFile(object):
|
class SystemLockFile(object):
|
||||||
|
|
||||||
"""
|
"""
|
||||||
An abstract base class for platform-specific locking.
|
An abstract base class for platform-specific locking.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self, path):
|
def __init__(self, path):
|
||||||
self.path = path
|
self.path = path
|
||||||
|
|
||||||
try:
|
try:
|
||||||
# Open lockfile for writing without truncation:
|
# Open lockfile for writing without truncation:
|
||||||
self.fp = open(path, 'r+')
|
self.fp = open(path, 'r+')
|
||||||
except IOError:
|
except IOError:
|
||||||
# If the file doesn't exist, IOError is raised; Use a+ instead.
|
# If the file doesn't exist, IOError is raised; Use a+ instead.
|
||||||
# Note that there may be a race here. Multiple processes
|
# Note that there may be a race here. Multiple processes
|
||||||
# could fail on the r+ open and open the file a+, but only
|
# could fail on the r+ open and open the file a+, but only
|
||||||
# one will get the the lock and write a pid.
|
# one will get the the lock and write a pid.
|
||||||
self.fp = open(path, 'a+')
|
self.fp = open(path, 'a+')
|
||||||
|
|
||||||
try:
|
try:
|
||||||
self._lock_file()
|
self._lock_file()
|
||||||
except:
|
except:
|
||||||
self.fp.seek(1)
|
self.fp.seek(1)
|
||||||
self.fp.close()
|
self.fp.close()
|
||||||
del self.fp
|
del self.fp
|
||||||
raise
|
raise
|
||||||
|
|
||||||
self.fp.write(" %s\n" % os.getpid())
|
self.fp.write(' %s\n' % os.getpid())
|
||||||
self.fp.truncate()
|
self.fp.truncate()
|
||||||
self.fp.flush()
|
self.fp.flush()
|
||||||
|
|
||||||
def release(self):
|
def release(self):
|
||||||
if not hasattr(self, 'fp'):
|
if not hasattr(self, 'fp'):
|
||||||
return
|
return
|
||||||
self._unlock_file()
|
self._unlock_file()
|
||||||
self.fp.close()
|
self.fp.close()
|
||||||
del self.fp
|
del self.fp
|
||||||
|
|
||||||
def remove(self):
|
def remove(self):
|
||||||
"""
|
"""
|
||||||
Attempt to remove the file
|
Attempt to remove the file
|
||||||
"""
|
"""
|
||||||
try:
|
try:
|
||||||
os.remove(self.path)
|
os.remove(self.path)
|
||||||
except:
|
except:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
#@abc.abstract_method
|
def _unlock_file(self):
|
||||||
# def _lock_file(self):
|
"""Attempt to obtain the lock on self.fp. Raise UnlockError if not
|
||||||
# """Attempt to obtain the lock on self.fp. Raise LockError if not
|
released."""
|
||||||
# acquired."""
|
|
||||||
|
|
||||||
def _unlock_file(self):
|
class WindowsLockFile(SystemLockFile):
|
||||||
"""Attempt to obtain the lock on self.fp. Raise UnlockError if not
|
|
||||||
released."""
|
def _lock_file(self):
|
||||||
|
# Lock just the first byte
|
||||||
|
try:
|
||||||
class WindowsLockFile(SystemLockFile):
|
msvcrt.locking(self.fp.fileno(), msvcrt.LK_NBLCK, 1)
|
||||||
|
except IOError:
|
||||||
def _lock_file(self):
|
raise LockError(self.fp.name)
|
||||||
# Lock just the first byte
|
|
||||||
try:
|
def _unlock_file(self):
|
||||||
msvcrt.locking(self.fp.fileno(), msvcrt.LK_NBLCK, 1)
|
try:
|
||||||
except IOError:
|
self.fp.seek(0)
|
||||||
raise LockError(self.fp.name)
|
msvcrt.locking(self.fp.fileno(), msvcrt.LK_UNLCK, 1)
|
||||||
|
except IOError:
|
||||||
def _unlock_file(self):
|
raise UnlockError(self.fp.name)
|
||||||
try:
|
|
||||||
self.fp.seek(0)
|
if 'msvcrt' in globals():
|
||||||
msvcrt.locking(self.fp.fileno(), msvcrt.LK_UNLCK, 1)
|
LockFile = WindowsLockFile
|
||||||
except IOError:
|
|
||||||
raise UnlockError(self.fp.name)
|
|
||||||
|
class UnixLockFile(SystemLockFile):
|
||||||
if 'msvcrt' in globals():
|
|
||||||
LockFile = WindowsLockFile
|
def _lock_file(self):
|
||||||
|
flags = fcntl.LOCK_EX | fcntl.LOCK_NB
|
||||||
|
try:
|
||||||
class UnixLockFile(SystemLockFile):
|
fcntl.flock(self.fp.fileno(), flags)
|
||||||
|
except IOError:
|
||||||
def _lock_file(self):
|
raise LockError(self.fp.name)
|
||||||
flags = fcntl.LOCK_EX | fcntl.LOCK_NB
|
|
||||||
try:
|
# no need to implement _unlock_file, it will be unlocked on close()
|
||||||
fcntl.flock(self.fp.fileno(), flags)
|
|
||||||
except IOError:
|
if 'fcntl' in globals():
|
||||||
raise LockError(self.fp.name)
|
LockFile = UnixLockFile
|
||||||
|
|
||||||
# no need to implement _unlock_file, it will be unlocked on close()
|
|
||||||
|
|
||||||
if 'fcntl' in globals():
|
|
||||||
LockFile = UnixLockFile
|
|
||||||
@@ -11,7 +11,7 @@ class Timer(object):
|
|||||||
A simple timer that will indicate when an expiration time has passed.
|
A simple timer that will indicate when an expiration time has passed.
|
||||||
"""
|
"""
|
||||||
def __init__(self, expiration):
|
def __init__(self, expiration):
|
||||||
"Create a timer that expires at `expiration` (UTC datetime)"
|
'Create a timer that expires at `expiration` (UTC datetime)'
|
||||||
self.expiration = expiration
|
self.expiration = expiration
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
@@ -26,7 +26,7 @@ class Timer(object):
|
|||||||
|
|
||||||
|
|
||||||
class LockTimeout(Exception):
|
class LockTimeout(Exception):
|
||||||
"An exception when a lock could not be acquired before a timeout period"
|
'An exception when a lock could not be acquired before a timeout period'
|
||||||
|
|
||||||
|
|
||||||
class LockChecker(object):
|
class LockChecker(object):
|
||||||
@@ -43,5 +43,5 @@ class LockChecker(object):
|
|||||||
def expired(self):
|
def expired(self):
|
||||||
if self.timer.expired():
|
if self.timer.expired():
|
||||||
raise LockTimeout(
|
raise LockTimeout(
|
||||||
"Timeout acquiring lock for %(session_id)s" % vars(self))
|
'Timeout acquiring lock for %(session_id)s' % vars(self))
|
||||||
return False
|
return False
|
||||||
@@ -34,30 +34,31 @@ module from the command line, it will call ``serve()`` for you.
|
|||||||
"""
|
"""
|
||||||
|
|
||||||
import io
|
import io
|
||||||
|
import os
|
||||||
|
import os.path
|
||||||
|
import sys
|
||||||
|
import warnings
|
||||||
|
|
||||||
import cherrypy
|
import cherrypy
|
||||||
|
|
||||||
|
|
||||||
def new_func_strip_path(func_name):
|
|
||||||
"""Make profiler output more readable by adding `__init__` modules' parents
|
|
||||||
"""
|
|
||||||
filename, line, name = func_name
|
|
||||||
if filename.endswith("__init__.py"):
|
|
||||||
return os.path.basename(filename[:-12]) + filename[-12:], line, name
|
|
||||||
return os.path.basename(filename), line, name
|
|
||||||
|
|
||||||
try:
|
try:
|
||||||
import profile
|
import profile
|
||||||
import pstats
|
import pstats
|
||||||
|
|
||||||
|
def new_func_strip_path(func_name):
|
||||||
|
"""Make profiler output more readable by adding `__init__` modules' parents
|
||||||
|
"""
|
||||||
|
filename, line, name = func_name
|
||||||
|
if filename.endswith('__init__.py'):
|
||||||
|
return os.path.basename(filename[:-12]) + filename[-12:], line, name
|
||||||
|
return os.path.basename(filename), line, name
|
||||||
|
|
||||||
pstats.func_strip_path = new_func_strip_path
|
pstats.func_strip_path = new_func_strip_path
|
||||||
except ImportError:
|
except ImportError:
|
||||||
profile = None
|
profile = None
|
||||||
pstats = None
|
pstats = None
|
||||||
|
|
||||||
import os
|
|
||||||
import os.path
|
|
||||||
import sys
|
|
||||||
import warnings
|
|
||||||
|
|
||||||
_count = 0
|
_count = 0
|
||||||
|
|
||||||
@@ -66,7 +67,7 @@ class Profiler(object):
|
|||||||
|
|
||||||
def __init__(self, path=None):
|
def __init__(self, path=None):
|
||||||
if not path:
|
if not path:
|
||||||
path = os.path.join(os.path.dirname(__file__), "profile")
|
path = os.path.join(os.path.dirname(__file__), 'profile')
|
||||||
self.path = path
|
self.path = path
|
||||||
if not os.path.exists(path):
|
if not os.path.exists(path):
|
||||||
os.makedirs(path)
|
os.makedirs(path)
|
||||||
@@ -75,7 +76,7 @@ class Profiler(object):
|
|||||||
"""Dump profile data into self.path."""
|
"""Dump profile data into self.path."""
|
||||||
global _count
|
global _count
|
||||||
c = _count = _count + 1
|
c = _count = _count + 1
|
||||||
path = os.path.join(self.path, "cp_%04d.prof" % c)
|
path = os.path.join(self.path, 'cp_%04d.prof' % c)
|
||||||
prof = profile.Profile()
|
prof = profile.Profile()
|
||||||
result = prof.runcall(func, *args, **params)
|
result = prof.runcall(func, *args, **params)
|
||||||
prof.dump_stats(path)
|
prof.dump_stats(path)
|
||||||
@@ -85,7 +86,7 @@ class Profiler(object):
|
|||||||
""":rtype: list of available profiles.
|
""":rtype: list of available profiles.
|
||||||
"""
|
"""
|
||||||
return [f for f in os.listdir(self.path)
|
return [f for f in os.listdir(self.path)
|
||||||
if f.startswith("cp_") and f.endswith(".prof")]
|
if f.startswith('cp_') and f.endswith('.prof')]
|
||||||
|
|
||||||
def stats(self, filename, sortby='cumulative'):
|
def stats(self, filename, sortby='cumulative'):
|
||||||
""":rtype stats(index): output of print_stats() for the given profile.
|
""":rtype stats(index): output of print_stats() for the given profile.
|
||||||
@@ -125,8 +126,8 @@ class Profiler(object):
|
|||||||
|
|
||||||
@cherrypy.expose
|
@cherrypy.expose
|
||||||
def menu(self):
|
def menu(self):
|
||||||
yield "<h2>Profiling runs</h2>"
|
yield '<h2>Profiling runs</h2>'
|
||||||
yield "<p>Click on one of the runs below to see profiling data.</p>"
|
yield '<p>Click on one of the runs below to see profiling data.</p>'
|
||||||
runs = self.statfiles()
|
runs = self.statfiles()
|
||||||
runs.sort()
|
runs.sort()
|
||||||
for i in runs:
|
for i in runs:
|
||||||
@@ -135,7 +136,6 @@ class Profiler(object):
|
|||||||
|
|
||||||
@cherrypy.expose
|
@cherrypy.expose
|
||||||
def report(self, filename):
|
def report(self, filename):
|
||||||
import cherrypy
|
|
||||||
cherrypy.response.headers['Content-Type'] = 'text/plain'
|
cherrypy.response.headers['Content-Type'] = 'text/plain'
|
||||||
return self.stats(filename)
|
return self.stats(filename)
|
||||||
|
|
||||||
@@ -149,7 +149,7 @@ class ProfileAggregator(Profiler):
|
|||||||
self.profiler = profile.Profile()
|
self.profiler = profile.Profile()
|
||||||
|
|
||||||
def run(self, func, *args, **params):
|
def run(self, func, *args, **params):
|
||||||
path = os.path.join(self.path, "cp_%04d.prof" % self.count)
|
path = os.path.join(self.path, 'cp_%04d.prof' % self.count)
|
||||||
result = self.profiler.runcall(func, *args, **params)
|
result = self.profiler.runcall(func, *args, **params)
|
||||||
self.profiler.dump_stats(path)
|
self.profiler.dump_stats(path)
|
||||||
return result
|
return result
|
||||||
@@ -174,11 +174,11 @@ class make_app:
|
|||||||
|
|
||||||
"""
|
"""
|
||||||
if profile is None or pstats is None:
|
if profile is None or pstats is None:
|
||||||
msg = ("Your installation of Python does not have a profile "
|
msg = ('Your installation of Python does not have a profile '
|
||||||
"module. If you're on Debian, try "
|
"module. If you're on Debian, try "
|
||||||
"`sudo apt-get install python-profiler`. "
|
'`sudo apt-get install python-profiler`. '
|
||||||
"See http://www.cherrypy.org/wiki/ProfilingOnDebian "
|
'See http://www.cherrypy.org/wiki/ProfilingOnDebian '
|
||||||
"for details.")
|
'for details.')
|
||||||
warnings.warn(msg)
|
warnings.warn(msg)
|
||||||
|
|
||||||
self.nextapp = nextapp
|
self.nextapp = nextapp
|
||||||
@@ -199,20 +199,19 @@ class make_app:
|
|||||||
|
|
||||||
def serve(path=None, port=8080):
|
def serve(path=None, port=8080):
|
||||||
if profile is None or pstats is None:
|
if profile is None or pstats is None:
|
||||||
msg = ("Your installation of Python does not have a profile module. "
|
msg = ('Your installation of Python does not have a profile module. '
|
||||||
"If you're on Debian, try "
|
"If you're on Debian, try "
|
||||||
"`sudo apt-get install python-profiler`. "
|
'`sudo apt-get install python-profiler`. '
|
||||||
"See http://www.cherrypy.org/wiki/ProfilingOnDebian "
|
'See http://www.cherrypy.org/wiki/ProfilingOnDebian '
|
||||||
"for details.")
|
'for details.')
|
||||||
warnings.warn(msg)
|
warnings.warn(msg)
|
||||||
|
|
||||||
import cherrypy
|
|
||||||
cherrypy.config.update({'server.socket_port': int(port),
|
cherrypy.config.update({'server.socket_port': int(port),
|
||||||
'server.thread_pool': 10,
|
'server.thread_pool': 10,
|
||||||
'environment': "production",
|
'environment': 'production',
|
||||||
})
|
})
|
||||||
cherrypy.quickstart(Profiler(path))
|
cherrypy.quickstart(Profiler(path))
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == '__main__':
|
||||||
serve(*tuple(sys.argv[1:]))
|
serve(*tuple(sys.argv[1:]))
|
||||||
@@ -25,14 +25,9 @@ except ImportError:
|
|||||||
from ConfigParser import ConfigParser
|
from ConfigParser import ConfigParser
|
||||||
|
|
||||||
try:
|
try:
|
||||||
set
|
text_or_bytes
|
||||||
except NameError:
|
except NameError:
|
||||||
from sets import Set as set
|
text_or_bytes = str
|
||||||
|
|
||||||
try:
|
|
||||||
basestring
|
|
||||||
except NameError:
|
|
||||||
basestring = str
|
|
||||||
|
|
||||||
try:
|
try:
|
||||||
# Python 3
|
# Python 3
|
||||||
@@ -47,7 +42,7 @@ import sys
|
|||||||
|
|
||||||
def as_dict(config):
|
def as_dict(config):
|
||||||
"""Return a dict from 'config' whether it is a dict, file, or filename."""
|
"""Return a dict from 'config' whether it is a dict, file, or filename."""
|
||||||
if isinstance(config, basestring):
|
if isinstance(config, text_or_bytes):
|
||||||
config = Parser().dict_from_file(config)
|
config = Parser().dict_from_file(config)
|
||||||
elif hasattr(config, 'read'):
|
elif hasattr(config, 'read'):
|
||||||
config = Parser().dict_from_file(config)
|
config = Parser().dict_from_file(config)
|
||||||
@@ -83,8 +78,8 @@ class NamespaceSet(dict):
|
|||||||
# Separate the given config into namespaces
|
# Separate the given config into namespaces
|
||||||
ns_confs = {}
|
ns_confs = {}
|
||||||
for k in config:
|
for k in config:
|
||||||
if "." in k:
|
if '.' in k:
|
||||||
ns, name = k.split(".", 1)
|
ns, name = k.split('.', 1)
|
||||||
bucket = ns_confs.setdefault(ns, {})
|
bucket = ns_confs.setdefault(ns, {})
|
||||||
bucket[name] = config[k]
|
bucket[name] = config[k]
|
||||||
|
|
||||||
@@ -95,7 +90,7 @@ class NamespaceSet(dict):
|
|||||||
# for k, v in ns_confs.get(ns, {}).iteritems():
|
# for k, v in ns_confs.get(ns, {}).iteritems():
|
||||||
# callable(k, v)
|
# callable(k, v)
|
||||||
for ns, handler in self.items():
|
for ns, handler in self.items():
|
||||||
exit = getattr(handler, "__exit__", None)
|
exit = getattr(handler, '__exit__', None)
|
||||||
if exit:
|
if exit:
|
||||||
callable = handler.__enter__()
|
callable = handler.__enter__()
|
||||||
no_exc = True
|
no_exc = True
|
||||||
@@ -120,7 +115,7 @@ class NamespaceSet(dict):
|
|||||||
handler(k, v)
|
handler(k, v)
|
||||||
|
|
||||||
def __repr__(self):
|
def __repr__(self):
|
||||||
return "%s.%s(%s)" % (self.__module__, self.__class__.__name__,
|
return '%s.%s(%s)' % (self.__module__, self.__class__.__name__,
|
||||||
dict.__repr__(self))
|
dict.__repr__(self))
|
||||||
|
|
||||||
def __copy__(self):
|
def __copy__(self):
|
||||||
@@ -155,7 +150,7 @@ class Config(dict):
|
|||||||
|
|
||||||
def update(self, config):
|
def update(self, config):
|
||||||
"""Update self from a dict, file or filename."""
|
"""Update self from a dict, file or filename."""
|
||||||
if isinstance(config, basestring):
|
if isinstance(config, text_or_bytes):
|
||||||
# Filename
|
# Filename
|
||||||
config = Parser().dict_from_file(config)
|
config = Parser().dict_from_file(config)
|
||||||
elif hasattr(config, 'read'):
|
elif hasattr(config, 'read'):
|
||||||
@@ -192,7 +187,7 @@ class Parser(ConfigParser):
|
|||||||
return optionstr
|
return optionstr
|
||||||
|
|
||||||
def read(self, filenames):
|
def read(self, filenames):
|
||||||
if isinstance(filenames, basestring):
|
if isinstance(filenames, text_or_bytes):
|
||||||
filenames = [filenames]
|
filenames = [filenames]
|
||||||
for filename in filenames:
|
for filename in filenames:
|
||||||
# try:
|
# try:
|
||||||
@@ -218,8 +213,8 @@ class Parser(ConfigParser):
|
|||||||
value = unrepr(value)
|
value = unrepr(value)
|
||||||
except Exception:
|
except Exception:
|
||||||
x = sys.exc_info()[1]
|
x = sys.exc_info()[1]
|
||||||
msg = ("Config error in section: %r, option: %r, "
|
msg = ('Config error in section: %r, option: %r, '
|
||||||
"value: %r. Config values must be valid Python." %
|
'value: %r. Config values must be valid Python.' %
|
||||||
(section, option, value))
|
(section, option, value))
|
||||||
raise ValueError(msg, x.__class__.__name__, x.args)
|
raise ValueError(msg, x.__class__.__name__, x.args)
|
||||||
result[section][option] = value
|
result[section][option] = value
|
||||||
@@ -241,7 +236,7 @@ class _Builder2:
|
|||||||
def build(self, o):
|
def build(self, o):
|
||||||
m = getattr(self, 'build_' + o.__class__.__name__, None)
|
m = getattr(self, 'build_' + o.__class__.__name__, None)
|
||||||
if m is None:
|
if m is None:
|
||||||
raise TypeError("unrepr does not recognize %s" %
|
raise TypeError('unrepr does not recognize %s' %
|
||||||
repr(o.__class__.__name__))
|
repr(o.__class__.__name__))
|
||||||
return m(o)
|
return m(o)
|
||||||
|
|
||||||
@@ -254,7 +249,7 @@ class _Builder2:
|
|||||||
# e.g. IronPython 1.0.
|
# e.g. IronPython 1.0.
|
||||||
return eval(s)
|
return eval(s)
|
||||||
|
|
||||||
p = compiler.parse("__tempvalue__ = " + s)
|
p = compiler.parse('__tempvalue__ = ' + s)
|
||||||
return p.getChildren()[1].getChildren()[0].getChildren()[1]
|
return p.getChildren()[1].getChildren()[0].getChildren()[1]
|
||||||
|
|
||||||
def build_Subscript(self, o):
|
def build_Subscript(self, o):
|
||||||
@@ -327,7 +322,7 @@ class _Builder2:
|
|||||||
except AttributeError:
|
except AttributeError:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
raise TypeError("unrepr could not resolve the name %s" % repr(name))
|
raise TypeError('unrepr could not resolve the name %s' % repr(name))
|
||||||
|
|
||||||
def build_Add(self, o):
|
def build_Add(self, o):
|
||||||
left, right = map(self.build, o.getChildren())
|
left, right = map(self.build, o.getChildren())
|
||||||
@@ -356,7 +351,7 @@ class _Builder3:
|
|||||||
def build(self, o):
|
def build(self, o):
|
||||||
m = getattr(self, 'build_' + o.__class__.__name__, None)
|
m = getattr(self, 'build_' + o.__class__.__name__, None)
|
||||||
if m is None:
|
if m is None:
|
||||||
raise TypeError("unrepr does not recognize %s" %
|
raise TypeError('unrepr does not recognize %s' %
|
||||||
repr(o.__class__.__name__))
|
repr(o.__class__.__name__))
|
||||||
return m(o)
|
return m(o)
|
||||||
|
|
||||||
@@ -369,7 +364,7 @@ class _Builder3:
|
|||||||
# e.g. IronPython 1.0.
|
# e.g. IronPython 1.0.
|
||||||
return eval(s)
|
return eval(s)
|
||||||
|
|
||||||
p = ast.parse("__tempvalue__ = " + s)
|
p = ast.parse('__tempvalue__ = ' + s)
|
||||||
return p.body[0].value
|
return p.body[0].value
|
||||||
|
|
||||||
def build_Subscript(self, o):
|
def build_Subscript(self, o):
|
||||||
@@ -397,8 +392,8 @@ class _Builder3:
|
|||||||
if kw.arg is None: # double asterix `**`
|
if kw.arg is None: # double asterix `**`
|
||||||
rst = self.build(kw.value)
|
rst = self.build(kw.value)
|
||||||
if not isinstance(rst, dict):
|
if not isinstance(rst, dict):
|
||||||
raise TypeError("Invalid argument for call."
|
raise TypeError('Invalid argument for call.'
|
||||||
"Must be a mapping object.")
|
'Must be a mapping object.')
|
||||||
# give preference to the keys set directly from arg=value
|
# give preference to the keys set directly from arg=value
|
||||||
for k, v in rst.items():
|
for k, v in rst.items():
|
||||||
if k not in kwargs:
|
if k not in kwargs:
|
||||||
@@ -471,7 +466,7 @@ class _Builder3:
|
|||||||
except AttributeError:
|
except AttributeError:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
raise TypeError("unrepr could not resolve the name %s" % repr(name))
|
raise TypeError('unrepr could not resolve the name %s' % repr(name))
|
||||||
|
|
||||||
def build_NameConstant(self, o):
|
def build_NameConstant(self, o):
|
||||||
return o.value
|
return o.value
|
||||||
@@ -523,7 +518,7 @@ def attributes(full_attribute_name):
|
|||||||
"""Load a module and retrieve an attribute of that module."""
|
"""Load a module and retrieve an attribute of that module."""
|
||||||
|
|
||||||
# Parse out the path, module, and attribute
|
# Parse out the path, module, and attribute
|
||||||
last_dot = full_attribute_name.rfind(".")
|
last_dot = full_attribute_name.rfind('.')
|
||||||
attr_name = full_attribute_name[last_dot + 1:]
|
attr_name = full_attribute_name[last_dot + 1:]
|
||||||
mod_path = full_attribute_name[:last_dot]
|
mod_path = full_attribute_name[:last_dot]
|
||||||
|
|
||||||
|
|||||||
@@ -4,13 +4,13 @@ You need to edit your config file to use sessions. Here's an example::
|
|||||||
|
|
||||||
[/]
|
[/]
|
||||||
tools.sessions.on = True
|
tools.sessions.on = True
|
||||||
tools.sessions.storage_type = "file"
|
tools.sessions.storage_class = cherrypy.lib.sessions.FileSession
|
||||||
tools.sessions.storage_path = "/home/site/sessions"
|
tools.sessions.storage_path = "/home/site/sessions"
|
||||||
tools.sessions.timeout = 60
|
tools.sessions.timeout = 60
|
||||||
|
|
||||||
This sets the session to be stored in files in the directory
|
This sets the session to be stored in files in the directory
|
||||||
/home/site/sessions, and the session timeout to 60 minutes. If you omit
|
/home/site/sessions, and the session timeout to 60 minutes. If you omit
|
||||||
``storage_type`` the sessions will be saved in RAM.
|
``storage_class``, the sessions will be saved in RAM.
|
||||||
``tools.sessions.on`` is the only required line for working sessions,
|
``tools.sessions.on`` is the only required line for working sessions,
|
||||||
the rest are optional.
|
the rest are optional.
|
||||||
|
|
||||||
@@ -95,8 +95,6 @@ import os
|
|||||||
import time
|
import time
|
||||||
import threading
|
import threading
|
||||||
|
|
||||||
import six
|
|
||||||
|
|
||||||
import cherrypy
|
import cherrypy
|
||||||
from cherrypy._cpcompat import copyitems, pickle, random20
|
from cherrypy._cpcompat import copyitems, pickle, random20
|
||||||
from cherrypy.lib import httputil
|
from cherrypy.lib import httputil
|
||||||
@@ -123,10 +121,10 @@ class Session(object):
|
|||||||
self._id = value
|
self._id = value
|
||||||
for o in self.id_observers:
|
for o in self.id_observers:
|
||||||
o(value)
|
o(value)
|
||||||
id = property(_get_id, _set_id, doc="The current session ID.")
|
id = property(_get_id, _set_id, doc='The current session ID.')
|
||||||
|
|
||||||
timeout = 60
|
timeout = 60
|
||||||
"Number of minutes after which to delete session data."
|
'Number of minutes after which to delete session data.'
|
||||||
|
|
||||||
locked = False
|
locked = False
|
||||||
"""
|
"""
|
||||||
@@ -139,16 +137,16 @@ class Session(object):
|
|||||||
automatically on the first attempt to access session data."""
|
automatically on the first attempt to access session data."""
|
||||||
|
|
||||||
clean_thread = None
|
clean_thread = None
|
||||||
"Class-level Monitor which calls self.clean_up."
|
'Class-level Monitor which calls self.clean_up.'
|
||||||
|
|
||||||
clean_freq = 5
|
clean_freq = 5
|
||||||
"The poll rate for expired session cleanup in minutes."
|
'The poll rate for expired session cleanup in minutes.'
|
||||||
|
|
||||||
originalid = None
|
originalid = None
|
||||||
"The session id passed by the client. May be missing or unsafe."
|
'The session id passed by the client. May be missing or unsafe.'
|
||||||
|
|
||||||
missing = False
|
missing = False
|
||||||
"True if the session requested by the client did not exist."
|
'True if the session requested by the client did not exist.'
|
||||||
|
|
||||||
regenerated = False
|
regenerated = False
|
||||||
"""
|
"""
|
||||||
@@ -156,7 +154,7 @@ class Session(object):
|
|||||||
internal calls to regenerate the session id."""
|
internal calls to regenerate the session id."""
|
||||||
|
|
||||||
debug = False
|
debug = False
|
||||||
"If True, log debug information."
|
'If True, log debug information.'
|
||||||
|
|
||||||
# --------------------- Session management methods --------------------- #
|
# --------------------- Session management methods --------------------- #
|
||||||
|
|
||||||
@@ -472,9 +470,10 @@ class FileSession(Session):
|
|||||||
if isinstance(self.lock_timeout, (int, float)):
|
if isinstance(self.lock_timeout, (int, float)):
|
||||||
self.lock_timeout = datetime.timedelta(seconds=self.lock_timeout)
|
self.lock_timeout = datetime.timedelta(seconds=self.lock_timeout)
|
||||||
if not isinstance(self.lock_timeout, (datetime.timedelta, type(None))):
|
if not isinstance(self.lock_timeout, (datetime.timedelta, type(None))):
|
||||||
raise ValueError("Lock timeout must be numeric seconds or "
|
raise ValueError('Lock timeout must be numeric seconds or '
|
||||||
"a timedelta instance.")
|
'a timedelta instance.')
|
||||||
|
|
||||||
|
@classmethod
|
||||||
def setup(cls, **kwargs):
|
def setup(cls, **kwargs):
|
||||||
"""Set up the storage system for file-based sessions.
|
"""Set up the storage system for file-based sessions.
|
||||||
|
|
||||||
@@ -486,12 +485,11 @@ class FileSession(Session):
|
|||||||
|
|
||||||
for k, v in kwargs.items():
|
for k, v in kwargs.items():
|
||||||
setattr(cls, k, v)
|
setattr(cls, k, v)
|
||||||
setup = classmethod(setup)
|
|
||||||
|
|
||||||
def _get_file_path(self):
|
def _get_file_path(self):
|
||||||
f = os.path.join(self.storage_path, self.SESSION_PREFIX + self.id)
|
f = os.path.join(self.storage_path, self.SESSION_PREFIX + self.id)
|
||||||
if not os.path.abspath(f).startswith(self.storage_path):
|
if not os.path.abspath(f).startswith(self.storage_path):
|
||||||
raise cherrypy.HTTPError(400, "Invalid session id in cookie.")
|
raise cherrypy.HTTPError(400, 'Invalid session id in cookie.')
|
||||||
return f
|
return f
|
||||||
|
|
||||||
def _exists(self):
|
def _exists(self):
|
||||||
@@ -499,12 +497,12 @@ class FileSession(Session):
|
|||||||
return os.path.exists(path)
|
return os.path.exists(path)
|
||||||
|
|
||||||
def _load(self, path=None):
|
def _load(self, path=None):
|
||||||
assert self.locked, ("The session load without being locked. "
|
assert self.locked, ('The session load without being locked. '
|
||||||
"Check your tools' priority levels.")
|
"Check your tools' priority levels.")
|
||||||
if path is None:
|
if path is None:
|
||||||
path = self._get_file_path()
|
path = self._get_file_path()
|
||||||
try:
|
try:
|
||||||
f = open(path, "rb")
|
f = open(path, 'rb')
|
||||||
try:
|
try:
|
||||||
return pickle.load(f)
|
return pickle.load(f)
|
||||||
finally:
|
finally:
|
||||||
@@ -512,21 +510,21 @@ class FileSession(Session):
|
|||||||
except (IOError, EOFError):
|
except (IOError, EOFError):
|
||||||
e = sys.exc_info()[1]
|
e = sys.exc_info()[1]
|
||||||
if self.debug:
|
if self.debug:
|
||||||
cherrypy.log("Error loading the session pickle: %s" %
|
cherrypy.log('Error loading the session pickle: %s' %
|
||||||
e, 'TOOLS.SESSIONS')
|
e, 'TOOLS.SESSIONS')
|
||||||
return None
|
return None
|
||||||
|
|
||||||
def _save(self, expiration_time):
|
def _save(self, expiration_time):
|
||||||
assert self.locked, ("The session was saved without being locked. "
|
assert self.locked, ('The session was saved without being locked. '
|
||||||
"Check your tools' priority levels.")
|
"Check your tools' priority levels.")
|
||||||
f = open(self._get_file_path(), "wb")
|
f = open(self._get_file_path(), 'wb')
|
||||||
try:
|
try:
|
||||||
pickle.dump((self._data, expiration_time), f, self.pickle_protocol)
|
pickle.dump((self._data, expiration_time), f, self.pickle_protocol)
|
||||||
finally:
|
finally:
|
||||||
f.close()
|
f.close()
|
||||||
|
|
||||||
def _delete(self):
|
def _delete(self):
|
||||||
assert self.locked, ("The session deletion without being locked. "
|
assert self.locked, ('The session deletion without being locked. '
|
||||||
"Check your tools' priority levels.")
|
"Check your tools' priority levels.")
|
||||||
try:
|
try:
|
||||||
os.unlink(self._get_file_path())
|
os.unlink(self._get_file_path())
|
||||||
@@ -603,6 +601,7 @@ class MemcachedSession(Session):
|
|||||||
|
|
||||||
servers = ['127.0.0.1:11211']
|
servers = ['127.0.0.1:11211']
|
||||||
|
|
||||||
|
@classmethod
|
||||||
def setup(cls, **kwargs):
|
def setup(cls, **kwargs):
|
||||||
"""Set up the storage system for memcached-based sessions.
|
"""Set up the storage system for memcached-based sessions.
|
||||||
|
|
||||||
@@ -614,21 +613,6 @@ class MemcachedSession(Session):
|
|||||||
|
|
||||||
import memcache
|
import memcache
|
||||||
cls.cache = memcache.Client(cls.servers)
|
cls.cache = memcache.Client(cls.servers)
|
||||||
setup = classmethod(setup)
|
|
||||||
|
|
||||||
def _get_id(self):
|
|
||||||
return self._id
|
|
||||||
|
|
||||||
def _set_id(self, value):
|
|
||||||
# This encode() call is where we differ from the superclass.
|
|
||||||
# Memcache keys MUST be byte strings, not unicode.
|
|
||||||
if isinstance(value, six.text_type):
|
|
||||||
value = value.encode('utf-8')
|
|
||||||
|
|
||||||
self._id = value
|
|
||||||
for o in self.id_observers:
|
|
||||||
o(value)
|
|
||||||
id = property(_get_id, _set_id, doc="The current session ID.")
|
|
||||||
|
|
||||||
def _exists(self):
|
def _exists(self):
|
||||||
self.mc_lock.acquire()
|
self.mc_lock.acquire()
|
||||||
@@ -651,7 +635,7 @@ class MemcachedSession(Session):
|
|||||||
try:
|
try:
|
||||||
if not self.cache.set(self.id, (self._data, expiration_time), td):
|
if not self.cache.set(self.id, (self._data, expiration_time), td):
|
||||||
raise AssertionError(
|
raise AssertionError(
|
||||||
"Session data for id %r not set." % self.id)
|
'Session data for id %r not set.' % self.id)
|
||||||
finally:
|
finally:
|
||||||
self.mc_lock.release()
|
self.mc_lock.release()
|
||||||
|
|
||||||
@@ -680,13 +664,13 @@ class MemcachedSession(Session):
|
|||||||
def save():
|
def save():
|
||||||
"""Save any changed session data."""
|
"""Save any changed session data."""
|
||||||
|
|
||||||
if not hasattr(cherrypy.serving, "session"):
|
if not hasattr(cherrypy.serving, 'session'):
|
||||||
return
|
return
|
||||||
request = cherrypy.serving.request
|
request = cherrypy.serving.request
|
||||||
response = cherrypy.serving.response
|
response = cherrypy.serving.response
|
||||||
|
|
||||||
# Guard against running twice
|
# Guard against running twice
|
||||||
if hasattr(request, "_sessionsaved"):
|
if hasattr(request, '_sessionsaved'):
|
||||||
return
|
return
|
||||||
request._sessionsaved = True
|
request._sessionsaved = True
|
||||||
|
|
||||||
@@ -705,8 +689,8 @@ save.failsafe = True
|
|||||||
|
|
||||||
def close():
|
def close():
|
||||||
"""Close the session object for this request."""
|
"""Close the session object for this request."""
|
||||||
sess = getattr(cherrypy.serving, "session", None)
|
sess = getattr(cherrypy.serving, 'session', None)
|
||||||
if getattr(sess, "locked", False):
|
if getattr(sess, 'locked', False):
|
||||||
# If the session is still locked we release the lock
|
# If the session is still locked we release the lock
|
||||||
sess.release_lock()
|
sess.release_lock()
|
||||||
if sess.debug:
|
if sess.debug:
|
||||||
@@ -715,12 +699,19 @@ close.failsafe = True
|
|||||||
close.priority = 90
|
close.priority = 90
|
||||||
|
|
||||||
|
|
||||||
def init(storage_type='ram', path=None, path_header=None, name='session_id',
|
def init(storage_type=None, path=None, path_header=None, name='session_id',
|
||||||
timeout=60, domain=None, secure=False, clean_freq=5,
|
timeout=60, domain=None, secure=False, clean_freq=5,
|
||||||
persistent=True, httponly=False, debug=False, **kwargs):
|
persistent=True, httponly=False, debug=False,
|
||||||
|
# Py27 compat
|
||||||
|
# *, storage_class=RamSession,
|
||||||
|
**kwargs):
|
||||||
"""Initialize session object (using cookies).
|
"""Initialize session object (using cookies).
|
||||||
|
|
||||||
|
storage_class
|
||||||
|
The Session subclass to use. Defaults to RamSession.
|
||||||
|
|
||||||
storage_type
|
storage_type
|
||||||
|
(deprecated)
|
||||||
One of 'ram', 'file', memcached'. This will be
|
One of 'ram', 'file', memcached'. This will be
|
||||||
used to look up the corresponding class in cherrypy.lib.sessions
|
used to look up the corresponding class in cherrypy.lib.sessions
|
||||||
globals. For example, 'file' will use the FileSession class.
|
globals. For example, 'file' will use the FileSession class.
|
||||||
@@ -765,10 +756,13 @@ def init(storage_type='ram', path=None, path_header=None, name='session_id',
|
|||||||
you're using for more information.
|
you're using for more information.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
# Py27 compat
|
||||||
|
storage_class = kwargs.pop('storage_class', RamSession)
|
||||||
|
|
||||||
request = cherrypy.serving.request
|
request = cherrypy.serving.request
|
||||||
|
|
||||||
# Guard against running twice
|
# Guard against running twice
|
||||||
if hasattr(request, "_session_init_flag"):
|
if hasattr(request, '_session_init_flag'):
|
||||||
return
|
return
|
||||||
request._session_init_flag = True
|
request._session_init_flag = True
|
||||||
|
|
||||||
@@ -780,11 +774,18 @@ def init(storage_type='ram', path=None, path_header=None, name='session_id',
|
|||||||
cherrypy.log('ID obtained from request.cookie: %r' % id,
|
cherrypy.log('ID obtained from request.cookie: %r' % id,
|
||||||
'TOOLS.SESSIONS')
|
'TOOLS.SESSIONS')
|
||||||
|
|
||||||
# Find the storage class and call setup (first time only).
|
first_time = not hasattr(cherrypy, 'session')
|
||||||
storage_class = storage_type.title() + 'Session'
|
|
||||||
storage_class = globals()[storage_class]
|
if storage_type:
|
||||||
if not hasattr(cherrypy, "session"):
|
if first_time:
|
||||||
if hasattr(storage_class, "setup"):
|
msg = 'storage_type is deprecated. Supply storage_class instead'
|
||||||
|
cherrypy.log(msg)
|
||||||
|
storage_class = storage_type.title() + 'Session'
|
||||||
|
storage_class = globals()[storage_class]
|
||||||
|
|
||||||
|
# call setup first time only
|
||||||
|
if first_time:
|
||||||
|
if hasattr(storage_class, 'setup'):
|
||||||
storage_class.setup(**kwargs)
|
storage_class.setup(**kwargs)
|
||||||
|
|
||||||
# Create and attach a new Session instance to cherrypy.serving.
|
# Create and attach a new Session instance to cherrypy.serving.
|
||||||
@@ -801,7 +802,7 @@ def init(storage_type='ram', path=None, path_header=None, name='session_id',
|
|||||||
sess.id_observers.append(update_cookie)
|
sess.id_observers.append(update_cookie)
|
||||||
|
|
||||||
# Create cherrypy.session which will proxy to cherrypy.serving.session
|
# Create cherrypy.session which will proxy to cherrypy.serving.session
|
||||||
if not hasattr(cherrypy, "session"):
|
if not hasattr(cherrypy, 'session'):
|
||||||
cherrypy.session = cherrypy._ThreadLocalProxy('session')
|
cherrypy.session = cherrypy._ThreadLocalProxy('session')
|
||||||
|
|
||||||
if persistent:
|
if persistent:
|
||||||
@@ -869,7 +870,7 @@ def set_response_cookie(path=None, path_header=None, name='session_id',
|
|||||||
cookie[name]['secure'] = 1
|
cookie[name]['secure'] = 1
|
||||||
if httponly:
|
if httponly:
|
||||||
if not cookie[name].isReservedKey('httponly'):
|
if not cookie[name].isReservedKey('httponly'):
|
||||||
raise ValueError("The httponly cookie token is not supported.")
|
raise ValueError('The httponly cookie token is not supported.')
|
||||||
cookie[name]['httponly'] = 1
|
cookie[name]['httponly'] = 1
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -71,7 +71,7 @@ def serve_file(path, content_type=None, disposition=None, name=None,
|
|||||||
|
|
||||||
if content_type is None:
|
if content_type is None:
|
||||||
# Set content-type based on filename extension
|
# Set content-type based on filename extension
|
||||||
ext = ""
|
ext = ''
|
||||||
i = path.rfind('.')
|
i = path.rfind('.')
|
||||||
if i != -1:
|
if i != -1:
|
||||||
ext = path[i:].lower()
|
ext = path[i:].lower()
|
||||||
@@ -86,7 +86,7 @@ def serve_file(path, content_type=None, disposition=None, name=None,
|
|||||||
if name is None:
|
if name is None:
|
||||||
name = os.path.basename(path)
|
name = os.path.basename(path)
|
||||||
cd = '%s; filename="%s"' % (disposition, name)
|
cd = '%s; filename="%s"' % (disposition, name)
|
||||||
response.headers["Content-Disposition"] = cd
|
response.headers['Content-Disposition'] = cd
|
||||||
if debug:
|
if debug:
|
||||||
cherrypy.log('Content-Disposition: %r' % cd, 'TOOLS.STATIC')
|
cherrypy.log('Content-Disposition: %r' % cd, 'TOOLS.STATIC')
|
||||||
|
|
||||||
@@ -144,7 +144,7 @@ def serve_fileobj(fileobj, content_type=None, disposition=None, name=None,
|
|||||||
cd = disposition
|
cd = disposition
|
||||||
else:
|
else:
|
||||||
cd = '%s; filename="%s"' % (disposition, name)
|
cd = '%s; filename="%s"' % (disposition, name)
|
||||||
response.headers["Content-Disposition"] = cd
|
response.headers['Content-Disposition'] = cd
|
||||||
if debug:
|
if debug:
|
||||||
cherrypy.log('Content-Disposition: %r' % cd, 'TOOLS.STATIC')
|
cherrypy.log('Content-Disposition: %r' % cd, 'TOOLS.STATIC')
|
||||||
|
|
||||||
@@ -158,12 +158,12 @@ def _serve_fileobj(fileobj, content_type, content_length, debug=False):
|
|||||||
# HTTP/1.0 didn't have Range/Accept-Ranges headers, or the 206 code
|
# HTTP/1.0 didn't have Range/Accept-Ranges headers, or the 206 code
|
||||||
request = cherrypy.serving.request
|
request = cherrypy.serving.request
|
||||||
if request.protocol >= (1, 1):
|
if request.protocol >= (1, 1):
|
||||||
response.headers["Accept-Ranges"] = "bytes"
|
response.headers['Accept-Ranges'] = 'bytes'
|
||||||
r = httputil.get_ranges(request.headers.get('Range'), content_length)
|
r = httputil.get_ranges(request.headers.get('Range'), content_length)
|
||||||
if r == []:
|
if r == []:
|
||||||
response.headers['Content-Range'] = "bytes */%s" % content_length
|
response.headers['Content-Range'] = 'bytes */%s' % content_length
|
||||||
message = ("Invalid Range (first-byte-pos greater than "
|
message = ('Invalid Range (first-byte-pos greater than '
|
||||||
"Content-Length)")
|
'Content-Length)')
|
||||||
if debug:
|
if debug:
|
||||||
cherrypy.log(message, 'TOOLS.STATIC')
|
cherrypy.log(message, 'TOOLS.STATIC')
|
||||||
raise cherrypy.HTTPError(416, message)
|
raise cherrypy.HTTPError(416, message)
|
||||||
@@ -179,15 +179,15 @@ def _serve_fileobj(fileobj, content_type, content_length, debug=False):
|
|||||||
cherrypy.log(
|
cherrypy.log(
|
||||||
'Single part; start: %r, stop: %r' % (start, stop),
|
'Single part; start: %r, stop: %r' % (start, stop),
|
||||||
'TOOLS.STATIC')
|
'TOOLS.STATIC')
|
||||||
response.status = "206 Partial Content"
|
response.status = '206 Partial Content'
|
||||||
response.headers['Content-Range'] = (
|
response.headers['Content-Range'] = (
|
||||||
"bytes %s-%s/%s" % (start, stop - 1, content_length))
|
'bytes %s-%s/%s' % (start, stop - 1, content_length))
|
||||||
response.headers['Content-Length'] = r_len
|
response.headers['Content-Length'] = r_len
|
||||||
fileobj.seek(start)
|
fileobj.seek(start)
|
||||||
response.body = file_generator_limited(fileobj, r_len)
|
response.body = file_generator_limited(fileobj, r_len)
|
||||||
else:
|
else:
|
||||||
# Return a multipart/byteranges response.
|
# Return a multipart/byteranges response.
|
||||||
response.status = "206 Partial Content"
|
response.status = '206 Partial Content'
|
||||||
try:
|
try:
|
||||||
# Python 3
|
# Python 3
|
||||||
from email.generator import _make_boundary as make_boundary
|
from email.generator import _make_boundary as make_boundary
|
||||||
@@ -195,15 +195,15 @@ def _serve_fileobj(fileobj, content_type, content_length, debug=False):
|
|||||||
# Python 2
|
# Python 2
|
||||||
from mimetools import choose_boundary as make_boundary
|
from mimetools import choose_boundary as make_boundary
|
||||||
boundary = make_boundary()
|
boundary = make_boundary()
|
||||||
ct = "multipart/byteranges; boundary=%s" % boundary
|
ct = 'multipart/byteranges; boundary=%s' % boundary
|
||||||
response.headers['Content-Type'] = ct
|
response.headers['Content-Type'] = ct
|
||||||
if "Content-Length" in response.headers:
|
if 'Content-Length' in response.headers:
|
||||||
# Delete Content-Length header so finalize() recalcs it.
|
# Delete Content-Length header so finalize() recalcs it.
|
||||||
del response.headers["Content-Length"]
|
del response.headers['Content-Length']
|
||||||
|
|
||||||
def file_ranges():
|
def file_ranges():
|
||||||
# Apache compatibility:
|
# Apache compatibility:
|
||||||
yield ntob("\r\n")
|
yield ntob('\r\n')
|
||||||
|
|
||||||
for start, stop in r:
|
for start, stop in r:
|
||||||
if debug:
|
if debug:
|
||||||
@@ -211,23 +211,23 @@ def _serve_fileobj(fileobj, content_type, content_length, debug=False):
|
|||||||
'Multipart; start: %r, stop: %r' % (
|
'Multipart; start: %r, stop: %r' % (
|
||||||
start, stop),
|
start, stop),
|
||||||
'TOOLS.STATIC')
|
'TOOLS.STATIC')
|
||||||
yield ntob("--" + boundary, 'ascii')
|
yield ntob('--' + boundary, 'ascii')
|
||||||
yield ntob("\r\nContent-type: %s" % content_type,
|
yield ntob('\r\nContent-type: %s' % content_type,
|
||||||
'ascii')
|
'ascii')
|
||||||
yield ntob(
|
yield ntob(
|
||||||
"\r\nContent-range: bytes %s-%s/%s\r\n\r\n" % (
|
'\r\nContent-range: bytes %s-%s/%s\r\n\r\n' % (
|
||||||
start, stop - 1, content_length),
|
start, stop - 1, content_length),
|
||||||
'ascii')
|
'ascii')
|
||||||
fileobj.seek(start)
|
fileobj.seek(start)
|
||||||
gen = file_generator_limited(fileobj, stop - start)
|
gen = file_generator_limited(fileobj, stop - start)
|
||||||
for chunk in gen:
|
for chunk in gen:
|
||||||
yield chunk
|
yield chunk
|
||||||
yield ntob("\r\n")
|
yield ntob('\r\n')
|
||||||
# Final boundary
|
# Final boundary
|
||||||
yield ntob("--" + boundary + "--", 'ascii')
|
yield ntob('--' + boundary + '--', 'ascii')
|
||||||
|
|
||||||
# Apache compatibility:
|
# Apache compatibility:
|
||||||
yield ntob("\r\n")
|
yield ntob('\r\n')
|
||||||
response.body = file_ranges()
|
response.body = file_ranges()
|
||||||
return response.body
|
return response.body
|
||||||
else:
|
else:
|
||||||
@@ -244,7 +244,7 @@ def _serve_fileobj(fileobj, content_type, content_length, debug=False):
|
|||||||
def serve_download(path, name=None):
|
def serve_download(path, name=None):
|
||||||
"""Serve 'path' as an application/x-download attachment."""
|
"""Serve 'path' as an application/x-download attachment."""
|
||||||
# This is such a common idiom I felt it deserved its own wrapper.
|
# This is such a common idiom I felt it deserved its own wrapper.
|
||||||
return serve_file(path, "application/x-download", "attachment", name)
|
return serve_file(path, 'application/x-download', 'attachment', name)
|
||||||
|
|
||||||
|
|
||||||
def _attempt(filename, content_types, debug=False):
|
def _attempt(filename, content_types, debug=False):
|
||||||
@@ -268,7 +268,7 @@ def _attempt(filename, content_types, debug=False):
|
|||||||
return False
|
return False
|
||||||
|
|
||||||
|
|
||||||
def staticdir(section, dir, root="", match="", content_types=None, index="",
|
def staticdir(section, dir, root='', match='', content_types=None, index='',
|
||||||
debug=False):
|
debug=False):
|
||||||
"""Serve a static resource from the given (root +) dir.
|
"""Serve a static resource from the given (root +) dir.
|
||||||
|
|
||||||
@@ -306,7 +306,7 @@ def staticdir(section, dir, root="", match="", content_types=None, index="",
|
|||||||
# If dir is relative, make absolute using "root".
|
# If dir is relative, make absolute using "root".
|
||||||
if not os.path.isabs(dir):
|
if not os.path.isabs(dir):
|
||||||
if not root:
|
if not root:
|
||||||
msg = "Static dir requires an absolute dir (or root)."
|
msg = 'Static dir requires an absolute dir (or root).'
|
||||||
if debug:
|
if debug:
|
||||||
cherrypy.log(msg, 'TOOLS.STATICDIR')
|
cherrypy.log(msg, 'TOOLS.STATICDIR')
|
||||||
raise ValueError(msg)
|
raise ValueError(msg)
|
||||||
@@ -315,10 +315,10 @@ def staticdir(section, dir, root="", match="", content_types=None, index="",
|
|||||||
# Determine where we are in the object tree relative to 'section'
|
# Determine where we are in the object tree relative to 'section'
|
||||||
# (where the static tool was defined).
|
# (where the static tool was defined).
|
||||||
if section == 'global':
|
if section == 'global':
|
||||||
section = "/"
|
section = '/'
|
||||||
section = section.rstrip(r"\/")
|
section = section.rstrip(r'\/')
|
||||||
branch = request.path_info[len(section) + 1:]
|
branch = request.path_info[len(section) + 1:]
|
||||||
branch = unquote(branch.lstrip(r"\/"))
|
branch = unquote(branch.lstrip(r'\/'))
|
||||||
|
|
||||||
# If branch is "", filename will end in a slash
|
# If branch is "", filename will end in a slash
|
||||||
filename = os.path.join(dir, branch)
|
filename = os.path.join(dir, branch)
|
||||||
@@ -338,11 +338,11 @@ def staticdir(section, dir, root="", match="", content_types=None, index="",
|
|||||||
if index:
|
if index:
|
||||||
handled = _attempt(os.path.join(filename, index), content_types)
|
handled = _attempt(os.path.join(filename, index), content_types)
|
||||||
if handled:
|
if handled:
|
||||||
request.is_index = filename[-1] in (r"\/")
|
request.is_index = filename[-1] in (r'\/')
|
||||||
return handled
|
return handled
|
||||||
|
|
||||||
|
|
||||||
def staticfile(filename, root=None, match="", content_types=None, debug=False):
|
def staticfile(filename, root=None, match='', content_types=None, debug=False):
|
||||||
"""Serve a static resource from the given (root +) filename.
|
"""Serve a static resource from the given (root +) filename.
|
||||||
|
|
||||||
match
|
match
|
||||||
|
|||||||
@@ -10,5 +10,5 @@ use with the bus. Some use tool-specific channels; see the documentation
|
|||||||
for each class.
|
for each class.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
from cherrypy.process.wspbus import bus
|
from cherrypy.process.wspbus import bus # noqa
|
||||||
from cherrypy.process import plugins, servers
|
from cherrypy.process import plugins, servers # noqa
|
||||||
@@ -7,8 +7,8 @@ import sys
|
|||||||
import time
|
import time
|
||||||
import threading
|
import threading
|
||||||
|
|
||||||
from cherrypy._cpcompat import basestring, get_daemon, get_thread_ident
|
from cherrypy._cpcompat import text_or_bytes, get_thread_ident
|
||||||
from cherrypy._cpcompat import ntob, Timer, SetDaemonProperty
|
from cherrypy._cpcompat import ntob, Timer
|
||||||
|
|
||||||
# _module__file__base is used by Autoreload to make
|
# _module__file__base is used by Autoreload to make
|
||||||
# absolute any filenames retrieved from sys.modules which are not
|
# absolute any filenames retrieved from sys.modules which are not
|
||||||
@@ -104,8 +104,8 @@ class SignalHandler(object):
|
|||||||
if sys.platform[:4] == 'java':
|
if sys.platform[:4] == 'java':
|
||||||
del self.handlers['SIGUSR1']
|
del self.handlers['SIGUSR1']
|
||||||
self.handlers['SIGUSR2'] = self.bus.graceful
|
self.handlers['SIGUSR2'] = self.bus.graceful
|
||||||
self.bus.log("SIGUSR1 cannot be set on the JVM platform. "
|
self.bus.log('SIGUSR1 cannot be set on the JVM platform. '
|
||||||
"Using SIGUSR2 instead.")
|
'Using SIGUSR2 instead.')
|
||||||
self.handlers['SIGINT'] = self._jython_SIGINT_handler
|
self.handlers['SIGINT'] = self._jython_SIGINT_handler
|
||||||
|
|
||||||
self._previous_handlers = {}
|
self._previous_handlers = {}
|
||||||
@@ -152,19 +152,19 @@ class SignalHandler(object):
|
|||||||
signame = self.signals[signum]
|
signame = self.signals[signum]
|
||||||
|
|
||||||
if handler is None:
|
if handler is None:
|
||||||
self.bus.log("Restoring %s handler to SIG_DFL." % signame)
|
self.bus.log('Restoring %s handler to SIG_DFL.' % signame)
|
||||||
handler = _signal.SIG_DFL
|
handler = _signal.SIG_DFL
|
||||||
else:
|
else:
|
||||||
self.bus.log("Restoring %s handler %r." % (signame, handler))
|
self.bus.log('Restoring %s handler %r.' % (signame, handler))
|
||||||
|
|
||||||
try:
|
try:
|
||||||
our_handler = _signal.signal(signum, handler)
|
our_handler = _signal.signal(signum, handler)
|
||||||
if our_handler is None:
|
if our_handler is None:
|
||||||
self.bus.log("Restored old %s handler %r, but our "
|
self.bus.log('Restored old %s handler %r, but our '
|
||||||
"handler was not registered." %
|
'handler was not registered.' %
|
||||||
(signame, handler), level=30)
|
(signame, handler), level=30)
|
||||||
except ValueError:
|
except ValueError:
|
||||||
self.bus.log("Unable to restore %s handler %r." %
|
self.bus.log('Unable to restore %s handler %r.' %
|
||||||
(signame, handler), level=40, traceback=True)
|
(signame, handler), level=40, traceback=True)
|
||||||
|
|
||||||
def set_handler(self, signal, listener=None):
|
def set_handler(self, signal, listener=None):
|
||||||
@@ -176,39 +176,39 @@ class SignalHandler(object):
|
|||||||
If the given signal name or number is not available on the current
|
If the given signal name or number is not available on the current
|
||||||
platform, ValueError is raised.
|
platform, ValueError is raised.
|
||||||
"""
|
"""
|
||||||
if isinstance(signal, basestring):
|
if isinstance(signal, text_or_bytes):
|
||||||
signum = getattr(_signal, signal, None)
|
signum = getattr(_signal, signal, None)
|
||||||
if signum is None:
|
if signum is None:
|
||||||
raise ValueError("No such signal: %r" % signal)
|
raise ValueError('No such signal: %r' % signal)
|
||||||
signame = signal
|
signame = signal
|
||||||
else:
|
else:
|
||||||
try:
|
try:
|
||||||
signame = self.signals[signal]
|
signame = self.signals[signal]
|
||||||
except KeyError:
|
except KeyError:
|
||||||
raise ValueError("No such signal: %r" % signal)
|
raise ValueError('No such signal: %r' % signal)
|
||||||
signum = signal
|
signum = signal
|
||||||
|
|
||||||
prev = _signal.signal(signum, self._handle_signal)
|
prev = _signal.signal(signum, self._handle_signal)
|
||||||
self._previous_handlers[signum] = prev
|
self._previous_handlers[signum] = prev
|
||||||
|
|
||||||
if listener is not None:
|
if listener is not None:
|
||||||
self.bus.log("Listening for %s." % signame)
|
self.bus.log('Listening for %s.' % signame)
|
||||||
self.bus.subscribe(signame, listener)
|
self.bus.subscribe(signame, listener)
|
||||||
|
|
||||||
def _handle_signal(self, signum=None, frame=None):
|
def _handle_signal(self, signum=None, frame=None):
|
||||||
"""Python signal handler (self.set_handler subscribes it for you)."""
|
"""Python signal handler (self.set_handler subscribes it for you)."""
|
||||||
signame = self.signals[signum]
|
signame = self.signals[signum]
|
||||||
self.bus.log("Caught signal %s." % signame)
|
self.bus.log('Caught signal %s.' % signame)
|
||||||
self.bus.publish(signame)
|
self.bus.publish(signame)
|
||||||
|
|
||||||
def handle_SIGHUP(self):
|
def handle_SIGHUP(self):
|
||||||
"""Restart if daemonized, else exit."""
|
"""Restart if daemonized, else exit."""
|
||||||
if self._is_daemonized():
|
if self._is_daemonized():
|
||||||
self.bus.log("SIGHUP caught while daemonized. Restarting.")
|
self.bus.log('SIGHUP caught while daemonized. Restarting.')
|
||||||
self.bus.restart()
|
self.bus.restart()
|
||||||
else:
|
else:
|
||||||
# not daemonized (may be foreground or background)
|
# not daemonized (may be foreground or background)
|
||||||
self.bus.log("SIGHUP caught but not daemonized. Exiting.")
|
self.bus.log('SIGHUP caught but not daemonized. Exiting.')
|
||||||
self.bus.exit()
|
self.bus.exit()
|
||||||
|
|
||||||
|
|
||||||
@@ -239,14 +239,14 @@ class DropPrivileges(SimplePlugin):
|
|||||||
def _set_uid(self, val):
|
def _set_uid(self, val):
|
||||||
if val is not None:
|
if val is not None:
|
||||||
if pwd is None:
|
if pwd is None:
|
||||||
self.bus.log("pwd module not available; ignoring uid.",
|
self.bus.log('pwd module not available; ignoring uid.',
|
||||||
level=30)
|
level=30)
|
||||||
val = None
|
val = None
|
||||||
elif isinstance(val, basestring):
|
elif isinstance(val, text_or_bytes):
|
||||||
val = pwd.getpwnam(val)[2]
|
val = pwd.getpwnam(val)[2]
|
||||||
self._uid = val
|
self._uid = val
|
||||||
uid = property(_get_uid, _set_uid,
|
uid = property(_get_uid, _set_uid,
|
||||||
doc="The uid under which to run. Availability: Unix.")
|
doc='The uid under which to run. Availability: Unix.')
|
||||||
|
|
||||||
def _get_gid(self):
|
def _get_gid(self):
|
||||||
return self._gid
|
return self._gid
|
||||||
@@ -254,14 +254,14 @@ class DropPrivileges(SimplePlugin):
|
|||||||
def _set_gid(self, val):
|
def _set_gid(self, val):
|
||||||
if val is not None:
|
if val is not None:
|
||||||
if grp is None:
|
if grp is None:
|
||||||
self.bus.log("grp module not available; ignoring gid.",
|
self.bus.log('grp module not available; ignoring gid.',
|
||||||
level=30)
|
level=30)
|
||||||
val = None
|
val = None
|
||||||
elif isinstance(val, basestring):
|
elif isinstance(val, text_or_bytes):
|
||||||
val = grp.getgrnam(val)[2]
|
val = grp.getgrnam(val)[2]
|
||||||
self._gid = val
|
self._gid = val
|
||||||
gid = property(_get_gid, _set_gid,
|
gid = property(_get_gid, _set_gid,
|
||||||
doc="The gid under which to run. Availability: Unix.")
|
doc='The gid under which to run. Availability: Unix.')
|
||||||
|
|
||||||
def _get_umask(self):
|
def _get_umask(self):
|
||||||
return self._umask
|
return self._umask
|
||||||
@@ -271,7 +271,7 @@ class DropPrivileges(SimplePlugin):
|
|||||||
try:
|
try:
|
||||||
os.umask
|
os.umask
|
||||||
except AttributeError:
|
except AttributeError:
|
||||||
self.bus.log("umask function not available; ignoring umask.",
|
self.bus.log('umask function not available; ignoring umask.',
|
||||||
level=30)
|
level=30)
|
||||||
val = None
|
val = None
|
||||||
self._umask = val
|
self._umask = val
|
||||||
@@ -393,7 +393,7 @@ class Daemonizer(SimplePlugin):
|
|||||||
except OSError:
|
except OSError:
|
||||||
# Python raises OSError rather than returning negative numbers.
|
# Python raises OSError rather than returning negative numbers.
|
||||||
exc = sys.exc_info()[1]
|
exc = sys.exc_info()[1]
|
||||||
sys.exit("%s: fork #1 failed: (%d) %s\n"
|
sys.exit('%s: fork #1 failed: (%d) %s\n'
|
||||||
% (sys.argv[0], exc.errno, exc.strerror))
|
% (sys.argv[0], exc.errno, exc.strerror))
|
||||||
|
|
||||||
os.setsid()
|
os.setsid()
|
||||||
@@ -406,15 +406,15 @@ class Daemonizer(SimplePlugin):
|
|||||||
os._exit(0) # Exit second parent
|
os._exit(0) # Exit second parent
|
||||||
except OSError:
|
except OSError:
|
||||||
exc = sys.exc_info()[1]
|
exc = sys.exc_info()[1]
|
||||||
sys.exit("%s: fork #2 failed: (%d) %s\n"
|
sys.exit('%s: fork #2 failed: (%d) %s\n'
|
||||||
% (sys.argv[0], exc.errno, exc.strerror))
|
% (sys.argv[0], exc.errno, exc.strerror))
|
||||||
|
|
||||||
os.chdir("/")
|
os.chdir('/')
|
||||||
os.umask(0)
|
os.umask(0)
|
||||||
|
|
||||||
si = open(self.stdin, "r")
|
si = open(self.stdin, 'r')
|
||||||
so = open(self.stdout, "a+")
|
so = open(self.stdout, 'a+')
|
||||||
se = open(self.stderr, "a+")
|
se = open(self.stderr, 'a+')
|
||||||
|
|
||||||
# os.dup2(fd, fd2) will close fd2 if necessary,
|
# os.dup2(fd, fd2) will close fd2 if necessary,
|
||||||
# so we don't explicitly close stdin/out/err.
|
# so we don't explicitly close stdin/out/err.
|
||||||
@@ -442,7 +442,7 @@ class PIDFile(SimplePlugin):
|
|||||||
if self.finalized:
|
if self.finalized:
|
||||||
self.bus.log('PID %r already written to %r.' % (pid, self.pidfile))
|
self.bus.log('PID %r already written to %r.' % (pid, self.pidfile))
|
||||||
else:
|
else:
|
||||||
open(self.pidfile, "wb").write(ntob("%s\n" % pid, 'utf8'))
|
open(self.pidfile, 'wb').write(ntob('%s\n' % pid, 'utf8'))
|
||||||
self.bus.log('PID %r written to %r.' % (pid, self.pidfile))
|
self.bus.log('PID %r written to %r.' % (pid, self.pidfile))
|
||||||
self.finalized = True
|
self.finalized = True
|
||||||
start.priority = 70
|
start.priority = 70
|
||||||
@@ -481,13 +481,13 @@ class PerpetualTimer(Timer):
|
|||||||
except Exception:
|
except Exception:
|
||||||
if self.bus:
|
if self.bus:
|
||||||
self.bus.log(
|
self.bus.log(
|
||||||
"Error in perpetual timer thread function %r." %
|
'Error in perpetual timer thread function %r.' %
|
||||||
self.function, level=40, traceback=True)
|
self.function, level=40, traceback=True)
|
||||||
# Quit on first error to avoid massive logs.
|
# Quit on first error to avoid massive logs.
|
||||||
raise
|
raise
|
||||||
|
|
||||||
|
|
||||||
class BackgroundTask(SetDaemonProperty, threading.Thread):
|
class BackgroundTask(threading.Thread):
|
||||||
|
|
||||||
"""A subclass of threading.Thread whose run() method repeats.
|
"""A subclass of threading.Thread whose run() method repeats.
|
||||||
|
|
||||||
@@ -499,7 +499,7 @@ class BackgroundTask(SetDaemonProperty, threading.Thread):
|
|||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self, interval, function, args=[], kwargs={}, bus=None):
|
def __init__(self, interval, function, args=[], kwargs={}, bus=None):
|
||||||
threading.Thread.__init__(self)
|
super(BackgroundTask, self).__init__()
|
||||||
self.interval = interval
|
self.interval = interval
|
||||||
self.function = function
|
self.function = function
|
||||||
self.args = args
|
self.args = args
|
||||||
@@ -523,7 +523,7 @@ class BackgroundTask(SetDaemonProperty, threading.Thread):
|
|||||||
self.function(*self.args, **self.kwargs)
|
self.function(*self.args, **self.kwargs)
|
||||||
except Exception:
|
except Exception:
|
||||||
if self.bus:
|
if self.bus:
|
||||||
self.bus.log("Error in background task thread function %r."
|
self.bus.log('Error in background task thread function %r.'
|
||||||
% self.function, level=40, traceback=True)
|
% self.function, level=40, traceback=True)
|
||||||
# Quit on first error to avoid massive logs.
|
# Quit on first error to avoid massive logs.
|
||||||
raise
|
raise
|
||||||
@@ -560,24 +560,24 @@ class Monitor(SimplePlugin):
|
|||||||
bus=self.bus)
|
bus=self.bus)
|
||||||
self.thread.setName(threadname)
|
self.thread.setName(threadname)
|
||||||
self.thread.start()
|
self.thread.start()
|
||||||
self.bus.log("Started monitor thread %r." % threadname)
|
self.bus.log('Started monitor thread %r.' % threadname)
|
||||||
else:
|
else:
|
||||||
self.bus.log("Monitor thread %r already started." % threadname)
|
self.bus.log('Monitor thread %r already started.' % threadname)
|
||||||
start.priority = 70
|
start.priority = 70
|
||||||
|
|
||||||
def stop(self):
|
def stop(self):
|
||||||
"""Stop our callback's background task thread."""
|
"""Stop our callback's background task thread."""
|
||||||
if self.thread is None:
|
if self.thread is None:
|
||||||
self.bus.log("No thread running for %s." %
|
self.bus.log('No thread running for %s.' %
|
||||||
self.name or self.__class__.__name__)
|
self.name or self.__class__.__name__)
|
||||||
else:
|
else:
|
||||||
if self.thread is not threading.currentThread():
|
if self.thread is not threading.currentThread():
|
||||||
name = self.thread.getName()
|
name = self.thread.getName()
|
||||||
self.thread.cancel()
|
self.thread.cancel()
|
||||||
if not get_daemon(self.thread):
|
if not self.thread.daemon:
|
||||||
self.bus.log("Joining %r" % name)
|
self.bus.log('Joining %r' % name)
|
||||||
self.thread.join()
|
self.thread.join()
|
||||||
self.bus.log("Stopped thread %r." % name)
|
self.bus.log('Stopped thread %r.' % name)
|
||||||
self.thread = None
|
self.thread = None
|
||||||
|
|
||||||
def graceful(self):
|
def graceful(self):
|
||||||
@@ -674,10 +674,10 @@ class Autoreloader(Monitor):
|
|||||||
else:
|
else:
|
||||||
if mtime is None or mtime > oldtime:
|
if mtime is None or mtime > oldtime:
|
||||||
# The file has been deleted or modified.
|
# The file has been deleted or modified.
|
||||||
self.bus.log("Restarting because %s changed." %
|
self.bus.log('Restarting because %s changed.' %
|
||||||
filename)
|
filename)
|
||||||
self.thread.cancel()
|
self.thread.cancel()
|
||||||
self.bus.log("Stopped thread %r." %
|
self.bus.log('Stopped thread %r.' %
|
||||||
self.thread.getName())
|
self.thread.getName())
|
||||||
self.bus.restart()
|
self.bus.restart()
|
||||||
return
|
return
|
||||||
|
|||||||
@@ -113,6 +113,7 @@ Please see `Lighttpd FastCGI Docs
|
|||||||
an explanation of the possible configuration options.
|
an explanation of the possible configuration options.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
import os
|
||||||
import sys
|
import sys
|
||||||
import time
|
import time
|
||||||
import warnings
|
import warnings
|
||||||
@@ -151,32 +152,33 @@ class ServerAdapter(object):
|
|||||||
def start(self):
|
def start(self):
|
||||||
"""Start the HTTP server."""
|
"""Start the HTTP server."""
|
||||||
if self.bind_addr is None:
|
if self.bind_addr is None:
|
||||||
on_what = "unknown interface (dynamic?)"
|
on_what = 'unknown interface (dynamic?)'
|
||||||
elif isinstance(self.bind_addr, tuple):
|
elif isinstance(self.bind_addr, tuple):
|
||||||
on_what = self._get_base()
|
on_what = self._get_base()
|
||||||
else:
|
else:
|
||||||
on_what = "socket file: %s" % self.bind_addr
|
on_what = 'socket file: %s' % self.bind_addr
|
||||||
|
|
||||||
if self.running:
|
if self.running:
|
||||||
self.bus.log("Already serving on %s" % on_what)
|
self.bus.log('Already serving on %s' % on_what)
|
||||||
return
|
return
|
||||||
|
|
||||||
self.interrupt = None
|
self.interrupt = None
|
||||||
if not self.httpserver:
|
if not self.httpserver:
|
||||||
raise ValueError("No HTTP server has been created.")
|
raise ValueError('No HTTP server has been created.')
|
||||||
|
|
||||||
# Start the httpserver in a new thread.
|
if not os.environ.get('LISTEN_PID', None):
|
||||||
if isinstance(self.bind_addr, tuple):
|
# Start the httpserver in a new thread.
|
||||||
wait_for_free_port(*self.bind_addr)
|
if isinstance(self.bind_addr, tuple):
|
||||||
|
wait_for_free_port(*self.bind_addr)
|
||||||
|
|
||||||
import threading
|
import threading
|
||||||
t = threading.Thread(target=self._start_http_thread)
|
t = threading.Thread(target=self._start_http_thread)
|
||||||
t.setName("HTTPServer " + t.getName())
|
t.setName('HTTPServer ' + t.getName())
|
||||||
t.start()
|
t.start()
|
||||||
|
|
||||||
self.wait()
|
self.wait()
|
||||||
self.running = True
|
self.running = True
|
||||||
self.bus.log("Serving on %s" % on_what)
|
self.bus.log('Serving on %s' % on_what)
|
||||||
start.priority = 75
|
start.priority = 75
|
||||||
|
|
||||||
def _get_base(self):
|
def _get_base(self):
|
||||||
@@ -184,15 +186,15 @@ class ServerAdapter(object):
|
|||||||
return ''
|
return ''
|
||||||
host, port = self.bind_addr
|
host, port = self.bind_addr
|
||||||
if getattr(self.httpserver, 'ssl_adapter', None):
|
if getattr(self.httpserver, 'ssl_adapter', None):
|
||||||
scheme = "https"
|
scheme = 'https'
|
||||||
if port != 443:
|
if port != 443:
|
||||||
host += ":%s" % port
|
host += ':%s' % port
|
||||||
else:
|
else:
|
||||||
scheme = "http"
|
scheme = 'http'
|
||||||
if port != 80:
|
if port != 80:
|
||||||
host += ":%s" % port
|
host += ':%s' % port
|
||||||
|
|
||||||
return "%s://%s" % (scheme, host)
|
return '%s://%s' % (scheme, host)
|
||||||
|
|
||||||
def _start_http_thread(self):
|
def _start_http_thread(self):
|
||||||
"""HTTP servers MUST be running in new threads, so that the
|
"""HTTP servers MUST be running in new threads, so that the
|
||||||
@@ -204,32 +206,35 @@ class ServerAdapter(object):
|
|||||||
try:
|
try:
|
||||||
self.httpserver.start()
|
self.httpserver.start()
|
||||||
except KeyboardInterrupt:
|
except KeyboardInterrupt:
|
||||||
self.bus.log("<Ctrl-C> hit: shutting down HTTP server")
|
self.bus.log('<Ctrl-C> hit: shutting down HTTP server')
|
||||||
self.interrupt = sys.exc_info()[1]
|
self.interrupt = sys.exc_info()[1]
|
||||||
self.bus.exit()
|
self.bus.exit()
|
||||||
except SystemExit:
|
except SystemExit:
|
||||||
self.bus.log("SystemExit raised: shutting down HTTP server")
|
self.bus.log('SystemExit raised: shutting down HTTP server')
|
||||||
self.interrupt = sys.exc_info()[1]
|
self.interrupt = sys.exc_info()[1]
|
||||||
self.bus.exit()
|
self.bus.exit()
|
||||||
raise
|
raise
|
||||||
except:
|
except:
|
||||||
self.interrupt = sys.exc_info()[1]
|
self.interrupt = sys.exc_info()[1]
|
||||||
self.bus.log("Error in HTTP server: shutting down",
|
self.bus.log('Error in HTTP server: shutting down',
|
||||||
traceback=True, level=40)
|
traceback=True, level=40)
|
||||||
self.bus.exit()
|
self.bus.exit()
|
||||||
raise
|
raise
|
||||||
|
|
||||||
def wait(self):
|
def wait(self):
|
||||||
"""Wait until the HTTP server is ready to receive requests."""
|
"""Wait until the HTTP server is ready to receive requests."""
|
||||||
while not getattr(self.httpserver, "ready", False):
|
while not getattr(self.httpserver, 'ready', False):
|
||||||
if self.interrupt:
|
if self.interrupt:
|
||||||
raise self.interrupt
|
raise self.interrupt
|
||||||
time.sleep(.1)
|
time.sleep(.1)
|
||||||
|
|
||||||
# Wait for port to be occupied
|
# Wait for port to be occupied
|
||||||
if isinstance(self.bind_addr, tuple):
|
if not os.environ.get('LISTEN_PID', None):
|
||||||
host, port = self.bind_addr
|
# Wait for port to be occupied if not running via socket-activation
|
||||||
wait_for_occupied_port(host, port)
|
# (for socket-activation the port will be managed by systemd )
|
||||||
|
if isinstance(self.bind_addr, tuple):
|
||||||
|
host, port = self.bind_addr
|
||||||
|
wait_for_occupied_port(host, port)
|
||||||
|
|
||||||
def stop(self):
|
def stop(self):
|
||||||
"""Stop the HTTP server."""
|
"""Stop the HTTP server."""
|
||||||
@@ -240,9 +245,9 @@ class ServerAdapter(object):
|
|||||||
if isinstance(self.bind_addr, tuple):
|
if isinstance(self.bind_addr, tuple):
|
||||||
wait_for_free_port(*self.bind_addr)
|
wait_for_free_port(*self.bind_addr)
|
||||||
self.running = False
|
self.running = False
|
||||||
self.bus.log("HTTP Server %s shut down" % self.httpserver)
|
self.bus.log('HTTP Server %s shut down' % self.httpserver)
|
||||||
else:
|
else:
|
||||||
self.bus.log("HTTP Server %s already shut down" % self.httpserver)
|
self.bus.log('HTTP Server %s already shut down' % self.httpserver)
|
||||||
stop.priority = 25
|
stop.priority = 25
|
||||||
|
|
||||||
def restart(self):
|
def restart(self):
|
||||||
@@ -389,10 +394,10 @@ def check_port(host, port, timeout=1.0):
|
|||||||
except socket.gaierror:
|
except socket.gaierror:
|
||||||
if ':' in host:
|
if ':' in host:
|
||||||
info = [(
|
info = [(
|
||||||
socket.AF_INET6, socket.SOCK_STREAM, 0, "", (host, port, 0, 0)
|
socket.AF_INET6, socket.SOCK_STREAM, 0, '', (host, port, 0, 0)
|
||||||
)]
|
)]
|
||||||
else:
|
else:
|
||||||
info = [(socket.AF_INET, socket.SOCK_STREAM, 0, "", (host, port))]
|
info = [(socket.AF_INET, socket.SOCK_STREAM, 0, '', (host, port))]
|
||||||
|
|
||||||
for res in info:
|
for res in info:
|
||||||
af, socktype, proto, canonname, sa = res
|
af, socktype, proto, canonname, sa = res
|
||||||
@@ -408,8 +413,8 @@ def check_port(host, port, timeout=1.0):
|
|||||||
if s:
|
if s:
|
||||||
s.close()
|
s.close()
|
||||||
else:
|
else:
|
||||||
raise IOError("Port %s is in use on %s; perhaps the previous "
|
raise IOError('Port %s is in use on %s; perhaps the previous '
|
||||||
"httpserver did not shut down properly." %
|
'httpserver did not shut down properly.' %
|
||||||
(repr(port), repr(host)))
|
(repr(port), repr(host)))
|
||||||
|
|
||||||
|
|
||||||
@@ -435,7 +440,7 @@ def wait_for_free_port(host, port, timeout=None):
|
|||||||
else:
|
else:
|
||||||
return
|
return
|
||||||
|
|
||||||
raise IOError("Port %r not free on %r" % (port, host))
|
raise IOError('Port %r not free on %r' % (port, host))
|
||||||
|
|
||||||
|
|
||||||
def wait_for_occupied_port(host, port, timeout=None):
|
def wait_for_occupied_port(host, port, timeout=None):
|
||||||
@@ -455,11 +460,11 @@ def wait_for_occupied_port(host, port, timeout=None):
|
|||||||
time.sleep(timeout)
|
time.sleep(timeout)
|
||||||
|
|
||||||
if host == client_host(host):
|
if host == client_host(host):
|
||||||
raise IOError("Port %r not bound on %r" % (port, host))
|
raise IOError('Port %r not bound on %r' % (port, host))
|
||||||
|
|
||||||
# On systems where a loopback interface is not available and the
|
# On systems where a loopback interface is not available and the
|
||||||
# server is bound to all interfaces, it's difficult to determine
|
# server is bound to all interfaces, it's difficult to determine
|
||||||
# whether the server is in fact occupying the port. In this case,
|
# whether the server is in fact occupying the port. In this case,
|
||||||
# just issue a warning and move on. See issue #1100.
|
# just issue a warning and move on. See issue #1100.
|
||||||
msg = "Unable to verify that the server is bound on %r" % port
|
msg = 'Unable to verify that the server is bound on %r' % port
|
||||||
warnings.warn(msg)
|
warnings.warn(msg)
|
||||||
@@ -85,7 +85,7 @@ class Win32Bus(wspbus.Bus):
|
|||||||
return self.events[state]
|
return self.events[state]
|
||||||
except KeyError:
|
except KeyError:
|
||||||
event = win32event.CreateEvent(None, 0, 0,
|
event = win32event.CreateEvent(None, 0, 0,
|
||||||
"WSPBus %s Event (pid=%r)" %
|
'WSPBus %s Event (pid=%r)' %
|
||||||
(state.name, os.getpid()))
|
(state.name, os.getpid()))
|
||||||
self.events[state] = event
|
self.events[state] = event
|
||||||
return event
|
return event
|
||||||
@@ -135,7 +135,7 @@ class _ControlCodes(dict):
|
|||||||
for key, val in self.items():
|
for key, val in self.items():
|
||||||
if val is obj:
|
if val is obj:
|
||||||
return key
|
return key
|
||||||
raise ValueError("The given object could not be found: %r" % obj)
|
raise ValueError('The given object could not be found: %r' % obj)
|
||||||
|
|
||||||
control_codes = _ControlCodes({'graceful': 138})
|
control_codes = _ControlCodes({'graceful': 138})
|
||||||
|
|
||||||
@@ -153,14 +153,14 @@ class PyWebService(win32serviceutil.ServiceFramework):
|
|||||||
|
|
||||||
"""Python Web Service."""
|
"""Python Web Service."""
|
||||||
|
|
||||||
_svc_name_ = "Python Web Service"
|
_svc_name_ = 'Python Web Service'
|
||||||
_svc_display_name_ = "Python Web Service"
|
_svc_display_name_ = 'Python Web Service'
|
||||||
_svc_deps_ = None # sequence of service names on which this depends
|
_svc_deps_ = None # sequence of service names on which this depends
|
||||||
_exe_name_ = "pywebsvc"
|
_exe_name_ = 'pywebsvc'
|
||||||
_exe_args_ = None # Default to no arguments
|
_exe_args_ = None # Default to no arguments
|
||||||
|
|
||||||
# Only exists on Windows 2000 or later, ignored on windows NT
|
# Only exists on Windows 2000 or later, ignored on windows NT
|
||||||
_svc_description_ = "Python Web Service"
|
_svc_description_ = 'Python Web Service'
|
||||||
|
|
||||||
def SvcDoRun(self):
|
def SvcDoRun(self):
|
||||||
from cherrypy import process
|
from cherrypy import process
|
||||||
|
|||||||
@@ -61,13 +61,20 @@ the new state.::
|
|||||||
"""
|
"""
|
||||||
|
|
||||||
import atexit
|
import atexit
|
||||||
|
import ctypes
|
||||||
|
import operator
|
||||||
import os
|
import os
|
||||||
|
import subprocess
|
||||||
import sys
|
import sys
|
||||||
import threading
|
import threading
|
||||||
import time
|
import time
|
||||||
import traceback as _traceback
|
import traceback as _traceback
|
||||||
import warnings
|
import warnings
|
||||||
import operator
|
|
||||||
|
import six
|
||||||
|
|
||||||
|
from cherrypy._cpcompat import _args_from_interpreter_flags
|
||||||
|
|
||||||
|
|
||||||
# Here I save the value of os.getcwd(), which, if I am imported early enough,
|
# Here I save the value of os.getcwd(), which, if I am imported early enough,
|
||||||
# will be the directory from which the startup script was run. This is needed
|
# will be the directory from which the startup script was run. This is needed
|
||||||
@@ -85,9 +92,7 @@ class ChannelFailures(Exception):
|
|||||||
delimiter = '\n'
|
delimiter = '\n'
|
||||||
|
|
||||||
def __init__(self, *args, **kwargs):
|
def __init__(self, *args, **kwargs):
|
||||||
# Don't use 'super' here; Exceptions are old-style in Py2.4
|
super(Exception, self).__init__(*args, **kwargs)
|
||||||
# See https://github.com/cherrypy/cherrypy/issues/959
|
|
||||||
Exception.__init__(self, *args, **kwargs)
|
|
||||||
self._exceptions = list()
|
self._exceptions = list()
|
||||||
|
|
||||||
def handle_exception(self):
|
def handle_exception(self):
|
||||||
@@ -117,7 +122,7 @@ class _StateEnum(object):
|
|||||||
name = None
|
name = None
|
||||||
|
|
||||||
def __repr__(self):
|
def __repr__(self):
|
||||||
return "states.%s" % self.name
|
return 'states.%s' % self.name
|
||||||
|
|
||||||
def __setattr__(self, key, value):
|
def __setattr__(self, key, value):
|
||||||
if isinstance(value, self.State):
|
if isinstance(value, self.State):
|
||||||
@@ -170,9 +175,8 @@ class Bus(object):
|
|||||||
|
|
||||||
def subscribe(self, channel, callback, priority=None):
|
def subscribe(self, channel, callback, priority=None):
|
||||||
"""Add the given callback at the given channel (if not present)."""
|
"""Add the given callback at the given channel (if not present)."""
|
||||||
if channel not in self.listeners:
|
ch_listeners = self.listeners.setdefault(channel, set())
|
||||||
self.listeners[channel] = set()
|
ch_listeners.add(callback)
|
||||||
self.listeners[channel].add(callback)
|
|
||||||
|
|
||||||
if priority is None:
|
if priority is None:
|
||||||
priority = getattr(callback, 'priority', 50)
|
priority = getattr(callback, 'priority', 50)
|
||||||
@@ -215,7 +219,7 @@ class Bus(object):
|
|||||||
# Assume any further messages to 'log' will fail.
|
# Assume any further messages to 'log' will fail.
|
||||||
pass
|
pass
|
||||||
else:
|
else:
|
||||||
self.log("Error in %r listener %r" % (channel, listener),
|
self.log('Error in %r listener %r' % (channel, listener),
|
||||||
level=40, traceback=True)
|
level=40, traceback=True)
|
||||||
if exc:
|
if exc:
|
||||||
raise exc
|
raise exc
|
||||||
@@ -225,10 +229,10 @@ class Bus(object):
|
|||||||
"""An atexit handler which asserts the Bus is not running."""
|
"""An atexit handler which asserts the Bus is not running."""
|
||||||
if self.state != states.EXITING:
|
if self.state != states.EXITING:
|
||||||
warnings.warn(
|
warnings.warn(
|
||||||
"The main thread is exiting, but the Bus is in the %r state; "
|
'The main thread is exiting, but the Bus is in the %r state; '
|
||||||
"shutting it down automatically now. You must either call "
|
'shutting it down automatically now. You must either call '
|
||||||
"bus.block() after start(), or call bus.exit() before the "
|
'bus.block() after start(), or call bus.exit() before the '
|
||||||
"main thread exits." % self.state, RuntimeWarning)
|
'main thread exits.' % self.state, RuntimeWarning)
|
||||||
self.exit()
|
self.exit()
|
||||||
|
|
||||||
def start(self):
|
def start(self):
|
||||||
@@ -244,7 +248,7 @@ class Bus(object):
|
|||||||
except (KeyboardInterrupt, SystemExit):
|
except (KeyboardInterrupt, SystemExit):
|
||||||
raise
|
raise
|
||||||
except:
|
except:
|
||||||
self.log("Shutting down due to error in start listener:",
|
self.log('Shutting down due to error in start listener:',
|
||||||
level=40, traceback=True)
|
level=40, traceback=True)
|
||||||
e_info = sys.exc_info()[1]
|
e_info = sys.exc_info()[1]
|
||||||
try:
|
try:
|
||||||
@@ -321,7 +325,7 @@ class Bus(object):
|
|||||||
# It's also good to let them all shut down before allowing
|
# It's also good to let them all shut down before allowing
|
||||||
# the main thread to call atexit handlers.
|
# the main thread to call atexit handlers.
|
||||||
# See https://github.com/cherrypy/cherrypy/issues/751.
|
# See https://github.com/cherrypy/cherrypy/issues/751.
|
||||||
self.log("Waiting for child threads to terminate...")
|
self.log('Waiting for child threads to terminate...')
|
||||||
for t in threading.enumerate():
|
for t in threading.enumerate():
|
||||||
# Validate the we're not trying to join the MainThread
|
# Validate the we're not trying to join the MainThread
|
||||||
# that will cause a deadlock and the case exist when
|
# that will cause a deadlock and the case exist when
|
||||||
@@ -333,13 +337,13 @@ class Bus(object):
|
|||||||
not isinstance(t, threading._MainThread)
|
not isinstance(t, threading._MainThread)
|
||||||
):
|
):
|
||||||
# Note that any dummy (external) threads are always daemonic.
|
# Note that any dummy (external) threads are always daemonic.
|
||||||
if hasattr(threading.Thread, "daemon"):
|
if hasattr(threading.Thread, 'daemon'):
|
||||||
# Python 2.6+
|
# Python 2.6+
|
||||||
d = t.daemon
|
d = t.daemon
|
||||||
else:
|
else:
|
||||||
d = t.isDaemon()
|
d = t.isDaemon()
|
||||||
if not d:
|
if not d:
|
||||||
self.log("Waiting for thread %s." % t.getName())
|
self.log('Waiting for thread %s.' % t.getName())
|
||||||
t.join()
|
t.join()
|
||||||
|
|
||||||
if self.execv:
|
if self.execv:
|
||||||
@@ -376,14 +380,25 @@ class Bus(object):
|
|||||||
This must be called from the main thread, because certain platforms
|
This must be called from the main thread, because certain platforms
|
||||||
(OS X) don't allow execv to be called in a child thread very well.
|
(OS X) don't allow execv to be called in a child thread very well.
|
||||||
"""
|
"""
|
||||||
args = sys.argv[:]
|
try:
|
||||||
|
args = self._get_true_argv()
|
||||||
|
except NotImplementedError:
|
||||||
|
"""It's probably win32"""
|
||||||
|
# For the SABnzbd.exe binary we don't want interpreter flags
|
||||||
|
# https://github.com/cherrypy/cherrypy/issues/1526
|
||||||
|
if getattr(sys, 'frozen', False):
|
||||||
|
args = [sys.executable] + sys.argv
|
||||||
|
else:
|
||||||
|
args = [sys.executable] + _args_from_interpreter_flags() + sys.argv
|
||||||
|
|
||||||
self.log('Re-spawning %s' % ' '.join(args))
|
self.log('Re-spawning %s' % ' '.join(args))
|
||||||
|
|
||||||
|
self._extend_pythonpath(os.environ)
|
||||||
|
|
||||||
if sys.platform[:4] == 'java':
|
if sys.platform[:4] == 'java':
|
||||||
from _systemrestart import SystemRestart
|
from _systemrestart import SystemRestart
|
||||||
raise SystemRestart
|
raise SystemRestart
|
||||||
else:
|
else:
|
||||||
args.insert(0, sys.executable)
|
|
||||||
if sys.platform == 'win32':
|
if sys.platform == 'win32':
|
||||||
args = ['"%s"' % arg for arg in args]
|
args = ['"%s"' % arg for arg in args]
|
||||||
|
|
||||||
@@ -392,6 +407,58 @@ class Bus(object):
|
|||||||
self._set_cloexec()
|
self._set_cloexec()
|
||||||
os.execv(sys.executable, args)
|
os.execv(sys.executable, args)
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def _get_true_argv():
|
||||||
|
"""Retrieves all real arguments of the python interpreter
|
||||||
|
|
||||||
|
...even those not listed in ``sys.argv``
|
||||||
|
|
||||||
|
:seealso: http://stackoverflow.com/a/28338254/595220
|
||||||
|
:seealso: http://stackoverflow.com/a/6683222/595220
|
||||||
|
:seealso: http://stackoverflow.com/a/28414807/595220
|
||||||
|
"""
|
||||||
|
|
||||||
|
try:
|
||||||
|
char_p = ctypes.c_char_p if six.PY2 else ctypes.c_wchar_p
|
||||||
|
|
||||||
|
argv = ctypes.POINTER(char_p)()
|
||||||
|
argc = ctypes.c_int()
|
||||||
|
|
||||||
|
ctypes.pythonapi.Py_GetArgcArgv(ctypes.byref(argc), ctypes.byref(argv))
|
||||||
|
except AttributeError:
|
||||||
|
"""It looks Py_GetArgcArgv is completely absent in MS Windows
|
||||||
|
|
||||||
|
:seealso: https://github.com/cherrypy/cherrypy/issues/1506
|
||||||
|
:ref: https://chromium.googlesource.com/infra/infra/+/69eb0279c12bcede5937ce9298020dd4581e38dd%5E!/
|
||||||
|
"""
|
||||||
|
raise NotImplementedError
|
||||||
|
else:
|
||||||
|
return argv[:argc.value]
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def _extend_pythonpath(env):
|
||||||
|
"""
|
||||||
|
If sys.path[0] is an empty string, the interpreter was likely
|
||||||
|
invoked with -m and the effective path is about to change on
|
||||||
|
re-exec. Add the current directory to $PYTHONPATH to ensure
|
||||||
|
that the new process sees the same path.
|
||||||
|
|
||||||
|
This issue cannot be addressed in the general case because
|
||||||
|
Python cannot reliably reconstruct the
|
||||||
|
original command line (http://bugs.python.org/issue14208).
|
||||||
|
|
||||||
|
(This idea filched from tornado.autoreload)
|
||||||
|
"""
|
||||||
|
path_prefix = '.' + os.pathsep
|
||||||
|
existing_path = env.get('PYTHONPATH', '')
|
||||||
|
needs_patch = (
|
||||||
|
sys.path[0] == '' and
|
||||||
|
not existing_path.startswith(path_prefix)
|
||||||
|
)
|
||||||
|
|
||||||
|
if needs_patch:
|
||||||
|
env['PYTHONPATH'] = path_prefix + existing_path
|
||||||
|
|
||||||
def _set_cloexec(self):
|
def _set_cloexec(self):
|
||||||
"""Set the CLOEXEC flag on all open files (except stdin/out/err).
|
"""Set the CLOEXEC flag on all open files (except stdin/out/err).
|
||||||
|
|
||||||
@@ -437,7 +504,7 @@ class Bus(object):
|
|||||||
|
|
||||||
return t
|
return t
|
||||||
|
|
||||||
def log(self, msg="", level=20, traceback=False):
|
def log(self, msg='', level=20, traceback=False):
|
||||||
"""Log the given message. Append the last traceback if requested."""
|
"""Log the given message. Append the last traceback if requested."""
|
||||||
if traceback:
|
if traceback:
|
||||||
# Work-around for bug in Python's traceback implementation
|
# Work-around for bug in Python's traceback implementation
|
||||||
|
|||||||
@@ -33,10 +33,10 @@ class BuiltinSSLAdapter(wsgiserver.SSLAdapter):
|
|||||||
|
|
||||||
private_key = None
|
private_key = None
|
||||||
"""The filename of the server's private key file."""
|
"""The filename of the server's private key file."""
|
||||||
|
|
||||||
certificate_chain = None
|
certificate_chain = None
|
||||||
"""The filename of the certificate chain file."""
|
"""The filename of the certificate chain file."""
|
||||||
|
|
||||||
"""The ssl.SSLContext that will be used to wrap sockets where available
|
"""The ssl.SSLContext that will be used to wrap sockets where available
|
||||||
(on Python > 2.7.9 / 3.3)
|
(on Python > 2.7.9 / 3.3)
|
||||||
"""
|
"""
|
||||||
@@ -44,7 +44,7 @@ class BuiltinSSLAdapter(wsgiserver.SSLAdapter):
|
|||||||
|
|
||||||
def __init__(self, certificate, private_key, certificate_chain=None):
|
def __init__(self, certificate, private_key, certificate_chain=None):
|
||||||
if ssl is None:
|
if ssl is None:
|
||||||
raise ImportError("You must install the ssl module to use HTTPS.")
|
raise ImportError('You must install the ssl module to use HTTPS.')
|
||||||
self.certificate = certificate
|
self.certificate = certificate
|
||||||
self.private_key = private_key
|
self.private_key = private_key
|
||||||
self.certificate_chain = certificate_chain
|
self.certificate_chain = certificate_chain
|
||||||
@@ -79,13 +79,30 @@ class BuiltinSSLAdapter(wsgiserver.SSLAdapter):
|
|||||||
# the 'ping' isn't SSL.
|
# the 'ping' isn't SSL.
|
||||||
return None, {}
|
return None, {}
|
||||||
elif e.errno == ssl.SSL_ERROR_SSL:
|
elif e.errno == ssl.SSL_ERROR_SSL:
|
||||||
if e.args[1].endswith('http request'):
|
if 'http request' in e.args[1]:
|
||||||
# The client is speaking HTTP to an HTTPS server.
|
# The client is speaking HTTP to an HTTPS server.
|
||||||
raise wsgiserver.NoSSLError
|
raise wsgiserver.NoSSLError
|
||||||
elif e.args[1].endswith('unknown protocol'):
|
|
||||||
# The client is speaking some non-HTTP protocol.
|
# Check if it's one of the known errors
|
||||||
# Drop the conn.
|
# Errors that are caught by PyOpenSSL, but thrown by built-in ssl
|
||||||
return None, {}
|
_block_errors = ('unknown protocol', 'unknown ca', 'unknown_ca', 'unknown error',
|
||||||
|
'https proxy request', 'inappropriate fallback', 'wrong version number',
|
||||||
|
'no shared cipher', 'certificate unknown', 'ccs received early')
|
||||||
|
for error_text in _block_errors:
|
||||||
|
if error_text in e.args[1].lower():
|
||||||
|
# Accepted error, let's pass
|
||||||
|
return None, {}
|
||||||
|
elif 'handshake operation timed out' in e.args[0]:
|
||||||
|
# This error is thrown by builtin SSL after a timeout
|
||||||
|
# when client is speaking HTTP to an HTTPS server.
|
||||||
|
# The connection can safely be dropped.
|
||||||
|
return None, {}
|
||||||
|
raise
|
||||||
|
except:
|
||||||
|
# Temporary fix for https://github.com/cherrypy/cherrypy/issues/1618
|
||||||
|
e = sys.exc_info()[1]
|
||||||
|
if e.args == (0, 'Error'):
|
||||||
|
return None, {}
|
||||||
raise
|
raise
|
||||||
return s, self.get_environ(s)
|
return s, self.get_environ(s)
|
||||||
|
|
||||||
@@ -94,8 +111,8 @@ class BuiltinSSLAdapter(wsgiserver.SSLAdapter):
|
|||||||
"""Create WSGI environ entries to be merged into each request."""
|
"""Create WSGI environ entries to be merged into each request."""
|
||||||
cipher = sock.cipher()
|
cipher = sock.cipher()
|
||||||
ssl_environ = {
|
ssl_environ = {
|
||||||
"wsgi.url_scheme": "https",
|
'wsgi.url_scheme': 'https',
|
||||||
"HTTPS": "on",
|
'HTTPS': 'on',
|
||||||
'SSL_PROTOCOL': cipher[1],
|
'SSL_PROTOCOL': cipher[1],
|
||||||
'SSL_CIPHER': cipher[0]
|
'SSL_CIPHER': cipher[0]
|
||||||
# SSL_VERSION_INTERFACE string The mod_ssl program version
|
# SSL_VERSION_INTERFACE string The mod_ssl program version
|
||||||
@@ -103,9 +120,5 @@ class BuiltinSSLAdapter(wsgiserver.SSLAdapter):
|
|||||||
}
|
}
|
||||||
return ssl_environ
|
return ssl_environ
|
||||||
|
|
||||||
if sys.version_info >= (3, 0):
|
def makefile(self, sock, mode='r', bufsize=DEFAULT_BUFFER_SIZE):
|
||||||
def makefile(self, sock, mode='r', bufsize=DEFAULT_BUFFER_SIZE):
|
return wsgiserver.CP_makefile(sock, mode, bufsize)
|
||||||
return wsgiserver.CP_makefile(sock, mode, bufsize)
|
|
||||||
else:
|
|
||||||
def makefile(self, sock, mode='r', bufsize=DEFAULT_BUFFER_SIZE):
|
|
||||||
return wsgiserver.CP_fileobject(sock, mode, bufsize)
|
|
||||||
@@ -43,7 +43,7 @@ except ImportError:
|
|||||||
SSL = None
|
SSL = None
|
||||||
|
|
||||||
|
|
||||||
class SSL_fileobject(wsgiserver.CP_fileobject):
|
class SSL_fileobject(wsgiserver.CP_makefile):
|
||||||
|
|
||||||
"""SSL file object attached to a socket object."""
|
"""SSL file object attached to a socket object."""
|
||||||
|
|
||||||
@@ -70,15 +70,15 @@ class SSL_fileobject(wsgiserver.CP_fileobject):
|
|||||||
time.sleep(self.ssl_retry)
|
time.sleep(self.ssl_retry)
|
||||||
except SSL.SysCallError as e:
|
except SSL.SysCallError as e:
|
||||||
if is_reader and e.args == (-1, 'Unexpected EOF'):
|
if is_reader and e.args == (-1, 'Unexpected EOF'):
|
||||||
return ""
|
return ''
|
||||||
|
|
||||||
errnum = e.args[0]
|
errnum = e.args[0]
|
||||||
if is_reader and errnum in wsgiserver.socket_errors_to_ignore:
|
if is_reader and errnum in wsgiserver.socket_errors_to_ignore:
|
||||||
return ""
|
return ''
|
||||||
raise socket.error(errnum)
|
raise socket.error(errnum)
|
||||||
except SSL.Error as e:
|
except SSL.Error as e:
|
||||||
if is_reader and e.args == (-1, 'Unexpected EOF'):
|
if is_reader and e.args == (-1, 'Unexpected EOF'):
|
||||||
return ""
|
return ''
|
||||||
|
|
||||||
thirdarg = None
|
thirdarg = None
|
||||||
try:
|
try:
|
||||||
@@ -95,7 +95,7 @@ class SSL_fileobject(wsgiserver.CP_fileobject):
|
|||||||
raise
|
raise
|
||||||
|
|
||||||
if time.time() - start > self.ssl_timeout:
|
if time.time() - start > self.ssl_timeout:
|
||||||
raise socket.timeout("timed out")
|
raise socket.timeout('timed out')
|
||||||
|
|
||||||
def recv(self, size):
|
def recv(self, size):
|
||||||
return self._safe_call(True, super(SSL_fileobject, self).recv, size)
|
return self._safe_call(True, super(SSL_fileobject, self).recv, size)
|
||||||
@@ -166,7 +166,7 @@ class pyOpenSSLAdapter(wsgiserver.SSLAdapter):
|
|||||||
|
|
||||||
def __init__(self, certificate, private_key, certificate_chain=None):
|
def __init__(self, certificate, private_key, certificate_chain=None):
|
||||||
if SSL is None:
|
if SSL is None:
|
||||||
raise ImportError("You must install pyOpenSSL to use HTTPS.")
|
raise ImportError('You must install pyOpenSSL to use HTTPS.')
|
||||||
|
|
||||||
self.context = None
|
self.context = None
|
||||||
self.certificate = certificate
|
self.certificate = certificate
|
||||||
@@ -192,18 +192,14 @@ class pyOpenSSLAdapter(wsgiserver.SSLAdapter):
|
|||||||
c = SSL.Context(SSL.SSLv23_METHOD)
|
c = SSL.Context(SSL.SSLv23_METHOD)
|
||||||
c.use_privatekey_file(self.private_key)
|
c.use_privatekey_file(self.private_key)
|
||||||
if self.certificate_chain:
|
if self.certificate_chain:
|
||||||
if isinstance(self.certificate_chain, unicode) and self.certificate_chain.encode('cp1252', 'ignore') == self.certificate_chain.encode('cp1252', 'replace'):
|
c.load_verify_locations(self.certificate_chain)
|
||||||
# Support buggy PyOpenSSL 0.14, which cannot handle Unicode names
|
|
||||||
c.load_verify_locations(self.certificate_chain.encode('cp1252', 'ignore'))
|
|
||||||
else:
|
|
||||||
c.load_verify_locations(self.certificate_chain)
|
|
||||||
c.use_certificate_file(self.certificate)
|
c.use_certificate_file(self.certificate)
|
||||||
return c
|
return c
|
||||||
|
|
||||||
def get_environ(self):
|
def get_environ(self):
|
||||||
"""Return WSGI environ entries to be merged into each request."""
|
"""Return WSGI environ entries to be merged into each request."""
|
||||||
ssl_environ = {
|
ssl_environ = {
|
||||||
"HTTPS": "on",
|
'HTTPS': 'on',
|
||||||
# pyOpenSSL doesn't provide access to any of these AFAICT
|
# pyOpenSSL doesn't provide access to any of these AFAICT
|
||||||
# 'SSL_PROTOCOL': 'SSLv2',
|
# 'SSL_PROTOCOL': 'SSLv2',
|
||||||
# SSL_CIPHER string The cipher specification name
|
# SSL_CIPHER string The cipher specification name
|
||||||
@@ -224,8 +220,8 @@ class pyOpenSSLAdapter(wsgiserver.SSLAdapter):
|
|||||||
# Validity of server's certificate (end time),
|
# Validity of server's certificate (end time),
|
||||||
})
|
})
|
||||||
|
|
||||||
for prefix, dn in [("I", cert.get_issuer()),
|
for prefix, dn in [('I', cert.get_issuer()),
|
||||||
("S", cert.get_subject())]:
|
('S', cert.get_subject())]:
|
||||||
# X509Name objects don't seem to have a way to get the
|
# X509Name objects don't seem to have a way to get the
|
||||||
# complete DN string. Use str() and slice it instead,
|
# complete DN string. Use str() and slice it instead,
|
||||||
# because str(dn) == "<X509Name object '/C=US/ST=...'>"
|
# because str(dn) == "<X509Name object '/C=US/ST=...'>"
|
||||||
@@ -237,9 +233,9 @@ class pyOpenSSLAdapter(wsgiserver.SSLAdapter):
|
|||||||
# The DN should be of the form: /k1=v1/k2=v2, but we must allow
|
# The DN should be of the form: /k1=v1/k2=v2, but we must allow
|
||||||
# for any value to contain slashes itself (in a URL).
|
# for any value to contain slashes itself (in a URL).
|
||||||
while dnstr:
|
while dnstr:
|
||||||
pos = dnstr.rfind("=")
|
pos = dnstr.rfind('=')
|
||||||
dnstr, value = dnstr[:pos], dnstr[pos + 1:]
|
dnstr, value = dnstr[:pos], dnstr[pos + 1:]
|
||||||
pos = dnstr.rfind("/")
|
pos = dnstr.rfind('/')
|
||||||
dnstr, key = dnstr[:pos], dnstr[pos + 1:]
|
dnstr, key = dnstr[:pos], dnstr[pos + 1:]
|
||||||
if key and value:
|
if key and value:
|
||||||
wsgikey = 'SSL_SERVER_%s_DN_%s' % (prefix, key)
|
wsgikey = 'SSL_SERVER_%s_DN_%s' % (prefix, key)
|
||||||
|
|||||||
@@ -0,0 +1,16 @@
|
|||||||
|
import six
|
||||||
|
|
||||||
|
import mock
|
||||||
|
|
||||||
|
from cherrypy import wsgiserver
|
||||||
|
|
||||||
|
|
||||||
|
class TestWSGIGateway_u0:
|
||||||
|
@mock.patch('cherrypy.wsgiserver.WSGIGateway_10.get_environ',
|
||||||
|
lambda self: {'foo': 'bar'})
|
||||||
|
def test_decodes_items(self):
|
||||||
|
req = mock.MagicMock(path=b'/', qs=b'')
|
||||||
|
gw = wsgiserver.WSGIGateway_u0(req=req)
|
||||||
|
env = gw.get_environ()
|
||||||
|
assert env['foo'] == 'bar'
|
||||||
|
assert isinstance(env['foo'], six.text_type)
|
||||||
@@ -1,509 +0,0 @@
|
|||||||
import re
|
|
||||||
import hashlib
|
|
||||||
import time
|
|
||||||
import StringIO
|
|
||||||
|
|
||||||
__version__ = '0.8'
|
|
||||||
|
|
||||||
#GNTP/<version> <messagetype> <encryptionAlgorithmID>[:<ivValue>][ <keyHashAlgorithmID>:<keyHash>.<salt>]
|
|
||||||
GNTP_INFO_LINE = re.compile(
|
|
||||||
'GNTP/(?P<version>\d+\.\d+) (?P<messagetype>REGISTER|NOTIFY|SUBSCRIBE|\-OK|\-ERROR)' +
|
|
||||||
' (?P<encryptionAlgorithmID>[A-Z0-9]+(:(?P<ivValue>[A-F0-9]+))?) ?' +
|
|
||||||
'((?P<keyHashAlgorithmID>[A-Z0-9]+):(?P<keyHash>[A-F0-9]+).(?P<salt>[A-F0-9]+))?\r\n',
|
|
||||||
re.IGNORECASE
|
|
||||||
)
|
|
||||||
|
|
||||||
GNTP_INFO_LINE_SHORT = re.compile(
|
|
||||||
'GNTP/(?P<version>\d+\.\d+) (?P<messagetype>REGISTER|NOTIFY|SUBSCRIBE|\-OK|\-ERROR)',
|
|
||||||
re.IGNORECASE
|
|
||||||
)
|
|
||||||
|
|
||||||
GNTP_HEADER = re.compile('([\w-]+):(.+)')
|
|
||||||
|
|
||||||
GNTP_EOL = '\r\n'
|
|
||||||
|
|
||||||
|
|
||||||
class BaseError(Exception):
|
|
||||||
def gntp_error(self):
|
|
||||||
error = GNTPError(self.errorcode, self.errordesc)
|
|
||||||
return error.encode()
|
|
||||||
|
|
||||||
|
|
||||||
class ParseError(BaseError):
|
|
||||||
errorcode = 500
|
|
||||||
errordesc = 'Error parsing the message'
|
|
||||||
|
|
||||||
|
|
||||||
class AuthError(BaseError):
|
|
||||||
errorcode = 400
|
|
||||||
errordesc = 'Error with authorization'
|
|
||||||
|
|
||||||
|
|
||||||
class UnsupportedError(BaseError):
|
|
||||||
errorcode = 500
|
|
||||||
errordesc = 'Currently unsupported by gntp.py'
|
|
||||||
|
|
||||||
|
|
||||||
class _GNTPBuffer(StringIO.StringIO):
|
|
||||||
"""GNTP Buffer class"""
|
|
||||||
def writefmt(self, message="", *args):
|
|
||||||
"""Shortcut function for writing GNTP Headers"""
|
|
||||||
self.write((message % args).encode('utf8', 'replace'))
|
|
||||||
self.write(GNTP_EOL)
|
|
||||||
|
|
||||||
|
|
||||||
class _GNTPBase(object):
|
|
||||||
"""Base initilization
|
|
||||||
|
|
||||||
:param string messagetype: GNTP Message type
|
|
||||||
:param string version: GNTP Protocol version
|
|
||||||
:param string encription: Encryption protocol
|
|
||||||
"""
|
|
||||||
def __init__(self, messagetype=None, version='1.0', encryption=None):
|
|
||||||
self.info = {
|
|
||||||
'version': version,
|
|
||||||
'messagetype': messagetype,
|
|
||||||
'encryptionAlgorithmID': encryption
|
|
||||||
}
|
|
||||||
self.headers = {}
|
|
||||||
self.resources = {}
|
|
||||||
|
|
||||||
def __str__(self):
|
|
||||||
return self.encode()
|
|
||||||
|
|
||||||
def _parse_info(self, data):
|
|
||||||
"""Parse the first line of a GNTP message to get security and other info values
|
|
||||||
|
|
||||||
:param string data: GNTP Message
|
|
||||||
:return dict: Parsed GNTP Info line
|
|
||||||
"""
|
|
||||||
|
|
||||||
match = GNTP_INFO_LINE.match(data)
|
|
||||||
|
|
||||||
if not match:
|
|
||||||
raise ParseError('ERROR_PARSING_INFO_LINE')
|
|
||||||
|
|
||||||
info = match.groupdict()
|
|
||||||
if info['encryptionAlgorithmID'] == 'NONE':
|
|
||||||
info['encryptionAlgorithmID'] = None
|
|
||||||
|
|
||||||
return info
|
|
||||||
|
|
||||||
def set_password(self, password, encryptAlgo='MD5'):
|
|
||||||
"""Set a password for a GNTP Message
|
|
||||||
|
|
||||||
:param string password: Null to clear password
|
|
||||||
:param string encryptAlgo: Supports MD5, SHA1, SHA256, SHA512
|
|
||||||
"""
|
|
||||||
hash = {
|
|
||||||
'MD5': hashlib.md5,
|
|
||||||
'SHA1': hashlib.sha1,
|
|
||||||
'SHA256': hashlib.sha256,
|
|
||||||
'SHA512': hashlib.sha512,
|
|
||||||
}
|
|
||||||
|
|
||||||
self.password = password
|
|
||||||
self.encryptAlgo = encryptAlgo.upper()
|
|
||||||
if not password:
|
|
||||||
self.info['encryptionAlgorithmID'] = None
|
|
||||||
self.info['keyHashAlgorithm'] = None
|
|
||||||
return
|
|
||||||
if not self.encryptAlgo in hash.keys():
|
|
||||||
raise UnsupportedError('INVALID HASH "%s"' % self.encryptAlgo)
|
|
||||||
|
|
||||||
hashfunction = hash.get(self.encryptAlgo)
|
|
||||||
|
|
||||||
password = password.encode('utf8')
|
|
||||||
seed = time.ctime()
|
|
||||||
salt = hashfunction(seed).hexdigest()
|
|
||||||
saltHash = hashfunction(seed).digest()
|
|
||||||
keyBasis = password + saltHash
|
|
||||||
key = hashfunction(keyBasis).digest()
|
|
||||||
keyHash = hashfunction(key).hexdigest()
|
|
||||||
|
|
||||||
self.info['keyHashAlgorithmID'] = self.encryptAlgo
|
|
||||||
self.info['keyHash'] = keyHash.upper()
|
|
||||||
self.info['salt'] = salt.upper()
|
|
||||||
|
|
||||||
def _decode_hex(self, value):
|
|
||||||
"""Helper function to decode hex string to `proper` hex string
|
|
||||||
|
|
||||||
:param string value: Human readable hex string
|
|
||||||
:return string: Hex string
|
|
||||||
"""
|
|
||||||
result = ''
|
|
||||||
for i in range(0, len(value), 2):
|
|
||||||
tmp = int(value[i:i + 2], 16)
|
|
||||||
result += chr(tmp)
|
|
||||||
return result
|
|
||||||
|
|
||||||
def _decode_binary(self, rawIdentifier, identifier):
|
|
||||||
rawIdentifier += '\r\n\r\n'
|
|
||||||
dataLength = int(identifier['Length'])
|
|
||||||
pointerStart = self.raw.find(rawIdentifier) + len(rawIdentifier)
|
|
||||||
pointerEnd = pointerStart + dataLength
|
|
||||||
data = self.raw[pointerStart:pointerEnd]
|
|
||||||
if not len(data) == dataLength:
|
|
||||||
raise ParseError('INVALID_DATA_LENGTH Expected: %s Recieved %s' % (dataLength, len(data)))
|
|
||||||
return data
|
|
||||||
|
|
||||||
def _validate_password(self, password):
|
|
||||||
"""Validate GNTP Message against stored password"""
|
|
||||||
self.password = password
|
|
||||||
if password == None:
|
|
||||||
raise AuthError('Missing password')
|
|
||||||
keyHash = self.info.get('keyHash', None)
|
|
||||||
if keyHash is None and self.password is None:
|
|
||||||
return True
|
|
||||||
if keyHash is None:
|
|
||||||
raise AuthError('Invalid keyHash')
|
|
||||||
if self.password is None:
|
|
||||||
raise AuthError('Missing password')
|
|
||||||
|
|
||||||
password = self.password.encode('utf8')
|
|
||||||
saltHash = self._decode_hex(self.info['salt'])
|
|
||||||
|
|
||||||
keyBasis = password + saltHash
|
|
||||||
key = hashlib.md5(keyBasis).digest()
|
|
||||||
keyHash = hashlib.md5(key).hexdigest()
|
|
||||||
|
|
||||||
if not keyHash.upper() == self.info['keyHash'].upper():
|
|
||||||
raise AuthError('Invalid Hash')
|
|
||||||
return True
|
|
||||||
|
|
||||||
def validate(self):
|
|
||||||
"""Verify required headers"""
|
|
||||||
for header in self._requiredHeaders:
|
|
||||||
if not self.headers.get(header, False):
|
|
||||||
raise ParseError('Missing Notification Header: ' + header)
|
|
||||||
|
|
||||||
def _format_info(self):
|
|
||||||
"""Generate info line for GNTP Message
|
|
||||||
|
|
||||||
:return string:
|
|
||||||
"""
|
|
||||||
info = u'GNTP/%s %s' % (
|
|
||||||
self.info.get('version'),
|
|
||||||
self.info.get('messagetype'),
|
|
||||||
)
|
|
||||||
if self.info.get('encryptionAlgorithmID', None):
|
|
||||||
info += ' %s:%s' % (
|
|
||||||
self.info.get('encryptionAlgorithmID'),
|
|
||||||
self.info.get('ivValue'),
|
|
||||||
)
|
|
||||||
else:
|
|
||||||
info += ' NONE'
|
|
||||||
|
|
||||||
if self.info.get('keyHashAlgorithmID', None):
|
|
||||||
info += ' %s:%s.%s' % (
|
|
||||||
self.info.get('keyHashAlgorithmID'),
|
|
||||||
self.info.get('keyHash'),
|
|
||||||
self.info.get('salt')
|
|
||||||
)
|
|
||||||
|
|
||||||
return info
|
|
||||||
|
|
||||||
def _parse_dict(self, data):
|
|
||||||
"""Helper function to parse blocks of GNTP headers into a dictionary
|
|
||||||
|
|
||||||
:param string data:
|
|
||||||
:return dict:
|
|
||||||
"""
|
|
||||||
dict = {}
|
|
||||||
for line in data.split('\r\n'):
|
|
||||||
match = GNTP_HEADER.match(line)
|
|
||||||
if not match:
|
|
||||||
continue
|
|
||||||
|
|
||||||
key = unicode(match.group(1).strip(), 'utf8', 'replace')
|
|
||||||
val = unicode(match.group(2).strip(), 'utf8', 'replace')
|
|
||||||
dict[key] = val
|
|
||||||
return dict
|
|
||||||
|
|
||||||
def add_header(self, key, value):
|
|
||||||
if isinstance(value, unicode):
|
|
||||||
self.headers[key] = value
|
|
||||||
else:
|
|
||||||
self.headers[key] = unicode('%s' % value, 'utf8', 'replace')
|
|
||||||
|
|
||||||
def add_resource(self, data):
|
|
||||||
"""Add binary resource
|
|
||||||
|
|
||||||
:param string data: Binary Data
|
|
||||||
"""
|
|
||||||
identifier = hashlib.md5(data).hexdigest()
|
|
||||||
self.resources[identifier] = data
|
|
||||||
return 'x-growl-resource://%s' % identifier
|
|
||||||
|
|
||||||
def decode(self, data, password=None):
|
|
||||||
"""Decode GNTP Message
|
|
||||||
|
|
||||||
:param string data:
|
|
||||||
"""
|
|
||||||
self.password = password
|
|
||||||
self.raw = data
|
|
||||||
parts = self.raw.split('\r\n\r\n')
|
|
||||||
self.info = self._parse_info(data)
|
|
||||||
self.headers = self._parse_dict(parts[0])
|
|
||||||
|
|
||||||
def encode(self):
|
|
||||||
"""Encode a generic GNTP Message
|
|
||||||
|
|
||||||
:return string: GNTP Message ready to be sent
|
|
||||||
"""
|
|
||||||
|
|
||||||
buffer = _GNTPBuffer()
|
|
||||||
|
|
||||||
buffer.writefmt(self._format_info())
|
|
||||||
|
|
||||||
#Headers
|
|
||||||
for k, v in self.headers.iteritems():
|
|
||||||
buffer.writefmt('%s: %s', k, v)
|
|
||||||
buffer.writefmt()
|
|
||||||
|
|
||||||
#Resources
|
|
||||||
for resource, data in self.resources.iteritems():
|
|
||||||
buffer.writefmt('Identifier: %s', resource)
|
|
||||||
buffer.writefmt('Length: %d', len(data))
|
|
||||||
buffer.writefmt()
|
|
||||||
buffer.write(data)
|
|
||||||
buffer.writefmt()
|
|
||||||
buffer.writefmt()
|
|
||||||
|
|
||||||
return buffer.getvalue()
|
|
||||||
|
|
||||||
|
|
||||||
class GNTPRegister(_GNTPBase):
|
|
||||||
"""Represents a GNTP Registration Command
|
|
||||||
|
|
||||||
:param string data: (Optional) See decode()
|
|
||||||
:param string password: (Optional) Password to use while encoding/decoding messages
|
|
||||||
"""
|
|
||||||
_requiredHeaders = [
|
|
||||||
'Application-Name',
|
|
||||||
'Notifications-Count'
|
|
||||||
]
|
|
||||||
_requiredNotificationHeaders = ['Notification-Name']
|
|
||||||
|
|
||||||
def __init__(self, data=None, password=None):
|
|
||||||
_GNTPBase.__init__(self, 'REGISTER')
|
|
||||||
self.notifications = []
|
|
||||||
|
|
||||||
if data:
|
|
||||||
self.decode(data, password)
|
|
||||||
else:
|
|
||||||
self.set_password(password)
|
|
||||||
self.add_header('Application-Name', 'pygntp')
|
|
||||||
self.add_header('Notifications-Count', 0)
|
|
||||||
|
|
||||||
def validate(self):
|
|
||||||
'''Validate required headers and validate notification headers'''
|
|
||||||
for header in self._requiredHeaders:
|
|
||||||
if not self.headers.get(header, False):
|
|
||||||
raise ParseError('Missing Registration Header: ' + header)
|
|
||||||
for notice in self.notifications:
|
|
||||||
for header in self._requiredNotificationHeaders:
|
|
||||||
if not notice.get(header, False):
|
|
||||||
raise ParseError('Missing Notification Header: ' + header)
|
|
||||||
|
|
||||||
def decode(self, data, password):
|
|
||||||
"""Decode existing GNTP Registration message
|
|
||||||
|
|
||||||
:param string data: Message to decode
|
|
||||||
"""
|
|
||||||
self.raw = data
|
|
||||||
parts = self.raw.split('\r\n\r\n')
|
|
||||||
self.info = self._parse_info(data)
|
|
||||||
self._validate_password(password)
|
|
||||||
self.headers = self._parse_dict(parts[0])
|
|
||||||
|
|
||||||
for i, part in enumerate(parts):
|
|
||||||
if i == 0:
|
|
||||||
continue # Skip Header
|
|
||||||
if part.strip() == '':
|
|
||||||
continue
|
|
||||||
notice = self._parse_dict(part)
|
|
||||||
if notice.get('Notification-Name', False):
|
|
||||||
self.notifications.append(notice)
|
|
||||||
elif notice.get('Identifier', False):
|
|
||||||
notice['Data'] = self._decode_binary(part, notice)
|
|
||||||
#open('register.png','wblol').write(notice['Data'])
|
|
||||||
self.resources[notice.get('Identifier')] = notice
|
|
||||||
|
|
||||||
def add_notification(self, name, enabled=True):
|
|
||||||
"""Add new Notification to Registration message
|
|
||||||
|
|
||||||
:param string name: Notification Name
|
|
||||||
:param boolean enabled: Enable this notification by default
|
|
||||||
"""
|
|
||||||
notice = {}
|
|
||||||
notice['Notification-Name'] = u'%s' % name
|
|
||||||
notice['Notification-Enabled'] = u'%s' % enabled
|
|
||||||
|
|
||||||
self.notifications.append(notice)
|
|
||||||
self.add_header('Notifications-Count', len(self.notifications))
|
|
||||||
|
|
||||||
def encode(self):
|
|
||||||
"""Encode a GNTP Registration Message
|
|
||||||
|
|
||||||
:return string: Encoded GNTP Registration message
|
|
||||||
"""
|
|
||||||
|
|
||||||
buffer = _GNTPBuffer()
|
|
||||||
|
|
||||||
buffer.writefmt(self._format_info())
|
|
||||||
|
|
||||||
#Headers
|
|
||||||
for k, v in self.headers.iteritems():
|
|
||||||
buffer.writefmt('%s: %s', k, v)
|
|
||||||
buffer.writefmt()
|
|
||||||
|
|
||||||
#Notifications
|
|
||||||
if len(self.notifications) > 0:
|
|
||||||
for notice in self.notifications:
|
|
||||||
for k, v in notice.iteritems():
|
|
||||||
buffer.writefmt('%s: %s', k, v)
|
|
||||||
buffer.writefmt()
|
|
||||||
|
|
||||||
#Resources
|
|
||||||
for resource, data in self.resources.iteritems():
|
|
||||||
buffer.writefmt('Identifier: %s', resource)
|
|
||||||
buffer.writefmt('Length: %d', len(data))
|
|
||||||
buffer.writefmt()
|
|
||||||
buffer.write(data)
|
|
||||||
buffer.writefmt()
|
|
||||||
buffer.writefmt()
|
|
||||||
|
|
||||||
return buffer.getvalue()
|
|
||||||
|
|
||||||
|
|
||||||
class GNTPNotice(_GNTPBase):
|
|
||||||
"""Represents a GNTP Notification Command
|
|
||||||
|
|
||||||
:param string data: (Optional) See decode()
|
|
||||||
:param string app: (Optional) Set Application-Name
|
|
||||||
:param string name: (Optional) Set Notification-Name
|
|
||||||
:param string title: (Optional) Set Notification Title
|
|
||||||
:param string password: (Optional) Password to use while encoding/decoding messages
|
|
||||||
"""
|
|
||||||
_requiredHeaders = [
|
|
||||||
'Application-Name',
|
|
||||||
'Notification-Name',
|
|
||||||
'Notification-Title'
|
|
||||||
]
|
|
||||||
|
|
||||||
def __init__(self, data=None, app=None, name=None, title=None, password=None):
|
|
||||||
_GNTPBase.__init__(self, 'NOTIFY')
|
|
||||||
|
|
||||||
if data:
|
|
||||||
self.decode(data, password)
|
|
||||||
else:
|
|
||||||
self.set_password(password)
|
|
||||||
if app:
|
|
||||||
self.add_header('Application-Name', app)
|
|
||||||
if name:
|
|
||||||
self.add_header('Notification-Name', name)
|
|
||||||
if title:
|
|
||||||
self.add_header('Notification-Title', title)
|
|
||||||
|
|
||||||
def decode(self, data, password):
|
|
||||||
"""Decode existing GNTP Notification message
|
|
||||||
|
|
||||||
:param string data: Message to decode.
|
|
||||||
"""
|
|
||||||
self.raw = data
|
|
||||||
parts = self.raw.split('\r\n\r\n')
|
|
||||||
self.info = self._parse_info(data)
|
|
||||||
self._validate_password(password)
|
|
||||||
self.headers = self._parse_dict(parts[0])
|
|
||||||
|
|
||||||
for i, part in enumerate(parts):
|
|
||||||
if i == 0:
|
|
||||||
continue # Skip Header
|
|
||||||
if part.strip() == '':
|
|
||||||
continue
|
|
||||||
notice = self._parse_dict(part)
|
|
||||||
if notice.get('Identifier', False):
|
|
||||||
notice['Data'] = self._decode_binary(part, notice)
|
|
||||||
#open('notice.png','wblol').write(notice['Data'])
|
|
||||||
self.resources[notice.get('Identifier')] = notice
|
|
||||||
|
|
||||||
|
|
||||||
class GNTPSubscribe(_GNTPBase):
|
|
||||||
"""Represents a GNTP Subscribe Command
|
|
||||||
|
|
||||||
:param string data: (Optional) See decode()
|
|
||||||
:param string password: (Optional) Password to use while encoding/decoding messages
|
|
||||||
"""
|
|
||||||
_requiredHeaders = [
|
|
||||||
'Subscriber-ID',
|
|
||||||
'Subscriber-Name',
|
|
||||||
]
|
|
||||||
|
|
||||||
def __init__(self, data=None, password=None):
|
|
||||||
_GNTPBase.__init__(self, 'SUBSCRIBE')
|
|
||||||
if data:
|
|
||||||
self.decode(data, password)
|
|
||||||
else:
|
|
||||||
self.set_password(password)
|
|
||||||
|
|
||||||
|
|
||||||
class GNTPOK(_GNTPBase):
|
|
||||||
"""Represents a GNTP OK Response
|
|
||||||
|
|
||||||
:param string data: (Optional) See _GNTPResponse.decode()
|
|
||||||
:param string action: (Optional) Set type of action the OK Response is for
|
|
||||||
"""
|
|
||||||
_requiredHeaders = ['Response-Action']
|
|
||||||
|
|
||||||
def __init__(self, data=None, action=None):
|
|
||||||
_GNTPBase.__init__(self, '-OK')
|
|
||||||
if data:
|
|
||||||
self.decode(data)
|
|
||||||
if action:
|
|
||||||
self.add_header('Response-Action', action)
|
|
||||||
|
|
||||||
|
|
||||||
class GNTPError(_GNTPBase):
|
|
||||||
"""Represents a GNTP Error response
|
|
||||||
|
|
||||||
:param string data: (Optional) See _GNTPResponse.decode()
|
|
||||||
:param string errorcode: (Optional) Error code
|
|
||||||
:param string errordesc: (Optional) Error Description
|
|
||||||
"""
|
|
||||||
_requiredHeaders = ['Error-Code', 'Error-Description']
|
|
||||||
|
|
||||||
def __init__(self, data=None, errorcode=None, errordesc=None):
|
|
||||||
_GNTPBase.__init__(self, '-ERROR')
|
|
||||||
if data:
|
|
||||||
self.decode(data)
|
|
||||||
if errorcode:
|
|
||||||
self.add_header('Error-Code', errorcode)
|
|
||||||
self.add_header('Error-Description', errordesc)
|
|
||||||
|
|
||||||
def error(self):
|
|
||||||
return (self.headers.get('Error-Code', None),
|
|
||||||
self.headers.get('Error-Description', None))
|
|
||||||
|
|
||||||
|
|
||||||
def parse_gntp(data, password=None):
|
|
||||||
"""Attempt to parse a message as a GNTP message
|
|
||||||
|
|
||||||
:param string data: Message to be parsed
|
|
||||||
:param string password: Optional password to be used to verify the message
|
|
||||||
"""
|
|
||||||
match = GNTP_INFO_LINE_SHORT.match(data)
|
|
||||||
if not match:
|
|
||||||
raise ParseError('INVALID_GNTP_INFO')
|
|
||||||
info = match.groupdict()
|
|
||||||
if info['messagetype'] == 'REGISTER':
|
|
||||||
return GNTPRegister(data, password=password)
|
|
||||||
elif info['messagetype'] == 'NOTIFY':
|
|
||||||
return GNTPNotice(data, password=password)
|
|
||||||
elif info['messagetype'] == 'SUBSCRIBE':
|
|
||||||
return GNTPSubscribe(data, password=password)
|
|
||||||
elif info['messagetype'] == '-OK':
|
|
||||||
return GNTPOK(data)
|
|
||||||
elif info['messagetype'] == '-ERROR':
|
|
||||||
return GNTPError(data)
|
|
||||||
raise ParseError('INVALID_GNTP_MESSAGE')
|
|
||||||
@@ -0,0 +1,141 @@
|
|||||||
|
# Copyright: 2013 Paul Traylor
|
||||||
|
# These sources are released under the terms of the MIT license: see LICENSE
|
||||||
|
|
||||||
|
import logging
|
||||||
|
import os
|
||||||
|
import sys
|
||||||
|
from optparse import OptionParser, OptionGroup
|
||||||
|
|
||||||
|
from gntp.notifier import GrowlNotifier
|
||||||
|
from gntp.shim import RawConfigParser
|
||||||
|
from gntp.version import __version__
|
||||||
|
|
||||||
|
DEFAULT_CONFIG = os.path.expanduser('~/.gntp')
|
||||||
|
|
||||||
|
config = RawConfigParser({
|
||||||
|
'hostname': 'localhost',
|
||||||
|
'password': None,
|
||||||
|
'port': 23053,
|
||||||
|
})
|
||||||
|
config.read([DEFAULT_CONFIG])
|
||||||
|
if not config.has_section('gntp'):
|
||||||
|
config.add_section('gntp')
|
||||||
|
|
||||||
|
|
||||||
|
class ClientParser(OptionParser):
|
||||||
|
def __init__(self):
|
||||||
|
OptionParser.__init__(self, version="%%prog %s" % __version__)
|
||||||
|
|
||||||
|
group = OptionGroup(self, "Network Options")
|
||||||
|
group.add_option("-H", "--host",
|
||||||
|
dest="host", default=config.get('gntp', 'hostname'),
|
||||||
|
help="Specify a hostname to which to send a remote notification. [%default]")
|
||||||
|
group.add_option("--port",
|
||||||
|
dest="port", default=config.getint('gntp', 'port'), type="int",
|
||||||
|
help="port to listen on [%default]")
|
||||||
|
group.add_option("-P", "--password",
|
||||||
|
dest='password', default=config.get('gntp', 'password'),
|
||||||
|
help="Network password")
|
||||||
|
self.add_option_group(group)
|
||||||
|
|
||||||
|
group = OptionGroup(self, "Notification Options")
|
||||||
|
group.add_option("-n", "--name",
|
||||||
|
dest="app", default='Python GNTP Test Client',
|
||||||
|
help="Set the name of the application [%default]")
|
||||||
|
group.add_option("-s", "--sticky",
|
||||||
|
dest='sticky', default=False, action="store_true",
|
||||||
|
help="Make the notification sticky [%default]")
|
||||||
|
group.add_option("--image",
|
||||||
|
dest="icon", default=None,
|
||||||
|
help="Icon for notification (URL or /path/to/file)")
|
||||||
|
group.add_option("-m", "--message",
|
||||||
|
dest="message", default=None,
|
||||||
|
help="Sets the message instead of using stdin")
|
||||||
|
group.add_option("-p", "--priority",
|
||||||
|
dest="priority", default=0, type="int",
|
||||||
|
help="-2 to 2 [%default]")
|
||||||
|
group.add_option("-d", "--identifier",
|
||||||
|
dest="identifier",
|
||||||
|
help="Identifier for coalescing")
|
||||||
|
group.add_option("-t", "--title",
|
||||||
|
dest="title", default=None,
|
||||||
|
help="Set the title of the notification [%default]")
|
||||||
|
group.add_option("-N", "--notification",
|
||||||
|
dest="name", default='Notification',
|
||||||
|
help="Set the notification name [%default]")
|
||||||
|
group.add_option("--callback",
|
||||||
|
dest="callback",
|
||||||
|
help="URL callback")
|
||||||
|
self.add_option_group(group)
|
||||||
|
|
||||||
|
# Extra Options
|
||||||
|
self.add_option('-v', '--verbose',
|
||||||
|
dest='verbose', default=0, action='count',
|
||||||
|
help="Verbosity levels")
|
||||||
|
|
||||||
|
def parse_args(self, args=None, values=None):
|
||||||
|
values, args = OptionParser.parse_args(self, args, values)
|
||||||
|
|
||||||
|
if values.message is None:
|
||||||
|
print('Enter a message followed by Ctrl-D')
|
||||||
|
try:
|
||||||
|
message = sys.stdin.read()
|
||||||
|
except KeyboardInterrupt:
|
||||||
|
exit()
|
||||||
|
else:
|
||||||
|
message = values.message
|
||||||
|
|
||||||
|
if values.title is None:
|
||||||
|
values.title = ' '.join(args)
|
||||||
|
|
||||||
|
# If we still have an empty title, use the
|
||||||
|
# first bit of the message as the title
|
||||||
|
if values.title == '':
|
||||||
|
values.title = message[:20]
|
||||||
|
|
||||||
|
values.verbose = logging.WARNING - values.verbose * 10
|
||||||
|
|
||||||
|
return values, message
|
||||||
|
|
||||||
|
|
||||||
|
def main():
|
||||||
|
(options, message) = ClientParser().parse_args()
|
||||||
|
logging.basicConfig(level=options.verbose)
|
||||||
|
if not os.path.exists(DEFAULT_CONFIG):
|
||||||
|
logging.info('No config read found at %s', DEFAULT_CONFIG)
|
||||||
|
|
||||||
|
growl = GrowlNotifier(
|
||||||
|
applicationName=options.app,
|
||||||
|
notifications=[options.name],
|
||||||
|
defaultNotifications=[options.name],
|
||||||
|
hostname=options.host,
|
||||||
|
password=options.password,
|
||||||
|
port=options.port,
|
||||||
|
)
|
||||||
|
result = growl.register()
|
||||||
|
if result is not True:
|
||||||
|
exit(result)
|
||||||
|
|
||||||
|
# This would likely be better placed within the growl notifier
|
||||||
|
# class but until I make _checkIcon smarter this is "easier"
|
||||||
|
if options.icon and growl._checkIcon(options.icon) is False:
|
||||||
|
logging.info('Loading image %s', options.icon)
|
||||||
|
f = open(options.icon, 'rb')
|
||||||
|
options.icon = f.read()
|
||||||
|
f.close()
|
||||||
|
|
||||||
|
result = growl.notify(
|
||||||
|
noteType=options.name,
|
||||||
|
title=options.title,
|
||||||
|
description=message,
|
||||||
|
icon=options.icon,
|
||||||
|
sticky=options.sticky,
|
||||||
|
priority=options.priority,
|
||||||
|
callback=options.callback,
|
||||||
|
identifier=options.identifier,
|
||||||
|
)
|
||||||
|
if result is not True:
|
||||||
|
exit(result)
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
main()
|
||||||
@@ -0,0 +1,77 @@
|
|||||||
|
# Copyright: 2013 Paul Traylor
|
||||||
|
# These sources are released under the terms of the MIT license: see LICENSE
|
||||||
|
|
||||||
|
"""
|
||||||
|
The gntp.config module is provided as an extended GrowlNotifier object that takes
|
||||||
|
advantage of the ConfigParser module to allow us to setup some default values
|
||||||
|
(such as hostname, password, and port) in a more global way to be shared among
|
||||||
|
programs using gntp
|
||||||
|
"""
|
||||||
|
import logging
|
||||||
|
import os
|
||||||
|
|
||||||
|
import gntp.notifier
|
||||||
|
import gntp.shim
|
||||||
|
|
||||||
|
__all__ = [
|
||||||
|
'mini',
|
||||||
|
'GrowlNotifier'
|
||||||
|
]
|
||||||
|
|
||||||
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
class GrowlNotifier(gntp.notifier.GrowlNotifier):
|
||||||
|
"""
|
||||||
|
ConfigParser enhanced GrowlNotifier object
|
||||||
|
|
||||||
|
For right now, we are only interested in letting users overide certain
|
||||||
|
values from ~/.gntp
|
||||||
|
|
||||||
|
::
|
||||||
|
|
||||||
|
[gntp]
|
||||||
|
hostname = ?
|
||||||
|
password = ?
|
||||||
|
port = ?
|
||||||
|
"""
|
||||||
|
def __init__(self, *args, **kwargs):
|
||||||
|
config = gntp.shim.RawConfigParser({
|
||||||
|
'hostname': kwargs.get('hostname', 'localhost'),
|
||||||
|
'password': kwargs.get('password'),
|
||||||
|
'port': kwargs.get('port', 23053),
|
||||||
|
})
|
||||||
|
|
||||||
|
config.read([os.path.expanduser('~/.gntp')])
|
||||||
|
|
||||||
|
# If the file does not exist, then there will be no gntp section defined
|
||||||
|
# and the config.get() lines below will get confused. Since we are not
|
||||||
|
# saving the config, it should be safe to just add it here so the
|
||||||
|
# code below doesn't complain
|
||||||
|
if not config.has_section('gntp'):
|
||||||
|
logger.info('Error reading ~/.gntp config file')
|
||||||
|
config.add_section('gntp')
|
||||||
|
|
||||||
|
kwargs['password'] = config.get('gntp', 'password')
|
||||||
|
kwargs['hostname'] = config.get('gntp', 'hostname')
|
||||||
|
kwargs['port'] = config.getint('gntp', 'port')
|
||||||
|
|
||||||
|
super(GrowlNotifier, self).__init__(*args, **kwargs)
|
||||||
|
|
||||||
|
|
||||||
|
def mini(description, **kwargs):
|
||||||
|
"""Single notification function
|
||||||
|
|
||||||
|
Simple notification function in one line. Has only one required parameter
|
||||||
|
and attempts to use reasonable defaults for everything else
|
||||||
|
:param string description: Notification message
|
||||||
|
"""
|
||||||
|
kwargs['notifierFactory'] = GrowlNotifier
|
||||||
|
gntp.notifier.mini(description, **kwargs)
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
# If we're running this module directly we're likely running it as a test
|
||||||
|
# so extra debugging is useful
|
||||||
|
logging.basicConfig(level=logging.INFO)
|
||||||
|
mini('Testing mini notification')
|
||||||
@@ -0,0 +1,518 @@
|
|||||||
|
# Copyright: 2013 Paul Traylor
|
||||||
|
# These sources are released under the terms of the MIT license: see LICENSE
|
||||||
|
|
||||||
|
import hashlib
|
||||||
|
import re
|
||||||
|
import time
|
||||||
|
|
||||||
|
import gntp.shim
|
||||||
|
import gntp.errors as errors
|
||||||
|
|
||||||
|
__all__ = [
|
||||||
|
'GNTPRegister',
|
||||||
|
'GNTPNotice',
|
||||||
|
'GNTPSubscribe',
|
||||||
|
'GNTPOK',
|
||||||
|
'GNTPError',
|
||||||
|
'parse_gntp',
|
||||||
|
]
|
||||||
|
|
||||||
|
#GNTP/<version> <messagetype> <encryptionAlgorithmID>[:<ivValue>][ <keyHashAlgorithmID>:<keyHash>.<salt>]
|
||||||
|
GNTP_INFO_LINE = re.compile(
|
||||||
|
'GNTP/(?P<version>\d+\.\d+) (?P<messagetype>REGISTER|NOTIFY|SUBSCRIBE|\-OK|\-ERROR)' +
|
||||||
|
' (?P<encryptionAlgorithmID>[A-Z0-9]+(:(?P<ivValue>[A-F0-9]+))?) ?' +
|
||||||
|
'((?P<keyHashAlgorithmID>[A-Z0-9]+):(?P<keyHash>[A-F0-9]+).(?P<salt>[A-F0-9]+))?\r\n',
|
||||||
|
re.IGNORECASE
|
||||||
|
)
|
||||||
|
|
||||||
|
GNTP_INFO_LINE_SHORT = re.compile(
|
||||||
|
'GNTP/(?P<version>\d+\.\d+) (?P<messagetype>REGISTER|NOTIFY|SUBSCRIBE|\-OK|\-ERROR)',
|
||||||
|
re.IGNORECASE
|
||||||
|
)
|
||||||
|
|
||||||
|
GNTP_HEADER = re.compile('([\w-]+):(.+)')
|
||||||
|
|
||||||
|
GNTP_EOL = gntp.shim.b('\r\n')
|
||||||
|
GNTP_SEP = gntp.shim.b(': ')
|
||||||
|
|
||||||
|
|
||||||
|
class _GNTPBuffer(gntp.shim.StringIO):
|
||||||
|
"""GNTP Buffer class"""
|
||||||
|
def writeln(self, value=None):
|
||||||
|
if value:
|
||||||
|
self.write(gntp.shim.b(value))
|
||||||
|
self.write(GNTP_EOL)
|
||||||
|
|
||||||
|
def writeheader(self, key, value):
|
||||||
|
if not isinstance(value, str):
|
||||||
|
value = str(value)
|
||||||
|
self.write(gntp.shim.b(key))
|
||||||
|
self.write(GNTP_SEP)
|
||||||
|
self.write(gntp.shim.b(value))
|
||||||
|
self.write(GNTP_EOL)
|
||||||
|
|
||||||
|
|
||||||
|
class _GNTPBase(object):
|
||||||
|
"""Base initilization
|
||||||
|
|
||||||
|
:param string messagetype: GNTP Message type
|
||||||
|
:param string version: GNTP Protocol version
|
||||||
|
:param string encription: Encryption protocol
|
||||||
|
"""
|
||||||
|
def __init__(self, messagetype=None, version='1.0', encryption=None):
|
||||||
|
self.info = {
|
||||||
|
'version': version,
|
||||||
|
'messagetype': messagetype,
|
||||||
|
'encryptionAlgorithmID': encryption
|
||||||
|
}
|
||||||
|
self.hash_algo = {
|
||||||
|
'MD5': hashlib.md5,
|
||||||
|
'SHA1': hashlib.sha1,
|
||||||
|
'SHA256': hashlib.sha256,
|
||||||
|
'SHA512': hashlib.sha512,
|
||||||
|
}
|
||||||
|
self.headers = {}
|
||||||
|
self.resources = {}
|
||||||
|
|
||||||
|
# For Python2 we can just return the bytes as is without worry
|
||||||
|
# but on Python3 we want to make sure we return the packet as
|
||||||
|
# a unicode string so that things like logging won't get confused
|
||||||
|
if gntp.shim.PY2:
|
||||||
|
def __str__(self):
|
||||||
|
return self.encode()
|
||||||
|
else:
|
||||||
|
def __str__(self):
|
||||||
|
return gntp.shim.u(self.encode())
|
||||||
|
|
||||||
|
def _parse_info(self, data):
|
||||||
|
"""Parse the first line of a GNTP message to get security and other info values
|
||||||
|
|
||||||
|
:param string data: GNTP Message
|
||||||
|
:return dict: Parsed GNTP Info line
|
||||||
|
"""
|
||||||
|
|
||||||
|
match = GNTP_INFO_LINE.match(data)
|
||||||
|
|
||||||
|
if not match:
|
||||||
|
raise errors.ParseError('ERROR_PARSING_INFO_LINE')
|
||||||
|
|
||||||
|
info = match.groupdict()
|
||||||
|
if info['encryptionAlgorithmID'] == 'NONE':
|
||||||
|
info['encryptionAlgorithmID'] = None
|
||||||
|
|
||||||
|
return info
|
||||||
|
|
||||||
|
def set_password(self, password, encryptAlgo='MD5'):
|
||||||
|
"""Set a password for a GNTP Message
|
||||||
|
|
||||||
|
:param string password: Null to clear password
|
||||||
|
:param string encryptAlgo: Supports MD5, SHA1, SHA256, SHA512
|
||||||
|
"""
|
||||||
|
if not password:
|
||||||
|
self.info['encryptionAlgorithmID'] = None
|
||||||
|
self.info['keyHashAlgorithm'] = None
|
||||||
|
return
|
||||||
|
|
||||||
|
self.password = gntp.shim.b(password)
|
||||||
|
self.encryptAlgo = encryptAlgo.upper()
|
||||||
|
|
||||||
|
if not self.encryptAlgo in self.hash_algo:
|
||||||
|
raise errors.UnsupportedError('INVALID HASH "%s"' % self.encryptAlgo)
|
||||||
|
|
||||||
|
hashfunction = self.hash_algo.get(self.encryptAlgo)
|
||||||
|
|
||||||
|
password = password.encode('utf8')
|
||||||
|
seed = time.ctime().encode('utf8')
|
||||||
|
salt = hashfunction(seed).hexdigest()
|
||||||
|
saltHash = hashfunction(seed).digest()
|
||||||
|
keyBasis = password + saltHash
|
||||||
|
key = hashfunction(keyBasis).digest()
|
||||||
|
keyHash = hashfunction(key).hexdigest()
|
||||||
|
|
||||||
|
self.info['keyHashAlgorithmID'] = self.encryptAlgo
|
||||||
|
self.info['keyHash'] = keyHash.upper()
|
||||||
|
self.info['salt'] = salt.upper()
|
||||||
|
|
||||||
|
def _decode_hex(self, value):
|
||||||
|
"""Helper function to decode hex string to `proper` hex string
|
||||||
|
|
||||||
|
:param string value: Human readable hex string
|
||||||
|
:return string: Hex string
|
||||||
|
"""
|
||||||
|
result = ''
|
||||||
|
for i in range(0, len(value), 2):
|
||||||
|
tmp = int(value[i:i + 2], 16)
|
||||||
|
result += chr(tmp)
|
||||||
|
return result
|
||||||
|
|
||||||
|
def _decode_binary(self, rawIdentifier, identifier):
|
||||||
|
rawIdentifier += '\r\n\r\n'
|
||||||
|
dataLength = int(identifier['Length'])
|
||||||
|
pointerStart = self.raw.find(rawIdentifier) + len(rawIdentifier)
|
||||||
|
pointerEnd = pointerStart + dataLength
|
||||||
|
data = self.raw[pointerStart:pointerEnd]
|
||||||
|
if not len(data) == dataLength:
|
||||||
|
raise errors.ParseError('INVALID_DATA_LENGTH Expected: %s Recieved %s' % (dataLength, len(data)))
|
||||||
|
return data
|
||||||
|
|
||||||
|
def _validate_password(self, password):
|
||||||
|
"""Validate GNTP Message against stored password"""
|
||||||
|
self.password = password
|
||||||
|
if password is None:
|
||||||
|
raise errors.AuthError('Missing password')
|
||||||
|
keyHash = self.info.get('keyHash', None)
|
||||||
|
if keyHash is None and self.password is None:
|
||||||
|
return True
|
||||||
|
if keyHash is None:
|
||||||
|
raise errors.AuthError('Invalid keyHash')
|
||||||
|
if self.password is None:
|
||||||
|
raise errors.AuthError('Missing password')
|
||||||
|
|
||||||
|
keyHashAlgorithmID = self.info.get('keyHashAlgorithmID','MD5')
|
||||||
|
|
||||||
|
password = self.password.encode('utf8')
|
||||||
|
saltHash = self._decode_hex(self.info['salt'])
|
||||||
|
|
||||||
|
keyBasis = password + saltHash
|
||||||
|
self.key = self.hash_algo[keyHashAlgorithmID](keyBasis).digest()
|
||||||
|
keyHash = self.hash_algo[keyHashAlgorithmID](self.key).hexdigest()
|
||||||
|
|
||||||
|
if not keyHash.upper() == self.info['keyHash'].upper():
|
||||||
|
raise errors.AuthError('Invalid Hash')
|
||||||
|
return True
|
||||||
|
|
||||||
|
def validate(self):
|
||||||
|
"""Verify required headers"""
|
||||||
|
for header in self._requiredHeaders:
|
||||||
|
if not self.headers.get(header, False):
|
||||||
|
raise errors.ParseError('Missing Notification Header: ' + header)
|
||||||
|
|
||||||
|
def _format_info(self):
|
||||||
|
"""Generate info line for GNTP Message
|
||||||
|
|
||||||
|
:return string:
|
||||||
|
"""
|
||||||
|
info = 'GNTP/%s %s' % (
|
||||||
|
self.info.get('version'),
|
||||||
|
self.info.get('messagetype'),
|
||||||
|
)
|
||||||
|
if self.info.get('encryptionAlgorithmID', None):
|
||||||
|
info += ' %s:%s' % (
|
||||||
|
self.info.get('encryptionAlgorithmID'),
|
||||||
|
self.info.get('ivValue'),
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
info += ' NONE'
|
||||||
|
|
||||||
|
if self.info.get('keyHashAlgorithmID', None):
|
||||||
|
info += ' %s:%s.%s' % (
|
||||||
|
self.info.get('keyHashAlgorithmID'),
|
||||||
|
self.info.get('keyHash'),
|
||||||
|
self.info.get('salt')
|
||||||
|
)
|
||||||
|
|
||||||
|
return info
|
||||||
|
|
||||||
|
def _parse_dict(self, data):
|
||||||
|
"""Helper function to parse blocks of GNTP headers into a dictionary
|
||||||
|
|
||||||
|
:param string data:
|
||||||
|
:return dict: Dictionary of parsed GNTP Headers
|
||||||
|
"""
|
||||||
|
d = {}
|
||||||
|
for line in data.split('\r\n'):
|
||||||
|
match = GNTP_HEADER.match(line)
|
||||||
|
if not match:
|
||||||
|
continue
|
||||||
|
|
||||||
|
key = match.group(1).strip()
|
||||||
|
val = match.group(2).strip()
|
||||||
|
d[key] = val
|
||||||
|
return d
|
||||||
|
|
||||||
|
def add_header(self, key, value):
|
||||||
|
self.headers[key] = value
|
||||||
|
|
||||||
|
def add_resource(self, data):
|
||||||
|
"""Add binary resource
|
||||||
|
|
||||||
|
:param string data: Binary Data
|
||||||
|
"""
|
||||||
|
data = gntp.shim.b(data)
|
||||||
|
identifier = hashlib.md5(data).hexdigest()
|
||||||
|
self.resources[identifier] = data
|
||||||
|
return 'x-growl-resource://%s' % identifier
|
||||||
|
|
||||||
|
def decode(self, data, password=None):
|
||||||
|
"""Decode GNTP Message
|
||||||
|
|
||||||
|
:param string data:
|
||||||
|
"""
|
||||||
|
self.password = password
|
||||||
|
self.raw = gntp.shim.u(data)
|
||||||
|
parts = self.raw.split('\r\n\r\n')
|
||||||
|
self.info = self._parse_info(self.raw)
|
||||||
|
self.headers = self._parse_dict(parts[0])
|
||||||
|
|
||||||
|
def encode(self):
|
||||||
|
"""Encode a generic GNTP Message
|
||||||
|
|
||||||
|
:return string: GNTP Message ready to be sent. Returned as a byte string
|
||||||
|
"""
|
||||||
|
|
||||||
|
buff = _GNTPBuffer()
|
||||||
|
|
||||||
|
buff.writeln(self._format_info())
|
||||||
|
|
||||||
|
#Headers
|
||||||
|
for k, v in self.headers.items():
|
||||||
|
buff.writeheader(k, v)
|
||||||
|
buff.writeln()
|
||||||
|
|
||||||
|
#Resources
|
||||||
|
for resource, data in self.resources.items():
|
||||||
|
buff.writeheader('Identifier', resource)
|
||||||
|
buff.writeheader('Length', len(data))
|
||||||
|
buff.writeln()
|
||||||
|
buff.write(data)
|
||||||
|
buff.writeln()
|
||||||
|
buff.writeln()
|
||||||
|
|
||||||
|
return buff.getvalue()
|
||||||
|
|
||||||
|
|
||||||
|
class GNTPRegister(_GNTPBase):
|
||||||
|
"""Represents a GNTP Registration Command
|
||||||
|
|
||||||
|
:param string data: (Optional) See decode()
|
||||||
|
:param string password: (Optional) Password to use while encoding/decoding messages
|
||||||
|
"""
|
||||||
|
_requiredHeaders = [
|
||||||
|
'Application-Name',
|
||||||
|
'Notifications-Count'
|
||||||
|
]
|
||||||
|
_requiredNotificationHeaders = ['Notification-Name']
|
||||||
|
|
||||||
|
def __init__(self, data=None, password=None):
|
||||||
|
_GNTPBase.__init__(self, 'REGISTER')
|
||||||
|
self.notifications = []
|
||||||
|
|
||||||
|
if data:
|
||||||
|
self.decode(data, password)
|
||||||
|
else:
|
||||||
|
self.set_password(password)
|
||||||
|
self.add_header('Application-Name', 'pygntp')
|
||||||
|
self.add_header('Notifications-Count', 0)
|
||||||
|
|
||||||
|
def validate(self):
|
||||||
|
'''Validate required headers and validate notification headers'''
|
||||||
|
for header in self._requiredHeaders:
|
||||||
|
if not self.headers.get(header, False):
|
||||||
|
raise errors.ParseError('Missing Registration Header: ' + header)
|
||||||
|
for notice in self.notifications:
|
||||||
|
for header in self._requiredNotificationHeaders:
|
||||||
|
if not notice.get(header, False):
|
||||||
|
raise errors.ParseError('Missing Notification Header: ' + header)
|
||||||
|
|
||||||
|
def decode(self, data, password):
|
||||||
|
"""Decode existing GNTP Registration message
|
||||||
|
|
||||||
|
:param string data: Message to decode
|
||||||
|
"""
|
||||||
|
self.raw = gntp.shim.u(data)
|
||||||
|
parts = self.raw.split('\r\n\r\n')
|
||||||
|
self.info = self._parse_info(self.raw)
|
||||||
|
self._validate_password(password)
|
||||||
|
self.headers = self._parse_dict(parts[0])
|
||||||
|
|
||||||
|
for i, part in enumerate(parts):
|
||||||
|
if i == 0:
|
||||||
|
continue # Skip Header
|
||||||
|
if part.strip() == '':
|
||||||
|
continue
|
||||||
|
notice = self._parse_dict(part)
|
||||||
|
if notice.get('Notification-Name', False):
|
||||||
|
self.notifications.append(notice)
|
||||||
|
elif notice.get('Identifier', False):
|
||||||
|
notice['Data'] = self._decode_binary(part, notice)
|
||||||
|
#open('register.png','wblol').write(notice['Data'])
|
||||||
|
self.resources[notice.get('Identifier')] = notice
|
||||||
|
|
||||||
|
def add_notification(self, name, enabled=True):
|
||||||
|
"""Add new Notification to Registration message
|
||||||
|
|
||||||
|
:param string name: Notification Name
|
||||||
|
:param boolean enabled: Enable this notification by default
|
||||||
|
"""
|
||||||
|
notice = {}
|
||||||
|
notice['Notification-Name'] = name
|
||||||
|
notice['Notification-Enabled'] = enabled
|
||||||
|
|
||||||
|
self.notifications.append(notice)
|
||||||
|
self.add_header('Notifications-Count', len(self.notifications))
|
||||||
|
|
||||||
|
def encode(self):
|
||||||
|
"""Encode a GNTP Registration Message
|
||||||
|
|
||||||
|
:return string: Encoded GNTP Registration message. Returned as a byte string
|
||||||
|
"""
|
||||||
|
|
||||||
|
buff = _GNTPBuffer()
|
||||||
|
|
||||||
|
buff.writeln(self._format_info())
|
||||||
|
|
||||||
|
#Headers
|
||||||
|
for k, v in self.headers.items():
|
||||||
|
buff.writeheader(k, v)
|
||||||
|
buff.writeln()
|
||||||
|
|
||||||
|
#Notifications
|
||||||
|
if len(self.notifications) > 0:
|
||||||
|
for notice in self.notifications:
|
||||||
|
for k, v in notice.items():
|
||||||
|
buff.writeheader(k, v)
|
||||||
|
buff.writeln()
|
||||||
|
|
||||||
|
#Resources
|
||||||
|
for resource, data in self.resources.items():
|
||||||
|
buff.writeheader('Identifier', resource)
|
||||||
|
buff.writeheader('Length', len(data))
|
||||||
|
buff.writeln()
|
||||||
|
buff.write(data)
|
||||||
|
buff.writeln()
|
||||||
|
buff.writeln()
|
||||||
|
|
||||||
|
return buff.getvalue()
|
||||||
|
|
||||||
|
|
||||||
|
class GNTPNotice(_GNTPBase):
|
||||||
|
"""Represents a GNTP Notification Command
|
||||||
|
|
||||||
|
:param string data: (Optional) See decode()
|
||||||
|
:param string app: (Optional) Set Application-Name
|
||||||
|
:param string name: (Optional) Set Notification-Name
|
||||||
|
:param string title: (Optional) Set Notification Title
|
||||||
|
:param string password: (Optional) Password to use while encoding/decoding messages
|
||||||
|
"""
|
||||||
|
_requiredHeaders = [
|
||||||
|
'Application-Name',
|
||||||
|
'Notification-Name',
|
||||||
|
'Notification-Title'
|
||||||
|
]
|
||||||
|
|
||||||
|
def __init__(self, data=None, app=None, name=None, title=None, password=None):
|
||||||
|
_GNTPBase.__init__(self, 'NOTIFY')
|
||||||
|
|
||||||
|
if data:
|
||||||
|
self.decode(data, password)
|
||||||
|
else:
|
||||||
|
self.set_password(password)
|
||||||
|
if app:
|
||||||
|
self.add_header('Application-Name', app)
|
||||||
|
if name:
|
||||||
|
self.add_header('Notification-Name', name)
|
||||||
|
if title:
|
||||||
|
self.add_header('Notification-Title', title)
|
||||||
|
|
||||||
|
def decode(self, data, password):
|
||||||
|
"""Decode existing GNTP Notification message
|
||||||
|
|
||||||
|
:param string data: Message to decode.
|
||||||
|
"""
|
||||||
|
self.raw = gntp.shim.u(data)
|
||||||
|
parts = self.raw.split('\r\n\r\n')
|
||||||
|
self.info = self._parse_info(self.raw)
|
||||||
|
self._validate_password(password)
|
||||||
|
self.headers = self._parse_dict(parts[0])
|
||||||
|
|
||||||
|
for i, part in enumerate(parts):
|
||||||
|
if i == 0:
|
||||||
|
continue # Skip Header
|
||||||
|
if part.strip() == '':
|
||||||
|
continue
|
||||||
|
notice = self._parse_dict(part)
|
||||||
|
if notice.get('Identifier', False):
|
||||||
|
notice['Data'] = self._decode_binary(part, notice)
|
||||||
|
#open('notice.png','wblol').write(notice['Data'])
|
||||||
|
self.resources[notice.get('Identifier')] = notice
|
||||||
|
|
||||||
|
|
||||||
|
class GNTPSubscribe(_GNTPBase):
|
||||||
|
"""Represents a GNTP Subscribe Command
|
||||||
|
|
||||||
|
:param string data: (Optional) See decode()
|
||||||
|
:param string password: (Optional) Password to use while encoding/decoding messages
|
||||||
|
"""
|
||||||
|
_requiredHeaders = [
|
||||||
|
'Subscriber-ID',
|
||||||
|
'Subscriber-Name',
|
||||||
|
]
|
||||||
|
|
||||||
|
def __init__(self, data=None, password=None):
|
||||||
|
_GNTPBase.__init__(self, 'SUBSCRIBE')
|
||||||
|
if data:
|
||||||
|
self.decode(data, password)
|
||||||
|
else:
|
||||||
|
self.set_password(password)
|
||||||
|
|
||||||
|
|
||||||
|
class GNTPOK(_GNTPBase):
|
||||||
|
"""Represents a GNTP OK Response
|
||||||
|
|
||||||
|
:param string data: (Optional) See _GNTPResponse.decode()
|
||||||
|
:param string action: (Optional) Set type of action the OK Response is for
|
||||||
|
"""
|
||||||
|
_requiredHeaders = ['Response-Action']
|
||||||
|
|
||||||
|
def __init__(self, data=None, action=None):
|
||||||
|
_GNTPBase.__init__(self, '-OK')
|
||||||
|
if data:
|
||||||
|
self.decode(data)
|
||||||
|
if action:
|
||||||
|
self.add_header('Response-Action', action)
|
||||||
|
|
||||||
|
|
||||||
|
class GNTPError(_GNTPBase):
|
||||||
|
"""Represents a GNTP Error response
|
||||||
|
|
||||||
|
:param string data: (Optional) See _GNTPResponse.decode()
|
||||||
|
:param string errorcode: (Optional) Error code
|
||||||
|
:param string errordesc: (Optional) Error Description
|
||||||
|
"""
|
||||||
|
_requiredHeaders = ['Error-Code', 'Error-Description']
|
||||||
|
|
||||||
|
def __init__(self, data=None, errorcode=None, errordesc=None):
|
||||||
|
_GNTPBase.__init__(self, '-ERROR')
|
||||||
|
if data:
|
||||||
|
self.decode(data)
|
||||||
|
if errorcode:
|
||||||
|
self.add_header('Error-Code', errorcode)
|
||||||
|
self.add_header('Error-Description', errordesc)
|
||||||
|
|
||||||
|
def error(self):
|
||||||
|
return (self.headers.get('Error-Code', None),
|
||||||
|
self.headers.get('Error-Description', None))
|
||||||
|
|
||||||
|
|
||||||
|
def parse_gntp(data, password=None):
|
||||||
|
"""Attempt to parse a message as a GNTP message
|
||||||
|
|
||||||
|
:param string data: Message to be parsed
|
||||||
|
:param string password: Optional password to be used to verify the message
|
||||||
|
"""
|
||||||
|
data = gntp.shim.u(data)
|
||||||
|
match = GNTP_INFO_LINE_SHORT.match(data)
|
||||||
|
if not match:
|
||||||
|
raise errors.ParseError('INVALID_GNTP_INFO')
|
||||||
|
info = match.groupdict()
|
||||||
|
if info['messagetype'] == 'REGISTER':
|
||||||
|
return GNTPRegister(data, password=password)
|
||||||
|
elif info['messagetype'] == 'NOTIFY':
|
||||||
|
return GNTPNotice(data, password=password)
|
||||||
|
elif info['messagetype'] == 'SUBSCRIBE':
|
||||||
|
return GNTPSubscribe(data, password=password)
|
||||||
|
elif info['messagetype'] == '-OK':
|
||||||
|
return GNTPOK(data)
|
||||||
|
elif info['messagetype'] == '-ERROR':
|
||||||
|
return GNTPError(data)
|
||||||
|
raise errors.ParseError('INVALID_GNTP_MESSAGE')
|
||||||
@@ -0,0 +1,25 @@
|
|||||||
|
# Copyright: 2013 Paul Traylor
|
||||||
|
# These sources are released under the terms of the MIT license: see LICENSE
|
||||||
|
|
||||||
|
class BaseError(Exception):
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
class ParseError(BaseError):
|
||||||
|
errorcode = 500
|
||||||
|
errordesc = 'Error parsing the message'
|
||||||
|
|
||||||
|
|
||||||
|
class AuthError(BaseError):
|
||||||
|
errorcode = 400
|
||||||
|
errordesc = 'Error with authorization'
|
||||||
|
|
||||||
|
|
||||||
|
class UnsupportedError(BaseError):
|
||||||
|
errorcode = 500
|
||||||
|
errordesc = 'Currently unsupported by gntp.py'
|
||||||
|
|
||||||
|
|
||||||
|
class NetworkError(BaseError):
|
||||||
|
errorcode = 500
|
||||||
|
errordesc = "Error connecting to growl server"
|
||||||
@@ -1,3 +1,6 @@
|
|||||||
|
# Copyright: 2013 Paul Traylor
|
||||||
|
# These sources are released under the terms of the MIT license: see LICENSE
|
||||||
|
|
||||||
"""
|
"""
|
||||||
The gntp.notifier module is provided as a simple way to send notifications
|
The gntp.notifier module is provided as a simple way to send notifications
|
||||||
using GNTP
|
using GNTP
|
||||||
@@ -9,10 +12,15 @@ using GNTP
|
|||||||
`Original Python bindings <http://code.google.com/p/growl/source/browse/Bindings/python/Growl.py>`_
|
`Original Python bindings <http://code.google.com/p/growl/source/browse/Bindings/python/Growl.py>`_
|
||||||
|
|
||||||
"""
|
"""
|
||||||
import gntp
|
|
||||||
import socket
|
|
||||||
import logging
|
import logging
|
||||||
import platform
|
import platform
|
||||||
|
import socket
|
||||||
|
import sys
|
||||||
|
|
||||||
|
from gntp.version import __version__
|
||||||
|
import gntp.core
|
||||||
|
import gntp.errors as errors
|
||||||
|
import gntp.shim
|
||||||
|
|
||||||
__all__ = [
|
__all__ = [
|
||||||
'mini',
|
'mini',
|
||||||
@@ -22,45 +30,6 @@ __all__ = [
|
|||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
def mini(description, applicationName='PythonMini', noteType="Message",
|
|
||||||
title="Mini Message", applicationIcon=None, hostname='localhost',
|
|
||||||
password=None, port=23053, sticky=False, priority=None,
|
|
||||||
callback=None, notificationIcon=None, identifier=None):
|
|
||||||
"""Single notification function
|
|
||||||
|
|
||||||
Simple notification function in one line. Has only one required parameter
|
|
||||||
and attempts to use reasonable defaults for everything else
|
|
||||||
:param string description: Notification message
|
|
||||||
|
|
||||||
.. warning::
|
|
||||||
For now, only URL callbacks are supported. In the future, the
|
|
||||||
callback argument will also support a function
|
|
||||||
"""
|
|
||||||
growl = GrowlNotifier(
|
|
||||||
applicationName=applicationName,
|
|
||||||
notifications=[noteType],
|
|
||||||
defaultNotifications=[noteType],
|
|
||||||
applicationIcon=applicationIcon,
|
|
||||||
hostname=hostname,
|
|
||||||
password=password,
|
|
||||||
port=port,
|
|
||||||
)
|
|
||||||
result = growl.register()
|
|
||||||
if result is not True:
|
|
||||||
return result
|
|
||||||
|
|
||||||
return growl.notify(
|
|
||||||
noteType=noteType,
|
|
||||||
title=title,
|
|
||||||
description=description,
|
|
||||||
icon=notificationIcon,
|
|
||||||
sticky=sticky,
|
|
||||||
priority=priority,
|
|
||||||
callback=callback,
|
|
||||||
identifier=identifier,
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
class GrowlNotifier(object):
|
class GrowlNotifier(object):
|
||||||
"""Helper class to simplfy sending Growl messages
|
"""Helper class to simplfy sending Growl messages
|
||||||
|
|
||||||
@@ -99,8 +68,9 @@ class GrowlNotifier(object):
|
|||||||
If it's a simple URL icon, then we return True. If it's a data icon
|
If it's a simple URL icon, then we return True. If it's a data icon
|
||||||
then we return False
|
then we return False
|
||||||
'''
|
'''
|
||||||
logger.debug('Checking icon')
|
logger.info('Checking icon')
|
||||||
return data.startswith('http')
|
|
||||||
|
return gntp.shim.u(data)[:4] in ['http', 'file']
|
||||||
|
|
||||||
def register(self):
|
def register(self):
|
||||||
"""Send GNTP Registration
|
"""Send GNTP Registration
|
||||||
@@ -109,8 +79,8 @@ class GrowlNotifier(object):
|
|||||||
Before sending notifications to Growl, you need to have
|
Before sending notifications to Growl, you need to have
|
||||||
sent a registration message at least once
|
sent a registration message at least once
|
||||||
"""
|
"""
|
||||||
logger.debug('Sending registration to %s:%s', self.hostname, self.port)
|
logger.info('Sending registration to %s:%s', self.hostname, self.port)
|
||||||
register = gntp.GNTPRegister()
|
register = gntp.core.GNTPRegister()
|
||||||
register.add_header('Application-Name', self.applicationName)
|
register.add_header('Application-Name', self.applicationName)
|
||||||
for notification in self.notifications:
|
for notification in self.notifications:
|
||||||
enabled = notification in self.defaultNotifications
|
enabled = notification in self.defaultNotifications
|
||||||
@@ -119,8 +89,8 @@ class GrowlNotifier(object):
|
|||||||
if self._checkIcon(self.applicationIcon):
|
if self._checkIcon(self.applicationIcon):
|
||||||
register.add_header('Application-Icon', self.applicationIcon)
|
register.add_header('Application-Icon', self.applicationIcon)
|
||||||
else:
|
else:
|
||||||
id = register.add_resource(self.applicationIcon)
|
resource = register.add_resource(self.applicationIcon)
|
||||||
register.add_header('Application-Icon', id)
|
register.add_header('Application-Icon', resource)
|
||||||
if self.password:
|
if self.password:
|
||||||
register.set_password(self.password, self.passwordHash)
|
register.set_password(self.password, self.passwordHash)
|
||||||
self.add_origin_info(register)
|
self.add_origin_info(register)
|
||||||
@@ -128,7 +98,7 @@ class GrowlNotifier(object):
|
|||||||
return self._send('register', register)
|
return self._send('register', register)
|
||||||
|
|
||||||
def notify(self, noteType, title, description, icon=None, sticky=False,
|
def notify(self, noteType, title, description, icon=None, sticky=False,
|
||||||
priority=None, callback=None, identifier=None):
|
priority=None, callback=None, identifier=None, custom={}):
|
||||||
"""Send a GNTP notifications
|
"""Send a GNTP notifications
|
||||||
|
|
||||||
.. warning::
|
.. warning::
|
||||||
@@ -141,14 +111,16 @@ class GrowlNotifier(object):
|
|||||||
:param boolean sticky: Sticky notification
|
:param boolean sticky: Sticky notification
|
||||||
:param integer priority: Message priority level from -2 to 2
|
:param integer priority: Message priority level from -2 to 2
|
||||||
:param string callback: URL callback
|
:param string callback: URL callback
|
||||||
|
:param dict custom: Custom attributes. Key names should be prefixed with X-
|
||||||
|
according to the spec but this is not enforced by this class
|
||||||
|
|
||||||
.. warning::
|
.. warning::
|
||||||
For now, only URL callbacks are supported. In the future, the
|
For now, only URL callbacks are supported. In the future, the
|
||||||
callback argument will also support a function
|
callback argument will also support a function
|
||||||
"""
|
"""
|
||||||
logger.debug('Sending notification [%s] to %s:%s', noteType, self.hostname, self.port)
|
logger.info('Sending notification [%s] to %s:%s', noteType, self.hostname, self.port)
|
||||||
assert noteType in self.notifications
|
assert noteType in self.notifications
|
||||||
notice = gntp.GNTPNotice()
|
notice = gntp.core.GNTPNotice()
|
||||||
notice.add_header('Application-Name', self.applicationName)
|
notice.add_header('Application-Name', self.applicationName)
|
||||||
notice.add_header('Notification-Name', noteType)
|
notice.add_header('Notification-Name', noteType)
|
||||||
notice.add_header('Notification-Title', title)
|
notice.add_header('Notification-Title', title)
|
||||||
@@ -162,8 +134,8 @@ class GrowlNotifier(object):
|
|||||||
if self._checkIcon(icon):
|
if self._checkIcon(icon):
|
||||||
notice.add_header('Notification-Icon', icon)
|
notice.add_header('Notification-Icon', icon)
|
||||||
else:
|
else:
|
||||||
id = notice.add_resource(icon)
|
resource = notice.add_resource(icon)
|
||||||
notice.add_header('Notification-Icon', id)
|
notice.add_header('Notification-Icon', resource)
|
||||||
|
|
||||||
if description:
|
if description:
|
||||||
notice.add_header('Notification-Text', description)
|
notice.add_header('Notification-Text', description)
|
||||||
@@ -172,6 +144,9 @@ class GrowlNotifier(object):
|
|||||||
if identifier:
|
if identifier:
|
||||||
notice.add_header('Notification-Coalescing-ID', identifier)
|
notice.add_header('Notification-Coalescing-ID', identifier)
|
||||||
|
|
||||||
|
for key in custom:
|
||||||
|
notice.add_header(key, custom[key])
|
||||||
|
|
||||||
self.add_origin_info(notice)
|
self.add_origin_info(notice)
|
||||||
self.notify_hook(notice)
|
self.notify_hook(notice)
|
||||||
|
|
||||||
@@ -179,7 +154,7 @@ class GrowlNotifier(object):
|
|||||||
|
|
||||||
def subscribe(self, id, name, port):
|
def subscribe(self, id, name, port):
|
||||||
"""Send a Subscribe request to a remote machine"""
|
"""Send a Subscribe request to a remote machine"""
|
||||||
sub = gntp.GNTPSubscribe()
|
sub = gntp.core.GNTPSubscribe()
|
||||||
sub.add_header('Subscriber-ID', id)
|
sub.add_header('Subscriber-ID', id)
|
||||||
sub.add_header('Subscriber-Name', name)
|
sub.add_header('Subscriber-Name', name)
|
||||||
sub.add_header('Subscriber-Port', port)
|
sub.add_header('Subscriber-Port', port)
|
||||||
@@ -195,7 +170,7 @@ class GrowlNotifier(object):
|
|||||||
"""Add optional Origin headers to message"""
|
"""Add optional Origin headers to message"""
|
||||||
packet.add_header('Origin-Machine-Name', platform.node())
|
packet.add_header('Origin-Machine-Name', platform.node())
|
||||||
packet.add_header('Origin-Software-Name', 'gntp.py')
|
packet.add_header('Origin-Software-Name', 'gntp.py')
|
||||||
packet.add_header('Origin-Software-Version', gntp.__version__)
|
packet.add_header('Origin-Software-Version', __version__)
|
||||||
packet.add_header('Origin-Platform-Name', platform.system())
|
packet.add_header('Origin-Platform-Name', platform.system())
|
||||||
packet.add_header('Origin-Platform-Version', platform.platform())
|
packet.add_header('Origin-Platform-Version', platform.platform())
|
||||||
|
|
||||||
@@ -214,34 +189,78 @@ class GrowlNotifier(object):
|
|||||||
packet.validate()
|
packet.validate()
|
||||||
data = packet.encode()
|
data = packet.encode()
|
||||||
|
|
||||||
#logger.debug('To : %s:%s <%s>\n%s', self.hostname, self.port, packet.__class__, data)
|
logger.debug('To : %s:%s <%s>\n%s', self.hostname, self.port, packet.__class__, data)
|
||||||
#Less verbose
|
|
||||||
logger.debug('To : %s:%s <%s>', self.hostname, self.port, packet.__class__)
|
|
||||||
|
|
||||||
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
|
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
|
||||||
s.settimeout(self.socketTimeout)
|
s.settimeout(self.socketTimeout)
|
||||||
s.connect((self.hostname, self.port))
|
try:
|
||||||
s.send(data)
|
s.connect((self.hostname, self.port))
|
||||||
recv_data = s.recv(1024)
|
s.send(data)
|
||||||
while not recv_data.endswith("\r\n\r\n"):
|
recv_data = s.recv(1024)
|
||||||
recv_data += s.recv(1024)
|
while not recv_data.endswith(gntp.shim.b("\r\n\r\n")):
|
||||||
response = gntp.parse_gntp(recv_data)
|
recv_data += s.recv(1024)
|
||||||
|
except socket.error:
|
||||||
|
# Python2.5 and Python3 compatibile exception
|
||||||
|
exc = sys.exc_info()[1]
|
||||||
|
raise errors.NetworkError(exc)
|
||||||
|
|
||||||
|
response = gntp.core.parse_gntp(recv_data)
|
||||||
s.close()
|
s.close()
|
||||||
|
|
||||||
#logger.debug('From : %s:%s <%s>\n%s', self.hostname, self.port, response.__class__, response)
|
logger.debug('From : %s:%s <%s>\n%s', self.hostname, self.port, response.__class__, response)
|
||||||
#Less verbose
|
|
||||||
logger.debug('From : %s:%s <%s>', self.hostname, self.port, response.__class__)
|
|
||||||
|
|
||||||
if type(response) == gntp.GNTPOK:
|
if type(response) == gntp.core.GNTPOK:
|
||||||
return True
|
|
||||||
if response.error()[0] == '404' and 'disabled' in response.error()[1]:
|
|
||||||
# Ignore message saying that user has disabled this class
|
|
||||||
return True
|
return True
|
||||||
logger.error('Invalid response: %s', response.error())
|
logger.error('Invalid response: %s', response.error())
|
||||||
return response.error()
|
return response.error()
|
||||||
|
|
||||||
|
|
||||||
|
def mini(description, applicationName='PythonMini', noteType="Message",
|
||||||
|
title="Mini Message", applicationIcon=None, hostname='localhost',
|
||||||
|
password=None, port=23053, sticky=False, priority=None,
|
||||||
|
callback=None, notificationIcon=None, identifier=None,
|
||||||
|
notifierFactory=GrowlNotifier):
|
||||||
|
"""Single notification function
|
||||||
|
|
||||||
|
Simple notification function in one line. Has only one required parameter
|
||||||
|
and attempts to use reasonable defaults for everything else
|
||||||
|
:param string description: Notification message
|
||||||
|
|
||||||
|
.. warning::
|
||||||
|
For now, only URL callbacks are supported. In the future, the
|
||||||
|
callback argument will also support a function
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
growl = notifierFactory(
|
||||||
|
applicationName=applicationName,
|
||||||
|
notifications=[noteType],
|
||||||
|
defaultNotifications=[noteType],
|
||||||
|
applicationIcon=applicationIcon,
|
||||||
|
hostname=hostname,
|
||||||
|
password=password,
|
||||||
|
port=port,
|
||||||
|
)
|
||||||
|
result = growl.register()
|
||||||
|
if result is not True:
|
||||||
|
return result
|
||||||
|
|
||||||
|
return growl.notify(
|
||||||
|
noteType=noteType,
|
||||||
|
title=title,
|
||||||
|
description=description,
|
||||||
|
icon=notificationIcon,
|
||||||
|
sticky=sticky,
|
||||||
|
priority=priority,
|
||||||
|
callback=callback,
|
||||||
|
identifier=identifier,
|
||||||
|
)
|
||||||
|
except Exception:
|
||||||
|
# We want the "mini" function to be simple and swallow Exceptions
|
||||||
|
# in order to be less invasive
|
||||||
|
logger.exception("Growl error")
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
# If we're running this module directly we're likely running it as a test
|
# If we're running this module directly we're likely running it as a test
|
||||||
# so extra debugging is useful
|
# so extra debugging is useful
|
||||||
logging.basicConfig(level=logging.DEBUG)
|
logging.basicConfig(level=logging.INFO)
|
||||||
mini('Testing mini notification')
|
mini('Testing mini notification')
|
||||||
@@ -0,0 +1,46 @@
|
|||||||
|
# Copyright: 2013 Paul Traylor
|
||||||
|
# These sources are released under the terms of the MIT license: see LICENSE
|
||||||
|
|
||||||
|
"""
|
||||||
|
Python2.5 and Python3.3 compatibility shim
|
||||||
|
|
||||||
|
Heavily inspirted by the "six" library.
|
||||||
|
https://pypi.python.org/pypi/six
|
||||||
|
"""
|
||||||
|
|
||||||
|
import sys
|
||||||
|
|
||||||
|
PY2 = sys.version_info[0] == 2
|
||||||
|
PY3 = sys.version_info[0] == 3
|
||||||
|
|
||||||
|
if PY3:
|
||||||
|
def b(s):
|
||||||
|
if isinstance(s, bytes):
|
||||||
|
return s
|
||||||
|
return s.encode('utf8', 'replace')
|
||||||
|
|
||||||
|
def u(s):
|
||||||
|
if isinstance(s, bytes):
|
||||||
|
return s.decode('utf8', 'replace')
|
||||||
|
return s
|
||||||
|
|
||||||
|
from io import BytesIO as StringIO
|
||||||
|
from configparser import RawConfigParser
|
||||||
|
else:
|
||||||
|
def b(s):
|
||||||
|
if isinstance(s, unicode):
|
||||||
|
return s.encode('utf8', 'replace')
|
||||||
|
return s
|
||||||
|
|
||||||
|
def u(s):
|
||||||
|
if isinstance(s, unicode):
|
||||||
|
return s
|
||||||
|
if isinstance(s, int):
|
||||||
|
s = str(s)
|
||||||
|
return unicode(s, "utf8", "replace")
|
||||||
|
|
||||||
|
from StringIO import StringIO
|
||||||
|
from ConfigParser import RawConfigParser
|
||||||
|
|
||||||
|
b.__doc__ = "Ensure we have a byte string"
|
||||||
|
u.__doc__ = "Ensure we have a unicode string"
|
||||||
@@ -0,0 +1,4 @@
|
|||||||
|
# Copyright: 2013 Paul Traylor
|
||||||
|
# These sources are released under the terms of the MIT license: see LICENSE
|
||||||
|
|
||||||
|
__version__ = '1.0.3'
|
||||||
@@ -20,7 +20,7 @@ jQuery Form Plugin
|
|||||||
* http://www.opensource.org/licenses/mit-license.php
|
* http://www.opensource.org/licenses/mit-license.php
|
||||||
|
|
||||||
Bootstrap v3.3.6 (http://getbootstrap.com)
|
Bootstrap v3.3.6 (http://getbootstrap.com)
|
||||||
* Licensed under the MIT license
|
* Licensed under the MIT license
|
||||||
Changed by Safihre, Nov 2015
|
Changed by Safihre, Nov 2015
|
||||||
We include the icon-file directly into the CSS,
|
We include the icon-file directly into the CSS,
|
||||||
this way we avoid errors when HTTPS is enabled
|
this way we avoid errors when HTTPS is enabled
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
<!-- Content end -->
|
<!-- Content end -->
|
||||||
<div class="clearfix"></div>
|
<div class="clearfix"></div>
|
||||||
|
|
||||||
<!-- Filebrowser modal -->
|
<!-- Filebrowser modal -->
|
||||||
<div class="modal fade" id="filebrowser_modal">
|
<div class="modal fade" id="filebrowser_modal">
|
||||||
<div class="modal-dialog">
|
<div class="modal-dialog">
|
||||||
@@ -28,7 +28,7 @@
|
|||||||
</div>
|
</div>
|
||||||
<div class="main-restarting modal-backdrop fade in" style="display: none;">
|
<div class="main-restarting modal-backdrop fade in" style="display: none;">
|
||||||
<div>
|
<div>
|
||||||
<strong><span class="glyphicon glyphicon-retweet"></span> $T('wizard-restarting')</strong><br />
|
<strong><span class="glyphicon glyphicon-retweet"></span> $T('restarting-sab')</strong><br />
|
||||||
<small><span class="restarting-url"></span><span class="dotOne">.</span><span class="dotTwo">.</span><span class="dotThree">.</span></small>
|
<small><span class="restarting-url"></span><span class="dotOne">.</span><span class="dotTwo">.</span><span class="dotThree">.</span></small>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -20,31 +20,35 @@
|
|||||||
#if $pane == "Special" then $T('cmenu-special') else ""#
|
#if $pane == "Special" then $T('cmenu-special') else ""#
|
||||||
#if $pane == "RSS" then $T('cmenu-rss') else ""#
|
#if $pane == "RSS" then $T('cmenu-rss') else ""#
|
||||||
</title>
|
</title>
|
||||||
|
|
||||||
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
|
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
<meta name="viewport" content="width=device-width, initial-scale=1, user-scalable=no, maximum-scale=1" />
|
||||||
<meta name="apple-mobile-web-app-title" content="SABnzbd" />
|
<meta name="apple-mobile-web-app-title" content="SABnzbd" />
|
||||||
|
|
||||||
<link rel="apple-touch-icon" sizes="76x76" href="${root}staticcfg/ico/apple-touch-icon-76x76-precomposed.png" />
|
<link rel="apple-touch-icon" sizes="76x76" href="${root}staticcfg/ico/apple-touch-icon-76x76-precomposed.png" />
|
||||||
<link rel="apple-touch-icon" sizes="120x120" href="${root}staticcfg/ico/apple-touch-icon-120x120-precomposed.png" />
|
<link rel="apple-touch-icon" sizes="120x120" href="${root}staticcfg/ico/apple-touch-icon-120x120-precomposed.png" />
|
||||||
<link rel="apple-touch-icon" sizes="152x152" href="${root}staticcfg/ico/apple-touch-icon-152x152-precomposed.png" />
|
<link rel="apple-touch-icon" sizes="152x152" href="${root}staticcfg/ico/apple-touch-icon-152x152-precomposed.png" />
|
||||||
<link rel="apple-touch-icon" sizes="180x180" href="${root}staticcfg/ico/apple-touch-icon-180x180-precomposed.png" />
|
<link rel="apple-touch-icon" sizes="180x180" href="${root}staticcfg/ico/apple-touch-icon-180x180-precomposed.png" />
|
||||||
<link rel="apple-touch-icon" sizes="192x192" href="${root}staticcfg/ico/android-192x192.png" />
|
<link rel="apple-touch-icon" sizes="192x192" href="${root}staticcfg/ico/android-192x192.png" />
|
||||||
|
|
||||||
<link rel="stylesheet" type="text/css" href="${root}staticcfg/bootstrap/css/bootstrap.min.css" />
|
<link rel="stylesheet" type="text/css" href="${root}staticcfg/bootstrap/css/bootstrap.min.css?v=$version" />
|
||||||
<link rel="stylesheet" type="text/css" href="${root}staticcfg/css/style.css?p=$pid" />
|
<link rel="stylesheet" type="text/css" href="${root}staticcfg/css/chartist.min.css" />
|
||||||
<link rel="shortcut icon" href="${root}staticcfg/ico/favicon.ico?v=1.1.0" />
|
<link rel="stylesheet" type="text/css" href="${root}staticcfg/css/style.css?v=$version" />
|
||||||
|
|
||||||
|
<link rel="shortcut icon" href="${root}staticcfg/ico/favicon.ico?v=$version" />
|
||||||
|
|
||||||
<script type="text/javascript">
|
<script type="text/javascript">
|
||||||
// Keeping track of the form state
|
// Keeping track of the form state
|
||||||
var formHasChanged = false;
|
var formHasChanged = false;
|
||||||
var formWasSubmitted = false;
|
var formWasSubmitted = false;
|
||||||
|
|
||||||
// Information we need
|
// Information we need
|
||||||
var sabSession = '$session';
|
var sabSession = '$session';
|
||||||
var folderBrowseUrl = '${root}tapi?mode=browse&output=json&apikey=$session';
|
var rootURL = '${root}'
|
||||||
|
var urlBase = '${url_base}'
|
||||||
|
var folderBrowseUrl = '${root}tapi?mode=browse&output=json&apikey=$session';
|
||||||
var folderSeperator = '#if $os.sep == '\\' then '\\\\' else '/'#'
|
var folderSeperator = '#if $os.sep == '\\' then '\\\\' else '/'#'
|
||||||
|
|
||||||
// Translations
|
// Translations
|
||||||
var configTranslate = new Object();
|
var configTranslate = new Object();
|
||||||
configTranslate.browseText = "$T('browse-folder')";
|
configTranslate.browseText = "$T('browse-folder')";
|
||||||
@@ -52,14 +56,15 @@
|
|||||||
configTranslate.saving = "$T('smpl-saving')";
|
configTranslate.saving = "$T('smpl-saving')";
|
||||||
configTranslate.failed = "$T('smpl-failed')";
|
configTranslate.failed = "$T('smpl-failed')";
|
||||||
configTranslate.explainRestart = "$T('explain-Restart')";
|
configTranslate.explainRestart = "$T('explain-Restart')";
|
||||||
configTranslate.wizzardRestart = "$T('wizard-restarting')";
|
configTranslate.wizzardRestart = "$T('restarting-sab')";
|
||||||
configTranslate.needRestart = "$T('restartRequired') $T('explain-needNewLogin')".replace(/\<br(\s*\/|)\>/g, '\n');
|
configTranslate.needRestart = "$T('restartRequired') $T('explain-needNewLogin')".replace(/\<br(\s*\/|)\>/g, '\n');
|
||||||
configTranslate.buttonRestart = "$T('button-restart')";
|
configTranslate.buttonRestart = "$T('button-restart')";
|
||||||
configTranslate.confirmLeave = "$T('confirmWithoutSavingPrompt')";
|
configTranslate.confirmLeave = "$T('confirmWithoutSavingPrompt')";
|
||||||
|
configTranslate.searchPages = ['$T('cmenu-general')', '$T('cmenu-folders')', '$T('cmenu-switches')', '$T('cmenu-sorting')', '$T('cmenu-notif')', '$T('cmenu-special')']
|
||||||
</script>
|
</script>
|
||||||
<script type="text/javascript" src="${root}staticcfg/js/jquery-1.11.2.min.js"></script>
|
<script type="text/javascript" src="${root}staticcfg/js/jquery-3.2.1.min.js?v=$version"></script>
|
||||||
<script type="text/javascript" src="${root}staticcfg/bootstrap/js/bootstrap.min.js"></script>
|
<script type="text/javascript" src="${root}staticcfg/bootstrap/js/bootstrap.min.js?v=$version"></script>
|
||||||
<script type="text/javascript" src="${root}staticcfg/js/script.js?p=$pid"></script>
|
<script type="text/javascript" src="${root}staticcfg/js/script.js?v=$version"></script>
|
||||||
<script type="text/javascript">
|
<script type="text/javascript">
|
||||||
// Set default functions for the autocomplete everywhere
|
// Set default functions for the autocomplete everywhere
|
||||||
\$.extend(\$.fn.typeahead.defaults, {
|
\$.extend(\$.fn.typeahead.defaults, {
|
||||||
@@ -83,6 +88,13 @@
|
|||||||
return item
|
return item
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
// to top right away
|
||||||
|
if(window.location.hash) {
|
||||||
|
scroll(0,0);
|
||||||
|
// void some browsers issue
|
||||||
|
setTimeout(function() { scroll(0,0); }, 1);
|
||||||
|
}
|
||||||
</script>
|
</script>
|
||||||
</head>
|
</head>
|
||||||
|
|
||||||
@@ -174,9 +186,23 @@
|
|||||||
<strong>$T('menu-help')</strong>
|
<strong>$T('menu-help')</strong>
|
||||||
</a>
|
</a>
|
||||||
</li>
|
</li>
|
||||||
|
<li class="dropdown" id="search-menu">
|
||||||
|
<a data-toggle="dropdown" href="#">
|
||||||
|
<span class="glyphicon glyphicon-search"></span>
|
||||||
|
<strong><span class="glyphicon glyphicon-search"></span></strong>
|
||||||
|
</a>
|
||||||
|
<ul class="dropdown-menu" id="search-dropdown">
|
||||||
|
<li>
|
||||||
|
<form onsubmit="return false">
|
||||||
|
<input type="text" name="config-search" id="search-box" placeholder="$T('cmenu-search')" onkeyup="doConfigSearch(this)">
|
||||||
|
</form>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</nav>
|
</nav>
|
||||||
|
|
||||||
<div id="content" class="container">
|
<div id="content" class="container">
|
||||||
<!-- Content start -->
|
<!-- Content start -->
|
||||||
@@ -1,11 +1,8 @@
|
|||||||
<!--#set global $pane="Config"#-->
|
<!--#set global $pane="Config"#-->
|
||||||
<!--#set global $help_uri="configuration/1.1/configure"#-->
|
<!--#set global $help_uri="configuration/2.2/configure"#-->
|
||||||
<!--#include $webdir + "/_inc_header_uc.tmpl"#-->
|
<!--#include $webdir + "/_inc_header_uc.tmpl"#-->
|
||||||
|
|
||||||
<!--#from sabnzbd.newswrapper import HAVE_SSL#-->
|
|
||||||
<!--#from locale import getpreferredencoding#-->
|
<!--#from locale import getpreferredencoding#-->
|
||||||
<!--#import sabnzbd.utils.sslinfo#-->
|
|
||||||
<!--#from sabnzbd.decoder import HAVE_YENC#-->
|
|
||||||
<div class="colmask">
|
<div class="colmask">
|
||||||
<div class="section padTable">
|
<div class="section padTable">
|
||||||
<table class="table table-striped">
|
<table class="table table-striped">
|
||||||
@@ -22,10 +19,6 @@
|
|||||||
<th scope="row">$T('confgFile'): </th>
|
<th scope="row">$T('confgFile'): </th>
|
||||||
<td>$configfn</td>
|
<td>$configfn</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
|
||||||
<th scope="row">$T('cache').capitalize(): </th>
|
|
||||||
<td><!--#set $msg=$T('ft-buffer@2')%($cache_art, $cache_size)#-->$msg</td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
<tr>
|
||||||
<th scope="row">$T('parameters'): </th>
|
<th scope="row">$T('parameters'): </th>
|
||||||
<td>$cmdline</td>
|
<td>$cmdline</td>
|
||||||
@@ -37,37 +30,57 @@
|
|||||||
<tr>
|
<tr>
|
||||||
<th scope="row">OpenSSL:</th>
|
<th scope="row">OpenSSL:</th>
|
||||||
<td>
|
<td>
|
||||||
<!--#if HAVE_SSL#-->
|
$ssl_version
|
||||||
<!--#set $sslversions = ', '.join(sabnzbd.utils.sslinfo.ssl_protocols_labels())#-->
|
|
||||||
$sabnzbd.utils.sslinfo.ssl_version() [$sslversions]
|
|
||||||
<!--#else#-->
|
|
||||||
<span class="label label-warning">$T('notAvailable')</span>
|
|
||||||
<a href="$helpuri$help_uri#no_ssl" target="_blank"><span class="glyphicon glyphicon-question-sign"></span></a>
|
|
||||||
<!--#end if#-->
|
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
|
<!--#if not $have_ssl_context#-->
|
||||||
<tr>
|
<tr>
|
||||||
<th scope="row">pyOpenSSL:</th>
|
<th scope="row"></th>
|
||||||
<td>
|
<td>
|
||||||
<!--#if HAVE_SSL#-->
|
<span class="label label-danger">$T('warning')</span> $T('explain-nosslcontext')
|
||||||
$sabnzbd.utils.sslinfo.pyopenssl_version()
|
</td>
|
||||||
|
</tr>
|
||||||
|
<!--#end if#-->
|
||||||
|
<!--#if not $nt and not $darwin#-->
|
||||||
|
<tr>
|
||||||
|
<th scope="row">$T('opt-multicore-par2')</th>
|
||||||
|
<td>
|
||||||
|
<!--#if $have_mt_par2#-->
|
||||||
|
<span class="glyphicon glyphicon-ok"></span>
|
||||||
<!--#else#-->
|
<!--#else#-->
|
||||||
<span class="label label-warning">$T('notAvailable')</span>
|
<span class="label label-warning">$T('notAvailable')</span> $T('explain-getpar2mt')
|
||||||
<a href="$helpuri$help_uri#no_ssl" target="_blank"><span class="glyphicon glyphicon-question-sign"></span></a>
|
<a href="${helpuri}installation/multicore-par2" target="_blank">${helpuri}installation/multicore-par2</a>
|
||||||
<!--#end if#-->
|
<!--#end if#-->
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
|
<!--#end if#-->
|
||||||
|
<!--#if not $have_cryptography #-->
|
||||||
|
<tr>
|
||||||
|
<th scope="row">Python Cryptography:</th>
|
||||||
|
<td>
|
||||||
|
<span class="label label-warning">$T('notAvailable')</span>
|
||||||
|
<a href="$helpuri$help_uri#no_cryptography" target="_blank"><span class="glyphicon glyphicon-question-sign"></span></a>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<!--#end if#-->
|
||||||
|
<!--#if not $have_yenc and not $have_sabyenc#-->
|
||||||
<tr>
|
<tr>
|
||||||
<th scope="row">yEnc:</th>
|
<th scope="row">yEnc:</th>
|
||||||
<td>
|
<td>
|
||||||
<!--#if HAVE_YENC#-->
|
<span class="label label-danger">$T('notAvailable')</span>
|
||||||
<span class="glyphicon glyphicon-ok"></span>
|
<a href="$helpuri$help_uri#no_yenc" target="_blank"><span class="glyphicon glyphicon-question-sign"></span></a>
|
||||||
<!--#else#-->
|
|
||||||
<span class="label label-warning">$T('notAvailable')</span>
|
|
||||||
<a href="$helpuri$help_uri#no_yenc" target="_blank"><span class="glyphicon glyphicon-question-sign"></span></a>
|
|
||||||
<!--#end if#-->
|
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
|
<!--#end if#-->
|
||||||
|
<!--#if not $have_sabyenc#-->
|
||||||
|
<tr>
|
||||||
|
<th scope="row">SABYenc:</th>
|
||||||
|
<td>
|
||||||
|
<span class="label label-danger">$T('notAvailable')</span>
|
||||||
|
<a href="$helpuri$help_uri#no_sabyenc" target="_blank"><span class="glyphicon glyphicon-question-sign"></span></a>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<!--#end if#-->
|
||||||
<!--#if not $have_unzip #-->
|
<!--#if not $have_unzip #-->
|
||||||
<tr>
|
<tr>
|
||||||
<th scope="row">$T('opt-enable_unzip'):</th>
|
<th scope="row">$T('opt-enable_unzip'):</th>
|
||||||
@@ -96,7 +109,7 @@
|
|||||||
<tbody>
|
<tbody>
|
||||||
<tr>
|
<tr>
|
||||||
<th scope="row">$T('homePage') </th>
|
<th scope="row">$T('homePage') </th>
|
||||||
<td><a href="http://sabnzbd.org/" target="_blank">http://sabnzbd.org/</a></td>
|
<td><a href="https://sabnzbd.org/" target="_blank">https://sabnzbd.org/</a></td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<th scope="row">$T('menu-wiki') </th>
|
<th scope="row">$T('menu-wiki') </th>
|
||||||
@@ -104,7 +117,7 @@
|
|||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<th scope="row">$T('menu-forums') </th>
|
<th scope="row">$T('menu-forums') </th>
|
||||||
<td><a href="http://forums.sabnzbd.org/" target="_blank">http://forums.sabnzbd.org/</a></td>
|
<td><a href="https://forums.sabnzbd.org/" target="_blank">https://forums.sabnzbd.org/</a></td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<th scope="row">$T('source') </th>
|
<th scope="row">$T('source') </th>
|
||||||
@@ -118,6 +131,10 @@
|
|||||||
<th scope="row">$T('menu-issues') </th>
|
<th scope="row">$T('menu-issues') </th>
|
||||||
<td><a href="https://sabnzbd.org/wiki/introduction/known-issues" target="_blank">https://sabnzbd.org/wiki/introduction/known-issues</a></td>
|
<td><a href="https://sabnzbd.org/wiki/introduction/known-issues" target="_blank">https://sabnzbd.org/wiki/introduction/known-issues</a></td>
|
||||||
</tr>
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<th scope="row">$T('menu-donate') </th>
|
||||||
|
<td><a href="https://sabnzbd.org/donate" target="_blank">https://sabnzbd.org/donate</a></td>
|
||||||
|
</tr>
|
||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
</div>
|
</div>
|
||||||
@@ -125,7 +142,7 @@
|
|||||||
|
|
||||||
<div class="colmask">
|
<div class="colmask">
|
||||||
<div class="padding alt">
|
<div class="padding alt">
|
||||||
<h5 class="copyright">Copyright © 2008-2016 The SABnzbd Team <<a href="mailto:team@sabnzbd.org">team@sabnzbd.org</a>></h5>
|
<h5 class="copyright">Copyright © 2008-2017 The SABnzbd Team <<a href="mailto:team@sabnzbd.org">team@sabnzbd.org</a>></h5>
|
||||||
<p class="copyright"><small>$T('yourRights')</small></p>
|
<p class="copyright"><small>$T('yourRights')</small></p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|||||||
@@ -1,35 +1,34 @@
|
|||||||
<!--#set global $pane="Categories"#-->
|
<!--#set global $pane="Categories"#-->
|
||||||
<!--#set global $help_uri="configuration/1.1/categories"#-->
|
<!--#set global $help_uri="configuration/2.2/categories"#-->
|
||||||
<!--#include $webdir + "/_inc_header_uc.tmpl"#-->
|
<!--#include $webdir + "/_inc_header_uc.tmpl"#-->
|
||||||
<div class="colmask">
|
<div class="colmask">
|
||||||
<div class="section">
|
<div class="section">
|
||||||
<div class="padTable"> <a class="main-helplink" href="$helpuri$help_uri" target="_blank"><span class="glyphicon glyphicon-question-sign"></span></a>
|
<div class="padTable"> <a class="main-helplink" href="$helpuri$help_uri" target="_blank"><span class="glyphicon glyphicon-question-sign"></span></a>
|
||||||
<p> $T('explain-catTags') $T('explain-catTags2')<br/> </p>
|
<p>$T('explain-catTags2')<br/>$T('explain-catTags')</p>
|
||||||
|
<hr>
|
||||||
<h5 class="darkred"><strong>$T('explain-relFolder'):</strong> <span class="path">$defdir</span></h5>
|
<h5 class="darkred"><strong>$T('explain-relFolder'):</strong> <span class="path">$defdir</span></h5>
|
||||||
|
<!--#for $cur, $slot in enumerate($slotinfo)#-->
|
||||||
<!--#set $odd = False#-->
|
<!--#set $cansort = $slot.name != '*' and $slot.name != ''#-->
|
||||||
<!--#set $cur = 0#-->
|
<form action="save" method="get" <!--#if $cansort#-->class="sorting-row"<!--#end if#-->>
|
||||||
<!--#for $slot in $slotinfo#-->
|
|
||||||
<!--#set $odd = not $odd#-->
|
|
||||||
<!--#set $cur = $cur+1#-->
|
|
||||||
<form action="save" method="get">
|
|
||||||
<table class="catTable">
|
<table class="catTable">
|
||||||
<!--#if $cur == 1#-->
|
<!--#if $cur == 0#-->
|
||||||
<tr>
|
<tr>
|
||||||
|
<th></th>
|
||||||
<th>$T('category')</th>
|
<th>$T('category')</th>
|
||||||
<th>$T('priority')</th>
|
<th>$T('priority')</th>
|
||||||
<th>$T('mode')</th>
|
<th>$T('mode')</th>
|
||||||
<!--#if $script_list#-->
|
|
||||||
<th>$T('script')</th>
|
<th>$T('script')</th>
|
||||||
<!--#end if#-->
|
|
||||||
<th>$T('catFolderPath')</th>
|
<th>$T('catFolderPath')</th>
|
||||||
<th>$T('catTags')</th>
|
<th colspan="2">$T('catTags')</th>
|
||||||
<th> </th>
|
|
||||||
</tr>
|
</tr>
|
||||||
<!--#end if#-->
|
<!--#end if#-->
|
||||||
<tr class="<!--#if $odd then " alt " else " "#-->" <!--#if $slot.name=='*' #-->style="background-color: #FFFFE0;"<!--#end if#-->>
|
<tr>
|
||||||
|
<td>
|
||||||
|
<span class="glyphicon glyphicon-option-vertical"></span>
|
||||||
|
</td>
|
||||||
<td>
|
<td>
|
||||||
<input type="hidden" name="session" value="$session" />
|
<input type="hidden" name="session" value="$session" />
|
||||||
|
<input type="hidden" name="order" value="$slot.order" />
|
||||||
<input type="hidden" value="$slot.name" name="name" />
|
<input type="hidden" value="$slot.name" name="name" />
|
||||||
<!--#if $slot.name != '*'#-->
|
<!--#if $slot.name != '*'#-->
|
||||||
<input type="text" name="newname" value="$slot.name" size="10" />
|
<input type="text" name="newname" value="$slot.name" size="10" />
|
||||||
@@ -60,49 +59,83 @@
|
|||||||
<option value="3" <!--#if $slot.pp=="3" then 'selected="selected"' else ""#-->>$T('pp-delete')</option>
|
<option value="3" <!--#if $slot.pp=="3" then 'selected="selected"' else ""#-->>$T('pp-delete')</option>
|
||||||
</select>
|
</select>
|
||||||
</td>
|
</td>
|
||||||
<!--#if $script_list#-->
|
|
||||||
<td>
|
<td>
|
||||||
|
<!--#if $scripts#-->
|
||||||
<select name="script">
|
<select name="script">
|
||||||
<!--#for $sc in $script_list#-->
|
<!--#for $sc in $scripts#-->
|
||||||
<!--#if not ($sc == 'Default' and $slot.name == '*')#-->
|
<!--#if not ($sc == 'Default' and $slot.name == '*')#-->
|
||||||
<option value="$sc" <!--#if $slot.script.lower()==$sc.lower() then 'selected="selected"' else ""#-->>$Tspec($sc)</option>
|
<option value="$sc" <!--#if $slot.script.lower()==$sc.lower() then 'selected="selected"' else ""#-->>$Tspec($sc)</option>
|
||||||
<!--#end if#-->
|
<!--#end if#-->
|
||||||
<!--#end for#-->
|
<!--#end for#-->
|
||||||
</select>
|
</select>
|
||||||
|
<!--#else#-->
|
||||||
|
<select name="script" disabled>
|
||||||
|
|
||||||
|
</select>
|
||||||
|
<!--#end if#-->
|
||||||
</td>
|
</td>
|
||||||
<!--#end if#-->
|
|
||||||
<td class="nowrap">
|
<td class="nowrap">
|
||||||
<input type="text" name="dir" class="fileBrowserSmall" value="$slot.dir" size="20" data-initialdir="$defdir" data-title="$T('catFolderPath')" />
|
<input type="text" name="dir" class="fileBrowserSmall" value="$slot.dir" size="20" data-initialdir="$defdir" data-title="$T('catFolderPath')" title="$T('explain-catTags2')" />
|
||||||
</td>
|
</td>
|
||||||
<td>
|
<td>
|
||||||
<input type="text" name="newzbin" value="$slot.newzbin" size="20" />
|
<input type="text" name="newzbin" value="$slot.newzbin" size="20" />
|
||||||
</td>
|
</td>
|
||||||
<td class="nowrap">
|
<td class="nowrap">
|
||||||
<button class="btn btn-default">
|
<button class="btn btn-default">
|
||||||
<!--#if $cur == 2#--><span class="glyphicon glyphicon-plus"></span> $T('button-add')
|
<!--#if $cur == 1#-->
|
||||||
<!--#else#--><span class="glyphicon glyphicon-ok"></span> $T('button-save')
|
<span class="glyphicon glyphicon-plus"></span> $T('button-add')
|
||||||
|
<!--#else#-->
|
||||||
|
<span class="glyphicon glyphicon-ok"></span> $T('button-save')
|
||||||
<!--#end if#-->
|
<!--#end if#-->
|
||||||
</button>
|
</button>
|
||||||
<!--#if $slot.name and $slot.name != '*'#-->
|
<!--#if $cansort#-->
|
||||||
<button class="btn btn-default delCat" type="button"><span class="glyphicon glyphicon-trash"></span></button>
|
<button class="btn btn-default delCat" type="button"><span class="glyphicon glyphicon-trash"></span></button>
|
||||||
<!--#end if#-->
|
<!--#end if#-->
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
</table>
|
</table>
|
||||||
</form>
|
</form>
|
||||||
|
<!--#if not $cansort#-->
|
||||||
|
<hr>
|
||||||
|
<!--#end if#-->
|
||||||
<!--#end for#-->
|
<!--#end for#-->
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<script type="text/javascript" src="${root}staticcfg/js/jquery-ui.min.js"></script>
|
||||||
<script type="text/javascript">
|
<script type="text/javascript">
|
||||||
\$(document).ready(function() {
|
\$(document).ready(function() {
|
||||||
\$('.delCat').click(function() {
|
\$('.delCat').click(function() {
|
||||||
var theForm = \$(this).closest("form");
|
var theForm = \$(this).closest("form");
|
||||||
theForm.attr("action", "delete").submit();
|
theForm.attr("action", "delete").submit();
|
||||||
});
|
});
|
||||||
|
|
||||||
// Add autocomplete and file-browser
|
// Add autocomplete and file-browser
|
||||||
\$('.fileBrowserSmall').typeahead().fileBrowser();
|
\$('.fileBrowserSmall').typeahead().fileBrowser();
|
||||||
|
|
||||||
|
// Make categories sortable
|
||||||
|
\$('.padTable').sortable({
|
||||||
|
items: '.sorting-row',
|
||||||
|
containment: '.colmask',
|
||||||
|
axis: 'y',
|
||||||
|
update: function(event, ui) {
|
||||||
|
\$('.Categories form.sorting-row').each(function(index, elm) {
|
||||||
|
// Update order of all elements
|
||||||
|
if(index != elm.order.value) {
|
||||||
|
elm.order.value = index
|
||||||
|
// Submit changed order
|
||||||
|
var data = {}
|
||||||
|
\$(elm).extractFormDataTo(data);
|
||||||
|
\$.ajax({
|
||||||
|
type: "GET",
|
||||||
|
url: window.location.pathname + 'save',
|
||||||
|
data: data,
|
||||||
|
async: false // To prevent race-conditions when updating categories
|
||||||
|
})
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
})
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
<!--#set global $pane="Folders"#-->
|
<!--#set global $pane="Folders"#-->
|
||||||
<!--#set global $help_uri="configuration/1.1/folders"#-->
|
<!--#set global $help_uri="configuration/2.2/folders"#-->
|
||||||
<!--#include $webdir + "/_inc_header_uc.tmpl"#-->
|
<!--#include $webdir + "/_inc_header_uc.tmpl"#-->
|
||||||
|
|
||||||
<div class="colmask">
|
<div class="colmask">
|
||||||
@@ -65,7 +65,7 @@
|
|||||||
</div>
|
</div>
|
||||||
<div class="field-pair">
|
<div class="field-pair">
|
||||||
<button class="btn btn-default saveButton"><span class="glyphicon glyphicon-ok"></span> $T('button-saveChanges')</button>
|
<button class="btn btn-default saveButton"><span class="glyphicon glyphicon-ok"></span> $T('button-saveChanges')</button>
|
||||||
|
|
||||||
<span> </span>
|
<span> </span>
|
||||||
<span id="config_err_msg" class="darkred nomargin"> </span>
|
<span id="config_err_msg" class="darkred nomargin"> </span>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
<!--#set global $pane="General"#-->
|
<!--#set global $pane="General"#-->
|
||||||
<!--#set global $help_uri="configuration/1.1/general"#-->
|
<!--#set global $help_uri="configuration/2.2/general"#-->
|
||||||
<!--#include $webdir + "/_inc_header_uc.tmpl"#-->
|
<!--#include $webdir + "/_inc_header_uc.tmpl"#-->
|
||||||
|
|
||||||
<div class="colmask">
|
<div class="colmask">
|
||||||
@@ -23,6 +23,11 @@
|
|||||||
<input type="number" name="port" id="port" value="$port" size="8" data-original="$port" />
|
<input type="number" name="port" id="port" value="$port" size="8" data-original="$port" />
|
||||||
<span class="desc">$T('explain-port')</span>
|
<span class="desc">$T('explain-port')</span>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="field-pair">
|
||||||
|
<label class="config" for="enable_https">$T('opt-enable_https')</label>
|
||||||
|
<input type="checkbox" name="enable_https" id="enable_https" value="1" <!--#if int($enable_https) > 0 then 'checked="checked" data-original="1"' else ""#-->/>
|
||||||
|
<span class="desc">$T('explain-enable_https')</span>
|
||||||
|
</div>
|
||||||
<div class="field-pair">
|
<div class="field-pair">
|
||||||
<label class="config" for="web_dir">$T('opt-web_dir')</label>
|
<label class="config" for="web_dir">$T('opt-web_dir')</label>
|
||||||
<select name="web_dir" id="web_dir">
|
<select name="web_dir" id="web_dir">
|
||||||
@@ -34,21 +39,7 @@
|
|||||||
<!--#end if#-->
|
<!--#end if#-->
|
||||||
<!--#end for#-->
|
<!--#end for#-->
|
||||||
</select>
|
</select>
|
||||||
<span class="desc">$T('explain-web_dir') <a href="$caller_url1">$caller_url1</a></span>
|
<span class="desc">$T('explain-web_dir') <a href="$caller_url">$caller_url</a></span>
|
||||||
</div>
|
|
||||||
<div class="field-pair">
|
|
||||||
<label class="config" for="web_dir2">$T('opt-web_dir2')</label>
|
|
||||||
<select name="web_dir2" id="web_dir2">
|
|
||||||
<option value="None" selected="selected">$T("None")</option>
|
|
||||||
<!--#for $webline in $web_list#-->
|
|
||||||
<!--#if $webline.lower() == $web_dir2.lower()#-->
|
|
||||||
<option value="$webline" selected="selected">$webline</option>
|
|
||||||
<!--#else#-->
|
|
||||||
<option value="$webline">$webline</option>
|
|
||||||
<!--#end if#-->
|
|
||||||
<!--#end for#-->
|
|
||||||
</select>
|
|
||||||
<span class="desc">$T('explain-web_dir2') <a href="$caller_url2">$caller_url2</a></span>
|
|
||||||
</div>
|
</div>
|
||||||
<div class="field-pair">
|
<div class="field-pair">
|
||||||
<label class="config" for="language">$T('opt-language')</label>
|
<label class="config" for="language">$T('opt-language')</label>
|
||||||
@@ -66,6 +57,50 @@
|
|||||||
$T('explain-ask-language') <a href="https://sabnzbd.org/wiki/translate" target="_blank" class="alert-link">https://sabnzbd.org/wiki/translate</a>
|
$T('explain-ask-language') <a href="https://sabnzbd.org/wiki/translate" target="_blank" class="alert-link">https://sabnzbd.org/wiki/translate</a>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="field-pair advanced-settings">
|
||||||
|
<h5 class="darkred nomargin">$T('base-folder'): <span class="path">$my_lcldata</span></h5>
|
||||||
|
</div>
|
||||||
|
<div class="field-pair advanced-settings">
|
||||||
|
<label class="config" for="https_port">$T('opt-https_port')</label>
|
||||||
|
<input type="number" name="https_port" id="https_port" value="$https_port" size="8" data-original="$https_port" />
|
||||||
|
<span class="desc">$T('explain-https_port')</span>
|
||||||
|
</div>
|
||||||
|
<div class="field-pair advanced-settings">
|
||||||
|
<label class="config" for="https_cert">$T('opt-https_cert')</label>
|
||||||
|
<input type="text" name="https_cert" id="https_cert" value="$https_cert" />
|
||||||
|
<button class="btn btn-default generate_cert" title="$T('explain-new-cert')" <!--#if int($have_cryptography) == 0 then "disabled" else ""#-->>
|
||||||
|
<span class="glyphicon glyphicon-repeat"></span>
|
||||||
|
</button>
|
||||||
|
<span class="desc">$T('explain-https_cert')</span>
|
||||||
|
</div>
|
||||||
|
<div class="field-pair advanced-settings">
|
||||||
|
<label class="config" for="https_key">$T('opt-https_key')</label>
|
||||||
|
<input type="text" name="https_key" id="https_key" value="$https_key" />
|
||||||
|
<button class="btn btn-default generate_cert" title="$T('explain-new-cert')" <!--#if int($have_cryptography) == 0 then "disabled" else ""#-->>
|
||||||
|
<span class="glyphicon glyphicon-repeat"></span>
|
||||||
|
</button>
|
||||||
|
<span class="desc">$T('explain-https_key')</span>
|
||||||
|
</div>
|
||||||
|
<div class="field-pair advanced-settings">
|
||||||
|
<label class="config" for="https_chain">$T('opt-https_chain')</label>
|
||||||
|
<input type="text" name="https_chain" id="https_chain" value="$https_chain" />
|
||||||
|
<span class="desc">$T('explain-https_chain')</span>
|
||||||
|
</div>
|
||||||
|
<div class="field-pair">
|
||||||
|
<button class="btn btn-default saveButton"><span class="glyphicon glyphicon-ok"></span> $T('button-saveChanges')</button>
|
||||||
|
<button class="btn btn-default sabnzbd_restart"><span class="glyphicon glyphicon-refresh"></span> $T('button-restart') SABnzbd</button>
|
||||||
|
<button class="btn btn-default advancedButton enable_https_options"><span class="glyphicon glyphicon-cog"></span> $T('button-advanced')</button>
|
||||||
|
</div>
|
||||||
|
</fieldset>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="section">
|
||||||
|
<div class="col2">
|
||||||
|
<h3>$T('security') <a href="$helpuri$help_uri" target="_blank"><span class="glyphicon glyphicon-question-sign"></span></a></h3>
|
||||||
|
<p><b>$T('restartRequired')</b></p>
|
||||||
|
</div><!-- /col2 -->
|
||||||
|
<div class="col1">
|
||||||
|
<fieldset>
|
||||||
<!-- Tricks to avoid browser auto-fill, fixed on-submit with javascript -->
|
<!-- Tricks to avoid browser auto-fill, fixed on-submit with javascript -->
|
||||||
<div class="field-pair">
|
<div class="field-pair">
|
||||||
<label class="config" for="${pid}_wu">$T('opt-web_username')</label>
|
<label class="config" for="${pid}_wu">$T('opt-web_username')</label>
|
||||||
@@ -76,29 +111,28 @@
|
|||||||
<label class="config" for="${pid}_wp">$T('opt-web_password')</label>
|
<label class="config" for="${pid}_wp">$T('opt-web_password')</label>
|
||||||
<input type="text" name="${pid}_wp" id="${pid}_wp" value="$password" data-hide="password" />
|
<input type="text" name="${pid}_wp" id="${pid}_wp" value="$password" data-hide="password" />
|
||||||
<span class="desc">$T('explain-web_password')</span>
|
<span class="desc">$T('explain-web_password')</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="field-pair">
|
<div class="field-pair">
|
||||||
<label class="config" for="inet_exposure">$T('opt-inet_exposure')</label>
|
<label class="config" for="inet_exposure">$T('opt-inet_exposure')</label>
|
||||||
<select name="inet_exposure" id="inet_exposure" class="select">
|
<select name="inet_exposure" id="inet_exposure" class="select">
|
||||||
<option value="0" <!--#if $inet_exposure == 0 then 'selected="selected"' else ""#-->>$T('inet-local')</option>
|
<optgroup label="API">
|
||||||
<option value="1" <!--#if $inet_exposure == 1 then 'selected="selected"' else ""#-->>$T('inet-nzb')</option>
|
<option value="0" <!--#if $inet_exposure == 0 then 'selected="selected"' else ""#-->>$T('inet-local')</option>
|
||||||
<option value="2" <!--#if $inet_exposure == 2 then 'selected="selected"' else ""#-->>$T('inet-api')</option>
|
<option value="1" <!--#if $inet_exposure == 1 then 'selected="selected"' else ""#-->>$T('inet-nzb')</option>
|
||||||
<option value="3" <!--#if $inet_exposure == 3 then 'selected="selected"' else ""#-->>$T('inet-fullapi')</option>
|
<option value="2" <!--#if $inet_exposure == 2 then 'selected="selected"' else ""#-->>$T('inet-api')</option>
|
||||||
<option value="4" <!--#if $inet_exposure == 4 then 'selected="selected"' else ""#-->>$T('inet-ui')</option>
|
<option value="3" <!--#if $inet_exposure == 3 then 'selected="selected"' else ""#-->>$T('inet-fullapi')</option>
|
||||||
<option value="5" <!--#if $inet_exposure == 5 then 'selected="selected"' else ""#-->>$T('inet-ui') - $T('inet-external_login')</option>
|
</optgroup>
|
||||||
|
<optgroup label="$T('inet-fullapi') & $T('opt-web_dir')">
|
||||||
|
<option value="4" <!--#if $inet_exposure == 4 then 'selected="selected"' else ""#-->>$T('inet-ui')</option>
|
||||||
|
<option value="5" <!--#if $inet_exposure == 5 then 'selected="selected"' else ""#-->>$T('inet-ui') - $T('inet-external_login')</option>
|
||||||
|
</optgroup>
|
||||||
</select>
|
</select>
|
||||||
<span class="desc">$T('explain-inet_exposure')</span>
|
<span class="desc">$T('explain-inet_exposure').replace('. ','.<br><span class="label label-warning">'+$T('warning').upper()+'</span> ')</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="field-pair">
|
<div class="field-pair">
|
||||||
<label class="config" for="local_ranges">$T('opt-local_ranges')</label>
|
<label class="config" for="local_ranges">$T('opt-local_ranges')</label>
|
||||||
<input type="text" name="local_ranges" id="local_ranges" value="$local_ranges" />
|
<input type="text" name="local_ranges" id="local_ranges" value="$local_ranges" />
|
||||||
<span class="desc">$T('explain-local_ranges')</span>
|
<span class="desc">$T('explain-local_ranges')</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="field-pair">
|
|
||||||
<label class="config" for="disable_api_key">$T('opt-disableApikey')</label>
|
|
||||||
<input type="checkbox" name="disable_api_key" id="disable_api_key" value="1" <!--#if int($disable_api_key) > 0 then 'checked="checked"' else ""#--> />
|
|
||||||
<span class="desc">$T('explain-disableApikey')</span>
|
|
||||||
</div>
|
|
||||||
<div class="field-pair">
|
<div class="field-pair">
|
||||||
<label class="config" for="apikey">$T('opt-apikey')</label>
|
<label class="config" for="apikey">$T('opt-apikey')</label>
|
||||||
<input type="text" id="apikey" class="fileBrowserField" value="$session" readonly />
|
<input type="text" id="apikey" class="fileBrowserField" value="$session" readonly />
|
||||||
@@ -115,51 +149,37 @@
|
|||||||
</div>
|
</div>
|
||||||
<div class="field-pair">
|
<div class="field-pair">
|
||||||
<button class="btn btn-default saveButton"><span class="glyphicon glyphicon-ok"></span> $T('button-saveChanges')</button>
|
<button class="btn btn-default saveButton"><span class="glyphicon glyphicon-ok"></span> $T('button-saveChanges')</button>
|
||||||
<button class="btn btn-default sabnzbd_restart"><span class="glyphicon glyphicon-refresh"></span> $T('button-restart') SABnzbd</button>
|
|
||||||
</div>
|
</div>
|
||||||
</fieldset>
|
</fieldset>
|
||||||
</div><!-- /col1 -->
|
</div><!-- /col1 -->
|
||||||
</div><!-- /section -->
|
</div><!-- /section -->
|
||||||
<div class="section">
|
<div class="section">
|
||||||
<div class="col2">
|
<div class="col2">
|
||||||
<h3>$T('httpsSupport') <a href="$helpuri$help_uri#toc1" target="_blank"><span class="glyphicon glyphicon-question-sign"></span></a></h3>
|
<h3>$T('cmenu-switches') <a href="$helpuri$help_uri" target="_blank"><span class="glyphicon glyphicon-question-sign"></span></a></h3>
|
||||||
<p><b>$T('restartRequired')</b></p>
|
|
||||||
</div><!-- /col2 -->
|
</div><!-- /col2 -->
|
||||||
<div class="col1">
|
<div class="col1">
|
||||||
<fieldset>
|
<fieldset>
|
||||||
<div class="field-pair">
|
<div class="field-pair">
|
||||||
<h5 class="darkred nomargin">$T('base-folder'): <span class="path">$my_lcldata</span></h5>
|
<label class="config" for="auto_browser">$T('opt-auto_browser')</label>
|
||||||
</div>
|
<input type="checkbox" name="auto_browser" id="auto_browser" value="1" <!--#if int($auto_browser) > 0 then 'checked="checked"' else ""#--> />
|
||||||
<div class="field-pair <!--#if int($have_ssl) == 0 then "disabled" else ""#-->">
|
<span class="desc">$T('explain-auto_browser')</span>
|
||||||
<label class="config" for="enable_https">$T('opt-enable_https')</label>
|
|
||||||
<input type="checkbox" name="enable_https" id="enable_https" value="1" <!--#if int($enable_https) > 0 then 'checked="checked"' else ""#--> <!--#if int($have_ssl) == 0 then "disabled" else ""#--> />
|
|
||||||
<span class="desc">$T('explain-enable_https')</span>
|
|
||||||
</div>
|
</div>
|
||||||
<div class="field-pair">
|
<div class="field-pair">
|
||||||
<label class="config" for="https_port">$T('opt-https_port')</label>
|
<label class="config" for="check_new_rel">$T('opt-check_new_rel')</label>
|
||||||
<input type="number" name="https_port" id="https_port" value="$https_port" size="8" data-original="$https_port" />
|
<select name="check_new_rel" id="check_new_rel">
|
||||||
<span class="desc">$T('explain-https_port')</span>
|
<option value="0" <!--#if $check_new_rel == 0 then 'selected="selected"' else ""#--> >$T('off')</option>
|
||||||
|
<option value="1" <!--#if $check_new_rel == 1 then 'selected="selected"' else ""#--> >$T('on')</option>
|
||||||
|
<option value="2" <!--#if $check_new_rel == 2 then 'selected="selected"' else ""#--> >$T('also-test')</option>
|
||||||
|
</select>
|
||||||
|
<span class="desc">$T('explain-check_new_rel')</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="field-pair">
|
<div class="field-pair <!--#if int($have_ssl_context) == 0 then "disabled" else ""#-->">
|
||||||
<label class="config" for="https_cert">$T('opt-https_cert')</label>
|
<label class="config" for="enable_https_verification">$T('opt-enable_https_verification')</label>
|
||||||
<input type="text" name="https_cert" id="https_cert" value="$https_cert" />
|
<input type="checkbox" name="enable_https_verification" id="enable_https_verification" value="1" <!--#if int($enable_https_verification) > 0 then 'checked="checked"' else ""#--> <!--#if int($have_ssl_context) == 0 then "disabled=\"disabled\"" else ""#--> />
|
||||||
<button class="btn btn-default generate_cert" title="$T('explain-new-cert')"><span class="glyphicon glyphicon-repeat"></span></button>
|
<span class="desc">$T('explain-enable_https_verification')</span>
|
||||||
<span class="desc">$T('explain-https_cert')</span>
|
|
||||||
</div>
|
|
||||||
<div class="field-pair">
|
|
||||||
<label class="config" for="https_key">$T('opt-https_key')</label>
|
|
||||||
<input type="text" name="https_key" id="https_key" value="$https_key" />
|
|
||||||
<button class="btn btn-default generate_cert" title="$T('explain-new-cert')"><span class="glyphicon glyphicon-repeat"></span></button>
|
|
||||||
<span class="desc">$T('explain-https_key')</span>
|
|
||||||
</div>
|
|
||||||
<div class="field-pair">
|
|
||||||
<label class="config" for="https_chain">$T('opt-https_chain')</label>
|
|
||||||
<input type="text" name="https_chain" id="https_chain" value="$https_chain" />
|
|
||||||
<span class="desc">$T('explain-https_chain')</span>
|
|
||||||
</div>
|
</div>
|
||||||
<div class="field-pair">
|
<div class="field-pair">
|
||||||
<button class="btn btn-default saveButton"><span class="glyphicon glyphicon-ok"></span> $T('button-saveChanges')</button>
|
<button class="btn btn-default saveButton"><span class="glyphicon glyphicon-ok"></span> $T('button-saveChanges')</button>
|
||||||
<button class="btn btn-default sabnzbd_restart"><span class="glyphicon glyphicon-refresh"></span> $T('button-restart') SABnzbd</button>
|
|
||||||
</div>
|
</div>
|
||||||
</fieldset>
|
</fieldset>
|
||||||
</div><!-- /col1 -->
|
</div><!-- /col1 -->
|
||||||
@@ -170,10 +190,15 @@
|
|||||||
</div><!-- /col2 -->
|
</div><!-- /col2 -->
|
||||||
<div class="col1">
|
<div class="col1">
|
||||||
<fieldset>
|
<fieldset>
|
||||||
<div class="field-pair">
|
<div class="field-pair value-and-select">
|
||||||
<label class="config" for="bandwidth_max">$T('opt-bandwidth_max')</label>
|
<label class="config" for="bandwidth_max_value">$T('opt-bandwidth_max')</label>
|
||||||
<input type="text" name="bandwidth_max" id="bandwidth_max" value="$bandwidth_max" class="smaller_input" />
|
<input type="number" name="bandwidth_max_value" id="bandwidth_max_value" class="smaller_input" />
|
||||||
<span class="desc">$T('explain-bandwidth_max')<br />$T('wizard-bandwidth-explain')</span>
|
<select name="bandwidth_max_dropdown" id="bandwidth_max_dropdown">
|
||||||
|
<option value="">B/s</option>
|
||||||
|
<option value="K">KB/s</option>
|
||||||
|
<option value="M" selected>MB/s</option>
|
||||||
|
</select>
|
||||||
|
<input type="hidden" name="bandwidth_max" id="bandwidth_max" value="$bandwidth_max" />
|
||||||
</div>
|
</div>
|
||||||
<div class="field-pair">
|
<div class="field-pair">
|
||||||
<label class="config" for="bandwidth_perc">$T('opt-bandwidth_perc')</label>
|
<label class="config" for="bandwidth_perc">$T('opt-bandwidth_perc')</label>
|
||||||
@@ -191,6 +216,7 @@
|
|||||||
</fieldset>
|
</fieldset>
|
||||||
</div><!-- /col1 -->
|
</div><!-- /col1 -->
|
||||||
</div><!-- /section -->
|
</div><!-- /section -->
|
||||||
|
|
||||||
</form>
|
</form>
|
||||||
</div><!-- /colmask -->
|
</div><!-- /colmask -->
|
||||||
|
|
||||||
@@ -214,11 +240,21 @@
|
|||||||
\$('.alert-translate').show()
|
\$('.alert-translate').show()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
\$('#language').on('change', hideOrShowTranslate)
|
\$('#language').on('change', function() {
|
||||||
|
// Show message
|
||||||
|
hideOrShowTranslate()
|
||||||
|
// Re-load page on submit
|
||||||
|
\$('.fullform').submit(function() {
|
||||||
|
// Skip the fancy stuff, just submit
|
||||||
|
this.submit()
|
||||||
|
})
|
||||||
|
// No JSON reponse
|
||||||
|
\$('#ajax').val('')
|
||||||
|
})
|
||||||
hideOrShowTranslate()
|
hideOrShowTranslate()
|
||||||
|
|
||||||
\$('#apikey, #nzbkey').click(function () { \$(this).select() });
|
\$('#apikey, #nzbkey').click(function () { \$(this).select() });
|
||||||
|
|
||||||
\$('#generate_new_apikey').click(function () {
|
\$('#generate_new_apikey').click(function () {
|
||||||
if (confirm("$T('Plush-confirm')")) {
|
if (confirm("$T('Plush-confirm')")) {
|
||||||
$.ajax({
|
$.ajax({
|
||||||
@@ -255,9 +291,9 @@
|
|||||||
"text": \$(this).attr('rel')
|
"text": \$(this).attr('rel')
|
||||||
});
|
});
|
||||||
\$('#modal_qr').modal('show');
|
\$('#modal_qr').modal('show');
|
||||||
|
|
||||||
// No save on this button click
|
// No save on this button click
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
});
|
});
|
||||||
|
|
||||||
\$('.generate_cert').click(function(e) {
|
\$('.generate_cert').click(function(e) {
|
||||||
@@ -282,6 +318,28 @@
|
|||||||
if(\$('#https_cert').val() != 'server.cert') {
|
if(\$('#https_cert').val() != 'server.cert') {
|
||||||
\$('.generate_cert').attr('disabled', 'disabled')
|
\$('.generate_cert').attr('disabled', 'disabled')
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Parse the text
|
||||||
|
var bandwidthLimit = \$('#bandwidth_max').val()
|
||||||
|
if(bandwidthLimit) {
|
||||||
|
var bandwithLimitNumber = parseFloat(bandwidthLimit)
|
||||||
|
var bandwithLimitText = bandwidthLimit.replace(/[^a-zA-Z]+/g, '');
|
||||||
|
if(bandwithLimitNumber) {
|
||||||
|
\$('#bandwidth_max_value').val(bandwithLimitNumber)
|
||||||
|
\$('#bandwidth_max_dropdown').val(bandwithLimitText)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// Update the value
|
||||||
|
\$('#bandwidth_max_value, #bandwidth_max_dropdown').on('change', function() {
|
||||||
|
if(\$('#bandwidth_max_value').val()) {
|
||||||
|
\$('#bandwidth_max').val(\$('#bandwidth_max_value').val() + \$('#bandwidth_max_dropdown').val())
|
||||||
|
} else {
|
||||||
|
\$('#bandwidth_max').val('')
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|||||||
@@ -1,7 +1,30 @@
|
|||||||
<!--#set global $pane="Email"#-->
|
<!--#set global $pane="Email"#-->
|
||||||
<!--#set global $help_uri="configuration/1.1/notifications"#-->
|
<!--#set global $help_uri="configuration/2.2/notifications"#-->
|
||||||
<!--#include $webdir + "/_inc_header_uc.tmpl"#-->
|
<!--#include $webdir + "/_inc_header_uc.tmpl"#-->
|
||||||
|
|
||||||
|
<!--#def show_notify_checkboxes($section_label)#-->
|
||||||
|
<!--#for $type in $notify_keys#-->
|
||||||
|
<div class="field-pair">
|
||||||
|
<label class="config wide" for="${section_label}_prio_$type">
|
||||||
|
$T($notify_texts[$type]).replace('/', ' / ') <!--#if $type == 'download'#--> / $T('link-pause') / $T('link-resume')<!--#end if#-->
|
||||||
|
</label>
|
||||||
|
<input type="checkbox" name="${section_label}_prio_$type" id="${section_label}_prio_$type" value="1" <!--#if int($getVar($section_label + '_prio_' + $type)) > 0 then 'checked="checked"' else ""#--> />
|
||||||
|
</div>
|
||||||
|
<!--#end for#-->
|
||||||
|
<!--#end def#-->
|
||||||
|
|
||||||
|
<!--#def show_cat_box($section_label)#-->
|
||||||
|
<div class="col2-cats" <!--#if int($getVar($section_label + '_enable')) > 0 then '' else 'style="display:none"'#-->>
|
||||||
|
<hr>
|
||||||
|
<b>$T('affectedCat')</b><br/>
|
||||||
|
<select name="${section_label}_cats" multiple="multiple" class="multiple_cats">
|
||||||
|
<!--#for $ct in $categories#-->
|
||||||
|
<option value="$ct" <!--#if $ct in $getVar($section_label + '_cats') then 'selected="selected"' else ""#-->>$Tspec($ct)</option>
|
||||||
|
<!--#end for#-->
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
<!--#end def#-->
|
||||||
|
|
||||||
<div class="colmask">
|
<div class="colmask">
|
||||||
<form action="saveEmail" method="post" name="fullform" class="fullform" autocomplete="off" novalidate>
|
<form action="saveEmail" method="post" name="fullform" class="fullform" autocomplete="off" novalidate>
|
||||||
<input type="hidden" id="session" name="session" value="$session" />
|
<input type="hidden" id="session" name="session" value="$session" />
|
||||||
@@ -9,7 +32,15 @@
|
|||||||
<div class="section" id="email">
|
<div class="section" id="email">
|
||||||
<div class="col2">
|
<div class="col2">
|
||||||
<h3>$T('cmenu-email') <a href="$helpuri$help_uri#toc0" target="_blank"><span class="glyphicon glyphicon-question-sign"></span></a></h3>
|
<h3>$T('cmenu-email') <a href="$helpuri$help_uri#toc0" target="_blank"><span class="glyphicon glyphicon-question-sign"></span></a></h3>
|
||||||
</div><!-- /col2 -->
|
<div class="col2-cats" <!--#if int($email_endjob) > 0 then '' else 'style="display:none"'#-->>
|
||||||
|
<b>$T('affectedCat')</b><br/>
|
||||||
|
<select name="email_cats" multiple="multiple" class="multiple_cats">
|
||||||
|
<!--#for $ct in $categories#-->
|
||||||
|
<option value="$ct" <!--#if $ct in $email_cats then 'selected="selected"' else ""#-->>$Tspec($ct)</option>
|
||||||
|
<!--#end for#-->
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
<div class="col1">
|
<div class="col1">
|
||||||
<fieldset>
|
<fieldset>
|
||||||
<div class="field-pair">
|
<div class="field-pair">
|
||||||
@@ -38,7 +69,7 @@
|
|||||||
<div class="field-pair">
|
<div class="field-pair">
|
||||||
<label class="config" for="email_server">$T('opt-email_server')</label>
|
<label class="config" for="email_server">$T('opt-email_server')</label>
|
||||||
<input type="text" name="email_server" id="email_server" value="$email_server" />
|
<input type="text" name="email_server" id="email_server" value="$email_server" />
|
||||||
<span class="desc">$T('explain-email_server') ($T('host').lower():$T('srv-port').lower())</span>
|
<span class="desc">$T('explain-email_server') ($T('host'):$T('srv-port'))</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="field-pair">
|
<div class="field-pair">
|
||||||
<label class="config" for="email_to">$T('opt-email_to')</label>
|
<label class="config" for="email_to">$T('opt-email_to')</label>
|
||||||
@@ -68,8 +99,8 @@
|
|||||||
<div class="alert"></div>
|
<div class="alert"></div>
|
||||||
</div>
|
</div>
|
||||||
</fieldset>
|
</fieldset>
|
||||||
</div><!-- /col1 -->
|
</div>
|
||||||
</div><!-- /section -->
|
</div>
|
||||||
<!--#if $have_ncenter#-->
|
<!--#if $have_ncenter#-->
|
||||||
<div class="section">
|
<div class="section">
|
||||||
<div class="col2">
|
<div class="col2">
|
||||||
@@ -80,53 +111,10 @@
|
|||||||
<td><label for="ncenter_enable"> $T('opt-ncenter_enable')</label></td>
|
<td><label for="ncenter_enable"> $T('opt-ncenter_enable')</label></td>
|
||||||
</tr>
|
</tr>
|
||||||
</table>
|
</table>
|
||||||
</div><!-- /col2 -->
|
</div>
|
||||||
<div class="col1" <!--#if int($ncenter_enable) > 0 then '' else 'style="display:none"'#-->>
|
<div class="col1" <!--#if int($ncenter_enable) > 0 then '' else 'style="display:none"'#-->>
|
||||||
<fieldset>
|
<fieldset>
|
||||||
<div class="field-pair">
|
$show_notify_checkboxes('ncenter')
|
||||||
<label class="config wide" for="ncenter_prio_startup">$T($notify_texts['startup']).replace('/', ' / ')</label>
|
|
||||||
<input type="checkbox" name="ncenter_prio_startup" id="ncenter_prio_startup" value="1" <!--#if int($ncenter_prio_startup) > 0 then 'checked="checked"' else ""#--> />
|
|
||||||
</div>
|
|
||||||
<div class="field-pair">
|
|
||||||
<label class="config wide" for="ncenter_prio_download">$T($notify_texts['download']) / $T('link-pause') / $T('link-resume')</label>
|
|
||||||
<input type="checkbox" name="ncenter_prio_download" id="ncenter_prio_download" value="1" <!--#if int($ncenter_prio_download) > 0 then 'checked="checked"' else ""#--> />
|
|
||||||
</div>
|
|
||||||
<div class="field-pair">
|
|
||||||
<label class="config wide" for="ncenter_prio_pp">$T($notify_texts['pp'])</label>
|
|
||||||
<input type="checkbox" name="ncenter_prio_pp" id="ncenter_prio_pp" value="1" <!--#if int($ncenter_prio_pp) > 0 then 'checked="checked"' else ""#--> />
|
|
||||||
</div>
|
|
||||||
<div class="field-pair">
|
|
||||||
<label class="config wide" for="ncenter_prio_complete">$T($notify_texts['complete'])</label>
|
|
||||||
<input type="checkbox" name="ncenter_prio_complete" id="ncenter_prio_complete" value="1" <!--#if int($ncenter_prio_complete) > 0 then 'checked="checked"' else ""#--> />
|
|
||||||
</div>
|
|
||||||
<div class="field-pair">
|
|
||||||
<label class="config wide" for="ncenter_prio_failed">$T($notify_texts['failed'])</label>
|
|
||||||
<input type="checkbox" name="ncenter_prio_failed" id="ncenter_prio_failed" value="1" <!--#if int($ncenter_prio_failed) > 0 then 'checked="checked"' else ""#--> />
|
|
||||||
</div>
|
|
||||||
<div class="field-pair">
|
|
||||||
<label class="config wide" for="ncenter_prio_queue_done">$T($notify_texts['queue_done'])</label>
|
|
||||||
<input type="checkbox" name="ncenter_prio_queue_done" id="ncenter_prio_queue_done" value="1" <!--#if int($ncenter_prio_queue_done) > 0 then 'checked="checked"' else ""#--> />
|
|
||||||
</div>
|
|
||||||
<div class="field-pair">
|
|
||||||
<label class="config wide" for="ncenter_prio_disk_full">$T($notify_texts['disk_full'])</label>
|
|
||||||
<input type="checkbox" name="ncenter_prio_disk_full" id="ncenter_prio_disk_full" value="1" <!--#if int($ncenter_prio_disk_full) > 0 then 'checked="checked"' else ""#--> />
|
|
||||||
</div>
|
|
||||||
<div class="field-pair">
|
|
||||||
<label class="config wide" for="ncenter_prio_new_login">$T($notify_texts['new_login'])</label>
|
|
||||||
<input type="checkbox" name="ncenter_prio_new_login" id="ncenter_prio_new_login" value="1" <!--#if int($ncenter_prio_new_login) > 0 then 'checked="checked"' else ""#--> />
|
|
||||||
</div>
|
|
||||||
<div class="field-pair">
|
|
||||||
<label class="config wide" for="ncenter_prio_warning">$T($notify_texts['warning'])</label>
|
|
||||||
<input type="checkbox" name="ncenter_prio_warning" id="ncenter_prio_warning" value="1" <!--#if int($ncenter_prio_warning) > 0 then 'checked="checked"' else ""#--> />
|
|
||||||
</div>
|
|
||||||
<div class="field-pair">
|
|
||||||
<label class="config wide" for="ncenter_prio_error">$T($notify_texts['error'])</label>
|
|
||||||
<input type="checkbox" name="ncenter_prio_error" id="ncenter_prio_error" value="1" <!--#if int($ncenter_prio_error) > 0 then 'checked="checked"' else ""#--> />
|
|
||||||
</div>
|
|
||||||
<div class="field-pair">
|
|
||||||
<label class="config wide" for="ncenter_prio_other">$T($notify_texts['other'])</label>
|
|
||||||
<input type="checkbox" name="ncenter_prio_other" id="ncenter_prio_other" value="1" <!--#if int($ncenter_prio_other) > 0 then 'checked="checked"' else ""#--> />
|
|
||||||
</div>
|
|
||||||
<div class="field-pair">
|
<div class="field-pair">
|
||||||
<button class="btn btn-default saveButton"><span class="glyphicon glyphicon-ok"></span> $T('button-saveChanges')</button>
|
<button class="btn btn-default saveButton"><span class="glyphicon glyphicon-ok"></span> $T('button-saveChanges')</button>
|
||||||
<button class="btn btn-default" type="button" id="test_notif"><span class="glyphicon glyphicon-comment"></span> $T('testNotify')</button>
|
<button class="btn btn-default" type="button" id="test_notif"><span class="glyphicon glyphicon-comment"></span> $T('testNotify')</button>
|
||||||
@@ -135,8 +123,8 @@
|
|||||||
<div class="alert"></div>
|
<div class="alert"></div>
|
||||||
</div>
|
</div>
|
||||||
</fieldset>
|
</fieldset>
|
||||||
</div><!-- /col1 -->
|
</div>
|
||||||
</div><!-- /section -->
|
</div>
|
||||||
<!--#end if#-->
|
<!--#end if#-->
|
||||||
<!--#if $nt#-->
|
<!--#if $nt#-->
|
||||||
<div class="section">
|
<div class="section">
|
||||||
@@ -148,53 +136,11 @@
|
|||||||
<td><label for="acenter_enable"> $T('opt-acenter_enable')</label></td>
|
<td><label for="acenter_enable"> $T('opt-acenter_enable')</label></td>
|
||||||
</tr>
|
</tr>
|
||||||
</table>
|
</table>
|
||||||
</div><!-- /col2 -->
|
$show_cat_box('acenter')
|
||||||
|
</div>
|
||||||
<div class="col1" <!--#if int($acenter_enable) > 0 then '' else 'style="display:none"'#-->>
|
<div class="col1" <!--#if int($acenter_enable) > 0 then '' else 'style="display:none"'#-->>
|
||||||
<fieldset>
|
<fieldset>
|
||||||
<div class="field-pair">
|
$show_notify_checkboxes('acenter')
|
||||||
<label class="config wide" for="acenter_prio_startup">$T($notify_texts['startup']).replace('/', ' / ')</label>
|
|
||||||
<input type="checkbox" name="acenter_prio_startup" id="acenter_prio_startup" value="1" <!--#if int($acenter_prio_startup) > 0 then 'checked="checked"' else ""#--> />
|
|
||||||
</div>
|
|
||||||
<div class="field-pair">
|
|
||||||
<label class="config wide" for="acenter_prio_download">$T($notify_texts['download']) / $T('link-pause') / $T('link-resume')</label>
|
|
||||||
<input type="checkbox" name="acenter_prio_download" id="acenter_prio_download" value="1" <!--#if int($acenter_prio_download) > 0 then 'checked="checked"' else ""#--> />
|
|
||||||
</div>
|
|
||||||
<div class="field-pair">
|
|
||||||
<label class="config wide" for="acenter_prio_pp">$T($notify_texts['pp'])</label>
|
|
||||||
<input type="checkbox" name="acenter_prio_pp" id="acenter_prio_pp" value="1" <!--#if int($acenter_prio_pp) > 0 then 'checked="checked"' else ""#--> />
|
|
||||||
</div>
|
|
||||||
<div class="field-pair">
|
|
||||||
<label class="config wide" for="acenter_prio_complete">$T($notify_texts['complete'])</label>
|
|
||||||
<input type="checkbox" name="acenter_prio_complete" id="acenter_prio_complete" value="1" <!--#if int($acenter_prio_complete) > 0 then 'checked="checked"' else ""#--> />
|
|
||||||
</div>
|
|
||||||
<div class="field-pair">
|
|
||||||
<label class="config wide" for="acenter_prio_failed">$T($notify_texts['failed'])</label>
|
|
||||||
<input type="checkbox" name="acenter_prio_failed" id="acenter_prio_failed" value="1" <!--#if int($acenter_prio_failed) > 0 then 'checked="checked"' else ""#--> />
|
|
||||||
</div>
|
|
||||||
<div class="field-pair">
|
|
||||||
<label class="config wide" for="acenter_prio_queue_done">$T($notify_texts['queue_done'])</label>
|
|
||||||
<input type="checkbox" name="acenter_prio_queue_done" id="acenter_prio_queue_done" value="1" <!--#if int($acenter_prio_queue_done) > 0 then 'checked="checked"' else ""#--> />
|
|
||||||
</div>
|
|
||||||
<div class="field-pair">
|
|
||||||
<label class="config wide" for="acenter_prio_disk_full">$T($notify_texts['disk_full'])</label>
|
|
||||||
<input type="checkbox" name="acenter_prio_disk_full" id="acenter_prio_disk_full" value="1" <!--#if int($acenter_prio_disk_full) > 0 then 'checked="checked"' else ""#--> />
|
|
||||||
</div>
|
|
||||||
<div class="field-pair">
|
|
||||||
<label class="config wide" for="acenter_prio_new_login">$T($notify_texts['new_login'])</label>
|
|
||||||
<input type="checkbox" name="acenter_prio_new_login" id="acenter_prio_new_login" value="1" <!--#if int($acenter_prio_new_login) > 0 then 'checked="checked"' else ""#--> />
|
|
||||||
</div>
|
|
||||||
<div class="field-pair">
|
|
||||||
<label class="config wide" for="acenter_prio_warning">$T($notify_texts['warning'])</label>
|
|
||||||
<input type="checkbox" name="acenter_prio_warning" id="acenter_prio_warning" value="1" <!--#if int($acenter_prio_warning) > 0 then 'checked="checked"' else ""#--> />
|
|
||||||
</div>
|
|
||||||
<div class="field-pair">
|
|
||||||
<label class="config wide" for="acenter_prio_error">$T($notify_texts['error'])</label>
|
|
||||||
<input type="checkbox" name="acenter_prio_error" id="acenter_prio_error" value="1" <!--#if int($acenter_prio_error) > 0 then 'checked="checked"' else ""#--> />
|
|
||||||
</div>
|
|
||||||
<div class="field-pair">
|
|
||||||
<label class="config wide" for="acenter_prio_other">$T($notify_texts['other'])</label>
|
|
||||||
<input type="checkbox" name="acenter_prio_other" id="acenter_prio_other" value="1" <!--#if int($acenter_prio_other) > 0 then 'checked="checked"' else ""#--> />
|
|
||||||
</div>
|
|
||||||
<div class="field-pair">
|
<div class="field-pair">
|
||||||
<button class="btn btn-default saveButton"><span class="glyphicon glyphicon-ok"></span> $T('button-saveChanges')</button>
|
<button class="btn btn-default saveButton"><span class="glyphicon glyphicon-ok"></span> $T('button-saveChanges')</button>
|
||||||
<button class="btn btn-default" type="button" id="test_windows"><span class="glyphicon glyphicon-comment"></span> $T('testNotify')</button>
|
<button class="btn btn-default" type="button" id="test_windows"><span class="glyphicon glyphicon-comment"></span> $T('testNotify')</button>
|
||||||
@@ -203,8 +149,8 @@
|
|||||||
<div class="alert"></div>
|
<div class="alert"></div>
|
||||||
</div>
|
</div>
|
||||||
</fieldset>
|
</fieldset>
|
||||||
</div><!-- /col1 -->
|
</div>
|
||||||
</div><!-- /section -->
|
</div>
|
||||||
<!--#end if#-->
|
<!--#end if#-->
|
||||||
<!--#if $have_ntfosd#-->
|
<!--#if $have_ntfosd#-->
|
||||||
<div class="section">
|
<div class="section">
|
||||||
@@ -216,53 +162,11 @@
|
|||||||
<td><label for="ntfosd_enable"> $T('opt-ntfosd_enable')</label></td>
|
<td><label for="ntfosd_enable"> $T('opt-ntfosd_enable')</label></td>
|
||||||
</tr>
|
</tr>
|
||||||
</table>
|
</table>
|
||||||
</div><!-- /col2 -->
|
$show_cat_box('ntfosd')
|
||||||
|
</div>
|
||||||
<div class="col1" <!--#if int($ntfosd_enable) > 0 then '' else 'style="display:none"'#-->>
|
<div class="col1" <!--#if int($ntfosd_enable) > 0 then '' else 'style="display:none"'#-->>
|
||||||
<fieldset>
|
<fieldset>
|
||||||
<div class="field-pair">
|
$show_notify_checkboxes('ntfosd')
|
||||||
<label class="config wide" for="ntfosd_prio_startup">$T($notify_texts['startup']).replace('/', ' / ')</label>
|
|
||||||
<input type="checkbox" name="ntfosd_prio_startup" id="ntfosd_prio_startup" value="1" <!--#if int($ntfosd_prio_startup) > 0 then 'checked="checked"' else ""#--> />
|
|
||||||
</div>
|
|
||||||
<div class="field-pair">
|
|
||||||
<label class="config wide" for="ntfosd_prio_download">$T($notify_texts['download']) / $T('link-pause') / $T('link-resume')</label>
|
|
||||||
<input type="checkbox" name="ntfosd_prio_download" id="ntfosd_prio_download" value="1" <!--#if int($ntfosd_prio_download) > 0 then 'checked="checked"' else ""#--> />
|
|
||||||
</div>
|
|
||||||
<div class="field-pair">
|
|
||||||
<label class="config wide" for="ntfosd_prio_pp">$T($notify_texts['pp'])</label>
|
|
||||||
<input type="checkbox" name="ntfosd_prio_pp" id="ntfosd_prio_pp" value="1" <!--#if int($ntfosd_prio_pp) > 0 then 'checked="checked"' else ""#--> />
|
|
||||||
</div>
|
|
||||||
<div class="field-pair">
|
|
||||||
<label class="config wide" for="ntfosd_prio_complete">$T($notify_texts['complete'])</label>
|
|
||||||
<input type="checkbox" name="ntfosd_prio_complete" id="ntfosd_prio_complete" value="1" <!--#if int($ntfosd_prio_complete) > 0 then 'checked="checked"' else ""#--> />
|
|
||||||
</div>
|
|
||||||
<div class="field-pair">
|
|
||||||
<label class="config wide" for="ntfosd_prio_failed">$T($notify_texts['failed'])</label>
|
|
||||||
<input type="checkbox" name="ntfosd_prio_failed" id="ntfosd_prio_failed" value="1" <!--#if int($ntfosd_prio_failed) > 0 then 'checked="checked"' else ""#--> />
|
|
||||||
</div>
|
|
||||||
<div class="field-pair">
|
|
||||||
<label class="config wide" for="ntfosd_prio_queue_done">$T($notify_texts['queue_done'])</label>
|
|
||||||
<input type="checkbox" name="ntfosd_prio_queue_done" id="ntfosd_prio_queue_done" value="1" <!--#if int($ntfosd_prio_queue_done) > 0 then 'checked="checked"' else ""#--> />
|
|
||||||
</div>
|
|
||||||
<div class="field-pair">
|
|
||||||
<label class="config wide" for="ntfosd_prio_disk_full">$T($notify_texts['disk_full'])</label>
|
|
||||||
<input type="checkbox" name="ntfosd_prio_disk_full" id="ntfosd_prio_disk_full" value="1" <!--#if int($ntfosd_prio_disk_full) > 0 then 'checked="checked"' else ""#--> />
|
|
||||||
</div>
|
|
||||||
<div class="field-pair">
|
|
||||||
<label class="config wide" for="ntfosd_prio_new_login">$T($notify_texts['new_login'])</label>
|
|
||||||
<input type="checkbox" name="ntfosd_prio_new_login" id="ntfosd_prio_new_login" value="1" <!--#if int($ntfosd_prio_new_login) > 0 then 'checked="checked"' else ""#--> />
|
|
||||||
</div>
|
|
||||||
<div class="field-pair">
|
|
||||||
<label class="config wide" for="ntfosd_prio_warning">$T($notify_texts['warning'])</label>
|
|
||||||
<input type="checkbox" name="ntfosd_prio_warning" id="ntfosd_prio_warning" value="1" <!--#if int($ntfosd_prio_warning) > 0 then 'checked="checked"' else ""#--> />
|
|
||||||
</div>
|
|
||||||
<div class="field-pair">
|
|
||||||
<label class="config wide" for="ntfosd_prio_error">$T($notify_texts['error'])</label>
|
|
||||||
<input type="checkbox" name="ntfosd_prio_error" id="ntfosd_prio_error" value="1" <!--#if int($ntfosd_prio_error) > 0 then 'checked="checked"' else ""#--> />
|
|
||||||
</div>
|
|
||||||
<div class="field-pair">
|
|
||||||
<label class="config wide" for="ntfosd_prio_other">$T($notify_texts['other'])</label>
|
|
||||||
<input type="checkbox" name="ntfosd_prio_other" id="ntfosd_prio_other" value="1" <!--#if int($ntfosd_prio_other) > 0 then 'checked="checked"' else ""#--> />
|
|
||||||
</div>
|
|
||||||
<div class="field-pair">
|
<div class="field-pair">
|
||||||
<button class="btn btn-default saveButton"><span class="glyphicon glyphicon-ok"></span> $T('button-saveChanges')</button>
|
<button class="btn btn-default saveButton"><span class="glyphicon glyphicon-ok"></span> $T('button-saveChanges')</button>
|
||||||
<button class="btn btn-default" type="button" id="test_osd"><span class="glyphicon glyphicon-comment"></span> $T('testNotify')</button>
|
<button class="btn btn-default" type="button" id="test_osd"><span class="glyphicon glyphicon-comment"></span> $T('testNotify')</button>
|
||||||
@@ -271,9 +175,48 @@
|
|||||||
<div class="alert"></div>
|
<div class="alert"></div>
|
||||||
</div>
|
</div>
|
||||||
</fieldset>
|
</fieldset>
|
||||||
</div><!-- /col1 -->
|
</div>
|
||||||
</div><!-- /section -->
|
</div>
|
||||||
<!--#end if#-->
|
<!--#end if#-->
|
||||||
|
<div class="section" id="nscript">
|
||||||
|
<div class="col2">
|
||||||
|
<h3>$T('section-NScript')</h3>
|
||||||
|
<table>
|
||||||
|
<tr>
|
||||||
|
<td><input type="checkbox" name="nscript_enable" id="nscript_enable" value="1" <!--#if int($nscript_enable) > 0 then 'checked="checked"' else ""#--> /></td>
|
||||||
|
<td><label for="nscript_enable"> $T('opt-nscript_enable')</label></td>
|
||||||
|
</tr>
|
||||||
|
</table>
|
||||||
|
<em>$T('explain-nscript_enable')</em><br><a href="$helpuri$help_uri#nscript" target="_blank">$T('readwiki')</a>
|
||||||
|
$show_cat_box('nscript')
|
||||||
|
</div>
|
||||||
|
<div class="col1" <!--#if int($nscript_enable) > 0 then '' else 'style="display:none"'#-->>
|
||||||
|
<fieldset>
|
||||||
|
<div class="field-pair">
|
||||||
|
<label class="config" for="nscript_script">$T('opt-nscript_script')</label>
|
||||||
|
<select name="nscript_script">
|
||||||
|
<!--#for $sc in $scripts#-->
|
||||||
|
<option value="$sc" <!--#if $nscript_script == $sc then 'selected="selected"' else ""#-->>$Tspec($sc)</option>
|
||||||
|
<!--#end for#-->
|
||||||
|
</select>
|
||||||
|
<span class="desc">$T('explain-nscript_script')</span>
|
||||||
|
</div>
|
||||||
|
<div class="field-pair">
|
||||||
|
<label class="config" for="nscript_parameters">$T('opt-nscript_parameters')</label>
|
||||||
|
<input type="text" name="nscript_parameters" id="nscript_parameters" value="$nscript_parameters" />
|
||||||
|
<span class="desc">$T('Optional') - $T('explain-nscript_parameters')</span>
|
||||||
|
</div>
|
||||||
|
$show_notify_checkboxes('nscript')
|
||||||
|
<div class="field-pair">
|
||||||
|
<button class="btn btn-default saveButton"><span class="glyphicon glyphicon-ok"></span> $T('button-saveChanges')</button>
|
||||||
|
<button class="btn btn-default" type="button" id="test_nscript"><span class="glyphicon glyphicon-comment"></span> $T('testNotify')</button>
|
||||||
|
</div>
|
||||||
|
<div class="field-pair result-box">
|
||||||
|
<div class="alert"></div>
|
||||||
|
</div>
|
||||||
|
</fieldset>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
<div class="section" id="growl">
|
<div class="section" id="growl">
|
||||||
<div class="col2">
|
<div class="col2">
|
||||||
<h3>$T('growlSettings') <a href="$helpuri$help_uri#toc3" target="_blank"><span class="glyphicon glyphicon-question-sign"></span></a></h3>
|
<h3>$T('growlSettings') <a href="$helpuri$help_uri#toc3" target="_blank"><span class="glyphicon glyphicon-question-sign"></span></a></h3>
|
||||||
@@ -283,7 +226,8 @@
|
|||||||
<td><label for="growl_enable"> $T('opt-growl_enable')</label></td>
|
<td><label for="growl_enable"> $T('opt-growl_enable')</label></td>
|
||||||
</tr>
|
</tr>
|
||||||
</table>
|
</table>
|
||||||
</div><!-- /col2 -->
|
$show_cat_box('growl')
|
||||||
|
</div>
|
||||||
<div class="col1" <!--#if int($growl_enable) > 0 then '' else 'style="display:none"'#-->>
|
<div class="col1" <!--#if int($growl_enable) > 0 then '' else 'style="display:none"'#-->>
|
||||||
<fieldset>
|
<fieldset>
|
||||||
<div class="field-pair">
|
<div class="field-pair">
|
||||||
@@ -296,50 +240,7 @@
|
|||||||
<input type="text" name="growl_password" id="growl_password" value="$growl_password" />
|
<input type="text" name="growl_password" id="growl_password" value="$growl_password" />
|
||||||
<span class="desc">$T('explain-growl_password')</span>
|
<span class="desc">$T('explain-growl_password')</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="field-pair">
|
$show_notify_checkboxes('growl')
|
||||||
<label class="config wide" for="growl_prio_startup">$T($notify_texts['startup']).replace('/', ' / ')</label>
|
|
||||||
<input type="checkbox" name="growl_prio_startup" id="growl_prio_startup" value="1" <!--#if int($growl_prio_startup) > 0 then 'checked="checked"' else ""#--> />
|
|
||||||
</div>
|
|
||||||
<div class="field-pair">
|
|
||||||
<label class="config wide" for="growl_prio_download">$T($notify_texts['download']) / $T('link-pause') / $T('link-resume')</label>
|
|
||||||
<input type="checkbox" name="growl_prio_download" id="growl_prio_download" value="1" <!--#if int($growl_prio_download) > 0 then 'checked="checked"' else ""#--> />
|
|
||||||
</div>
|
|
||||||
<div class="field-pair">
|
|
||||||
<label class="config wide" for="growl_prio_pp">$T($notify_texts['pp'])</label>
|
|
||||||
<input type="checkbox" name="growl_prio_pp" id="growl_prio_pp" value="1" <!--#if int($growl_prio_pp) > 0 then 'checked="checked"' else ""#--> />
|
|
||||||
</div>
|
|
||||||
<div class="field-pair">
|
|
||||||
<label class="config wide" for="growl_prio_complete">$T($notify_texts['complete'])</label>
|
|
||||||
<input type="checkbox" name="growl_prio_complete" id="growl_prio_complete" value="1" <!--#if int($growl_prio_complete) > 0 then 'checked="checked"' else ""#--> />
|
|
||||||
</div>
|
|
||||||
<div class="field-pair">
|
|
||||||
<label class="config wide" for="growl_prio_failed">$T($notify_texts['failed'])</label>
|
|
||||||
<input type="checkbox" name="growl_prio_failed" id="growl_prio_failed" value="1" <!--#if int($growl_prio_failed) > 0 then 'checked="checked"' else ""#--> />
|
|
||||||
</div>
|
|
||||||
<div class="field-pair">
|
|
||||||
<label class="config wide" for="growl_prio_queue_done">$T($notify_texts['queue_done'])</label>
|
|
||||||
<input type="checkbox" name="growl_prio_queue_done" id="growl_prio_queue_done" value="1" <!--#if int($growl_prio_queue_done) > 0 then 'checked="checked"' else ""#--> />
|
|
||||||
</div>
|
|
||||||
<div class="field-pair">
|
|
||||||
<label class="config wide" for="growl_prio_disk_full">$T($notify_texts['disk_full'])</label>
|
|
||||||
<input type="checkbox" name="growl_prio_disk_full" id="growl_prio_disk_full" value="1" <!--#if int($growl_prio_disk_full) > 0 then 'checked="checked"' else ""#--> />
|
|
||||||
</div>
|
|
||||||
<div class="field-pair">
|
|
||||||
<label class="config wide" for="growl_prio_new_login">$T($notify_texts['new_login'])</label>
|
|
||||||
<input type="checkbox" name="growl_prio_new_login" id="growl_prio_new_login" value="1" <!--#if int($growl_prio_new_login) > 0 then 'checked="checked"' else ""#--> />
|
|
||||||
</div>
|
|
||||||
<div class="field-pair">
|
|
||||||
<label class="config wide" for="growl_prio_warning">$T($notify_texts['warning'])</label>
|
|
||||||
<input type="checkbox" name="growl_prio_warning" id="growl_prio_warning" value="1" <!--#if int($growl_prio_warning) > 0 then 'checked="checked"' else ""#--> />
|
|
||||||
</div>
|
|
||||||
<div class="field-pair">
|
|
||||||
<label class="config wide" for="growl_prio_error">$T($notify_texts['error'])</label>
|
|
||||||
<input type="checkbox" name="growl_prio_error" id="growl_prio_error" value="1" <!--#if int($growl_prio_error) > 0 then 'checked="checked"' else ""#--> />
|
|
||||||
</div>
|
|
||||||
<div class="field-pair">
|
|
||||||
<label class="config wide" for="growl_prio_other">$T($notify_texts['other'])</label>
|
|
||||||
<input type="checkbox" name="growl_prio_other" id="growl_prio_other" value="1" <!--#if int($growl_prio_other) > 0 then 'checked="checked"' else ""#--> />
|
|
||||||
</div>
|
|
||||||
<div class="field-pair">
|
<div class="field-pair">
|
||||||
<button class="btn btn-default saveButton"><span class="glyphicon glyphicon-ok"></span> $T('button-saveChanges')</button>
|
<button class="btn btn-default saveButton"><span class="glyphicon glyphicon-ok"></span> $T('button-saveChanges')</button>
|
||||||
<button class="btn btn-default" type="button" id="test_growl"><span class="glyphicon glyphicon-comment"></span> $T('testNotify')</button>
|
<button class="btn btn-default" type="button" id="test_growl"><span class="glyphicon glyphicon-comment"></span> $T('testNotify')</button>
|
||||||
@@ -348,8 +249,8 @@
|
|||||||
<div class="alert"></div>
|
<div class="alert"></div>
|
||||||
</div>
|
</div>
|
||||||
</fieldset>
|
</fieldset>
|
||||||
</div><!-- /col1 -->
|
</div>
|
||||||
</div><!-- /section -->
|
</div>
|
||||||
<div class="section" id="prowl">
|
<div class="section" id="prowl">
|
||||||
<div class="col2">
|
<div class="col2">
|
||||||
<h3>$T('section-Prowl')</h3>
|
<h3>$T('section-Prowl')</h3>
|
||||||
@@ -360,7 +261,8 @@
|
|||||||
</tr>
|
</tr>
|
||||||
</table>
|
</table>
|
||||||
<em>$T('explain-prowl_enable')</em>
|
<em>$T('explain-prowl_enable')</em>
|
||||||
</div><!-- /col2 -->
|
$show_cat_box('prowl')
|
||||||
|
</div>
|
||||||
<div class="col1" <!--#if int($prowl_enable) > 0 then '' else 'style="display:none"'#-->>
|
<div class="col1" <!--#if int($prowl_enable) > 0 then '' else 'style="display:none"'#-->>
|
||||||
<fieldset>
|
<fieldset>
|
||||||
<div class="field-pair">
|
<div class="field-pair">
|
||||||
@@ -368,127 +270,22 @@
|
|||||||
<input type="text" name="prowl_apikey" id="prowl_apikey" value="$prowl_apikey" />
|
<input type="text" name="prowl_apikey" id="prowl_apikey" value="$prowl_apikey" />
|
||||||
<span class="desc">$T('explain-prowl_apikey')</span>
|
<span class="desc">$T('explain-prowl_apikey')</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="field-pair">
|
<!--#set $section_label = 'prowl'#-->
|
||||||
<label class="config" for="prowl_prio_startup">$T($notify_texts['startup']).replace('/', ' / ')</label>
|
<!--#for $type in $notify_keys#-->
|
||||||
<select name="prowl_prio_startup" id="prowl_prio_startup">
|
<div class="field-pair">
|
||||||
<option value="-3" <!--#if $prowl_prio_startup == -3 then 'selected="selected"' else ""#--> >$T('prowl-off')</option>
|
<label class="config" for="${section_label}_prio_$type">
|
||||||
<option value="-2" <!--#if $prowl_prio_startup == -2 then 'selected="selected"' else ""#--> >$T('prowl-very-low')</option>
|
$T($notify_texts[$type]).replace('/', ' / ') <!--#if $type == 'download'#--> / $T('link-pause') / $T('link-resume')<!--#end if#-->
|
||||||
<option value="-1" <!--#if $prowl_prio_startup == -1 then 'selected="selected"' else ""#--> >$T('prowl-moderate')</option>
|
</label>
|
||||||
<option value="0" <!--#if $prowl_prio_startup == 0 then 'selected="selected"' else ""#--> >$T('prowl-normal')</option>
|
<select name="${section_label}_prio_$type" id="${section_label}_prio_$type">
|
||||||
<option value="1" <!--#if $prowl_prio_startup == 1 then 'selected="selected"' else ""#--> >$T('prowl-high')</option>
|
<option value="-3" <!--#if $getVar($section_label + '_prio_' + $type) == -3 then 'selected="selected"' else ""#--> >$T('prowl-off')</option>
|
||||||
<option value="2" <!--#if $prowl_prio_startup == 2 then 'selected="selected"' else ""#--> >$T('prowl-emergency')</option>
|
<option value="-2" <!--#if $getVar($section_label + '_prio_' + $type) == -2 then 'selected="selected"' else ""#--> >$T('prowl-very-low')</option>
|
||||||
</select>
|
<option value="-1" <!--#if $getVar($section_label + '_prio_' + $type) == -1 then 'selected="selected"' else ""#--> >$T('prowl-moderate')</option>
|
||||||
</div>
|
<option value="0" <!--#if $getVar($section_label + '_prio_' + $type) == 0 then 'selected="selected"' else ""#--> >$T('prowl-normal')</option>
|
||||||
<div class="field-pair">
|
<option value="1" <!--#if $getVar($section_label + '_prio_' + $type) == 1 then 'selected="selected"' else ""#--> >$T('prowl-high')</option>
|
||||||
<label class="config" for="prowl_prio_download">$T($notify_texts['download']) / $T('link-pause') / $T('link-resume')</label>
|
<option value="2" <!--#if $getVar($section_label + '_prio_' + $type) == 2 then 'selected="selected"' else ""#--> >$T('prowl-emergency')</option>
|
||||||
<select name="prowl_prio_download" id="prowl_prio_download">
|
</select>
|
||||||
<option value="-3" <!--#if $prowl_prio_download == -3 then 'selected="selected"' else ""#--> >$T('prowl-off')</option>
|
</div>
|
||||||
<option value="-2" <!--#if $prowl_prio_download == -2 then 'selected="selected"' else ""#--> >$T('prowl-very-low')</option>
|
<!--#end for#-->
|
||||||
<option value="-1" <!--#if $prowl_prio_download == -1 then 'selected="selected"' else ""#--> >$T('prowl-moderate')</option>
|
|
||||||
<option value="0" <!--#if $prowl_prio_download == 0 then 'selected="selected"' else ""#--> >$T('prowl-normal')</option>
|
|
||||||
<option value="1" <!--#if $prowl_prio_download == 1 then 'selected="selected"' else ""#--> >$T('prowl-high')</option>
|
|
||||||
<option value="2" <!--#if $prowl_prio_download == 2 then 'selected="selected"' else ""#--> >$T('prowl-emergency')</option>
|
|
||||||
</select>
|
|
||||||
</div>
|
|
||||||
<div class="field-pair">
|
|
||||||
<label class="config" for="prowl_prio_pp">$T($notify_texts['pp'])</label>
|
|
||||||
<select name="prowl_prio_pp" id="prowl_prio_pp">
|
|
||||||
<option value="-3" <!--#if $prowl_prio_pp == -3 then 'selected="selected"' else ""#--> >$T('prowl-off')</option>
|
|
||||||
<option value="-2" <!--#if $prowl_prio_pp == -2 then 'selected="selected"' else ""#--> >$T('prowl-very-low')</option>
|
|
||||||
<option value="-1" <!--#if $prowl_prio_pp == -1 then 'selected="selected"' else ""#--> >$T('prowl-moderate')</option>
|
|
||||||
<option value="0" <!--#if $prowl_prio_pp == 0 then 'selected="selected"' else ""#--> >$T('prowl-normal')</option>
|
|
||||||
<option value="1" <!--#if $prowl_prio_pp == 1 then 'selected="selected"' else ""#--> >$T('prowl-high')</option>
|
|
||||||
<option value="2" <!--#if $prowl_prio_pp == 2 then 'selected="selected"' else ""#--> >$T('prowl-emergency')</option>
|
|
||||||
</select>
|
|
||||||
</div>
|
|
||||||
<div class="field-pair">
|
|
||||||
<label class="config" for="prowl_prio_complete">$T($notify_texts['complete'])</label>
|
|
||||||
<select name="prowl_prio_complete" id="prowl_prio_complete">
|
|
||||||
<option value="-3" <!--#if $prowl_prio_complete == -3 then 'selected="selected"' else ""#--> >$T('prowl-off')</option>
|
|
||||||
<option value="-2" <!--#if $prowl_prio_complete == -2 then 'selected="selected"' else ""#--> >$T('prowl-very-low')</option>
|
|
||||||
<option value="-1" <!--#if $prowl_prio_complete == -1 then 'selected="selected"' else ""#--> >$T('prowl-moderate')</option>
|
|
||||||
<option value="0" <!--#if $prowl_prio_complete == 0 then 'selected="selected"' else ""#--> >$T('prowl-normal')</option>
|
|
||||||
<option value="1" <!--#if $prowl_prio_complete == 1 then 'selected="selected"' else ""#--> >$T('prowl-high')</option>
|
|
||||||
<option value="2" <!--#if $prowl_prio_complete == 2 then 'selected="selected"' else ""#--> >$T('prowl-emergency')</option>
|
|
||||||
</select>
|
|
||||||
</div>
|
|
||||||
<div class="field-pair">
|
|
||||||
<label class="config" for="prowl_prio_failed">$T($notify_texts['failed'])</label>
|
|
||||||
<select name="prowl_prio_failed" id="prowl_prio_failed">
|
|
||||||
<option value="-3" <!--#if $prowl_prio_failed == -3 then 'selected="selected"' else ""#--> >$T('prowl-off')</option>
|
|
||||||
<option value="-2" <!--#if $prowl_prio_failed == -2 then 'selected="selected"' else ""#--> >$T('prowl-very-low')</option>
|
|
||||||
<option value="-1" <!--#if $prowl_prio_failed == -1 then 'selected="selected"' else ""#--> >$T('prowl-moderate')</option>
|
|
||||||
<option value="0" <!--#if $prowl_prio_failed == 0 then 'selected="selected"' else ""#--> >$T('prowl-normal')</option>
|
|
||||||
<option value="1" <!--#if $prowl_prio_failed == 1 then 'selected="selected"' else ""#--> >$T('prowl-high')</option>
|
|
||||||
<option value="2" <!--#if $prowl_prio_failed == 2 then 'selected="selected"' else ""#--> >$T('prowl-emergency')</option>
|
|
||||||
</select>
|
|
||||||
</div>
|
|
||||||
<div class="field-pair">
|
|
||||||
<label class="config" for="prowl_prio_queue_done">$T($notify_texts['queue_done'])</label>
|
|
||||||
<select name="prowl_prio_queue_done" id="prowl_prio_queue_done">
|
|
||||||
<option value="-3" <!--#if $prowl_prio_queue_done == -3 then 'selected="selected"' else ""#--> >$T('prowl-off')</option>
|
|
||||||
<option value="-2" <!--#if $prowl_prio_queue_done == -2 then 'selected="selected"' else ""#--> >$T('prowl-very-low')</option>
|
|
||||||
<option value="-1" <!--#if $prowl_prio_queue_done == -1 then 'selected="selected"' else ""#--> >$T('prowl-moderate')</option>
|
|
||||||
<option value="0" <!--#if $prowl_prio_queue_done == 0 then 'selected="selected"' else ""#--> >$T('prowl-normal')</option>
|
|
||||||
<option value="1" <!--#if $prowl_prio_queue_done == 1 then 'selected="selected"' else ""#--> >$T('prowl-high')</option>
|
|
||||||
<option value="2" <!--#if $prowl_prio_queue_done == 2 then 'selected="selected"' else ""#--> >$T('prowl-emergency')</option>
|
|
||||||
</select>
|
|
||||||
</div>
|
|
||||||
<div class="field-pair">
|
|
||||||
<label class="config" for="prowl_prio_disk_full">$T($notify_texts['disk_full'])</label>
|
|
||||||
<select name="prowl_prio_disk_full" id="prowl_prio_disk_full">
|
|
||||||
<option value="-3" <!--#if $prowl_prio_disk_full == -3 then 'selected="selected"' else ""#--> >$T('prowl-off')</option>
|
|
||||||
<option value="-2" <!--#if $prowl_prio_disk_full == -2 then 'selected="selected"' else ""#--> >$T('prowl-very-low')</option>
|
|
||||||
<option value="-1" <!--#if $prowl_prio_disk_full == -1 then 'selected="selected"' else ""#--> >$T('prowl-moderate')</option>
|
|
||||||
<option value="0" <!--#if $prowl_prio_disk_full == 0 then 'selected="selected"' else ""#--> >$T('prowl-normal')</option>
|
|
||||||
<option value="1" <!--#if $prowl_prio_disk_full == 1 then 'selected="selected"' else ""#--> >$T('prowl-high')</option>
|
|
||||||
<option value="2" <!--#if $prowl_prio_disk_full == 2 then 'selected="selected"' else ""#--> >$T('prowl-emergency')</option>
|
|
||||||
</select>
|
|
||||||
</div>
|
|
||||||
<div class="field-pair">
|
|
||||||
<label class="config" for="prowl_prio_new_login">$T($notify_texts['new_login'])</label>
|
|
||||||
<select name="prowl_prio_new_login" id="prowl_prio_new_login">
|
|
||||||
<option value="-3" <!--#if $prowl_prio_new_login == -3 then 'selected="selected"' else ""#--> >$T('prowl-off')</option>
|
|
||||||
<option value="-2" <!--#if $prowl_prio_new_login == -2 then 'selected="selected"' else ""#--> >$T('prowl-very-low')</option>
|
|
||||||
<option value="-1" <!--#if $prowl_prio_new_login == -1 then 'selected="selected"' else ""#--> >$T('prowl-moderate')</option>
|
|
||||||
<option value="0" <!--#if $prowl_prio_new_login == 0 then 'selected="selected"' else ""#--> >$T('prowl-normal')</option>
|
|
||||||
<option value="1" <!--#if $prowl_prio_new_login == 1 then 'selected="selected"' else ""#--> >$T('prowl-high')</option>
|
|
||||||
<option value="2" <!--#if $prowl_prio_new_login == 2 then 'selected="selected"' else ""#--> >$T('prowl-emergency')</option>
|
|
||||||
</select>
|
|
||||||
</div>
|
|
||||||
<div class="field-pair">
|
|
||||||
<label class="config" for="prowl_prio_warning">$T($notify_texts['warning'])</label>
|
|
||||||
<select name="prowl_prio_warning" id="prowl_prio_warning">
|
|
||||||
<option value="-3" <!--#if $prowl_prio_warning == -3 then 'selected="selected"' else ""#--> >$T('prowl-off')</option>
|
|
||||||
<option value="-2" <!--#if $prowl_prio_warning == -2 then 'selected="selected"' else ""#--> >$T('prowl-very-low')</option>
|
|
||||||
<option value="-1" <!--#if $prowl_prio_warning == -1 then 'selected="selected"' else ""#--> >$T('prowl-moderate')</option>
|
|
||||||
<option value="0" <!--#if $prowl_prio_warning == 0 then 'selected="selected"' else ""#--> >$T('prowl-normal')</option>
|
|
||||||
<option value="1" <!--#if $prowl_prio_warning == 1 then 'selected="selected"' else ""#--> >$T('prowl-high')</option>
|
|
||||||
<option value="2" <!--#if $prowl_prio_warning == 2 then 'selected="selected"' else ""#--> >$T('prowl-emergency')</option>
|
|
||||||
</select>
|
|
||||||
</div>
|
|
||||||
<div class="field-pair">
|
|
||||||
<label class="config" for="prowl_prio_error">$T($notify_texts['error'])</label>
|
|
||||||
<select name="prowl_prio_error" id="prowl_prio_error">
|
|
||||||
<option value="-3" <!--#if $prowl_prio_error == -3 then 'selected="selected"' else ""#--> >$T('prowl-off')</option>
|
|
||||||
<option value="-2" <!--#if $prowl_prio_error == -2 then 'selected="selected"' else ""#--> >$T('prowl-very-low')</option>
|
|
||||||
<option value="-1" <!--#if $prowl_prio_error == -1 then 'selected="selected"' else ""#--> >$T('prowl-moderate')</option>
|
|
||||||
<option value="0" <!--#if $prowl_prio_error == 0 then 'selected="selected"' else ""#--> >$T('prowl-normal')</option>
|
|
||||||
<option value="1" <!--#if $prowl_prio_error == 1 then 'selected="selected"' else ""#--> >$T('prowl-high')</option>
|
|
||||||
<option value="2" <!--#if $prowl_prio_error == 2 then 'selected="selected"' else ""#--> >$T('prowl-emergency')</option>
|
|
||||||
</select>
|
|
||||||
</div>
|
|
||||||
<div class="field-pair">
|
|
||||||
<label class="config" for="prowl_prio_other">$T($notify_texts['other'])</label>
|
|
||||||
<select name="prowl_prio_other" id="prowl_prio_other">
|
|
||||||
<option value="-3" <!--#if $prowl_prio_other == -3 then 'selected="selected"' else ""#--> >$T('prowl-off')</option>
|
|
||||||
<option value="-2" <!--#if $prowl_prio_other == -2 then 'selected="selected"' else ""#--> >$T('prowl-very-low')</option>
|
|
||||||
<option value="-1" <!--#if $prowl_prio_other == -1 then 'selected="selected"' else ""#--> >$T('prowl-moderate')</option>
|
|
||||||
<option value="0" <!--#if $prowl_prio_other == 0 then 'selected="selected"' else ""#--> >$T('prowl-normal')</option>
|
|
||||||
<option value="1" <!--#if $prowl_prio_other == 1 then 'selected="selected"' else ""#--> >$T('prowl-high')</option>
|
|
||||||
<option value="2" <!--#if $prowl_prio_other == 2 then 'selected="selected"' else ""#--> >$T('prowl-emergency')</option>
|
|
||||||
</select>
|
|
||||||
</div>
|
|
||||||
<div class="field-pair">
|
<div class="field-pair">
|
||||||
<button class="btn btn-default saveButton"><span class="glyphicon glyphicon-ok"></span> $T('button-saveChanges')</button>
|
<button class="btn btn-default saveButton"><span class="glyphicon glyphicon-ok"></span> $T('button-saveChanges')</button>
|
||||||
<button class="btn btn-default" type="button" id="test_prowl"><span class="glyphicon glyphicon-comment"></span> $T('testNotify')</button>
|
<button class="btn btn-default" type="button" id="test_prowl"><span class="glyphicon glyphicon-comment"></span> $T('testNotify')</button>
|
||||||
@@ -497,12 +294,12 @@
|
|||||||
<div class="alert"></div>
|
<div class="alert"></div>
|
||||||
</div>
|
</div>
|
||||||
</fieldset>
|
</fieldset>
|
||||||
</div><!-- /col1 -->
|
</div>
|
||||||
</div><!-- /section -->
|
</div>
|
||||||
|
|
||||||
<div class="section" id="pushover">
|
<div class="section" id="pushover">
|
||||||
<div class="col2">
|
<div class="col2">
|
||||||
<h3>$T('section-Pushover')</h3>
|
<h3>$T('section-Pushover')</h3>
|
||||||
<table>
|
<table>
|
||||||
<tr>
|
<tr>
|
||||||
<td><input type="checkbox" name="pushover_enable" id="pushover_enable" value="1" <!--#if int($pushover_enable) > 0 then 'checked="checked"' else ""#--> /></td>
|
<td><input type="checkbox" name="pushover_enable" id="pushover_enable" value="1" <!--#if int($pushover_enable) > 0 then 'checked="checked"' else ""#--> /></td>
|
||||||
@@ -510,146 +307,41 @@
|
|||||||
</tr>
|
</tr>
|
||||||
</table>
|
</table>
|
||||||
<em>$T('explain-pushover_enable')</em>
|
<em>$T('explain-pushover_enable')</em>
|
||||||
</div><!-- /col2 -->
|
$show_cat_box('pushover')
|
||||||
|
</div>
|
||||||
<div class="col1" <!--#if int($pushover_enable) > 0 then '' else 'style="display:none"'#-->>
|
<div class="col1" <!--#if int($pushover_enable) > 0 then '' else 'style="display:none"'#-->>
|
||||||
<fieldset>
|
<fieldset>
|
||||||
<div class="field-pair">
|
<div class="field-pair">
|
||||||
<label class="config" for="pushover_token">$T('opt-pushover_token')</label>
|
<label class="config" for="pushover_token">$T('opt-pushover_token')</label>
|
||||||
<input type="text" name="pushover_token" id="pushover_token" value="$pushover_token" />
|
<input type="text" name="pushover_token" id="pushover_token" value="$pushover_token" />
|
||||||
<span class="desc">$T('explain-pushover_token')</span>
|
<span class="desc">$T('explain-pushover_token')</span>
|
||||||
</div>
|
|
||||||
<div class="field-pair">
|
|
||||||
<label class="config" for="pushover_userkey">$T('opt-pushover_userkey')</label>
|
|
||||||
<input type="text" name="pushover_userkey" id="pushover_userkey" value="$pushover_userkey" />
|
|
||||||
<span class="desc">$T('explain-pushover_userkey')</span>
|
|
||||||
</div>
|
|
||||||
<div class="field-pair">
|
|
||||||
<label class="config" for="pushover_device">$T('opt-pushover_device')</label>
|
|
||||||
<input type="text" name="pushover_device" id="pushover_device" value="$pushover_device" />
|
|
||||||
<span class="desc">$T('explain-pushover_device')</span>
|
|
||||||
</div>
|
|
||||||
<div class="field-pair">
|
|
||||||
<label class="config" for="pushover_prio_startup">$T($notify_texts['startup']).replace('/', ' / ')</label>
|
|
||||||
<select name="pushover_prio_startup" id="pushover_prio_startup">
|
|
||||||
<option value="-3" <!--#if $pushover_prio_startup == -3 then 'selected="selected"' else ""#--> >$T('pushover-off')</option>
|
|
||||||
<option value="-2" <!--#if $pushover_prio_startup == -2 then 'selected="selected"' else ""#--> >$T('prowl-very-low')</option>
|
|
||||||
<option value="-1" <!--#if $pushover_prio_startup == -1 then 'selected="selected"' else ""#--> >$T('pushover-low')</option>
|
|
||||||
<option value="0" <!--#if $pushover_prio_startup == 0 then 'selected="selected"' else ""#--> >$T('prowl-normal')</option>
|
|
||||||
<option value="1" <!--#if $pushover_prio_startup == 1 then 'selected="selected"' else ""#--> >$T('pushover-high')</option>
|
|
||||||
<option value="2" <!--#if $pushover_prio_startup == 2 then 'selected="selected"' else ""#--> >$T('prowl-emergency')</option>
|
|
||||||
</select>
|
|
||||||
</div>
|
|
||||||
<div class="field-pair">
|
|
||||||
<label class="config" for="pushover_prio_download">$T($notify_texts['download']) / $T('link-pause') / $T('link-resume')</label>
|
|
||||||
<select name="pushover_prio_download" id="pushover_prio_download">
|
|
||||||
<option value="-3" <!--#if $pushover_prio_download == -3 then 'selected="selected"' else ""#--> >$T('pushover-off')</option>
|
|
||||||
<option value="-2" <!--#if $pushover_prio_download == -2 then 'selected="selected"' else ""#--> >$T('prowl-very-low')</option>
|
|
||||||
<option value="-1" <!--#if $pushover_prio_download == -1 then 'selected="selected"' else ""#--> >$T('pushover-low')</option>
|
|
||||||
<option value="0" <!--#if $pushover_prio_download == 0 then 'selected="selected"' else ""#--> >$T('prowl-normal')</option>
|
|
||||||
<option value="1" <!--#if $pushover_prio_download == 1 then 'selected="selected"' else ""#--> >$T('pushover-high')</option>
|
|
||||||
<option value="2" <!--#if $pushover_prio_download == 2 then 'selected="selected"' else ""#--> >$T('prowl-emergency')</option>
|
|
||||||
</select>
|
|
||||||
</div>
|
|
||||||
<div class="field-pair">
|
|
||||||
<label class="config" for="pushover_prio_pp">$T($notify_texts['pp'])</label>
|
|
||||||
<select name="pushover_prio_pp" id="pushover_prio_pp">
|
|
||||||
<option value="-3" <!--#if $pushover_prio_pp == -3 then 'selected="selected"' else ""#--> >$T('pushover-off')</option>
|
|
||||||
<option value="-2" <!--#if $pushover_prio_pp == -2 then 'selected="selected"' else ""#--> >$T('prowl-very-low')</option>
|
|
||||||
<option value="-1" <!--#if $pushover_prio_pp == -1 then 'selected="selected"' else ""#--> >$T('pushover-low')</option>
|
|
||||||
<option value="0" <!--#if $pushover_prio_pp == 0 then 'selected="selected"' else ""#--> >$T('prowl-normal')</option>
|
|
||||||
<option value="1" <!--#if $pushover_prio_pp == 1 then 'selected="selected"' else ""#--> >$T('pushover-high')</option>
|
|
||||||
<option value="2" <!--#if $pushover_prio_pp == 2 then 'selected="selected"' else ""#--> >$T('prowl-emergency')</option>
|
|
||||||
</select>
|
|
||||||
</div>
|
|
||||||
<div class="field-pair">
|
|
||||||
<label class="config" for="pushover_prio_complete">$T($notify_texts['complete'])</label>
|
|
||||||
<select name="pushover_prio_complete" id="pushover_prio_complete">
|
|
||||||
<option value="-3" <!--#if $pushover_prio_complete == -3 then 'selected="selected"' else ""#--> >$T('pushover-off')</option>
|
|
||||||
<option value="-2" <!--#if $pushover_prio_complete == -2 then 'selected="selected"' else ""#--> >$T('prowl-very-low')</option>
|
|
||||||
<option value="-1" <!--#if $pushover_prio_complete == -1 then 'selected="selected"' else ""#--> >$T('pushover-low')</option>
|
|
||||||
<option value="0" <!--#if $pushover_prio_complete == 0 then 'selected="selected"' else ""#--> >$T('prowl-normal')</option>
|
|
||||||
<option value="1" <!--#if $pushover_prio_complete == 1 then 'selected="selected"' else ""#--> >$T('pushover-high')</option>
|
|
||||||
<option value="2" <!--#if $pushover_prio_complete == 2 then 'selected="selected"' else ""#--> >$T('prowl-emergency')</option>
|
|
||||||
</select>
|
|
||||||
</div>
|
|
||||||
<div class="field-pair">
|
|
||||||
<label class="config" for="pushover_prio_failed">$T($notify_texts['failed'])</label>
|
|
||||||
<select name="pushover_prio_failed" id="pushover_prio_failed">
|
|
||||||
<option value="-3" <!--#if $pushover_prio_failed == -3 then 'selected="selected"' else ""#--> >$T('pushover-off')</option>
|
|
||||||
<option value="-2" <!--#if $pushover_prio_failed == -2 then 'selected="selected"' else ""#--> >$T('prowl-very-low')</option>
|
|
||||||
<option value="-1" <!--#if $pushover_prio_failed == -1 then 'selected="selected"' else ""#--> >$T('pushover-low')</option>
|
|
||||||
<option value="0" <!--#if $pushover_prio_failed == 0 then 'selected="selected"' else ""#--> >$T('prowl-normal')</option>
|
|
||||||
<option value="1" <!--#if $pushover_prio_failed == 1 then 'selected="selected"' else ""#--> >$T('pushover-high')</option>
|
|
||||||
<option value="2" <!--#if $pushover_prio_failed == 2 then 'selected="selected"' else ""#--> >$T('prowl-emergency')</option>
|
|
||||||
</select>
|
|
||||||
</div>
|
|
||||||
<div class="field-pair">
|
|
||||||
<label class="config" for="pushover_prio_queue_done">$T($notify_texts['queue_done'])</label>
|
|
||||||
<select name="pushover_prio_queue_done" id="pushover_prio_queue_done">
|
|
||||||
<option value="-3" <!--#if $pushover_prio_queue_done == -3 then 'selected="selected"' else ""#--> >$T('pushover-off')</option>
|
|
||||||
<option value="-2" <!--#if $pushover_prio_queue_done == -2 then 'selected="selected"' else ""#--> >$T('prowl-very-low')</option>
|
|
||||||
<option value="-1" <!--#if $pushover_prio_queue_done == -1 then 'selected="selected"' else ""#--> >$T('pushover-low')</option>
|
|
||||||
<option value="0" <!--#if $pushover_prio_queue_done == 0 then 'selected="selected"' else ""#--> >$T('prowl-normal')</option>
|
|
||||||
<option value="1" <!--#if $pushover_prio_queue_done == 1 then 'selected="selected"' else ""#--> >$T('pushover-high')</option>
|
|
||||||
<option value="2" <!--#if $pushover_prio_queue_done == 2 then 'selected="selected"' else ""#--> >$T('prowl-emergency')</option>
|
|
||||||
</select>
|
|
||||||
</div>
|
|
||||||
<div class="field-pair">
|
|
||||||
<label class="config" for="pushover_prio_disk_full">$T($notify_texts['disk_full'])</label>
|
|
||||||
<select name="pushover_prio_disk_full" id="pushover_prio_disk_full">
|
|
||||||
<option value="-3" <!--#if $pushover_prio_disk_full == -3 then 'selected="selected"' else ""#--> >$T('pushover-off')</option>
|
|
||||||
<option value="-2" <!--#if $pushover_prio_disk_full == -2 then 'selected="selected"' else ""#--> >$T('prowl-very-low')</option>
|
|
||||||
<option value="-1" <!--#if $pushover_prio_disk_full == -1 then 'selected="selected"' else ""#--> >$T('pushover-low')</option>
|
|
||||||
<option value="0" <!--#if $pushover_prio_disk_full == 0 then 'selected="selected"' else ""#--> >$T('prowl-normal')</option>
|
|
||||||
<option value="1" <!--#if $pushover_prio_disk_full == 1 then 'selected="selected"' else ""#--> >$T('pushover-high')</option>
|
|
||||||
<option value="2" <!--#if $pushover_prio_disk_full == 2 then 'selected="selected"' else ""#--> >$T('prowl-emergency')</option>
|
|
||||||
</select>
|
|
||||||
</div>
|
</div>
|
||||||
<div class="field-pair">
|
<div class="field-pair">
|
||||||
<label class="config" for="pushover_prio_new_login">$T($notify_texts['new_login'])</label>
|
<label class="config" for="pushover_userkey">$T('opt-pushover_userkey')</label>
|
||||||
<select name="pushover_prio_new_login" id="pushover_prio_new_login">
|
<input type="text" name="pushover_userkey" id="pushover_userkey" value="$pushover_userkey" />
|
||||||
<option value="-3" <!--#if $pushover_prio_new_login == -3 then 'selected="selected"' else ""#--> >$T('pushover-off')</option>
|
<span class="desc">$T('explain-pushover_userkey')</span>
|
||||||
<option value="-2" <!--#if $pushover_prio_new_login == -2 then 'selected="selected"' else ""#--> >$T('prowl-very-low')</option>
|
|
||||||
<option value="-1" <!--#if $pushover_prio_new_login == -1 then 'selected="selected"' else ""#--> >$T('pushover-low')</option>
|
|
||||||
<option value="0" <!--#if $pushover_prio_new_login == 0 then 'selected="selected"' else ""#--> >$T('prowl-normal')</option>
|
|
||||||
<option value="1" <!--#if $pushover_prio_new_login == 1 then 'selected="selected"' else ""#--> >$T('pushover-high')</option>
|
|
||||||
<option value="2" <!--#if $pushover_prio_new_login == 2 then 'selected="selected"' else ""#--> >$T('prowl-emergency')</option>
|
|
||||||
</select>
|
|
||||||
</div>
|
</div>
|
||||||
<div class="field-pair">
|
<div class="field-pair">
|
||||||
<label class="config" for="pushover_prio_warning">$T($notify_texts['warning'])</label>
|
<label class="config" for="pushover_device">$T('opt-pushover_device')</label>
|
||||||
<select name="pushover_prio_warning" id="pushover_prio_warning">
|
<input type="text" name="pushover_device" id="pushover_device" value="$pushover_device" />
|
||||||
<option value="-3" <!--#if $pushover_prio_warning == -3 then 'selected="selected"' else ""#--> >$T('pushover-off')</option>
|
<span class="desc">$T('explain-pushover_device')</span>
|
||||||
<option value="-2" <!--#if $pushover_prio_warning == -2 then 'selected="selected"' else ""#--> >$T('prowl-very-low')</option>
|
|
||||||
<option value="-1" <!--#if $pushover_prio_warning == -1 then 'selected="selected"' else ""#--> >$T('pushover-low')</option>
|
|
||||||
<option value="0" <!--#if $pushover_prio_warning == 0 then 'selected="selected"' else ""#--> >$T('prowl-normal')</option>
|
|
||||||
<option value="1" <!--#if $pushover_prio_warning == 1 then 'selected="selected"' else ""#--> >$T('pushover-high')</option>
|
|
||||||
<option value="2" <!--#if $pushover_prio_warning == 2 then 'selected="selected"' else ""#--> >$T('prowl-emergency')</option>
|
|
||||||
</select>
|
|
||||||
</div>
|
|
||||||
<div class="field-pair">
|
|
||||||
<label class="config" for="pushover_prio_error">$T($notify_texts['error'])</label>
|
|
||||||
<select name="pushover_prio_error" id="pushover_prio_error">
|
|
||||||
<option value="-3" <!--#if $pushover_prio_error == -3 then 'selected="selected"' else ""#--> >$T('pushover-off')</option>
|
|
||||||
<option value="-2" <!--#if $pushover_prio_error == -2 then 'selected="selected"' else ""#--> >$T('prowl-very-low')</option>
|
|
||||||
<option value="-1" <!--#if $pushover_prio_error == -1 then 'selected="selected"' else ""#--> >$T('pushover-low')</option>
|
|
||||||
<option value="0" <!--#if $pushover_prio_error == 0 then 'selected="selected"' else ""#--> >$T('prowl-normal')</option>
|
|
||||||
<option value="1" <!--#if $pushover_prio_error == 1 then 'selected="selected"' else ""#--> >$T('pushover-high')</option>
|
|
||||||
<option value="2" <!--#if $pushover_prio_error == 2 then 'selected="selected"' else ""#--> >$T('prowl-emergency')</option>
|
|
||||||
</select>
|
|
||||||
</div>
|
|
||||||
<div class="field-pair">
|
|
||||||
<label class="config" for="pushover_prio_other">$T($notify_texts['other'])</label>
|
|
||||||
<select name="pushover_prio_other" id="pushover_prio_other">
|
|
||||||
<option value="-3" <!--#if $pushover_prio_other == -3 then 'selected="selected"' else ""#--> >$T('pushover-off')</option>
|
|
||||||
<option value="-2" <!--#if $pushover_prio_other == -2 then 'selected="selected"' else ""#--> >$T('prowl-very-low')</option>
|
|
||||||
<option value="-1" <!--#if $pushover_prio_other == -1 then 'selected="selected"' else ""#--> >$T('pushover-low')</option>
|
|
||||||
<option value="0" <!--#if $pushover_prio_other == 0 then 'selected="selected"' else ""#--> >$T('prowl-normal')</option>
|
|
||||||
<option value="1" <!--#if $pushover_prio_other == 1 then 'selected="selected"' else ""#--> >$T('pushover-high')</option>
|
|
||||||
<option value="2" <!--#if $pushover_prio_other == 2 then 'selected="selected"' else ""#--> >$T('prowl-emergency')</option>
|
|
||||||
</select>
|
|
||||||
</div>
|
</div>
|
||||||
|
<!--#set $section_label = 'pushover'#-->
|
||||||
|
<!--#for $type in $notify_keys#-->
|
||||||
|
<div class="field-pair">
|
||||||
|
<label class="config" for="${section_label}_prio_$type">
|
||||||
|
$T($notify_texts[$type]).replace('/', ' / ') <!--#if $type == 'download'#--> / $T('link-pause') / $T('link-resume')<!--#end if#-->
|
||||||
|
</label>
|
||||||
|
<select name="${section_label}_prio_$type" id="${section_label}_prio_$type">
|
||||||
|
<option value="-3" <!--#if $getVar($section_label + '_prio_' + $type) == -3 then 'selected="selected"' else ""#--> >$T('pushover-off')</option>
|
||||||
|
<option value="-2" <!--#if $getVar($section_label + '_prio_' + $type) == -2 then 'selected="selected"' else ""#--> >$T('prowl-very-low')</option>
|
||||||
|
<option value="-1" <!--#if $getVar($section_label + '_prio_' + $type) == -1 then 'selected="selected"' else ""#--> >$T('pushover-low')</option>
|
||||||
|
<option value="0" <!--#if $getVar($section_label + '_prio_' + $type) == 0 then 'selected="selected"' else ""#--> >$T('prowl-normal')</option>
|
||||||
|
<option value="1" <!--#if $getVar($section_label + '_prio_' + $type) == 1 then 'selected="selected"' else ""#--> >$T('pushover-high')</option>
|
||||||
|
<option value="2" <!--#if $getVar($section_label + '_prio_' + $type) == 2 then 'selected="selected"' else ""#--> >$T('prowl-emergency')</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
<!--#end for#-->
|
||||||
<div class="field-pair">
|
<div class="field-pair">
|
||||||
<button class="btn btn-default saveButton"><span class="glyphicon glyphicon-ok"></span> $T('button-saveChanges')</button>
|
<button class="btn btn-default saveButton"><span class="glyphicon glyphicon-ok"></span> $T('button-saveChanges')</button>
|
||||||
<button class="btn btn-default" type="button" id="test_pushover"><span class="glyphicon glyphicon-comment"></span> $T('testNotify')</button>
|
<button class="btn btn-default" type="button" id="test_pushover"><span class="glyphicon glyphicon-comment"></span> $T('testNotify')</button>
|
||||||
@@ -658,11 +350,11 @@
|
|||||||
<div class="alert"></div>
|
<div class="alert"></div>
|
||||||
</div>
|
</div>
|
||||||
</fieldset>
|
</fieldset>
|
||||||
</div><!-- /col1 -->
|
</div>
|
||||||
</div><!-- /section -->
|
</div>
|
||||||
<div class="section" id="pushbullet">
|
<div class="section" id="pushbullet">
|
||||||
<div class="col2">
|
<div class="col2">
|
||||||
<h3>$T('section-Pushbullet')</h3>
|
<h3>$T('section-Pushbullet')</h3>
|
||||||
<table>
|
<table>
|
||||||
<tr>
|
<tr>
|
||||||
<td><input type="checkbox" name="pushbullet_enable" id="pushbullet_enable" value="1" <!--#if int($pushbullet_enable) > 0 then 'checked="checked"' else ""#--> /></td>
|
<td><input type="checkbox" name="pushbullet_enable" id="pushbullet_enable" value="1" <!--#if int($pushbullet_enable) > 0 then 'checked="checked"' else ""#--> /></td>
|
||||||
@@ -670,156 +362,33 @@
|
|||||||
</tr>
|
</tr>
|
||||||
</table>
|
</table>
|
||||||
<em>$T('explain-pushbullet_enable')</em>
|
<em>$T('explain-pushbullet_enable')</em>
|
||||||
</div><!-- /col2 -->
|
$show_cat_box('pushbullet')
|
||||||
|
</div>
|
||||||
<div class="col1" <!--#if int($pushbullet_enable) > 0 then '' else 'style="display:none"'#-->>
|
<div class="col1" <!--#if int($pushbullet_enable) > 0 then '' else 'style="display:none"'#-->>
|
||||||
<fieldset>
|
<fieldset>
|
||||||
<div class="field-pair">
|
|
||||||
<label class="config" for="pushbullet_apikey">$T('opt-pushbullet_apikey')</label>
|
|
||||||
<input type="text" name="pushbullet_apikey" id="pushbullet_apikey" value="$pushbullet_apikey" />
|
|
||||||
<span class="desc">$T('explain-pushbullet_apikey')</span>
|
|
||||||
</div>
|
|
||||||
<!--#if 0#-->
|
|
||||||
<div class="field-pair">
|
|
||||||
<label class="config" for="pushbullet_device">$T('opt-pushbullet_device')</label>
|
|
||||||
<input type="text" name="pushbullet_device" id="pushbullet_device" value="$pushbullet_device" />
|
|
||||||
<span class="desc">$T('explain-pushbullet_device')</span>
|
|
||||||
</div>
|
|
||||||
<!--#end if#-->
|
|
||||||
<div class="field-pair">
|
|
||||||
<label class="config wide" for="pushbullet_prio_startup">$T($notify_texts['startup']).replace('/', ' / ')</label>
|
|
||||||
<input type="checkbox" name="pushbullet_prio_startup" id="pushbullet_prio_startup" value="1" <!--#if int($pushbullet_prio_startup) > 0 then 'checked="checked"' else ""#--> />
|
|
||||||
</div>
|
|
||||||
<div class="field-pair">
|
|
||||||
<label class="config wide" for="pushbullet_prio_download">$T($notify_texts['download']) / $T('link-pause') / $T('link-resume')</label>
|
|
||||||
<input type="checkbox" name="pushbullet_prio_download" id="pushbullet_prio_download" value="1" <!--#if int($pushbullet_prio_download) > 0 then 'checked="checked"' else ""#--> />
|
|
||||||
</div>
|
|
||||||
<div class="field-pair">
|
|
||||||
<label class="config wide" for="pushbullet_prio_pp">$T($notify_texts['pp'])</label>
|
|
||||||
<input type="checkbox" name="pushbullet_prio_pp" id="pushbullet_prio_pp" value="1" <!--#if int($pushbullet_prio_pp) > 0 then 'checked="checked"' else ""#--> />
|
|
||||||
</div>
|
|
||||||
<div class="field-pair">
|
|
||||||
<label class="config wide" for="pushbullet_prio_complete">$T($notify_texts['complete'])</label>
|
|
||||||
<input type="checkbox" name="pushbullet_prio_complete" id="pushbullet_prio_complete" value="1" <!--#if int($pushbullet_prio_complete) > 0 then 'checked="checked"' else ""#--> />
|
|
||||||
</div>
|
|
||||||
<div class="field-pair">
|
|
||||||
<label class="config wide" for="pushbullet_prio_failed">$T($notify_texts['failed'])</label>
|
|
||||||
<input type="checkbox" name="pushbullet_prio_failed" id="pushbullet_prio_failed" value="1" <!--#if int($pushbullet_prio_failed) > 0 then 'checked="checked"' else ""#--> />
|
|
||||||
</div>
|
|
||||||
<div class="field-pair">
|
|
||||||
<label class="config wide" for="pushbullet_prio_queue_done">$T($notify_texts['queue_done'])</label>
|
|
||||||
<input type="checkbox" name="pushbullet_prio_queue_done" id="pushbullet_prio_queue_done" value="1" <!--#if int($pushbullet_prio_queue_done) > 0 then 'checked="checked"' else ""#--> />
|
|
||||||
</div>
|
|
||||||
<div class="field-pair">
|
|
||||||
<label class="config wide" for="pushbullet_prio_disk_full">$T($notify_texts['disk_full'])</label>
|
|
||||||
<input type="checkbox" name="pushbullet_prio_disk_full" id="pushbullet_prio_disk_full" value="1" <!--#if int($pushbullet_prio_disk_full) > 0 then 'checked="checked"' else ""#--> />
|
|
||||||
</div>
|
|
||||||
<div class="field-pair">
|
<div class="field-pair">
|
||||||
<label class="config wide" for="pushbullet_prio_new_login">$T($notify_texts['new_login'])</label>
|
<label class="config" for="pushbullet_apikey">$T('opt-pushbullet_apikey')</label>
|
||||||
<input type="checkbox" name="pushbullet_prio_new_login" id="pushbullet_prio_new_login" value="1" <!--#if int($pushbullet_prio_new_login) > 0 then 'checked="checked"' else ""#--> />
|
<input type="text" name="pushbullet_apikey" id="pushbullet_apikey" value="$pushbullet_apikey" />
|
||||||
|
<span class="desc">$T('explain-pushbullet_apikey')</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="field-pair">
|
<!--#if 0#-->
|
||||||
<label class="config wide" for="pushbullet_prio_warning">$T($notify_texts['warning'])</label>
|
<div class="field-pair">
|
||||||
<input type="checkbox" name="pushbullet_prio_warning" id="pushbullet_prio_warning" value="1" <!--#if int($pushbullet_prio_warning) > 0 then 'checked="checked"' else ""#--> />
|
<label class="config" for="pushbullet_device">$T('opt-pushbullet_device')</label>
|
||||||
</div>
|
<input type="text" name="pushbullet_device" id="pushbullet_device" value="$pushbullet_device" />
|
||||||
<div class="field-pair">
|
<span class="desc">$T('explain-pushbullet_device')</span>
|
||||||
<label class="config wide" for="pushbullet_prio_error">$T($notify_texts['error'])</label>
|
</div>
|
||||||
<input type="checkbox" name="pushbullet_prio_error" id="pushbullet_prio_error" value="1" <!--#if int($pushbullet_prio_error) > 0 then 'checked="checked"' else ""#--> />
|
<!--#end if#-->
|
||||||
</div>
|
$show_notify_checkboxes('pushbullet')
|
||||||
<div class="field-pair">
|
<div class="field-pair">
|
||||||
<label class="config wide" for="pushbullet_prio_other">$T($notify_texts['other'])</label>
|
<button class="btn btn-default saveButton"><span class="glyphicon glyphicon-ok"></span> $T('button-saveChanges')</button>
|
||||||
<input type="checkbox" name="pushbullet_prio_other" id="pushbullet_prio_other" value="1" <!--#if int($pushbullet_prio_other) > 0 then 'checked="checked"' else ""#--> />
|
|
||||||
</div>
|
|
||||||
<div class="field-pair">
|
|
||||||
<button class="btn btn-default saveButton"><span class="glyphicon glyphicon-ok"></span> $T('button-saveChanges')</button>
|
|
||||||
<button class="btn btn-default" type="button" id="test_pushbullet"><span class="glyphicon glyphicon-comment"></span> $T('testNotify')</button>
|
<button class="btn btn-default" type="button" id="test_pushbullet"><span class="glyphicon glyphicon-comment"></span> $T('testNotify')</button>
|
||||||
</div>
|
|
||||||
<div class="field-pair result-box">
|
|
||||||
<div class="alert"></div>
|
|
||||||
</div>
|
|
||||||
</fieldset>
|
|
||||||
</div><!-- /col1 -->
|
|
||||||
</div><!-- /section -->
|
|
||||||
<div class="section" id="nscript">
|
|
||||||
<div class="col2">
|
|
||||||
<h3>$T('section-NScript')</h3>
|
|
||||||
<table>
|
|
||||||
<tr>
|
|
||||||
<td><input type="checkbox" name="nscript_enable" id="nscript_enable" value="1" <!--#if int($nscript_enable) > 0 then 'checked="checked"' else ""#--> /></td>
|
|
||||||
<td><label for="nscript_enable"> $T('opt-nscript_enable')</label></td>
|
|
||||||
</tr>
|
|
||||||
</table>
|
|
||||||
<em>$T('explain-nscript_enable')</em>
|
|
||||||
</div><!-- /col2 -->
|
|
||||||
<div class="col1" <!--#if int($nscript_enable) > 0 then '' else 'style="display:none"'#-->>
|
|
||||||
<fieldset>
|
|
||||||
<div class="field-pair">
|
|
||||||
<label class="config" for="nscript_script">$T('opt-nscript_script')</label>
|
|
||||||
<select name="nscript_script">
|
|
||||||
<!--#for $sc in $script_list#-->
|
|
||||||
<option value="$sc" <!--#if $nscript_script.lower()==$sc.lower() then 'selected="selected"' else ""#-->>$Tspec($sc)</option>
|
|
||||||
<!--#end for#-->
|
|
||||||
</select>
|
|
||||||
<span class="desc">$T('explain-nscript_script')</span>
|
|
||||||
</div>
|
|
||||||
<div class="field-pair">
|
|
||||||
<label class="config" for="nscript_parameters">$T('opt-nscript_parameters')</label>
|
|
||||||
<input type="text" name="nscript_parameters" id="nscript_parameters" value="$nscript_parameters" />
|
|
||||||
<span class="desc">$T('Optional') - $T('explain-nscript_parameters')</span>
|
|
||||||
</div>
|
|
||||||
<div class="field-pair">
|
|
||||||
<label class="config wide" for="nscript_prio_startup">$T($notify_texts['startup']).replace('/', ' / ')</label>
|
|
||||||
<input type="checkbox" name="nscript_prio_startup" id="nscript_prio_startup" value="1" <!--#if int($nscript_prio_startup) > 0 then 'checked="checked"' else ""#--> />
|
|
||||||
</div>
|
|
||||||
<div class="field-pair">
|
|
||||||
<label class="config wide" for="nscript_prio_download">$T($notify_texts['download']) / $T('link-pause') / $T('link-resume')</label>
|
|
||||||
<input type="checkbox" name="nscript_prio_download" id="nscript_prio_download" value="1" <!--#if int($nscript_prio_download) > 0 then 'checked="checked"' else ""#--> />
|
|
||||||
</div>
|
|
||||||
<div class="field-pair">
|
|
||||||
<label class="config wide" for="nscript_prio_pp">$T($notify_texts['pp'])</label>
|
|
||||||
<input type="checkbox" name="nscript_prio_pp" id="nscript_prio_pp" value="1" <!--#if int($nscript_prio_pp) > 0 then 'checked="checked"' else ""#--> />
|
|
||||||
</div>
|
|
||||||
<div class="field-pair">
|
|
||||||
<label class="config wide" for="nscript_prio_complete">$T($notify_texts['complete'])</label>
|
|
||||||
<input type="checkbox" name="nscript_prio_complete" id="nscript_prio_complete" value="1" <!--#if int($nscript_prio_complete) > 0 then 'checked="checked"' else ""#--> />
|
|
||||||
</div>
|
|
||||||
<div class="field-pair">
|
|
||||||
<label class="config wide" for="nscript_prio_failed">$T($notify_texts['failed'])</label>
|
|
||||||
<input type="checkbox" name="nscript_prio_failed" id="nscript_prio_failed" value="1" <!--#if int($nscript_prio_failed) > 0 then 'checked="checked"' else ""#--> />
|
|
||||||
</div>
|
|
||||||
<div class="field-pair">
|
|
||||||
<label class="config wide" for="nscript_prio_queue_done">$T($notify_texts['queue_done'])</label>
|
|
||||||
<input type="checkbox" name="nscript_prio_queue_done" id="nscript_prio_queue_done" value="1" <!--#if int($nscript_prio_queue_done) > 0 then 'checked="checked"' else ""#--> />
|
|
||||||
</div>
|
|
||||||
<div class="field-pair">
|
|
||||||
<label class="config wide" for="nscript_prio_disk_full">$T($notify_texts['disk_full'])</label>
|
|
||||||
<input type="checkbox" name="nscript_prio_disk_full" id="nscript_prio_disk_full" value="1" <!--#if int($nscript_prio_disk_full) > 0 then 'checked="checked"' else ""#--> />
|
|
||||||
</div>
|
|
||||||
<div class="field-pair">
|
|
||||||
<label class="config wide" for="nscript_prio_new_login">$T($notify_texts['new_login'])</label>
|
|
||||||
<input type="checkbox" name="nscript_prio_new_login" id="nscript_prio_new_login" value="1" <!--#if int($nscript_prio_new_login) > 0 then 'checked="checked"' else ""#--> />
|
|
||||||
</div>
|
|
||||||
<div class="field-pair">
|
|
||||||
<label class="config wide" for="nscript_prio_warning">$T($notify_texts['warning'])</label>
|
|
||||||
<input type="checkbox" name="nscript_prio_warning" id="nscript_prio_warning" value="1" <!--#if int($nscript_prio_warning) > 0 then 'checked="checked"' else ""#--> />
|
|
||||||
</div>
|
|
||||||
<div class="field-pair">
|
|
||||||
<label class="config wide" for="nscript_prio_error">$T($notify_texts['error'])</label>
|
|
||||||
<input type="checkbox" name="nscript_prio_error" id="nscript_prio_error" value="1" <!--#if int($nscript_prio_error) > 0 then 'checked="checked"' else ""#--> />
|
|
||||||
</div>
|
|
||||||
<div class="field-pair">
|
|
||||||
<label class="config wide" for="nscript_prio_other">$T($notify_texts['other'])</label>
|
|
||||||
<input type="checkbox" name="nscript_prio_other" id="nscript_prio_other" value="1" <!--#if int($nscript_prio_other) > 0 then 'checked="checked"' else ""#--> />
|
|
||||||
</div>
|
|
||||||
<div class="field-pair">
|
|
||||||
<button class="btn btn-default saveButton"><span class="glyphicon glyphicon-ok"></span> $T('button-saveChanges')</button>
|
|
||||||
<button class="btn btn-default" type="button" id="test_nscript"><span class="glyphicon glyphicon-comment"></span> $T('testNotify')</button>
|
|
||||||
</div>
|
</div>
|
||||||
<div class="field-pair result-box">
|
<div class="field-pair result-box">
|
||||||
<div class="alert"></div>
|
<div class="alert"></div>
|
||||||
</div>
|
</div>
|
||||||
</fieldset>
|
</fieldset>
|
||||||
</div><!-- /col1 -->
|
</div>
|
||||||
</div><!-- /section -->
|
</div>
|
||||||
</form>
|
</form>
|
||||||
</div><!-- /colmask -->
|
</div><!-- /colmask -->
|
||||||
|
|
||||||
@@ -827,17 +396,26 @@
|
|||||||
\$(document).ready(function(){
|
\$(document).ready(function(){
|
||||||
// Autocomplete and filebrowser
|
// Autocomplete and filebrowser
|
||||||
\$('#email_dir').typeahead().fileBrowser();
|
\$('#email_dir').typeahead().fileBrowser();
|
||||||
|
|
||||||
// Expand on enable
|
// Expand on enable
|
||||||
\$('.col2 input[name$="enable"]').change(function() {
|
\$('.col2 input[name$="enable"]').change(function() {
|
||||||
if(this.checked) {
|
if(this.checked) {
|
||||||
\$(this).parents('.section').find('.col1').show()
|
\$(this).parents('.section').find('.col1').show()
|
||||||
|
\$(this).parents('.col2').find('.col2-cats').show()
|
||||||
} else {
|
} else {
|
||||||
\$(this).parents('.section').find('.col1').hide()
|
\$(this).parents('.section').find('.col1').hide()
|
||||||
|
\$(this).parents('.col2').find('.col2-cats').hide()
|
||||||
}
|
}
|
||||||
\$('form').submit()
|
\$('form').submit()
|
||||||
})
|
})
|
||||||
|
\$('#email_endjob').change(function() {
|
||||||
|
if(\$(this).val() > 0) {
|
||||||
|
\$(this).parents('.section').find('.col2-cats').show()
|
||||||
|
} else {
|
||||||
|
\$(this).parents('.section').find('.col2-cats').hide()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
/**
|
/**
|
||||||
Testing functions
|
Testing functions
|
||||||
**/
|
**/
|
||||||
|
|||||||
@@ -1,10 +1,10 @@
|
|||||||
<!--#set global $pane="RSS"#-->
|
<!--#set global $pane="RSS"#-->
|
||||||
<!--#set global $help_uri="configuration/1.1/rss"#-->
|
<!--#set global $help_uri="configuration/2.2/rss"#-->
|
||||||
<!--#include $webdir + "/_inc_header_uc.tmpl"#-->
|
<!--#include $webdir + "/_inc_header_uc.tmpl"#-->
|
||||||
<div class="colmask">
|
<div class="colmask">
|
||||||
<!--#if not $active_feed#-->
|
<!--#if not $active_feed#-->
|
||||||
<div class="section">
|
<div class="section">
|
||||||
<div class="padTable">
|
<div class="padTable">
|
||||||
<a class="main-helplink" href="$helpuri$help_uri" target="_blank"><span class="glyphicon glyphicon-question-sign"></span></a>
|
<a class="main-helplink" href="$helpuri$help_uri" target="_blank"><span class="glyphicon glyphicon-question-sign"></span></a>
|
||||||
<p>$T('explain-RSS')</p>
|
<p>$T('explain-RSS')</p>
|
||||||
<form action="add_rss_feed" method="post" autocomplete="off" novalidate>
|
<form action="add_rss_feed" method="post" autocomplete="off" novalidate>
|
||||||
@@ -24,7 +24,7 @@
|
|||||||
<input type="text" name="feed" class="smaller_input" value="$feed" />
|
<input type="text" name="feed" class="smaller_input" value="$feed" />
|
||||||
</td>
|
</td>
|
||||||
<td>
|
<td>
|
||||||
<input type="text" name="uri" />
|
<input type="text" name="uri" placeholder="$T('addMultipleFeeds')" />
|
||||||
</td>
|
</td>
|
||||||
<td class="nowrap">
|
<td class="nowrap">
|
||||||
<button type="submit" class="btn btn-default Save"><span class="glyphicon glyphicon-plus"></span> $T('button-add')</button>
|
<button type="submit" class="btn btn-default Save"><span class="glyphicon glyphicon-plus"></span> $T('button-add')</button>
|
||||||
@@ -47,26 +47,26 @@
|
|||||||
<!--#set $odd = not $odd#-->
|
<!--#set $odd = not $odd#-->
|
||||||
<tr class="data-row <!--#if $odd then " alt " else " "#-->">
|
<tr class="data-row <!--#if $odd then " alt " else " "#-->">
|
||||||
<td class="chk">
|
<td class="chk">
|
||||||
<input type="checkbox" class="toggleFeedCheckbox" name="enable" value="1" <!--#if int($rss[$feed_item]['enable']) !=0 then 'checked="checked"' else ""#--> rel="$feed_item" />
|
<input type="checkbox" class="toggleFeedCheckbox" name="enable" value="1" <!--#if int($rss[$feed_item]['enable']) !=0 then 'checked="checked"' else ""#--> rel="$feed_item" />
|
||||||
</td>
|
</td>
|
||||||
<td class="title">
|
<td class="title">
|
||||||
<a href="?feed=$rss[$feed_item]['link']" class="subscription-title path feed <!--#if int($rss[$feed_item]['enable']) != 0 then 'feed_enabled' else 'feed_disabled'#-->">
|
<a href="?feed=$rss[$feed_item]['link']" class="subscription-title path feed <!--#if int($rss[$feed_item]['enable']) != 0 then 'feed_enabled' else 'feed_disabled'#-->">
|
||||||
<div class="favicon" data-domain="$rss[$feed_item]['baselink']"></div> $feed_item
|
$feed_item
|
||||||
</a>
|
</a>
|
||||||
</td>
|
</td>
|
||||||
<td class="controls">
|
<td class="controls">
|
||||||
<button type="button" class="btn btn-default testFeed" rel="$feed_item"><span class="glyphicon glyphicon-sort"></span> $T('button-preFeed')</button>
|
<button type="button" class="btn btn-default testFeed" rel="$feed_item"><span class="glyphicon glyphicon-sort"></span> $T('button-preFeed')</button>
|
||||||
<input type="hidden" name="uri" value="$rss[$feed_item]['uri']" />
|
<input type="hidden" name="uri" value="$rss[$feed_item]['uris']" />
|
||||||
<button type="button" class="btn btn-default editFeed" rel="$feed_item"><span class="glyphicon glyphicon-pencil"></span> $T('Edit')</button>
|
<button type="button" class="btn btn-default editFeed" rel="$feed_item"><span class="glyphicon glyphicon-pencil"></span> $T('Edit')</button>
|
||||||
<button type="button" class="btn btn-default delFeed" rel="$feed_item"><span class="glyphicon glyphicon-trash"></span></button>
|
<button type="button" class="btn btn-default delFeed" rel="$feed_item"><span class="glyphicon glyphicon-trash"></span></button>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr class="feed-row <!--#if $odd then " alt " else " "#-->">
|
<!--#for $uri_index, $uri in enumerate($rss[$feed_item]['uri'])#-->
|
||||||
<td></td>
|
<tr class="feed-row <!--#if $odd then " alt " else " "#-->">
|
||||||
<td colspan="2">
|
<td><div class="favicon" style="background-image: url(//$rss[$feed_item]['baselink'][$uri_index]/favicon.ico);" data-domain="$rss[$feed_item]['baselink'][$uri_index]"></div></td>
|
||||||
<div>$rss[$feed_item]['uri']</div>
|
<td colspan="2"><div>$uri</div></td>
|
||||||
</td>
|
</tr>
|
||||||
</tr>
|
<!--#end for#-->
|
||||||
<!--#end for#-->
|
<!--#end for#-->
|
||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
@@ -89,9 +89,9 @@
|
|||||||
<div class="field-pair">
|
<div class="field-pair">
|
||||||
<label class="config narrow" for="rss_rate">$T('opt-rss_rate')</label>
|
<label class="config narrow" for="rss_rate">$T('opt-rss_rate')</label>
|
||||||
<input type="number" name="rss_rate" id="rss_rate" value="$rss_rate" min="15" max="1440" />
|
<input type="number" name="rss_rate" id="rss_rate" value="$rss_rate" min="15" max="1440" />
|
||||||
<button type="submit" class="btn btn-default"><span class="glyphicon glyphicon-ok"></span> $T('button-save')</button>
|
<button type="submit" class="btn btn-default"><span class="glyphicon glyphicon-ok"></span> $T('button-save')</button>
|
||||||
<span class="config narrow"> $T('Next scan at:') $rss_next</span>
|
<span class="config narrow"> $T('Next scan at:') $rss_next</span>
|
||||||
<span class="desc narrow">$T('explain-rss_rate')</span>
|
<span class="desc narrow">$T('explain-rss_rate')</span>
|
||||||
</div>
|
</div>
|
||||||
</fieldset>
|
</fieldset>
|
||||||
</div>
|
</div>
|
||||||
@@ -105,7 +105,7 @@
|
|||||||
<div class="padTable">
|
<div class="padTable">
|
||||||
<a class="main-helplink" href="$helpuri$help_uri" target="_blank"><span class="glyphicon glyphicon-question-sign"></span></a>
|
<a class="main-helplink" href="$helpuri$help_uri" target="_blank"><span class="glyphicon glyphicon-question-sign"></span></a>
|
||||||
<h2 class="nomargin activeRSS">
|
<h2 class="nomargin activeRSS">
|
||||||
<a href="${root}config/rss/">$T('cmenu-rss')</a> »
|
<a href="${root}config/rss/">$T('cmenu-rss')</a> »
|
||||||
<a href="$rss[$active_feed]['uri']" onclick="window.open(this.href); return false;">$active_feed</a>
|
<a href="$rss[$active_feed]['uri']" onclick="window.open(this.href); return false;">$active_feed</a>
|
||||||
</h2>
|
</h2>
|
||||||
<!--#if $error#-->
|
<!--#if $error#-->
|
||||||
@@ -117,7 +117,7 @@
|
|||||||
<form action="upd_rss_feed" method="post">
|
<form action="upd_rss_feed" method="post">
|
||||||
<input type="hidden" name="session" value="$session" />
|
<input type="hidden" name="session" value="$session" />
|
||||||
<input type="hidden" name="feed" value="$feed" />
|
<input type="hidden" name="feed" value="$feed" />
|
||||||
<input type="hidden" name="uri" value="$rss[$feed]['uri']" />
|
<input type="hidden" name="uri" value="$rss[$feed]['uris']" />
|
||||||
<table class="catTable">
|
<table class="catTable">
|
||||||
<thead>
|
<thead>
|
||||||
<tr>
|
<tr>
|
||||||
@@ -140,10 +140,10 @@
|
|||||||
<tbody>
|
<tbody>
|
||||||
<tr class="default">
|
<tr class="default">
|
||||||
<td>
|
<td>
|
||||||
<input type="checkbox" disabled="disabled" class="hidden" />
|
<input type="checkbox" disabled="disabled" class="hidden" />
|
||||||
</td>
|
</td>
|
||||||
<td>
|
<td>
|
||||||
<input type="text" disabled="disabled" size="1" class="hidden" />
|
<input type="text" disabled="disabled" size="1" class="hidden" />
|
||||||
</td>
|
</td>
|
||||||
<td>
|
<td>
|
||||||
<select name="filter_type" disabled="disabled" class="hidden">
|
<select name="filter_type" disabled="disabled" class="hidden">
|
||||||
@@ -154,15 +154,16 @@
|
|||||||
<option value=">"> $T('rss-atleast')</option>
|
<option value=">"> $T('rss-atleast')</option>
|
||||||
<option value="<"> $T('rss-atmost')</option>
|
<option value="<"> $T('rss-atmost')</option>
|
||||||
<option value="F"> $T('rss-from')</option>
|
<option value="F"> $T('rss-from')</option>
|
||||||
|
<option value="F"> $T('rss-from-show')</option>
|
||||||
</select>
|
</select>
|
||||||
</td>
|
</td>
|
||||||
<td>
|
<td>
|
||||||
<input type="text" disabled="disabled" value="" class="hidden" />
|
<input type="text" disabled="disabled" value="" class="hidden" />
|
||||||
</td>
|
</td>
|
||||||
<!--#if $rss[$feed]['pick_cat']#-->
|
<!--#if $rss[$feed]['pick_cat']#-->
|
||||||
<td>
|
<td>
|
||||||
<select name="cat">
|
<select name="cat">
|
||||||
<!--#for $ct in $cat_list#-->
|
<!--#for $ct in $categories#-->
|
||||||
<option value="$ct" <!--#if $ct==$rss[$feed]['cat'] then 'selected="selected"' else ""#-->>$Tspec($ct)</option>
|
<option value="$ct" <!--#if $ct==$rss[$feed]['cat'] then 'selected="selected"' else ""#-->>$Tspec($ct)</option>
|
||||||
<!--#end for#-->
|
<!--#end for#-->
|
||||||
</select>
|
</select>
|
||||||
@@ -190,7 +191,7 @@
|
|||||||
<!--#if $rss[$feed]['pick_script']#-->
|
<!--#if $rss[$feed]['pick_script']#-->
|
||||||
<td>
|
<td>
|
||||||
<select name="script">
|
<select name="script">
|
||||||
<!--#for $sc in $script_list#-->
|
<!--#for $sc in $scripts#-->
|
||||||
<option value="$sc" <!--#if $sc==$rss[$feed]['script'] then 'selected="selected"' else ""#-->>$Tspec($sc)</option>
|
<option value="$sc" <!--#if $sc==$rss[$feed]['script'] then 'selected="selected"' else ""#-->>$Tspec($sc)</option>
|
||||||
<!--#end for#-->
|
<!--#end for#-->
|
||||||
</select>
|
</select>
|
||||||
@@ -212,10 +213,10 @@
|
|||||||
<tbody>
|
<tbody>
|
||||||
<tr>
|
<tr>
|
||||||
<td>
|
<td>
|
||||||
<input type="checkbox" name="enabled" value="1" checked="checked" />
|
<input type="checkbox" name="enabled" value="1" checked="checked" />
|
||||||
</td>
|
</td>
|
||||||
<td>
|
<td>
|
||||||
<input type="text" name="new_index" size="1" />
|
<input type="text" name="new_index" size="1" />
|
||||||
</td>
|
</td>
|
||||||
<td>
|
<td>
|
||||||
<select name="filter_type">
|
<select name="filter_type">
|
||||||
@@ -225,16 +226,17 @@
|
|||||||
<option value="C"> $T('rss-mustcat')</option>
|
<option value="C"> $T('rss-mustcat')</option>
|
||||||
<option value=">"> $T('rss-atleast')</option>
|
<option value=">"> $T('rss-atleast')</option>
|
||||||
<option value="<"> $T('rss-atmost')</option>
|
<option value="<"> $T('rss-atmost')</option>
|
||||||
|
<option value="S"> $T('rss-from-show')</option>
|
||||||
<option value="F"> $T('rss-from')</option>
|
<option value="F"> $T('rss-from')</option>
|
||||||
</select>
|
</select>
|
||||||
</td>
|
</td>
|
||||||
<td>
|
<td>
|
||||||
<input type="text" name="filter_text" value="" />
|
<input type="text" name="filter_text" value="" />
|
||||||
</td>
|
</td>
|
||||||
<!--#if $rss[$feed]['pick_cat']#-->
|
<!--#if $rss[$feed]['pick_cat']#-->
|
||||||
<td>
|
<td>
|
||||||
<select name="cat">
|
<select name="cat">
|
||||||
<!--#for $ct in $cat_list#-->
|
<!--#for $ct in $categories#-->
|
||||||
<option value="$ct" <!--#if $ct=='Default' then 'selected="selected"' else ""#-->>$Tspec($ct)</option>
|
<option value="$ct" <!--#if $ct=='Default' then 'selected="selected"' else ""#-->>$Tspec($ct)</option>
|
||||||
<!--#end for#-->
|
<!--#end for#-->
|
||||||
</select>
|
</select>
|
||||||
@@ -262,7 +264,7 @@
|
|||||||
<!--#if $rss[$feed]['pick_script']#-->
|
<!--#if $rss[$feed]['pick_script']#-->
|
||||||
<td>
|
<td>
|
||||||
<select name="script">
|
<select name="script">
|
||||||
<!--#for $sc in $script_list#-->
|
<!--#for $sc in $scripts#-->
|
||||||
<option value="$sc" <!--#if $sc=='Default' then 'selected="selected"' else ""#-->>$Tspec($sc)</option>
|
<option value="$sc" <!--#if $sc=='Default' then 'selected="selected"' else ""#-->>$Tspec($sc)</option>
|
||||||
<!--#end for#-->
|
<!--#end for#-->
|
||||||
</select>
|
</select>
|
||||||
@@ -285,12 +287,12 @@
|
|||||||
<input type="hidden" name="feed" value="$feed" />
|
<input type="hidden" name="feed" value="$feed" />
|
||||||
<table class="catTable">
|
<table class="catTable">
|
||||||
<tbody>
|
<tbody>
|
||||||
<tr class="<!--#if $odd then " alt " else " "#-->">
|
<tr class="<!--#if $odd then " alt " else " "#--> <!--#if $filter[3]!="A" then 'disabled_options_rule' else ""#-->">
|
||||||
<td>
|
<td>
|
||||||
<input type="checkbox" name="enabled" value="1" <!--#if $filter[6]=='1' then 'checked="checked"' else ""#--> />
|
<input type="checkbox" name="enabled" value="1" <!--#if $filter[6]=='1' then 'checked="checked"' else ""#--> />
|
||||||
</td>
|
</td>
|
||||||
<td>
|
<td>
|
||||||
<input type="text" name="new_index" value="$fnum" size="1" />
|
<input type="text" name="new_index" value="$fnum" size="1" />
|
||||||
</td>
|
</td>
|
||||||
<td>
|
<td>
|
||||||
<select name="filter_type">
|
<select name="filter_type">
|
||||||
@@ -300,16 +302,17 @@
|
|||||||
<option value="C" <!--#if $filter[3]=="C" then 'selected="selected"' else ""#-->> $T('rss-mustcat')</option>
|
<option value="C" <!--#if $filter[3]=="C" then 'selected="selected"' else ""#-->> $T('rss-mustcat')</option>
|
||||||
<option value=">" <!--#if $filter[3]==">" then 'selected="selected"' else ""#-->> $T('rss-atleast')</option>
|
<option value=">" <!--#if $filter[3]==">" then 'selected="selected"' else ""#-->> $T('rss-atleast')</option>
|
||||||
<option value="<" <!--#if $filter[3]=="<" then 'selected="selected"' else ""#-->> $T('rss-atmost')</option>
|
<option value="<" <!--#if $filter[3]=="<" then 'selected="selected"' else ""#-->> $T('rss-atmost')</option>
|
||||||
|
<option value="S" <!--#if $filter[3]=="S" then 'selected="selected"' else ""#-->> $T('rss-from-show')</option>
|
||||||
<option value="F" <!--#if $filter[3]=="F" then 'selected="selected"' else ""#-->> $T('rss-from')</option>
|
<option value="F" <!--#if $filter[3]=="F" then 'selected="selected"' else ""#-->> $T('rss-from')</option>
|
||||||
</select>
|
</select>
|
||||||
</td>
|
</td>
|
||||||
<td>
|
<td>
|
||||||
<input type="text" name="filter_text" value="$filter[4]" />
|
<input type="text" name="filter_text" value="$filter[4]" />
|
||||||
</td>
|
</td>
|
||||||
<!--#if $rss[$feed]['pick_cat']#-->
|
<!--#if $rss[$feed]['pick_cat']#-->
|
||||||
<td>
|
<td>
|
||||||
<select name="cat">
|
<select name="cat">
|
||||||
<!--#for $ct in $cat_list#-->
|
<!--#for $ct in $categories#-->
|
||||||
<option value="$ct" <!--#if $ct==$filter[0] then 'selected="selected"' else ""#-->>$Tspec($ct)</option>
|
<option value="$ct" <!--#if $ct==$filter[0] then 'selected="selected"' else ""#-->>$Tspec($ct)</option>
|
||||||
<!--#end for#-->
|
<!--#end for#-->
|
||||||
</select>
|
</select>
|
||||||
@@ -337,7 +340,7 @@
|
|||||||
<!--#if $rss[$feed]['pick_script']#-->
|
<!--#if $rss[$feed]['pick_script']#-->
|
||||||
<td>
|
<td>
|
||||||
<select name="script">
|
<select name="script">
|
||||||
<!--#for $sc in $script_list#-->
|
<!--#for $sc in $scripts#-->
|
||||||
<option value="$sc" <!--#if $sc==$filter[2] then 'selected="selected"' else ""#-->>$Tspec($sc)</option>
|
<option value="$sc" <!--#if $sc==$filter[2] then 'selected="selected"' else ""#-->>$Tspec($sc)</option>
|
||||||
<!--#end for#-->
|
<!--#end for#-->
|
||||||
</select>
|
</select>
|
||||||
@@ -362,7 +365,10 @@
|
|||||||
<div class="padding">
|
<div class="padding">
|
||||||
<button type="button" class="btn btn-default testFeed" rel="$feed"><span class="glyphicon glyphicon-sort"></span> $T('button-preFeed')</button>
|
<button type="button" class="btn btn-default testFeed" rel="$feed"><span class="glyphicon glyphicon-sort"></span> $T('button-preFeed')</button>
|
||||||
<button type="submit" class="btn btn-default Save"><span class="glyphicon glyphicon-forward"></span> $T('button-forceFeed')</button>
|
<button type="submit" class="btn btn-default Save"><span class="glyphicon glyphicon-forward"></span> $T('button-forceFeed')</button>
|
||||||
<button type="button" class="btn btn-default cleanFeed"><span class="glyphicon glyphicon-trash"></span> $T('button-clear') $T('link-download')</button>
|
<button type="button" class="btn btn-default cleanFeed"><span class="glyphicon glyphicon-trash"></span> $T('button-clear') $T('rss-done')</button>
|
||||||
|
<!--#if $evalButton#-->
|
||||||
|
<button type="button" class="btn btn-default evalFeed" rel="$feed"><span class="glyphicon glyphicon-ok-circle"></span> $T('button-evalFeed')</button>
|
||||||
|
<!--#end if#-->
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
@@ -381,11 +387,12 @@
|
|||||||
<table class="catTable">
|
<table class="catTable">
|
||||||
<thead>
|
<thead>
|
||||||
<tr>
|
<tr>
|
||||||
<th>$T('link-download')</th>
|
<th class="no-sort">$T('link-download')</th>
|
||||||
<th>$T('rss-skip')</th>
|
|
||||||
<th>$T('rss-filter')</th>
|
<th>$T('rss-filter')</th>
|
||||||
<th>$T('size')</th>
|
<th>$T('size')</th>
|
||||||
<th>$T('sort-title')</th>
|
<th width="65%">$T('sort-title')</th>
|
||||||
|
<th>$T('category')</th>
|
||||||
|
<th class="default-sort">$T('nzo-age')</th>
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<!--#for $job in $matched#-->
|
<!--#for $job in $matched#-->
|
||||||
@@ -394,15 +401,16 @@
|
|||||||
<form action="download" method="get">
|
<form action="download" method="get">
|
||||||
<input type="hidden" value="$feed" name="feed" />
|
<input type="hidden" value="$feed" name="feed" />
|
||||||
<input type="hidden" name="session" value="$session" />
|
<input type="hidden" name="session" value="$session" />
|
||||||
<input type="hidden" name="url" value="$job[0]" />
|
<input type="hidden" name="url" value="$job['url']" />
|
||||||
<input type="hidden" name="nzbname" value="$job[4]" />
|
<input type="hidden" name="nzbname" value="$job['nzbname']" />
|
||||||
<button type="submit" class="btn btn-default"><span class="glyphicon glyphicon-plus-sign"></span> $T('link-download')</button>
|
<button type="submit" class="btn btn-default"><span class="glyphicon glyphicon-plus-sign"></span> $T('link-download')</button>
|
||||||
</form>
|
</form>
|
||||||
</td>
|
</td>
|
||||||
<td class="align-center">$job[2]</td>
|
<td>$job['rule'] $job['skip']</td>
|
||||||
<td class="align-center"> <span>$job[3]</span> </td>
|
<td data-sort-value="$job['size']">$job['size_units']</td>
|
||||||
<td>$job[5]</td>
|
<td>$job['title']</td>
|
||||||
<td>$job[1]</td>
|
<td>$job['cat']</td>
|
||||||
|
<td data-sort-value="$job['age_ms']">$job['age']</td>
|
||||||
</tr>
|
</tr>
|
||||||
<!--#end for#-->
|
<!--#end for#-->
|
||||||
</table>
|
</table>
|
||||||
@@ -415,11 +423,12 @@
|
|||||||
<table class="catTable">
|
<table class="catTable">
|
||||||
<thead>
|
<thead>
|
||||||
<tr>
|
<tr>
|
||||||
<th>$T('link-download')</th>
|
<th class="no-sort">$T('link-download')</th>
|
||||||
<th>$T('rss-skip')</th>
|
|
||||||
<th>$T('rss-filter')</th>
|
<th>$T('rss-filter')</th>
|
||||||
<th>$T('size')</th>
|
<th>$T('size')</th>
|
||||||
<th>$T('sort-title')</th>
|
<th width="65%">$T('sort-title')</th>
|
||||||
|
<th>$T('category')</th>
|
||||||
|
<th class="default-sort">$T('nzo-age')</th>
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<!--#for $job in $unmatched#-->
|
<!--#for $job in $unmatched#-->
|
||||||
@@ -428,15 +437,16 @@
|
|||||||
<form action="download" method="get">
|
<form action="download" method="get">
|
||||||
<input type="hidden" value="$feed" name="feed" />
|
<input type="hidden" value="$feed" name="feed" />
|
||||||
<input type="hidden" name="session" value="$session" />
|
<input type="hidden" name="session" value="$session" />
|
||||||
<input type="hidden" name="url" value="$job[0]" />
|
<input type="hidden" name="url" value="$job['url']" />
|
||||||
<input type="hidden" name="nzbname" value="$job[4]" />
|
<input type="hidden" name="nzbname" value="$job['nzbname']" />
|
||||||
<button type="submit" class="btn btn-default"><span class="glyphicon glyphicon-plus-sign"></span> $T('link-download')</button>
|
<button type="submit" class="btn btn-default"><span class="glyphicon glyphicon-plus-sign"></span> $T('link-download')</button>
|
||||||
</form>
|
</form>
|
||||||
</td>
|
</td>
|
||||||
<td class="align-center">$job[2]</td>
|
<td>$job['rule'] $job['skip']</td>
|
||||||
<td class="align-center"> <span>$job[3]</span> </td>
|
<td data-sort-value="$job['size']">$job['size_units']</td>
|
||||||
<td>$job[5]</td>
|
<td>$job['title']</td>
|
||||||
<td>$job[1]</td>
|
<td>$job['cat']</td>
|
||||||
|
<td data-sort-value="$job['age_ms']">$job['age']</td>
|
||||||
</tr>
|
</tr>
|
||||||
<!--#end for#-->
|
<!--#end for#-->
|
||||||
</table>
|
</table>
|
||||||
@@ -452,12 +462,24 @@
|
|||||||
<table class="catTable">
|
<table class="catTable">
|
||||||
<thead>
|
<thead>
|
||||||
<tr>
|
<tr>
|
||||||
<th>$T('sort-title')</th>
|
<th class="default-sort">$T('rss-added')</th>
|
||||||
|
<th>$T('size')</th>
|
||||||
|
<th width="60%">$T('sort-title')</th>
|
||||||
|
<th>$T('category')</th>
|
||||||
|
<th>$T('source')</th>
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<!--#for $job in $downloaded#-->
|
<!--#for $job in $downloaded#-->
|
||||||
<tr class="infoTableSeperator">
|
<tr class="infoTableSeperator">
|
||||||
<td>$job</td>
|
<td data-sort-value="$job['time_downloaded_ms']">$job['time_downloaded']</td>
|
||||||
|
<td data-sort-value="$job['size']">$job['size_units']</td>
|
||||||
|
<td>$job['title']</td>
|
||||||
|
<td>$job['cat']</td>
|
||||||
|
<td data-sort-value="$job['baselink']" title="$job['baselink']">
|
||||||
|
<!--#if $job['baselink']#-->
|
||||||
|
<div class="favicon source-icon" style="background-image: url(//$job['baselink']/favicon.ico);" data-domain="$job['baselink']"></div>
|
||||||
|
<!--#end if#-->
|
||||||
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
<!--#end for#-->
|
<!--#end for#-->
|
||||||
</table>
|
</table>
|
||||||
@@ -468,29 +490,28 @@
|
|||||||
</div>
|
</div>
|
||||||
<!--#end if#-->
|
<!--#end if#-->
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
<!-- /colmask -->
|
<!-- /colmask -->
|
||||||
|
<script type="text/javascript" src="${root}staticcfg/js/jquery.tablesort.min.js"></script>
|
||||||
<script type="text/javascript">
|
<script type="text/javascript">
|
||||||
function urlencode(str) {
|
function urlencode(str) {
|
||||||
return encodeURIComponent(str).replace(/!/g, '%21').replace(/'/g, '%27').replace(/\(/g, '%28').replace(/\)/g, '%29').replace(/\*/g, '%2A').replace(/%20/g, '+');
|
return encodeURIComponent(str).replace(/!/g, '%21').replace(/'/g, '%27').replace(/\(/g, '%28').replace(/\)/g, '%29').replace(/\*/g, '%2A').replace(/%20/g, '+');
|
||||||
}
|
}
|
||||||
|
|
||||||
\$(document).ready(function(){
|
\$(document).ready(function(){
|
||||||
\$('.favicon').each(function(i, theContainer) {
|
\$('.favicon').each(function(i, theContainer) {
|
||||||
// Easy favicon grabber
|
// Easy favicon grabber
|
||||||
var favUrl = '//' + \$(theContainer).data('domain') + '/favicon.ico'
|
var favUrl = '//' + \$(theContainer).data('domain') + '/favicon.ico'
|
||||||
\$(theContainer).css({'background': 'url('+favUrl+')', 'background-size': '16px 16px'})
|
|
||||||
|
|
||||||
// Does the image exist? Otherwise place a glyphicon
|
// Does the image exist? Otherwise place a glyphicon
|
||||||
var testFavImg= new Image();
|
var testFavImg = new Image();
|
||||||
testFavImg.src = favUrl;
|
testFavImg.src = favUrl;
|
||||||
testFavImg.onerror = function (evt){
|
testFavImg.onerror = function (evt){
|
||||||
\$(theContainer).append('<span class="glyphicon glyphicon-list"></span>')
|
\$(theContainer).append('<span class="glyphicon glyphicon-list"></span>')
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
\$('.tabs a').click(function (e) {
|
\$('.tabs a').click(function (e) {
|
||||||
e.preventDefault()
|
e.preventDefault()
|
||||||
\$(this).tab('show')
|
\$(this).tab('show')
|
||||||
@@ -498,7 +519,7 @@ function urlencode(str) {
|
|||||||
|
|
||||||
\$('.editFeed').click(function(){
|
\$('.editFeed').click(function(){
|
||||||
var oldURI = \$(this).prev().val();
|
var oldURI = \$(this).prev().val();
|
||||||
var newURI = prompt("$T('feed') URL", oldURI );
|
var newURI = prompt("$T('feed') URL. \n$T('addMultipleFeeds').", oldURI );
|
||||||
if(newURI != "" && newURI !== null) {
|
if(newURI != "" && newURI !== null) {
|
||||||
var whichFeed = \$(this).attr("rel");
|
var whichFeed = \$(this).attr("rel");
|
||||||
var isEnabled = \$('.toggleFeedCheckbox[rel="'+whichFeed+'"]').attr('checked') == "checked"? 1 : 0;
|
var isEnabled = \$('.toggleFeedCheckbox[rel="'+whichFeed+'"]').attr('checked') == "checked"? 1 : 0;
|
||||||
@@ -525,27 +546,12 @@ function urlencode(str) {
|
|||||||
}).done(function( msg ) {
|
}).done(function( msg ) {
|
||||||
// Let us leave!
|
// Let us leave!
|
||||||
formWasSubmitted = true;
|
formWasSubmitted = true;
|
||||||
formHasChanged = false;
|
formHasChanged = false;
|
||||||
location.reload();
|
location.reload();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
\$('.testFeed').click(function(){
|
|
||||||
var whichFeed = \$(this).attr("rel");
|
|
||||||
\$(this).attr('disabled', true)
|
|
||||||
\$.ajax({
|
|
||||||
type: "POST",
|
|
||||||
url: "test_rss_feed",
|
|
||||||
data: {feed: whichFeed, session: "$session" }
|
|
||||||
}).done(function( msg ) {
|
|
||||||
// Let us leave!
|
|
||||||
formWasSubmitted = true;
|
|
||||||
formHasChanged = false;
|
|
||||||
location = '?feed=' + urlencode(whichFeed);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
\$('.toggleFeedCheckbox').click(function(){
|
\$('.toggleFeedCheckbox').click(function(){
|
||||||
var whichFeed = \$(this).attr("rel");
|
var whichFeed = \$(this).attr("rel");
|
||||||
\$.ajax({
|
\$.ajax({
|
||||||
@@ -555,20 +561,82 @@ function urlencode(str) {
|
|||||||
}).done(function() {
|
}).done(function() {
|
||||||
// Let us leave!
|
// Let us leave!
|
||||||
formWasSubmitted = true;
|
formWasSubmitted = true;
|
||||||
formHasChanged = false;
|
formHasChanged = false;
|
||||||
location.reload();
|
location.reload();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Only the Accept filter needs all the options
|
||||||
|
\$('form[action="upd_rss_filter"]').find('select[name="filter_type"]').change(function() {
|
||||||
|
\$(this).parent().parent().find('select:not([name="filter_type"])').attr('disabled', \$(this).val() != "A")
|
||||||
|
})
|
||||||
|
// Trigger on-load for all
|
||||||
|
\$('.disabled_options_rule').find('td select:not([name="filter_type"])').attr('disabled', true)
|
||||||
|
|
||||||
|
function setActiveIcon(objButton) {
|
||||||
|
// Let's make it look like things are happening!
|
||||||
|
\$(objButton).attr('disabled', true)
|
||||||
|
\$(objButton).find('span').remove()
|
||||||
|
\$(objButton).prepend('<span class="glyphicon glyphicon-transfer"></span>')
|
||||||
|
}
|
||||||
|
|
||||||
|
// Enable sorting and set default
|
||||||
|
if (\$('#rss-tab-matched table').length) {
|
||||||
|
\$('#rss-tab-matched table').tablesort().data('tablesort').sort(\$('#rss-tab-matched th.default-sort'), 'desc');
|
||||||
|
}
|
||||||
|
if (\$('#rss-tab-not-matched table').length) {
|
||||||
|
\$('#rss-tab-not-matched table').tablesort().data('tablesort').sort(\$('#rss-tab-not-matched th.default-sort'), 'desc');
|
||||||
|
}
|
||||||
|
if (\$('#rss-tab-done table').length) {
|
||||||
|
\$('#rss-tab-done table').tablesort().data('tablesort').sort(\$('#rss-tab-done th.default-sort'), 'desc');
|
||||||
|
}
|
||||||
|
|
||||||
|
\$('.testFeed').click(function(){
|
||||||
|
setActiveIcon(this)
|
||||||
|
var whichFeed = \$(this).attr("rel");
|
||||||
|
\$.ajax({
|
||||||
|
type: "POST",
|
||||||
|
url: "test_rss_feed",
|
||||||
|
data: {feed: whichFeed, session: "$session" }
|
||||||
|
}).done(function( msg ) {
|
||||||
|
// Let us leave!
|
||||||
|
formWasSubmitted = true;
|
||||||
|
formHasChanged = false;
|
||||||
|
location = '?feed=' + urlencode(whichFeed);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
\$('.cleanFeed').click(function(){
|
\$('.cleanFeed').click(function(){
|
||||||
|
setActiveIcon(this)
|
||||||
var theForm = \$(this).closest("form");
|
var theForm = \$(this).closest("form");
|
||||||
theForm.attr("action", "clean_rss_jobs").submit();
|
theForm.attr("action", "clean_rss_jobs").submit();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
\$('.evalFeed').click(function(){
|
||||||
|
setActiveIcon(this)
|
||||||
|
var theForm = \$(this).closest("form");
|
||||||
|
theForm.attr("action", "eval_rss_feed").submit();
|
||||||
|
});
|
||||||
|
|
||||||
\$('.delFilter').click(function(){
|
\$('.delFilter').click(function(){
|
||||||
var theForm = \$(this).closest("form");
|
var theForm = \$(this).closest("form");
|
||||||
theForm.attr("action", "del_rss_filter").submit();
|
theForm.attr("action", "del_rss_filter").submit();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
\$('form[action="download"]').ajaxForm({
|
||||||
|
datatype: 'json',
|
||||||
|
beforeSubmit: function (_, form) {
|
||||||
|
\$(form).find('button').attr("disabled", "disabled")
|
||||||
|
// Remove icon and add new one
|
||||||
|
\$(form).find('button span').remove()
|
||||||
|
\$(form).find('button').prepend('<span class="glyphicon glyphicon-transfer"></span>')
|
||||||
|
},
|
||||||
|
success: function (_, _, _, form) {
|
||||||
|
// Set success
|
||||||
|
\$(form).find('button').html('<span class="glyphicon glyphicon-ok"></span> $T('rss-added')')
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
<!--#set global $pane="Scheduling"#-->
|
<!--#set global $pane="Scheduling"#-->
|
||||||
<!--#set global $help_uri="configuration/1.1/scheduling"#-->
|
<!--#set global $help_uri="configuration/2.2/scheduling"#-->
|
||||||
<!--#include $webdir + "/_inc_header_uc.tmpl"#-->
|
<!--#include $webdir + "/_inc_header_uc.tmpl"#-->
|
||||||
|
|
||||||
<%
|
<%
|
||||||
@@ -59,6 +59,13 @@ else:
|
|||||||
<option value="$server" data-action="0" data-noarg="1">$T('sch-disable_server') "$actions_servers[$server]"</option>
|
<option value="$server" data-action="0" data-noarg="1">$T('sch-disable_server') "$actions_servers[$server]"</option>
|
||||||
<!--#end for#-->
|
<!--#end for#-->
|
||||||
</optgroup>
|
</optgroup>
|
||||||
|
<optgroup label="$T('cmenu-cat')">
|
||||||
|
<!--#for $cat in $categories#-->
|
||||||
|
<!--#set $cat_text = $T('Default') if $cat == '*' else $cat#-->
|
||||||
|
<option value="pause_cat" data-action="$cat" data-noarg="1">$T('sch-pause_cat') "$cat_text"</option>
|
||||||
|
<option value="resume_cat" data-action="$cat" data-noarg="1">$T('sch-resume_cat') "$cat_text"</option>
|
||||||
|
<!--#end for#-->
|
||||||
|
</optgroup>
|
||||||
</select>
|
</select>
|
||||||
</div>
|
</div>
|
||||||
<div class="field-pair" id="hidden_arguments" style="display: none">
|
<div class="field-pair" id="hidden_arguments" style="display: none">
|
||||||
@@ -72,7 +79,7 @@ else:
|
|||||||
</div><!-- /col1 -->
|
</div><!-- /col1 -->
|
||||||
</form>
|
</form>
|
||||||
</div><!-- /section -->
|
</div><!-- /section -->
|
||||||
|
|
||||||
<div class="section">
|
<div class="section">
|
||||||
<div class="col2">
|
<div class="col2">
|
||||||
<h3>$T('currentSchedules')</h3>
|
<h3>$T('currentSchedules')</h3>
|
||||||
@@ -80,21 +87,20 @@ else:
|
|||||||
<div class="col1">
|
<div class="col1">
|
||||||
<fieldset>
|
<fieldset>
|
||||||
<!--#if $schedlines#-->
|
<!--#if $schedlines#-->
|
||||||
<!--#set $schednum = 0#-->
|
|
||||||
<!--#set $odd = True#-->
|
<!--#set $odd = True#-->
|
||||||
<!--#for $line in $schedlines#-->
|
<!--#for $schednum, $line in enumerate($schedlines)#-->
|
||||||
<!--#set $odd = not $odd#-->
|
<!--#set $odd = not $odd#-->
|
||||||
<form action="delSchedule" method="post">
|
<form action="delSchedule" method="post">
|
||||||
<input type="hidden" name="session" value="$session"/>
|
<input type="hidden" name="session" value="$session"/>
|
||||||
<input type="hidden" name="line" id="line" value="$line"/>
|
<input type="hidden" name="line" id="line" value="$line"/>
|
||||||
<div class="field-pair infoTableSeperator <!--#if $odd then "" else " alt"#-->">
|
<div class="field-pair infoTableSeperator <!--#if $odd then "" else " alt"#-->">
|
||||||
|
<input type="checkbox" name="schedenabled" value="$line" <!--#if int($taskinfo[$schednum][5]) > 0 then 'checked="checked"' else ""#-->>
|
||||||
<button class="btn btn-default float-left"><span class="glyphicon glyphicon-trash"></span></button>
|
<button class="btn btn-default float-left"><span class="glyphicon glyphicon-trash"></span></button>
|
||||||
<div class="scheduleEntry">
|
<div class="scheduleEntry">
|
||||||
<span class="time">$taskinfo[$schednum][1]:$taskinfo[$schednum][2]</span><span class="frequency">$taskinfo[$schednum][3]</span> <span class="darkred">$taskinfo[$schednum][4]</span>
|
<span class="time">$taskinfo[$schednum][1]:$taskinfo[$schednum][2]</span><span class="frequency">$taskinfo[$schednum][3]</span> <span class="darkred">$taskinfo[$schednum][4]</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
<!--#set $schednum = $schednum+1#-->
|
|
||||||
<!--#end for#-->
|
<!--#end for#-->
|
||||||
<!--#else#-->
|
<!--#else#-->
|
||||||
<div class="field-pair">
|
<div class="field-pair">
|
||||||
@@ -109,7 +115,7 @@ else:
|
|||||||
\$('#action').on('change', function() {
|
\$('#action').on('change', function() {
|
||||||
// Set the action
|
// Set the action
|
||||||
\$('#arguments').val((\$(this).find('option:selected').data('action')))
|
\$('#arguments').val((\$(this).find('option:selected').data('action')))
|
||||||
|
|
||||||
// Is it speedlimit?
|
// Is it speedlimit?
|
||||||
if(\$(this).find('option:selected').val() == 'speedlimit') {
|
if(\$(this).find('option:selected').val() == 'speedlimit') {
|
||||||
\$('#hidden_arguments').show()
|
\$('#hidden_arguments').show()
|
||||||
@@ -126,5 +132,18 @@ else:
|
|||||||
\$('#hidden_arguments').show()
|
\$('#hidden_arguments').show()
|
||||||
}*/
|
}*/
|
||||||
})
|
})
|
||||||
|
|
||||||
|
\$('[name="schedenabled"]').click(function() {
|
||||||
|
\$.ajax({
|
||||||
|
type: "POST",
|
||||||
|
url: "toggleSchedule",
|
||||||
|
data: {line: \$(this).val(), session: "$session" }
|
||||||
|
}).done(function() {
|
||||||
|
// Let us leave!
|
||||||
|
formWasSubmitted = true;
|
||||||
|
formHasChanged = false;
|
||||||
|
location.reload();
|
||||||
|
});
|
||||||
|
});
|
||||||
</script>
|
</script>
|
||||||
<!--#include $webdir + "/_inc_footer_uc.tmpl"#-->
|
<!--#include $webdir + "/_inc_footer_uc.tmpl"#-->
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
<!--#set global $pane="Servers"#-->
|
<!--#set global $pane="Servers"#-->
|
||||||
<!--#set global $help_uri="configuration/1.1/servers"#-->
|
<!--#set global $help_uri="configuration/2.2/servers"#-->
|
||||||
<!--#include $webdir + "/_inc_header_uc.tmpl"#-->
|
<!--#include $webdir + "/_inc_header_uc.tmpl"#-->
|
||||||
|
|
||||||
<div class="colmask">
|
<div class="colmask">
|
||||||
@@ -27,7 +27,12 @@
|
|||||||
</div>
|
</div>
|
||||||
<div class="field-pair">
|
<div class="field-pair">
|
||||||
<label class="config" for="port">$T('srv-port')</label>
|
<label class="config" for="port">$T('srv-port')</label>
|
||||||
<input type="number" name="port" id="port" size="8" />
|
<input type="number" name="port" id="port" size="8" value="119" />
|
||||||
|
</div>
|
||||||
|
<div class="field-pair">
|
||||||
|
<label class="config" for="ssl">$T('srv-ssl')</label>
|
||||||
|
<input type="checkbox" name="ssl" id="ssl" value="1" />
|
||||||
|
<span class="desc">$T('explain-ssl')</span>
|
||||||
</div>
|
</div>
|
||||||
<!-- Tricks to avoid browser auto-fill, fixed on-submit with javascript -->
|
<!-- Tricks to avoid browser auto-fill, fixed on-submit with javascript -->
|
||||||
<div class="field-pair">
|
<div class="field-pair">
|
||||||
@@ -40,80 +45,50 @@
|
|||||||
</div>
|
</div>
|
||||||
<div class="field-pair">
|
<div class="field-pair">
|
||||||
<label class="config" for="connections">$T('srv-connections')</label>
|
<label class="config" for="connections">$T('srv-connections')</label>
|
||||||
<input type="number" name="connections" id="connections" min="0" max="100" />
|
<input type="number" name="connections" id="connections" min="0" max="100" value="8" />
|
||||||
</div>
|
</div>
|
||||||
<div class="field-pair">
|
<div class="field-pair">
|
||||||
<label class="config" for="priority">$T('srv-priority')</label>
|
<label class="config" for="priority">$T('srv-priority')</label>
|
||||||
<input type="number" name="priority" id="priority" min="0" max="100" /> <i>$T('explain-svrprio')</i>
|
<input type="number" name="priority" id="priority" min="0" max="100" /> <i>$T('explain-svrprio')</i>
|
||||||
</div>
|
</div>
|
||||||
<div class="field-pair">
|
<div class="field-pair advanced-settings">
|
||||||
<label class="config" for="retention">$T('srv-retention')</label>
|
<label class="config" for="retention">$T('srv-retention')</label>
|
||||||
<input type="number" name="retention" id="retention" min="0" /> <i>$T('days')</i>
|
<input type="number" name="retention" id="retention" min="0" /> <i>$T('days')</i>
|
||||||
</div>
|
</div>
|
||||||
<div class="field-pair">
|
<div class="field-pair advanced-settings">
|
||||||
<label class="config" for="timeout">$T('srv-timeout')</label>
|
<label class="config" for="timeout">$T('srv-timeout')</label>
|
||||||
<input type="number" name="timeout" id="timeout" min="30" /> <i>$T('seconds')</i>
|
<input type="number" name="timeout" id="timeout" min="30" /> <i>$T('seconds')</i>
|
||||||
</div>
|
</div>
|
||||||
<div class="field-pair <!--#if int($have_ssl) == 0 then "disabled" else ""#-->">
|
<div class="field-pair <!--#if int($have_ssl_context) == 0 then "disabled" else ""#--> advanced-settings">
|
||||||
<label class="config" for="ssl">$T('srv-ssl')</label>
|
<label class="config" for="ssl_verify">$T('opt-ssl_verify')</label>
|
||||||
<input type="checkbox" name="ssl" id="ssl" value="1" <!--#if int($have_ssl) == 0 then "disabled=\"disabled\"" else ""#--> />
|
<select name="ssl_verify" id="ssl_verify" <!--#if int($have_ssl_context) == 0 then "disabled=\"disabled\"" else ""#-->>
|
||||||
<span class="desc">$T('srv-ssl')</span>
|
<option value="2" selected>$T('ssl_verify-strict')</option>
|
||||||
|
<option value="1">$T('ssl_verify-normal')</option>
|
||||||
|
<option value="0">$T('ssl_verify-disabled')</option>
|
||||||
|
</select>
|
||||||
|
<span class="desc">$T('explain-ssl_verify').replace('. ', '.<br/>')</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="field-pair <!--#if int($have_ssl) == 0 then "disabled" else ""#-->">
|
<div class="field-pair advanced-settings">
|
||||||
<label class="config" for="ssl_type">$T('srv-ssl_type')</label>
|
|
||||||
<!--#if int($have_ssl) == 1#-->
|
|
||||||
<select name="ssl_type" id="ssl_type">
|
|
||||||
<option value="">$T('Default')</option>
|
|
||||||
<!--#if 't12' in $ssl_protocols#-->
|
|
||||||
<option value="t12">TLS v1.2</option>
|
|
||||||
<!--#end if#-->
|
|
||||||
<!--#if 't11' in $ssl_protocols#-->
|
|
||||||
<option value="t11">TLS v1.1</option>
|
|
||||||
<!--#end if#-->
|
|
||||||
<!--#if 't1' in $ssl_protocols#-->
|
|
||||||
<option value="t1">TLS v1</option>
|
|
||||||
<!--#end if#-->
|
|
||||||
<!--#if 'v3' in $ssl_protocols#-->
|
|
||||||
<option value="v3">SSL v3</option>
|
|
||||||
<!--#end if#-->
|
|
||||||
<!--#if 'v2' in $ssl_protocols#-->
|
|
||||||
<option value="v2">SSL v2</option>
|
|
||||||
<!--#end if#-->
|
|
||||||
</select>
|
|
||||||
<!--#end if#-->
|
|
||||||
<span class="desc">$T('srv-explain-ssl_type')</span>
|
|
||||||
</div>
|
|
||||||
<div class="field-pair">
|
|
||||||
<label class="config" for="send_group">$T('srv-send_group')</label>
|
<label class="config" for="send_group">$T('srv-send_group')</label>
|
||||||
<input type="checkbox" name="send_group" id="send_group" value="1" />
|
<input type="checkbox" name="send_group" id="send_group" value="1" />
|
||||||
<span class="desc">$T('srv-explain-send_group')</span>
|
<span class="desc">$T('srv-explain-send_group')</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="field-pair">
|
<div class="field-pair advanced-settings">
|
||||||
<label class="config" for="optional">$T('srv-optional')</label>
|
<label class="config" for="optional">$T('srv-optional')</label>
|
||||||
<input type="checkbox" name="optional" id="optional" value="1" />
|
<input type="checkbox" name="optional" id="optional" value="1" />
|
||||||
<span class="desc">$T('srv-optional')</span>
|
<span class="desc">$T('explain-optional')</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="field-pair">
|
<div class="field-pair advanced-settings">
|
||||||
<label class="config" for="categories">$T('srv-categories')</label>
|
|
||||||
<select name="categories" id="categories" multiple>
|
|
||||||
<!--#for $cat in $cats#-->
|
|
||||||
<option value="$cat" <!--#if $cat == "Default"#-->selected<!--#end if#-->>
|
|
||||||
<!--#if $cat == "Default" then $T('Default') else $cat#-->
|
|
||||||
</option>
|
|
||||||
<!--#end for#-->
|
|
||||||
</select>
|
|
||||||
<span class="desc">$T('srv-explain-categories')</span>
|
|
||||||
</div>
|
|
||||||
<div class="field-pair">
|
|
||||||
<label class="config" for="displayname">$T('srv-displayname')</label>
|
<label class="config" for="displayname">$T('srv-displayname')</label>
|
||||||
<input type="text" name="displayname" id="displayname" />
|
<input type="text" name="displayname" id="displayname" />
|
||||||
</div>
|
</div>
|
||||||
<div class="field-pair">
|
<div class="field-pair advanced-settings">
|
||||||
<label class="config" for="notes">$T('srv-notes')</label>
|
<label class="config" for="notes">$T('srv-notes')</label>
|
||||||
<textarea name="notes" id="notes" rows="3" cols="50"></textarea>
|
<textarea name="notes" id="notes" rows="3" cols="50"></textarea>
|
||||||
</div>
|
</div>
|
||||||
<div class="field-pair">
|
<div class="field-pair">
|
||||||
<button class="btn btn-default"><span class="glyphicon glyphicon-plus"></span> $T('button-addServer')</button>
|
<button class="btn btn-default"><span class="glyphicon glyphicon-plus"></span> $T('button-addServer')</button>
|
||||||
|
<button class="btn btn-default advancedButton"><span class="glyphicon glyphicon-cog"></span> $T('button-advanced')</button>
|
||||||
<button class="btn btn-default testServer" type="button"><span class="glyphicon glyphicon-sort"></span> $T('button-testServer')</button>
|
<button class="btn btn-default testServer" type="button"><span class="glyphicon glyphicon-sort"></span> $T('button-testServer')</button>
|
||||||
</div>
|
</div>
|
||||||
<div class="field-pair result-box">
|
<div class="field-pair result-box">
|
||||||
@@ -124,10 +99,63 @@
|
|||||||
</div><!-- /section -->
|
</div><!-- /section -->
|
||||||
</form>
|
</form>
|
||||||
|
|
||||||
<!--#set $cur = 0#-->
|
<script type="text/javascript" src="${root}staticcfg/js/chartist.min.js"></script>
|
||||||
<!--#for $server in $servers#-->
|
<script type="text/javascript">
|
||||||
<!--#set $cur = $cur + 1#-->
|
// Define variables needed for the server-plots
|
||||||
|
var serverData = {}
|
||||||
|
var chartOptions = {
|
||||||
|
fullWidth: true,
|
||||||
|
showArea: true,
|
||||||
|
axisX: {
|
||||||
|
labelOffset: {
|
||||||
|
x: -5
|
||||||
|
},
|
||||||
|
showGrid: false
|
||||||
|
},
|
||||||
|
axisY: {
|
||||||
|
labelOffset: {
|
||||||
|
y: 7
|
||||||
|
},
|
||||||
|
scaleMinSpace: 30
|
||||||
|
},
|
||||||
|
chartPadding: {
|
||||||
|
top: 9,
|
||||||
|
bottom: 0,
|
||||||
|
left: 30,
|
||||||
|
right: 20
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
<!--
|
||||||
|
We need to find how many months we have recorded so far, so we
|
||||||
|
loop over all the dates to find the lowest value and then use
|
||||||
|
this to calculate the date-selector
|
||||||
|
-->
|
||||||
|
|
||||||
|
<!--#import json#-->
|
||||||
|
<!--#import datetime#-->
|
||||||
|
<!--#import sabnzbd.misc#-->
|
||||||
|
|
||||||
|
<!--#def show_date_selector($server, $id)#-->
|
||||||
|
<!--#set month_names = [$T('January'), $T('February'), $T('March'), $T('April'), $T('May'), $T('June'), $T('July'), $T('August'), $T('September'), $T('October'), $T('November'), $T('December')] #-->
|
||||||
|
<!--#set min_date = datetime.date.today()#-->
|
||||||
|
<!--#for date in $server['amounts'][4]#-->
|
||||||
|
<!--#set split_date = $date.split('-')#-->
|
||||||
|
<!--#set min_date = min(min_date, datetime.date(int(split_date[0]), int(split_date[1]), 1))#-->
|
||||||
|
<!--#end for#-->
|
||||||
|
<!--#set months_recorded = list(sabnzbd.misc.monthrange(min_date, datetime.date.today()))#-->
|
||||||
|
<!--#$months_recorded.reverse()#-->
|
||||||
|
<select class="chart-selector" name="chart-selector-${id}" id="chart-selector-${id}" data-id="${id}">
|
||||||
|
<!--#for $cur_date in months_recorded#-->
|
||||||
|
<option value="<!--#echo '%d-%02d' % ($cur_date.year, $cur_date.month)#-->">$month_names[$cur_date.month-1] $cur_date.year</option>
|
||||||
|
<!--#end for#-->
|
||||||
|
</select>
|
||||||
|
<!--#end def#-->
|
||||||
|
|
||||||
|
<!--#set $prio_colors = ["#59cc33", "#3366cc","#7f33cc", "#cc33a6", "#cc3333"] #-->
|
||||||
|
<!--#set $cur_prio_color = -1 #-->
|
||||||
|
<!--#set $last_prio = -1 #-->
|
||||||
|
<!--#for $cur, $server in enumerate($servers) #-->
|
||||||
<form action="saveServer" method="post" class="fullform" autocomplete="off" novalidate>
|
<form action="saveServer" method="post" class="fullform" autocomplete="off" novalidate>
|
||||||
<input type="hidden" name="session" value="$session" />
|
<input type="hidden" name="session" value="$session" />
|
||||||
<input type="hidden" name="server" value="$server['name']" />
|
<input type="hidden" name="server" value="$server['name']" />
|
||||||
@@ -135,9 +163,14 @@
|
|||||||
<div class="section <!--#if int($server['enable']) == 0 then 'server-disabled' else ""#-->">
|
<div class="section <!--#if int($server['enable']) == 0 then 'server-disabled' else ""#-->">
|
||||||
<div class="col2 <!--#if int($server['enable']) == 0 then 'server-disabled' else ""#-->">
|
<div class="col2 <!--#if int($server['enable']) == 0 then 'server-disabled' else ""#-->">
|
||||||
<h3>$server['displayname'] <a href="$helpuri$help_uri" target="_blank"><span class="glyphicon glyphicon-question-sign"></span></a></h3>
|
<h3>$server['displayname'] <a href="$helpuri$help_uri" target="_blank"><span class="glyphicon glyphicon-question-sign"></span></a></h3>
|
||||||
<!--#if int($server['enable']) != 0#-->
|
|
||||||
<span class="label label-primary" data-priority="$server['priority']#-->">$server['priority']</span>
|
<!--#if int($server['enable']) != 0 #-->
|
||||||
<span class="label label-primary" data-priority="$server['priority']#-->">$T('srv-priority'):</span>
|
<!--#if $last_prio != $server['priority'] and $cur_prio_color+1 < len($prio_colors) #-->
|
||||||
|
<!--#set $cur_prio_color = $cur_prio_color+1 #-->
|
||||||
|
<!--#set $last_prio = $server['priority'] #-->
|
||||||
|
<!--#end if#-->
|
||||||
|
<span class="label label-primary" style="background-color: $prio_colors[$cur_prio_color]">$server['priority']</span>
|
||||||
|
<span class="label label-primary" style="background-color: $prio_colors[$cur_prio_color]">$T('srv-priority'):</span>
|
||||||
<!--#end if#-->
|
<!--#end if#-->
|
||||||
<table><tr>
|
<table><tr>
|
||||||
<td><input type="checkbox" class="toggleServerCheckbox" id="enable_$cur" name="$server['name']" value="1" <!--#if int($server['enable']) != 0 then 'checked="checked"' else ""#--> /></td>
|
<td><input type="checkbox" class="toggleServerCheckbox" id="enable_$cur" name="$server['name']" value="1" <!--#if int($server['enable']) != 0 then 'checked="checked"' else ""#--> /></td>
|
||||||
@@ -158,6 +191,11 @@
|
|||||||
<label class="config" for="port$cur">$T('srv-port')</label>
|
<label class="config" for="port$cur">$T('srv-port')</label>
|
||||||
<input type="number" name="port" id="port$cur" value="$server['port']" size="8" />
|
<input type="number" name="port" id="port$cur" value="$server['port']" size="8" />
|
||||||
</div>
|
</div>
|
||||||
|
<div class="field-pair">
|
||||||
|
<label class="config" for="ssl$cur">$T('srv-ssl')</label>
|
||||||
|
<input type="checkbox" name="ssl" id="ssl$cur" value="1" <!--#if int($server['ssl']) != 0 then 'checked="checked"' else ""#--> />
|
||||||
|
<span class="desc">$T('explain-ssl')</span>
|
||||||
|
</div>
|
||||||
<!-- Tricks to avoid browser auto-fill, fixed on-submit with javascript -->
|
<!-- Tricks to avoid browser auto-fill, fixed on-submit with javascript -->
|
||||||
<div class="field-pair">
|
<div class="field-pair">
|
||||||
<label class="config" for="${pid}_${cur}0">$T('srv-username')</label>
|
<label class="config" for="${pid}_${cur}0">$T('srv-username')</label>
|
||||||
@@ -175,78 +213,45 @@
|
|||||||
<label class="config" for="priority$cur">$T('srv-priority')</label>
|
<label class="config" for="priority$cur">$T('srv-priority')</label>
|
||||||
<input type="number" name="priority" id="priority$cur" value="$server['priority']" min="0" max="100" /> <i>$T('explain-svrprio')</i>
|
<input type="number" name="priority" id="priority$cur" value="$server['priority']" min="0" max="100" /> <i>$T('explain-svrprio')</i>
|
||||||
</div>
|
</div>
|
||||||
<div class="field-pair">
|
<div class="field-pair advanced-settings">
|
||||||
<label class="config" for="retention$cur">$T('srv-retention')</label>
|
<label class="config" for="retention$cur">$T('srv-retention')</label>
|
||||||
<input type="number" name="retention" id="retention$cur" value="$server['retention']" min="0" /> <i>$T('days')</i>
|
<input type="number" name="retention" id="retention$cur" value="$server['retention']" min="0" /> <i>$T('days')</i>
|
||||||
</div>
|
</div>
|
||||||
<div class="field-pair">
|
<div class="field-pair advanced-settings">
|
||||||
<label class="config" for="timeout$cur">$T('srv-timeout')</label>
|
<label class="config" for="timeout$cur">$T('srv-timeout')</label>
|
||||||
<input type="number" name="timeout" id="timeout$cur" value="$server['timeout']" min="30" /> <i>$T('seconds')</i>
|
<input type="number" name="timeout" id="timeout$cur" value="$server['timeout']" min="30" /> <i>$T('seconds')</i>
|
||||||
</div>
|
</div>
|
||||||
<div class="field-pair <!--#if int($have_ssl) == 0 then "disabled" else ""#-->">
|
|
||||||
<label class="config" for="ssl$cur">$T('srv-ssl')</label>
|
<div class="field-pair <!--#if int($have_ssl_context) == 0 then "disabled" else ""#--> advanced-settings">
|
||||||
<input type="checkbox" name="ssl" id="ssl$cur" value="1" <!--#if int($server['ssl']) != 0 and int($have_ssl) == 1 then 'checked="checked"' else ""#--> <!--#if int($have_ssl) == 0 then "disabled=\"disabled\"" else ""#--> />
|
<label class="config" for="ssl_verify$cur">$T('opt-ssl_verify')</label>
|
||||||
<span class="desc">$T('srv-ssl')</span>
|
<select name="ssl_verify" id="ssl_verify$cur" <!--#if int($have_ssl_context) == 0 then "disabled=\"disabled\"" else ""#-->>
|
||||||
|
<option value="2" <!--#if $server['ssl_verify'] == 2 then 'selected="selected"' else ""#--> >$T('ssl_verify-strict')</option>
|
||||||
|
<option value="1" <!--#if $server['ssl_verify'] == 1 then 'selected="selected"' else ""#--> >$T('ssl_verify-normal')</option>
|
||||||
|
<option value="0" <!--#if $server['ssl_verify'] == 0 then 'selected="selected"' else ""#--> >$T('ssl_verify-disabled')</option>
|
||||||
|
</select>
|
||||||
|
<span class="desc">$T('explain-ssl_verify').replace('. ', '.<br/>')</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="field-pair <!--#if int($have_ssl) == 0 then "disabled" else ""#-->">
|
<div class="field-pair advanced-settings">
|
||||||
<label class="config" for="ssl_type$cur">$T('srv-ssl_type')</label>
|
|
||||||
<!--#if int($have_ssl) == 1#-->
|
|
||||||
<select name="ssl_type" id="ssl_type$cur">
|
|
||||||
<option value="" <!--#if $server['ssl_type'] == "" then 'selected="selected"' else ""#--> >$T('Default')</option>
|
|
||||||
<!--#if 't12' in $ssl_protocols#-->
|
|
||||||
<option value="t12" <!--#if $server['ssl_type'] == "t12" then 'selected="selected"' else ""#--> >TLS v1.2</option>
|
|
||||||
<!--#end if#-->
|
|
||||||
<!--#if 't11' in $ssl_protocols#-->
|
|
||||||
<option value="t11" <!--#if $server['ssl_type'] == "t11" then 'selected="selected"' else ""#--> >TLS v1.1</option>
|
|
||||||
<!--#end if#-->
|
|
||||||
<!--#if 't1' in $ssl_protocols#-->
|
|
||||||
<option value="t1" <!--#if $server['ssl_type'] == "t1" then 'selected="selected"' else ""#--> >TLS v1</option>
|
|
||||||
<!--#end if#-->
|
|
||||||
<!--#if 'v3' in $ssl_protocols#-->
|
|
||||||
<option value="v3" <!--#if $server['ssl_type'] == "v3" then 'selected="selected"' else ""#--> >SSL v3</option>
|
|
||||||
<!--#end if#-->
|
|
||||||
<!--#if 'v2' in $ssl_protocols#-->
|
|
||||||
<option value="v2" <!--#if $server['ssl_type'] == "v2" then 'selected="selected"' else ""#--> >SSL v2</option>
|
|
||||||
<!--#end if#-->
|
|
||||||
</select>
|
|
||||||
<!--#end if#-->
|
|
||||||
<span class="desc">$T('srv-explain-ssl_type')</span>
|
|
||||||
</div>
|
|
||||||
<div class="field-pair">
|
|
||||||
<label class="config" for="optional$cur">$T('srv-optional')</label>
|
<label class="config" for="optional$cur">$T('srv-optional')</label>
|
||||||
<input type="checkbox" name="optional" id="optional$cur" value="1" <!--#if int($server['optional']) != 0 then 'checked="checked"' else ""#--> />
|
<input type="checkbox" name="optional" id="optional$cur" value="1" <!--#if int($server['optional']) != 0 then 'checked="checked"' else ""#--> />
|
||||||
<span class="desc">$T('srv-optional')</span>
|
<span class="desc">$T('explain-optional')</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="field-pair">
|
<div class="field-pair advanced-settings">
|
||||||
<label class="config" for="send_group$cur">$T('srv-send_group')</label>
|
<label class="config" for="send_group$cur">$T('srv-send_group')</label>
|
||||||
<input type="checkbox" name="send_group" id="send_group$cur" value="1" <!--#if int($server['send_group']) != 0 then 'checked="checked"' else ""#--> />
|
<input type="checkbox" name="send_group" id="send_group$cur" value="1" <!--#if int($server['send_group']) != 0 then 'checked="checked"' else ""#--> />
|
||||||
<span class="desc">$T('srv-explain-send_group')</span>
|
<span class="desc">$T('srv-explain-send_group')</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="field-pair">
|
<div class="field-pair advanced-settings">
|
||||||
<label class="config" for="categories$cur">$T('srv-categories')</label>
|
|
||||||
<select name="categories" id="categories$cur" multiple>
|
|
||||||
<!--#for $cat in $cats#-->
|
|
||||||
<option value="$cat" <!--#if $cat in $server['categories'] then 'selected' else ""#-->>
|
|
||||||
<!--#if $cat == "Default" then $T('Default') else $cat#-->
|
|
||||||
</option>
|
|
||||||
<!--#end for#-->
|
|
||||||
</select>
|
|
||||||
<span class="desc">$T('srv-explain-categories')</span>
|
|
||||||
<div class="alert alert-info alert-no-category">
|
|
||||||
<span class="glyphicon glyphicon-info-sign"></span>
|
|
||||||
$T('srv-explain-no-categories')
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="field-pair">
|
|
||||||
<label class="config" for="displayname$cur">$T('srv-displayname')</label>
|
<label class="config" for="displayname$cur">$T('srv-displayname')</label>
|
||||||
<input type="text" name="displayname" id="displayname$cur" value="$server['displayname']" />
|
<input type="text" name="displayname" id="displayname$cur" value="$server['displayname']" />
|
||||||
</div>
|
</div>
|
||||||
<div class="field-pair">
|
<div class="field-pair advanced-settings">
|
||||||
<label class="config" for="notes$cur">$T('srv-notes')</label>
|
<label class="config" for="notes$cur">$T('srv-notes')</label>
|
||||||
<textarea name="notes" id="notes$cur" rows="3" cols="50">$server['notes']</textarea>
|
<textarea name="notes" id="notes$cur" rows="3" cols="50">$server['notes']</textarea>
|
||||||
</div>
|
</div>
|
||||||
<div class="field-pair">
|
<div class="field-pair">
|
||||||
<button class="btn btn-default saveButton"><span class="glyphicon glyphicon-ok"></span> $T('button-saveChanges')</button>
|
<button class="btn btn-default saveButton"><span class="glyphicon glyphicon-ok"></span> $T('button-saveChanges')</button>
|
||||||
|
<button class="btn btn-default advancedButton"><span class="glyphicon glyphicon-cog"></span> $T('button-advanced')</button>
|
||||||
<button class="btn btn-default testServer" type="button"><span class="glyphicon glyphicon-sort"></span> $T('button-testServer')</button>
|
<button class="btn btn-default testServer" type="button"><span class="glyphicon glyphicon-sort"></span> $T('button-testServer')</button>
|
||||||
<button class="btn btn-default delServer"><span class="glyphicon glyphicon-trash"></span> $T('button-delServer')</button>
|
<button class="btn btn-default delServer"><span class="glyphicon glyphicon-trash"></span> $T('button-delServer')</button>
|
||||||
</div>
|
</div>
|
||||||
@@ -254,157 +259,252 @@
|
|||||||
<div class="alert"></div>
|
<div class="alert"></div>
|
||||||
</div>
|
</div>
|
||||||
</fieldset>
|
</fieldset>
|
||||||
</div><!-- /col1 -->
|
|
||||||
<div class="col2" style="display:block;">
|
|
||||||
<!--#if 'amounts' in $server#-->
|
|
||||||
<b>$T('srv-bandwidth'):</b><br/>
|
|
||||||
$T('total'): $(server['amounts'][0])B<br/>
|
|
||||||
$T('today'): $(server['amounts'][3])B<br/>
|
|
||||||
$T('thisWeek'): $(server['amounts'][2])B<br/>
|
|
||||||
$T('thisMonth'): $(server['amounts'][1])B
|
|
||||||
<!--#end if#-->
|
|
||||||
</div>
|
</div>
|
||||||
</div><!-- /section -->
|
<div class="col1" style="display:block;">
|
||||||
|
<!--#if 'amounts' in $server#-->
|
||||||
|
<div class="server-amounts-text">
|
||||||
|
<b>$T('srv-bandwidth'):</b><br/>
|
||||||
|
$T('total'): $(server['amounts'][0])B<br/>
|
||||||
|
$T('today'): $(server['amounts'][3])B<br/>
|
||||||
|
$T('thisWeek'): $(server['amounts'][2])B<br/>
|
||||||
|
$T('thisMonth'): $(server['amounts'][1])B
|
||||||
|
</div>
|
||||||
|
<div class="server-chart">
|
||||||
|
$show_date_selector($server, $cur)
|
||||||
|
<div id="server-chart-${cur}" class="ct-chart"></div>
|
||||||
|
</div>
|
||||||
|
<script type="text/javascript">
|
||||||
|
// Server data
|
||||||
|
serverData[${cur}] = <!--#echo json.dumps($server['amounts'][4])#-->
|
||||||
|
\$(document).ready(function() {
|
||||||
|
showChart(${cur}, \$('#chart-selector-${cur}').val())
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
<!--#end if#-->
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</form>
|
</form>
|
||||||
<!--#end for#-->
|
<!--#end for#-->
|
||||||
|
|
||||||
</div><!-- /colmask -->
|
</div><!-- /colmask -->
|
||||||
|
|
||||||
<script type="text/javascript">
|
<script type="text/javascript">
|
||||||
\$(document).ready(function(){
|
function showChart(server_id, month) {
|
||||||
// Exception when change of priority, reload
|
// This month
|
||||||
\$('input[name="priority"], input[name="displayname"]').on('change', function() {
|
var thisDay = new Date()
|
||||||
\$('.fullform').submit(function() {
|
|
||||||
// Skip the fancy stuff, just submit
|
|
||||||
this.submit()
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
/**
|
|
||||||
Color the priority labels
|
|
||||||
**/
|
|
||||||
// They are already sorted
|
|
||||||
var colorList = ["#cc3333", "#cc33a6", "#7f33cc", "#3366cc", "#59cc33"];
|
|
||||||
var lastColor = colorList[0];
|
|
||||||
var lastPrio = '-1';
|
|
||||||
var theColor = '';
|
|
||||||
\$('[data-priority]').each(function() {
|
|
||||||
// New one?
|
|
||||||
if(\$(this).data('priority') != lastPrio) {
|
|
||||||
// Update
|
|
||||||
theColor = colorList.pop() || lastColor;
|
|
||||||
lastPrio = \$(this).data('priority');
|
|
||||||
}
|
|
||||||
\$(this).css('background-color', theColor)
|
|
||||||
})
|
|
||||||
|
|
||||||
/**
|
// What month are we doing?
|
||||||
Message on no Default category selected
|
var inputDate = new Date(month+'-01')
|
||||||
**/
|
var baseDate = new Date(inputDate.getUTCFullYear(), inputDate.getUTCMonth(), 1)
|
||||||
function checkServerCats() {
|
var maxDaysInMonth = new Date(baseDate.getFullYear(), baseDate.getMonth()+1, 0).getDate()
|
||||||
// Now we check all of them
|
|
||||||
var hasDefault = false;
|
// Fill the data array
|
||||||
// Only check the active servers, not the add-server one
|
var data = {
|
||||||
\$('.section:not(#addServerContent) select[name="categories"]').each(function() {
|
labels: [],
|
||||||
// See if this server is enabled
|
series: [[]]
|
||||||
if(!\$(this).parents('.section').find('.col2').hasClass('server-disabled') ) {
|
};
|
||||||
// Is there Default?
|
var largestVal = 0
|
||||||
if(\$(this).val().indexOf('Default') > -1) {
|
for(var i = 1; i < maxDaysInMonth+1; i++) {
|
||||||
// Hide
|
// Add X-label
|
||||||
\$('.alert-no-category').hide()
|
if(i % 3 == 1) {
|
||||||
hasDefault = true
|
data['labels'].push(i)
|
||||||
// All good!
|
} else {
|
||||||
return true
|
data['labels'].push(NaN)
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Get formatted date
|
||||||
|
baseDate.setDate(i)
|
||||||
|
var dateCheck = toFormattedDate(baseDate)
|
||||||
|
|
||||||
|
// Add data if we have it
|
||||||
|
if(dateCheck in serverData[server_id]) {
|
||||||
|
data['series'][0].push(serverData[server_id][dateCheck])
|
||||||
|
largestVal = Math.max(largestVal, serverData[server_id][dateCheck])
|
||||||
|
} else if(thisDay.getYear() == baseDate.getYear() && thisDay.getMonth() == baseDate.getMonth() && thisDay.getDate() < i) {
|
||||||
|
data['series'][0].push(NaN)
|
||||||
|
} else {
|
||||||
|
data['series'][0].push(0)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if we should shrink the Y-axis values
|
||||||
|
var devideBy = 1024
|
||||||
|
var axisLabel = 'KB'
|
||||||
|
if(largestVal > 1024*1024) {
|
||||||
|
devideBy = 1024*1024
|
||||||
|
axisLabel = 'MB'
|
||||||
|
}
|
||||||
|
if(largestVal > 1024*1024*1024) {
|
||||||
|
devideBy = 1024*1024*1024
|
||||||
|
axisLabel = 'GB'
|
||||||
|
}
|
||||||
|
if(largestVal > 1024*1024*1024*1024) {
|
||||||
|
devideBy = 1024*1024*1024*1024
|
||||||
|
axisLabel = 'TB'
|
||||||
|
}
|
||||||
|
|
||||||
|
// Shrink the value
|
||||||
|
data['series'][0] = data['series'][0].map(function(num) {
|
||||||
|
return num / devideBy;
|
||||||
})
|
})
|
||||||
// We found nothing.. Let's show a warning
|
|
||||||
if(!hasDefault) \$('.alert-no-category').show()
|
// Show the chart
|
||||||
|
chart = new Chartist.Line('#server-chart-'+server_id, data, chartOptions);
|
||||||
|
chart.on('created', function(context) {
|
||||||
|
// Make sure to add this as the first child so it's at the bottom
|
||||||
|
context.svg.elem('rect', {
|
||||||
|
x: context.chartRect.x1,
|
||||||
|
y: context.chartRect.y2-1,
|
||||||
|
width: context.chartRect.width(),
|
||||||
|
height: context.chartRect.height()+2,
|
||||||
|
fill: 'none',
|
||||||
|
stroke: '#B9B9B9',
|
||||||
|
'stroke-width': '1px'
|
||||||
|
}, '', context.svg, true)
|
||||||
|
\$('#server-chart-'+server_id+' .ct-label.ct-vertical').each(function(index, elmn) {
|
||||||
|
elmn.innerHTML += axisLabel
|
||||||
|
})
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
\$('select[name="categories"]').on('change', checkServerCats)
|
// Need to mitigate timezone effects!
|
||||||
checkServerCats()
|
function toFormattedDate(date) {
|
||||||
|
var local = new Date(date);
|
||||||
/**
|
local.setMinutes(date.getMinutes() - date.getTimezoneOffset());
|
||||||
Click events
|
return local.toJSON().slice(0, 10);
|
||||||
**/
|
}
|
||||||
\$('.showserver').click(function () {
|
|
||||||
if(\$(this).parent().hasClass('server-disabled')) {
|
|
||||||
\$(this).parent().parent().toggleClass('server-disabled')
|
|
||||||
}
|
|
||||||
\$(this).parent().next().toggle();
|
|
||||||
\$(this).parent().next().next().toggle();
|
|
||||||
if (\$(this).attr("value") == "$T('showDetails')") {
|
|
||||||
\$(this).attr("value", "$T('hideDetails')");
|
|
||||||
} else {
|
|
||||||
\$(this).attr("value", "$T('showDetails')");
|
|
||||||
}
|
|
||||||
});
|
|
||||||
\$('#addServerButton').click(function(){
|
|
||||||
\$('#addServer').hide();
|
|
||||||
\$('#addServerContent').show();
|
|
||||||
});
|
|
||||||
\$('.testServer').click(function(event){
|
|
||||||
removeObfuscation()
|
|
||||||
var theButton = \$(this)
|
|
||||||
var resultBox = theButton.parents('.col1').find('.result-box .alert');
|
|
||||||
theButton.attr("disabled", "disabled")
|
|
||||||
theButton.find('span').toggleClass('glyphicon-sort glyphicon-refresh spin-glyphicon')
|
|
||||||
\$.ajax({
|
|
||||||
type: "POST",
|
|
||||||
url: "../../tapi",
|
|
||||||
data: "mode=config&output=json&name=test_server&" + \$(this).parents('form:first').serialize()
|
|
||||||
}).then(function(data) {
|
|
||||||
// Fill the box and enable the button
|
|
||||||
resultBox.removeClass('alert-success alert-danger').show()
|
|
||||||
resultBox.text(data.value.message)
|
|
||||||
theButton.removeAttr("disabled")
|
|
||||||
theButton.find('span').toggleClass('glyphicon-sort glyphicon-refresh spin-glyphicon')
|
|
||||||
|
|
||||||
// Succes or not?
|
/**
|
||||||
if(data.value.result) {
|
When finished loading
|
||||||
resultBox.addClass('alert-success')
|
**/
|
||||||
resultBox.prepend('<span class="glyphicon glyphicon-ok-sign"></span> ')
|
\$(document).ready(function(){
|
||||||
|
// Exception when change of priority, reload
|
||||||
|
\$('input[name="priority"], input[name="displayname"]').on('change', function() {
|
||||||
|
\$('.fullform').submit(function() {
|
||||||
|
// Skip the fancy stuff, just submit
|
||||||
|
this.submit()
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
/**
|
||||||
|
Update charts when changed
|
||||||
|
**/
|
||||||
|
\$('.chart-selector').on('change', function(elemn) {
|
||||||
|
showChart(\$(elemn.target).data('id'), \$(elemn.target).val())
|
||||||
|
// Lets us leave (needs to be called after the change event)
|
||||||
|
setTimeout(function() {
|
||||||
|
formWasSubmitted = true;
|
||||||
|
formHasChanged = false;
|
||||||
|
}, 100)
|
||||||
|
})
|
||||||
|
|
||||||
|
/**
|
||||||
|
Click events
|
||||||
|
**/
|
||||||
|
\$('.showserver').click(function () {
|
||||||
|
if(\$(this).parent().hasClass('server-disabled')) {
|
||||||
|
\$(this).parent().parent().toggleClass('server-disabled')
|
||||||
|
}
|
||||||
|
\$(this).parent().next().toggle();
|
||||||
|
\$(this).parent().next().next().toggle();
|
||||||
|
if (\$(this).attr("value") == "$T('showDetails')") {
|
||||||
|
\$(this).attr("value", "$T('hideDetails')");
|
||||||
} else {
|
} else {
|
||||||
resultBox.addClass('alert-danger')
|
\$(this).attr("value", "$T('showDetails')");
|
||||||
resultBox.prepend('<span class="glyphicon glyphicon-exclamation-sign"></span> ')
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
});
|
|
||||||
\$('.delServer').click(function(){
|
\$('#addServerButton').click(function(){
|
||||||
if( confirm("$T('Plush-confirm')") ) {
|
\$('#addServer').hide();
|
||||||
\$(this).parents('form:first').attr('action','delServer').submit();
|
\$('#addServerContent').show();
|
||||||
// Let us leave!
|
});
|
||||||
formWasSubmitted = true;
|
|
||||||
formHasChanged = false;
|
\$('[name="ssl"]').click(function() {
|
||||||
setTimeout(function() { location.reload(); }, 500)
|
// Use CSS transitions to do some highlighting
|
||||||
}
|
var portBox = \$(this).parent().parent().find('[name="port"]')
|
||||||
return false;
|
if(this.checked) {
|
||||||
});
|
// Enabled SSL change port when not already a custom port
|
||||||
\$('.clrServer').click(function(){
|
if(portBox.val() == '119') {
|
||||||
if( confirm("$T('Plush-confirm')") ) {
|
portBox.val('563')
|
||||||
\$(this).parents('form:first').attr('action','clrServer').submit();
|
portBox.addClass('port-highlight')
|
||||||
// Let us leave!
|
}
|
||||||
formWasSubmitted = true;
|
} else {
|
||||||
formHasChanged = false;
|
// Remove SSL port
|
||||||
setTimeout(function() { location.reload(); }, 500)
|
if(portBox.val() == '563') {
|
||||||
}
|
portBox.val('119')
|
||||||
return false;
|
portBox.addClass('port-highlight')
|
||||||
});
|
}
|
||||||
\$('.toggleServerCheckbox').click(function(){
|
}
|
||||||
var whichServer = \$(this).attr("name");
|
setTimeout(function() { portBox.removeClass('port-highlight') }, 2000)
|
||||||
\$.ajax({
|
})
|
||||||
type: "POST",
|
|
||||||
url: "toggleServer",
|
\$('.testServer').click(function(event){
|
||||||
data: {server: whichServer, session: "$session" }
|
removeObfuscation()
|
||||||
}).done(function() {
|
var theButton = \$(this)
|
||||||
// Let us leave!
|
var resultBox = theButton.parents('.col1').find('.result-box .alert');
|
||||||
formWasSubmitted = true;
|
theButton.attr("disabled", "disabled")
|
||||||
formHasChanged = false;
|
theButton.find('span').toggleClass('glyphicon-sort glyphicon-refresh spin-glyphicon')
|
||||||
setTimeout(function() { location.reload(); }, 100)
|
\$.ajax({
|
||||||
|
type: "POST",
|
||||||
|
url: "../../tapi",
|
||||||
|
data: "mode=config&output=json&name=test_server&" + \$(this).parents('form:first').serialize()
|
||||||
|
}).then(function(data) {
|
||||||
|
// Let's replace the link
|
||||||
|
msg = data.value.message.replace('https://sabnzbd.org/certificate-errors', '<a href="https://sabnzbd.org/certificate-errors" class="alert-link" target="_blank">https://sabnzbd.org/certificate-errors</a>')
|
||||||
|
msg = msg.replace('-', '<br>')
|
||||||
|
// Fill the box and enable the button
|
||||||
|
resultBox.removeClass('alert-success alert-danger').show()
|
||||||
|
resultBox.html(msg)
|
||||||
|
theButton.removeAttr("disabled")
|
||||||
|
theButton.find('span').toggleClass('glyphicon-sort glyphicon-refresh spin-glyphicon')
|
||||||
|
|
||||||
|
// Succes or not?
|
||||||
|
if(data.value.result) {
|
||||||
|
resultBox.addClass('alert-success')
|
||||||
|
resultBox.prepend('<span class="glyphicon glyphicon-ok-sign"></span> ')
|
||||||
|
} else {
|
||||||
|
resultBox.addClass('alert-danger')
|
||||||
|
resultBox.prepend('<span class="glyphicon glyphicon-exclamation-sign"></span> ')
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
\$('.delServer').click(function(){
|
||||||
|
if( confirm("$T('Plush-confirm')") ) {
|
||||||
|
\$(this).parents('form:first').attr('action','delServer').submit();
|
||||||
|
// Let us leave!
|
||||||
|
formWasSubmitted = true;
|
||||||
|
formHasChanged = false;
|
||||||
|
setTimeout(function() { location.reload(); }, 500)
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
});
|
||||||
|
|
||||||
|
\$('.clrServer').click(function(){
|
||||||
|
if( confirm("$T('Plush-confirm')") ) {
|
||||||
|
\$(this).parents('form:first').attr('action','clrServer').submit();
|
||||||
|
// Let us leave!
|
||||||
|
formWasSubmitted = true;
|
||||||
|
formHasChanged = false;
|
||||||
|
setTimeout(function() { location.reload(); }, 500)
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
});
|
||||||
|
|
||||||
|
\$('.toggleServerCheckbox').click(function(){
|
||||||
|
var whichServer = \$(this).attr("name");
|
||||||
|
\$.ajax({
|
||||||
|
type: "POST",
|
||||||
|
url: "toggleServer",
|
||||||
|
data: {server: whichServer, session: "$session" }
|
||||||
|
}).done(function() {
|
||||||
|
// Let us leave!
|
||||||
|
formWasSubmitted = true;
|
||||||
|
formHasChanged = false;
|
||||||
|
setTimeout(function() { location.reload(); }, 100)
|
||||||
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<!--#include $webdir + "/_inc_footer_uc.tmpl"#-->
|
<!--#include $webdir + "/_inc_footer_uc.tmpl"#-->
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
<!--#set global $pane="Sorting"#-->
|
<!--#set global $pane="Sorting"#-->
|
||||||
<!--#set global $help_uri="configuration/1.1/sorting"#-->
|
<!--#set global $help_uri="configuration/2.2/sorting"#-->
|
||||||
<!--#include $webdir + "/_inc_header_uc.tmpl"#-->
|
<!--#include $webdir + "/_inc_header_uc.tmpl"#-->
|
||||||
|
|
||||||
<div class="colmask">
|
<div class="colmask">
|
||||||
@@ -9,10 +9,10 @@
|
|||||||
<div class="section">
|
<div class="section">
|
||||||
<div class="col2">
|
<div class="col2">
|
||||||
<h3>$T('seriesSorting') <a href="$helpuri$help_uri#toc0" target="_blank"><span class="glyphicon glyphicon-question-sign"></span></a></h3>
|
<h3>$T('seriesSorting') <a href="$helpuri$help_uri#toc0" target="_blank"><span class="glyphicon glyphicon-question-sign"></span></a></h3>
|
||||||
<p>
|
<p>
|
||||||
<b>$T('affectedCat')</b><br/>
|
<b>$T('affectedCat')</b><br/>
|
||||||
<select name="tv_cat" multiple="multiple" class="multiple_cats">
|
<select name="tv_cat" multiple="multiple" class="multiple_cats">
|
||||||
<!--#for $ct in $cat_list#-->
|
<!--#for $ct in $categories#-->
|
||||||
<option value="$ct" <!--#if $ct in $tv_categories then 'selected="selected"' else ""#--> >$Tspec($ct)</option>
|
<option value="$ct" <!--#if $ct in $tv_categories then 'selected="selected"' else ""#--> >$Tspec($ct)</option>
|
||||||
<!--#end for#-->
|
<!--#end for#-->
|
||||||
</select>
|
</select>
|
||||||
@@ -22,11 +22,11 @@
|
|||||||
<div class="col1">
|
<div class="col1">
|
||||||
<fieldset>
|
<fieldset>
|
||||||
<div class="field-pair">
|
<div class="field-pair">
|
||||||
<h5 class="darkred nomargin">$T('ft-download'): <span class="path">$complete_dir</span></h5>
|
<h5 class="darkred nomargin">$T('ft-download'): <span class="path">$complete_dir</span></h5>
|
||||||
</div>
|
</div>
|
||||||
<div class="field-pair">
|
<div class="field-pair">
|
||||||
<label class="config wide" for="enable_tv_sorting">$T('opt-tvsort')</label>
|
<label class="config wide" for="enable_tv_sorting">$T('opt-tvsort')</label>
|
||||||
<input type="checkbox" name="enable_tv_sorting" id="enable_tv_sorting" value="1" <!--#if int($enable_tv_sorting)> 0 then 'checked="checked"' else ""#--> />
|
<input type="checkbox" name="enable_tv_sorting" id="enable_tv_sorting" value="1" <!--#if int($enable_tv_sorting)> 0 then 'checked="checked"' else ""#--> />
|
||||||
</div>
|
</div>
|
||||||
<div class="field-pair">
|
<div class="field-pair">
|
||||||
<label class="config" for="tvfoldername">$T('sortString')</label>
|
<label class="config" for="tvfoldername">$T('sortString')</label>
|
||||||
@@ -39,7 +39,7 @@
|
|||||||
<input type="button" class="btn btn-default" onclick="tvSet('%sn/Season %s/%sn - S%0sE%0e - %en.%ext')" value="$T('button-SeasonS01E05')" />
|
<input type="button" class="btn btn-default" onclick="tvSet('%sn/Season %s/%sn - S%0sE%0e - %en.%ext')" value="$T('button-SeasonS01E05')" />
|
||||||
<br/>
|
<br/>
|
||||||
<input type="button" class="btn btn-default" onclick="tvSet('%sn/%sx%0e - %en/%sn - %sx%0e - %en.%ext')" value="$T('button-Ep1x05')" />
|
<input type="button" class="btn btn-default" onclick="tvSet('%sn/%sx%0e - %en/%sn - %sx%0e - %en.%ext')" value="$T('button-Ep1x05')" />
|
||||||
<input type="button" class="btn btn-default" onclick="tvSet('%sn/S%0sE%0e - %en/%sn - S%0sE%0e - %en.%ext')" value="$T('button-EpS01E05')" />
|
<input type="button" class="btn btn-default" onclick="tvSet('%sn/S%0sE%0e - %en/%sn - S%0sE%0e - %en.%ext')" value="$T('button-EpS01E05')" />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div id="previewtv" class="example">
|
<div id="previewtv" class="example">
|
||||||
@@ -48,7 +48,7 @@
|
|||||||
<input type="text" id="tvsamplename" name="tvsamplename" placeholder="$T('show-name') S01E05 - $T('ep-name') [DTS]" /><button class="btn btn-default clearBtn" type="button"><span class="glyphicon glyphicon-remove"></span> $T('button-clear')</button>
|
<input type="text" id="tvsamplename" name="tvsamplename" placeholder="$T('show-name') S01E05 - $T('ep-name') [DTS]" /><button class="btn btn-default clearBtn" type="button"><span class="glyphicon glyphicon-remove"></span> $T('button-clear')</button>
|
||||||
</div>
|
</div>
|
||||||
<div class="field-pair">
|
<div class="field-pair">
|
||||||
<label class="config">$T('sortResult')</label> <span class="desc path" id="previewtv-result"> </span>
|
<label class="config">$T('sortResult')</label> <span class="desc path" id="previewtv-result"> </span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="field-pair">
|
<div class="field-pair">
|
||||||
@@ -162,10 +162,10 @@
|
|||||||
<div class="section">
|
<div class="section">
|
||||||
<div class="col2">
|
<div class="col2">
|
||||||
<h3>$T('movieSort') <a href="$helpuri$help_uri#toc6" target="_blank"><span class="glyphicon glyphicon-question-sign"></span></a></h3>
|
<h3>$T('movieSort') <a href="$helpuri$help_uri#toc6" target="_blank"><span class="glyphicon glyphicon-question-sign"></span></a></h3>
|
||||||
<p>
|
<p>
|
||||||
<b>$T('affectedCat')</b><br/>
|
<b>$T('affectedCat')</b><br/>
|
||||||
<select name="movie_cat" multiple="multiple" class="multiple_cats">
|
<select name="movie_cat" multiple="multiple" class="multiple_cats">
|
||||||
<!--#for $ct in $cat_list#-->
|
<!--#for $ct in $categories#-->
|
||||||
<option value="$ct" <!--#if $ct in $movie_categories then 'selected="selected"' else ""#--> >$Tspec($ct)</option>
|
<option value="$ct" <!--#if $ct in $movie_categories then 'selected="selected"' else ""#--> >$Tspec($ct)</option>
|
||||||
<!--#end for#-->
|
<!--#end for#-->
|
||||||
</select>
|
</select>
|
||||||
@@ -175,7 +175,7 @@
|
|||||||
<div class="col1">
|
<div class="col1">
|
||||||
<fieldset>
|
<fieldset>
|
||||||
<div class="field-pair">
|
<div class="field-pair">
|
||||||
<h5 class="darkred nomargin">$T('ft-download'): <span class="path">$complete_dir</span></h5>
|
<h5 class="darkred nomargin">$T('ft-download'): <span class="path">$complete_dir</span></h5>
|
||||||
</div>
|
</div>
|
||||||
<div class="field-pair">
|
<div class="field-pair">
|
||||||
<label class="config wide" for="enable_movie_sorting">$T('opt-movieSort')</label>
|
<label class="config wide" for="enable_movie_sorting">$T('opt-movieSort')</label>
|
||||||
@@ -183,7 +183,7 @@
|
|||||||
</div>
|
</div>
|
||||||
<div class="field-pair">
|
<div class="field-pair">
|
||||||
<label class="config wide" for="movie_extra_folder">$T('opt-movieExtra')</label>
|
<label class="config wide" for="movie_extra_folder">$T('opt-movieExtra')</label>
|
||||||
<input type="checkbox" name="movie_extra_folder" id="movie_extra_folder" value="1" <!--#if int($movie_extra_folder)> 0 then 'checked="checked"' else ""#--> />
|
<input type="checkbox" name="movie_extra_folder" id="movie_extra_folder" value="1" <!--#if int($movie_extra_folder)> 0 then 'checked="checked"' else ""#--> />
|
||||||
</div>
|
</div>
|
||||||
<div class="field-pair">
|
<div class="field-pair">
|
||||||
<label class="config" for="moviefoldername">$T('sortString')</label>
|
<label class="config" for="moviefoldername">$T('sortString')</label>
|
||||||
@@ -198,7 +198,7 @@
|
|||||||
<div class="presets float-left">
|
<div class="presets float-left">
|
||||||
<input type="button" class="btn btn-default" onclick="movieSet('%title (%y)/%title (%y).%ext',' CD%1');movieExtraFolder(false)" value="$T('button-inFolders')" />
|
<input type="button" class="btn btn-default" onclick="movieSet('%title (%y)/%title (%y).%ext',' CD%1');movieExtraFolder(false)" value="$T('button-inFolders')" />
|
||||||
<input type="button" class="btn btn-default" onclick="movieSet('%title (%y).%ext',' CD%1');movieExtraFolder(true)" value="$T('button-noFolders')" />
|
<input type="button" class="btn btn-default" onclick="movieSet('%title (%y).%ext',' CD%1');movieExtraFolder(true)" value="$T('button-noFolders')" />
|
||||||
<input type="button" class="btn btn-default" onclick="movieSet('%0decade/%title (%y).%ext',' CD%1');movieExtraFolder(true)" value="Decades 1" />
|
<input type="button" class="btn btn-default" onclick="movieSet('%0decade/%title (%y).%ext',' CD%1');movieExtraFolder(true)" value="$T('decade')" />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div id="previewmovie" class="example">
|
<div id="previewmovie" class="example">
|
||||||
@@ -303,7 +303,7 @@
|
|||||||
<p>
|
<p>
|
||||||
<b>$T('affectedCat')</b><br/>
|
<b>$T('affectedCat')</b><br/>
|
||||||
<select name="date_cat" multiple="multiple" class="multiple_cats">
|
<select name="date_cat" multiple="multiple" class="multiple_cats">
|
||||||
<!--#for $ct in $cat_list#-->
|
<!--#for $ct in $categories#-->
|
||||||
<option value="$ct" <!--#if $ct in $date_categories then 'selected="selected"' else ""#--> >$Tspec($ct)</option>
|
<option value="$ct" <!--#if $ct in $date_categories then 'selected="selected"' else ""#--> >$Tspec($ct)</option>
|
||||||
<!--#end for#-->
|
<!--#end for#-->
|
||||||
</select>
|
</select>
|
||||||
@@ -313,11 +313,11 @@
|
|||||||
<div class="col1">
|
<div class="col1">
|
||||||
<fieldset>
|
<fieldset>
|
||||||
<div class="field-pair">
|
<div class="field-pair">
|
||||||
<h5 class="darkred nomargin">$T('ft-download'): <span class="path">$complete_dir</span></h5>
|
<h5 class="darkred nomargin">$T('ft-download'): <span class="path">$complete_dir</span></h5>
|
||||||
</div>
|
</div>
|
||||||
<div class="field-pair">
|
<div class="field-pair">
|
||||||
<label class="config wide" for="enable_date_sorting">$T('opt-dateSort')</label>
|
<label class="config wide" for="enable_date_sorting">$T('opt-dateSort')</label>
|
||||||
<input type="checkbox" name="enable_date_sorting" id="enable_date_sorting" value="1" <!--#if int($enable_date_sorting)> 0 then 'checked="checked"' else ""#--> />
|
<input type="checkbox" name="enable_date_sorting" id="enable_date_sorting" value="1" <!--#if int($enable_date_sorting)> 0 then 'checked="checked"' else ""#--> />
|
||||||
</div>
|
</div>
|
||||||
<div class="field-pair">
|
<div class="field-pair">
|
||||||
<label class="config" for="datefoldername">$T('sortString')</label>
|
<label class="config" for="datefoldername">$T('sortString')</label>
|
||||||
@@ -328,7 +328,7 @@
|
|||||||
<div class="presets float-left">
|
<div class="presets float-left">
|
||||||
<input type="button" class="btn btn-default" onclick="dateSet('%t/%t - %y-%0m-%0d - %desc.%ext')" value="$T('button-ShowNameF')" />
|
<input type="button" class="btn btn-default" onclick="dateSet('%t/%t - %y-%0m-%0d - %desc.%ext')" value="$T('button-ShowNameF')" />
|
||||||
<input type="button" class="btn btn-default" onclick="dateSet('%y-%0m/%t - %y-%0m-%0d - %desc.%ext')" value="$T('button-YMF')" />
|
<input type="button" class="btn btn-default" onclick="dateSet('%y-%0m/%t - %y-%0m-%0d - %desc.%ext')" value="$T('button-YMF')" />
|
||||||
<input type="button" class="btn btn-default" onclick="dateSet('%y-%0m-%0d/%t - %y-%0m-%0d - %desc.%ext')" value="$T('button-DailyF')" />
|
<input type="button" class="btn btn-default" onclick="dateSet('%y-%0m-%0d/%t - %y-%0m-%0d - %desc.%ext')" value="$T('button-DailyF')" />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div id="previewdate" class="example">
|
<div id="previewdate" class="example">
|
||||||
@@ -337,7 +337,7 @@
|
|||||||
<input type="text" id="datesamplename" name="datesamplename" placeholder="$T('show-name') 2009-01-02" /><button class="btn btn-default clearBtn" type="button"><span class="glyphicon glyphicon-remove"></span> $T('button-clear')</button>
|
<input type="text" id="datesamplename" name="datesamplename" placeholder="$T('show-name') 2009-01-02" /><button class="btn btn-default clearBtn" type="button"><span class="glyphicon glyphicon-remove"></span> $T('button-clear')</button>
|
||||||
</div>
|
</div>
|
||||||
<div class="field-pair">
|
<div class="field-pair">
|
||||||
<label class="config">$T('sortResult')</label> <span class="desc path" id="previewdate-result"> </span>
|
<label class="config">$T('sortResult')</label> <span class="desc path" id="previewdate-result"> </span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="field-pair">
|
<div class="field-pair">
|
||||||
@@ -519,12 +519,12 @@
|
|||||||
else
|
else
|
||||||
\$('#previewdate').hide();
|
\$('#previewdate').hide();
|
||||||
}
|
}
|
||||||
|
|
||||||
\$(document).ready(function(){
|
\$(document).ready(function(){
|
||||||
new_previewtv();
|
new_previewtv();
|
||||||
new_previewmovie();
|
new_previewmovie();
|
||||||
new_previewdate();
|
new_previewdate();
|
||||||
|
|
||||||
\$('#tvfoldername, #tvsamplename').bind("keyup focus", new_previewtv);
|
\$('#tvfoldername, #tvsamplename').bind("keyup focus", new_previewtv);
|
||||||
\$('#moviefoldername, #movieextra, #moviesamplename').bind("keyup focus", new_previewmovie);
|
\$('#moviefoldername, #movieextra, #moviesamplename').bind("keyup focus", new_previewmovie);
|
||||||
\$('#datefoldername, #datesamplename').bind("keyup focus", new_previewdate);
|
\$('#datefoldername, #datesamplename').bind("keyup focus", new_previewdate);
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
<!--#set global $pane="Special"#-->
|
<!--#set global $pane="Special"#-->
|
||||||
<!--#set global $help_uri="configuration/1.1/special"#-->
|
<!--#set global $help_uri="configuration/2.2/special"#-->
|
||||||
<!--#include $webdir + "/_inc_header_uc.tmpl"#-->
|
<!--#include $webdir + "/_inc_header_uc.tmpl"#-->
|
||||||
|
|
||||||
<div class="colmask">
|
<div class="colmask">
|
||||||
@@ -17,7 +17,7 @@
|
|||||||
<!--#for $option in $switches#-->
|
<!--#for $option in $switches#-->
|
||||||
<div class="field-pair">
|
<div class="field-pair">
|
||||||
<label class="config wide" for="$option[0]">
|
<label class="config wide" for="$option[0]">
|
||||||
$option[0] ( <span class="path"><!--#if $option[2] then $T('on') else $T('off')#--></span> )
|
$option[0] ( <span class="path"><!--#if $option[2] then $T('on') else $T('off')#--></span> )
|
||||||
<!--#if $option[1] != $option[2] then '<span class="glyphicon glyphicon-asterisk"></span>' else ''#-->
|
<!--#if $option[1] != $option[2] then '<span class="glyphicon glyphicon-asterisk"></span>' else ''#-->
|
||||||
</label>
|
</label>
|
||||||
<input type="checkbox" name="$option[0]" id="$option[0]" value="1" <!--#if int($option[1]) > 0 then 'checked="checked"' else ""#--> />
|
<input type="checkbox" name="$option[0]" id="$option[0]" value="1" <!--#if int($option[1]) > 0 then 'checked="checked"' else ""#--> />
|
||||||
|
|||||||
@@ -1,67 +1,36 @@
|
|||||||
<!--#set global $pane="Switches"#-->
|
<!--#set global $pane="Switches"#-->
|
||||||
<!--#set global $help_uri="configuration/1.1/switches"#-->
|
<!--#set global $help_uri="configuration/2.2/switches"#-->
|
||||||
<!--#include $webdir + "/_inc_header_uc.tmpl"#-->
|
<!--#include $webdir + "/_inc_header_uc.tmpl"#-->
|
||||||
|
|
||||||
<div class="colmask">
|
<div class="colmask">
|
||||||
<form action="saveSwitches" method="post" name="fullform" class="fullform" autocomplete="off">
|
<form action="saveSwitches" method="post" name="fullform" class="fullform" autocomplete="off">
|
||||||
<input type="hidden" id="session" name="session" value="$session" />
|
<input type="hidden" id="session" name="session" value="$session" />
|
||||||
<div class="section">
|
|
||||||
<div class="col2">
|
|
||||||
<h3>$T('swtag-general') <a href="$helpuri$help_uri" target="_blank"><span class="glyphicon glyphicon-question-sign"></span></a></h3>
|
|
||||||
</div><!-- /col2 -->
|
|
||||||
<div class="col1">
|
|
||||||
<fieldset>
|
|
||||||
<div class="field-pair">
|
|
||||||
<label class="config" for="auto_browser">$T('opt-auto_browser')</label>
|
|
||||||
<input type="checkbox" name="auto_browser" id="auto_browser" value="1" <!--#if int($auto_browser) > 0 then 'checked="checked"' else ""#--> />
|
|
||||||
<span class="desc">$T('explain-auto_browser')</span>
|
|
||||||
</div>
|
|
||||||
<div class="field-pair">
|
|
||||||
<label class="config" for="check_new_rel">$T('opt-check_new_rel')</label>
|
|
||||||
<select name="check_new_rel" id="check_new_rel">
|
|
||||||
<option value="0" <!--#if $check_new_rel == 0 then 'selected="selected"' else ""#--> >$T('off')</option>
|
|
||||||
<option value="1" <!--#if $check_new_rel == 1 then 'selected="selected"' else ""#--> >$T('on')</option>
|
|
||||||
<option value="2" <!--#if $check_new_rel == 2 then 'selected="selected"' else ""#--> >$T('also-test')</option>
|
|
||||||
</select>
|
|
||||||
<span class="desc">$T('explain-check_new_rel')</span>
|
|
||||||
</div>
|
|
||||||
<div class="field-pair">
|
|
||||||
<label class="config" for="enable_https_verification">$T('opt-enable_https_verification')</label>
|
|
||||||
<input type="checkbox" name="enable_https_verification" id="enable_https_verification" value="1" <!--#if int($enable_https_verification) > 0 then 'checked="checked"' else ""#--> />
|
|
||||||
<span class="desc">$T('explain-enable_https_verification')</span>
|
|
||||||
</div>
|
|
||||||
<div class="field-pair">
|
|
||||||
<button class="btn btn-default saveButton"><span class="glyphicon glyphicon-ok"></span> $T('button-saveChanges')</button>
|
|
||||||
<button class="btn btn-default restoreDefaults"><span class="glyphicon glyphicon-asterisk"></span> $T('button-restoreDefaults')</button>
|
|
||||||
</div>
|
|
||||||
</fieldset>
|
|
||||||
</div><!-- /col1 -->
|
|
||||||
</div><!-- /section -->
|
|
||||||
<div class="section">
|
<div class="section">
|
||||||
<div class="col2">
|
<div class="col2">
|
||||||
<h3>$T('swtag-server') <a href="$helpuri$help_uri#toc1" target="_blank"><span class="glyphicon glyphicon-question-sign"></span></a></h3>
|
<h3>$T('swtag-server') <a href="$helpuri$help_uri#toc1" target="_blank"><span class="glyphicon glyphicon-question-sign"></span></a></h3>
|
||||||
</div><!-- /col2 -->
|
</div><!-- /col2 -->
|
||||||
<div class="col1">
|
<div class="col1">
|
||||||
<fieldset>
|
<fieldset>
|
||||||
|
<div class="field-pair">
|
||||||
|
<label class="config" for="load_balancing">$T('opt-load_balancing')</label>
|
||||||
|
<select name="load_balancing" id="load_balancing">
|
||||||
|
<option value="0" <!--#if $load_balancing == 0 then 'selected="selected"' else ""#--> >$T('no-load-balancing')</option>
|
||||||
|
<option value="1" <!--#if $load_balancing == 1 then 'selected="selected"' else ""#--> >$T('load-balancing')</option>
|
||||||
|
<option value="2" <!--#if $load_balancing == 2 then 'selected="selected"' else ""#--> >$T('load-balancing-happy-eyeballs')</option>
|
||||||
|
</select>
|
||||||
|
<span class="desc">$T('explain-load_balancing')</span>
|
||||||
|
</div>
|
||||||
|
<div class="field-pair">
|
||||||
|
<label class="config" for="ssl_ciphers">$T('opt-ssl_ciphers')</label>
|
||||||
|
<input type="text" name="ssl_ciphers" id="ssl_ciphers" value="$ssl_ciphers" />
|
||||||
|
<span class="desc">$T('explain-ssl_ciphers') <br>$T('readwiki')
|
||||||
|
<a href="${helpuri}advanced/ssl-ciphers" target="_blank">${helpuri}advanced/ssl-ciphers</a></span>
|
||||||
|
</div>
|
||||||
<div class="field-pair">
|
<div class="field-pair">
|
||||||
<label class="config" for="max_art_tries">$T('opt-max_art_tries')</label>
|
<label class="config" for="max_art_tries">$T('opt-max_art_tries')</label>
|
||||||
<input type="number" name="max_art_tries" id="max_art_tries" value="$max_art_tries" min="2" max="2000" />
|
<input type="number" name="max_art_tries" id="max_art_tries" value="$max_art_tries" min="2" max="2000" />
|
||||||
<span class="desc">$T('explain-max_art_tries')</span>
|
<span class="desc">$T('explain-max_art_tries')</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="field-pair">
|
|
||||||
<label class="config" for="max_art_opt">$T('opt-max_art_opt')</label>
|
|
||||||
<input type="checkbox" name="max_art_opt" id="max_art_opt" value="1" <!--#if int($max_art_opt) > 0 then 'checked="checked"' else ""#--> />
|
|
||||||
<span class="desc">$T('explain-max_art_opt')</span>
|
|
||||||
</div>
|
|
||||||
<div class="field-pair">
|
|
||||||
<label class="config" for="load_balancing">$T('opt-load_balancing')</label>
|
|
||||||
<select name="load_balancing" id="load_balancing">
|
|
||||||
<option value="0" <!--#if $load_balancing == 0 then 'selected="selected"' else ""#--> >$T('no-load-balancing')</option>
|
|
||||||
<option value="1" <!--#if $load_balancing == 1 then 'selected="selected"' else ""#--> >$T('load-balancing')</option>
|
|
||||||
<option value="2" <!--#if $load_balancing == 2 then 'selected="selected"' else ""#--> >$T('load-balancing-happy-eyeballs')</option>
|
|
||||||
</select>
|
|
||||||
<span class="desc">$T('explain-load_balancing')</span>
|
|
||||||
</div>
|
|
||||||
<div class="field-pair">
|
<div class="field-pair">
|
||||||
<label class="config" for="auto_disconnect">$T('opt-auto_disconnect')</label>
|
<label class="config" for="auto_disconnect">$T('opt-auto_disconnect')</label>
|
||||||
<input type="checkbox" name="auto_disconnect" id="auto_disconnect" value="1" <!--#if int($auto_disconnect) > 0 then 'checked="checked"' else ""#--> />
|
<input type="checkbox" name="auto_disconnect" id="auto_disconnect" value="1" <!--#if int($auto_disconnect) > 0 then 'checked="checked"' else ""#--> />
|
||||||
@@ -83,7 +52,7 @@
|
|||||||
<div class="field-pair">
|
<div class="field-pair">
|
||||||
<label class="config" for="pre_script">$T('opt-pre_script')</label>
|
<label class="config" for="pre_script">$T('opt-pre_script')</label>
|
||||||
<select name="pre_script" id="pre_script">
|
<select name="pre_script" id="pre_script">
|
||||||
<!--#for $sc in $script_list#-->
|
<!--#for $sc in $scripts#-->
|
||||||
<!--#if $sc.lower() == $pre_script.lower()#-->
|
<!--#if $sc.lower() == $pre_script.lower()#-->
|
||||||
<option value="$sc" selected="selected">$Tspec($sc)</option>
|
<option value="$sc" selected="selected">$Tspec($sc)</option>
|
||||||
<!--#else#-->
|
<!--#else#-->
|
||||||
@@ -117,8 +86,10 @@
|
|||||||
<label class="config" for="no_dupes">$T('opt-no_dupes')</label>
|
<label class="config" for="no_dupes">$T('opt-no_dupes')</label>
|
||||||
<select name="no_dupes" id="no_dupes">
|
<select name="no_dupes" id="no_dupes">
|
||||||
<option value="0" <!--#if int($no_dupes) == 0 then 'selected="selected"' else ""#--> >$T('nodupes-off')</option>
|
<option value="0" <!--#if int($no_dupes) == 0 then 'selected="selected"' else ""#--> >$T('nodupes-off')</option>
|
||||||
<option value="1" <!--#if int($no_dupes) == 1 then 'selected="selected"' else ""#--> >$T('nodupes-ignore')</option>
|
<option value="4" <!--#if int($no_dupes) == 4 then 'selected="selected"' else ""#--> >$T('nodupes-tag')</option>
|
||||||
<option value="2" <!--#if int($no_dupes) == 2 then 'selected="selected"' else ""#--> >$T('nodupes-pause')</option>
|
<option value="2" <!--#if int($no_dupes) == 2 then 'selected="selected"' else ""#--> >$T('nodupes-pause')</option>
|
||||||
|
<option value="3" <!--#if int($no_dupes) == 3 then 'selected="selected"' else ""#--> >$T('nodupes-fail')</option>
|
||||||
|
<option value="1" <!--#if int($no_dupes) == 1 then 'selected="selected"' else ""#--> >$T('nodupes-ignore')</option>
|
||||||
</select>
|
</select>
|
||||||
<span class="desc">$T('explain-no_dupes')</span>
|
<span class="desc">$T('explain-no_dupes')</span>
|
||||||
</div>
|
</div>
|
||||||
@@ -126,11 +97,18 @@
|
|||||||
<label class="config" for="no_series_dupes">$T('opt-no_series_dupes')</label>
|
<label class="config" for="no_series_dupes">$T('opt-no_series_dupes')</label>
|
||||||
<select name="no_series_dupes" id="no_series_dupes">
|
<select name="no_series_dupes" id="no_series_dupes">
|
||||||
<option value="0" <!--#if int($no_series_dupes) == 0 then 'selected="selected"' else ""#--> >$T('nodupes-off')</option>
|
<option value="0" <!--#if int($no_series_dupes) == 0 then 'selected="selected"' else ""#--> >$T('nodupes-off')</option>
|
||||||
<option value="1" <!--#if int($no_series_dupes) == 1 then 'selected="selected"' else ""#--> >$T('nodupes-ignore')</option>
|
<option value="4" <!--#if int($no_series_dupes) == 4 then 'selected="selected"' else ""#--> >$T('nodupes-tag')</option>
|
||||||
<option value="2" <!--#if int($no_series_dupes) == 2 then 'selected="selected"' else ""#--> >$T('nodupes-pause')</option>
|
<option value="2" <!--#if int($no_series_dupes) == 2 then 'selected="selected"' else ""#--> >$T('nodupes-pause')</option>
|
||||||
|
<option value="3" <!--#if int($no_series_dupes) == 3 then 'selected="selected"' else ""#--> >$T('nodupes-fail')</option>
|
||||||
|
<option value="1" <!--#if int($no_series_dupes) == 1 then 'selected="selected"' else ""#--> >$T('nodupes-ignore')</option>
|
||||||
</select>
|
</select>
|
||||||
<span class="desc">$T('explain-no_series_dupes')</span>
|
<span class="desc">$T('explain-no_series_dupes')</span>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="field-pair">
|
||||||
|
<label class="config" for="series_propercheck">$T('opt-series_propercheck')</label>
|
||||||
|
<input type="checkbox" name="series_propercheck" id="series_propercheck" value="1" <!--#if int($series_propercheck) > 0 then 'checked="checked"' else ""#--> />
|
||||||
|
<span class="desc">$T('explain-series_propercheck')</span>
|
||||||
|
</div>
|
||||||
<div class="field-pair">
|
<div class="field-pair">
|
||||||
<label class="config" for="pause_on_pwrar">$T('opt-pause_on_pwrar')</label>
|
<label class="config" for="pause_on_pwrar">$T('opt-pause_on_pwrar')</label>
|
||||||
<select name="pause_on_pwrar" id="pause_on_pwrar">
|
<select name="pause_on_pwrar" id="pause_on_pwrar">
|
||||||
@@ -159,6 +137,11 @@
|
|||||||
<input type="checkbox" name="auto_sort" id="auto_sort" value="1" <!--#if int($auto_sort) > 0 then 'checked="checked"' else ""#--> />
|
<input type="checkbox" name="auto_sort" id="auto_sort" value="1" <!--#if int($auto_sort) > 0 then 'checked="checked"' else ""#--> />
|
||||||
<span class="desc">$T('explain-auto_sort')</span>
|
<span class="desc">$T('explain-auto_sort')</span>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="field-pair">
|
||||||
|
<label class="config" for="direct_unpack">$T('opt-direct_unpack')</label>
|
||||||
|
<input type="checkbox" name="direct_unpack" id="direct_unpack" value="1" <!--#if int($direct_unpack) > 0 then 'checked="checked"' else ""#--> />
|
||||||
|
<span class="desc">$T('explain-direct_unpack').replace('. ', '.<br/>')</span>
|
||||||
|
</div>
|
||||||
<div class="field-pair">
|
<div class="field-pair">
|
||||||
<button class="btn btn-default saveButton"><span class="glyphicon glyphicon-ok"></span> $T('button-saveChanges')</button>
|
<button class="btn btn-default saveButton"><span class="glyphicon glyphicon-ok"></span> $T('button-saveChanges')</button>
|
||||||
<button class="btn btn-default restoreDefaults"><span class="glyphicon glyphicon-asterisk"></span> $T('button-restoreDefaults')</button>
|
<button class="btn btn-default restoreDefaults"><span class="glyphicon glyphicon-asterisk"></span> $T('button-restoreDefaults')</button>
|
||||||
@@ -180,17 +163,7 @@
|
|||||||
<div class="field-pair">
|
<div class="field-pair">
|
||||||
<label class="config" for="enable_all_par">$T('opt-enable_all_par')</label>
|
<label class="config" for="enable_all_par">$T('opt-enable_all_par')</label>
|
||||||
<input type="checkbox" name="enable_all_par" id="enable_all_par" value="1" <!--#if int($enable_all_par) > 0 then 'checked="checked"' else ""#--> />
|
<input type="checkbox" name="enable_all_par" id="enable_all_par" value="1" <!--#if int($enable_all_par) > 0 then 'checked="checked"' else ""#--> />
|
||||||
<span class="desc">$T('explain-enable_all_par').replace('.', '<br/>')</span>
|
<span class="desc">$T('explain-enable_all_par').replace('. ', '.<br/>')</span>
|
||||||
</div>
|
|
||||||
<div class="field-pair">
|
|
||||||
<label class="config" for="quick_check">$T('opt-quick_check')</label>
|
|
||||||
<input type="checkbox" name="quick_check" id="quick_check" value="1" <!--#if int($quick_check) > 0 then 'checked="checked"' else ""#--> />
|
|
||||||
<span class="desc">$T('explain-quick_check')</span>
|
|
||||||
</div>
|
|
||||||
<div class="field-pair <!--#if not $have_multicore then "disabled" else "" #-->">
|
|
||||||
<label class="config" for="par2_multicore">$T('opt-par2_multicore')</label>
|
|
||||||
<input type="checkbox" name="par2_multicore" id="par2_multicore" value="1" <!--#if int($par2_multicore) > 0 then 'checked="checked"' else ""#--> <!--#if not $have_multicore then 'readonly="readonly" disabled="disabled"' else "" #--> />
|
|
||||||
<span class="desc">$T('explain-par2_multicore')</span>
|
|
||||||
</div>
|
</div>
|
||||||
<div class="field-pair">
|
<div class="field-pair">
|
||||||
<label class="config" for="par_option">$T('opt-par_option')</label>
|
<label class="config" for="par_option">$T('opt-par_option')</label>
|
||||||
@@ -217,17 +190,6 @@
|
|||||||
<input type="checkbox" name="flat_unpack" id="flat_unpack" value="1" <!--#if int($flat_unpack) > 0 then 'checked="checked"' else ""#--> />
|
<input type="checkbox" name="flat_unpack" id="flat_unpack" value="1" <!--#if int($flat_unpack) > 0 then 'checked="checked"' else ""#--> />
|
||||||
<span class="desc">$T('explain-flat_unpack')</span>
|
<span class="desc">$T('explain-flat_unpack')</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="field-pair">
|
|
||||||
<label class="config" for="overwrite_files">$T('opt-overwrite_files')</label>
|
|
||||||
<input type="checkbox" name="overwrite_files" id="overwrite_files" value="1" <!--#if int($overwrite_files) > 0 then 'checked="checked"' else ""#--> />
|
|
||||||
<span class="desc">$T('explain-overwrite_files')</span>
|
|
||||||
</div>
|
|
||||||
<div class="field-pair">
|
|
||||||
<label class="config" for="unpack_check">$T('opt-unpack_check')</label>
|
|
||||||
<input type="checkbox" name="unpack_check" id="unpack_check" value="1" <!--#if int($unpack_check) > 0 then 'checked="checked"' else ""#--> />
|
|
||||||
<span class="desc">$T('explain-unpack_check')</span>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="field-pair">
|
<div class="field-pair">
|
||||||
<label class="config" for="script_can_fail">$T('opt-script_can_fail')</label>
|
<label class="config" for="script_can_fail">$T('opt-script_can_fail')</label>
|
||||||
<input type="checkbox" name="script_can_fail" id="script_can_fail" value="1" <!--#if int($script_can_fail) > 0 then 'checked="checked"' else ""#--> />
|
<input type="checkbox" name="script_can_fail" id="script_can_fail" value="1" <!--#if int($script_can_fail) > 0 then 'checked="checked"' else ""#--> />
|
||||||
@@ -255,11 +217,28 @@
|
|||||||
<input type="checkbox" name="ignore_samples" id="ignore_samples" value="1" <!--#if int($ignore_samples) > 0 then 'checked="checked"' else ""#--> />
|
<input type="checkbox" name="ignore_samples" id="ignore_samples" value="1" <!--#if int($ignore_samples) > 0 then 'checked="checked"' else ""#--> />
|
||||||
<span class="desc">$T('explain-ignore_samples') $T('igsam-del').</span>
|
<span class="desc">$T('explain-ignore_samples') $T('igsam-del').</span>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="field-pair">
|
||||||
|
<label class="config" for="enable_meta">$T('opt-enable_meta')</label>
|
||||||
|
<input type="checkbox" name="enable_meta" id="enable_meta" value="1" <!--#if int($enable_meta) > 0 then 'checked="checked"' else ""#--> />
|
||||||
|
<span class="desc">$T('explain-enable_meta').replace('. ', '.<br/>')</span>
|
||||||
|
</div>
|
||||||
<div class="field-pair">
|
<div class="field-pair">
|
||||||
<label class="config" for="cleanup_list">$T('opt-cleanup_list')</label>
|
<label class="config" for="cleanup_list">$T('opt-cleanup_list')</label>
|
||||||
<input type="text" name="cleanup_list" id="cleanup_list" value="$cleanup_list"/>
|
<input type="text" name="cleanup_list" id="cleanup_list" value="$cleanup_list"/>
|
||||||
<span class="desc">$T('explain-cleanup_list')</span>
|
<span class="desc">$T('explain-cleanup_list')</span>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="field-pair">
|
||||||
|
<label class="config" for="history_retention_select">$T('opt-history_retention')</label>
|
||||||
|
<input type="hidden" name="history_retention" id="history_retention" value="$history_retention">
|
||||||
|
<select name="history_retention_select" id="history_retention_select">
|
||||||
|
<option value="0">$T('history_retention-all')</option>
|
||||||
|
<option value="n">$T('history_retention-number')</option>
|
||||||
|
<option value="d">$T('history_retention-days')</option>
|
||||||
|
<option value="-1">$T('history_retention-none')</option>
|
||||||
|
</select>
|
||||||
|
<input type="number" id="history_retention_number" name="history_retention_number" min="1">
|
||||||
|
<span class="desc">$T('explain-history_retention').replace('. ', '.<br/>')</span>
|
||||||
|
</div>
|
||||||
<div class="field-pair">
|
<div class="field-pair">
|
||||||
<button class="btn btn-default saveButton"><span class="glyphicon glyphicon-ok"></span> $T('button-saveChanges')</button>
|
<button class="btn btn-default saveButton"><span class="glyphicon glyphicon-ok"></span> $T('button-saveChanges')</button>
|
||||||
<button class="btn btn-default restoreDefaults"><span class="glyphicon glyphicon-asterisk"></span> $T('button-restoreDefaults')</button>
|
<button class="btn btn-default restoreDefaults"><span class="glyphicon glyphicon-asterisk"></span> $T('button-restoreDefaults')</button>
|
||||||
@@ -276,7 +255,7 @@
|
|||||||
<div class="field-pair">
|
<div class="field-pair">
|
||||||
<label class="config" for="folder_rename">$T('opt-folder_rename')</label>
|
<label class="config" for="folder_rename">$T('opt-folder_rename')</label>
|
||||||
<input type="checkbox" name="folder_rename" id="folder_rename" value="1" <!--#if int($folder_rename) > 0 then 'checked="checked"' else ""#--> />
|
<input type="checkbox" name="folder_rename" id="folder_rename" value="1" <!--#if int($folder_rename) > 0 then 'checked="checked"' else ""#--> />
|
||||||
<span class="desc">$T('explain-folder_rename')</span>
|
<span class="desc">$T('explain-folder_rename').replace('. ', '.<br/>')</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="field-pair">
|
<div class="field-pair">
|
||||||
<label class="config" for="replace_spaces">$T('opt-replace_spaces')</label>
|
<label class="config" for="replace_spaces">$T('opt-replace_spaces')</label>
|
||||||
@@ -288,11 +267,6 @@
|
|||||||
<input type="checkbox" name="replace_dots" id="replace_dots" value="1" <!--#if int($replace_dots) > 0 then 'checked="checked"' else ""#--> />
|
<input type="checkbox" name="replace_dots" id="replace_dots" value="1" <!--#if int($replace_dots) > 0 then 'checked="checked"' else ""#--> />
|
||||||
<span class="desc">$T('explain-replace_dots')</span>
|
<span class="desc">$T('explain-replace_dots')</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="field-pair">
|
|
||||||
<label class="config" for="replace_illegal">$T('opt-replace_illegal')</label>
|
|
||||||
<input type="checkbox" name="replace_illegal" id="replace_illegal" value="1" <!--#if int($replace_illegal) > 0 then 'checked="checked"' else ""#--> />
|
|
||||||
<span class="desc">$T('explain-replace_illegal')</span>
|
|
||||||
</div>
|
|
||||||
<!--#if not $nt#-->
|
<!--#if not $nt#-->
|
||||||
<div class="field-pair">
|
<div class="field-pair">
|
||||||
<label class="config" for="sanitize_safe">$T('opt-sanitize_safe')</label>
|
<label class="config" for="sanitize_safe">$T('opt-sanitize_safe')</label>
|
||||||
@@ -300,11 +274,6 @@
|
|||||||
<span class="desc">$T('explain-sanitize_safe')</span>
|
<span class="desc">$T('explain-sanitize_safe')</span>
|
||||||
</div>
|
</div>
|
||||||
<!--#end if#-->
|
<!--#end if#-->
|
||||||
<div class="field-pair">
|
|
||||||
<label class="config" for="enable_meta">$T('opt-enable_meta')</label>
|
|
||||||
<input type="checkbox" name="enable_meta" id="enable_meta" value="1" <!--#if int($enable_meta) > 0 then 'checked="checked"' else ""#--> />
|
|
||||||
<span class="desc">$T('explain-enable_meta')</span>
|
|
||||||
</div>
|
|
||||||
<div class="field-pair">
|
<div class="field-pair">
|
||||||
<button class="btn btn-default saveButton"><span class="glyphicon glyphicon-ok"></span> $T('button-saveChanges')</button>
|
<button class="btn btn-default saveButton"><span class="glyphicon glyphicon-ok"></span> $T('button-saveChanges')</button>
|
||||||
<button class="btn btn-default restoreDefaults"><span class="glyphicon glyphicon-asterisk"></span> $T('button-restoreDefaults')</button>
|
<button class="btn btn-default restoreDefaults"><span class="glyphicon glyphicon-asterisk"></span> $T('button-restoreDefaults')</button>
|
||||||
@@ -358,16 +327,7 @@
|
|||||||
<div class="field-pair">
|
<div class="field-pair">
|
||||||
<label class="config" for="rating_enable">$T('opt-rating_enable')</label>
|
<label class="config" for="rating_enable">$T('opt-rating_enable')</label>
|
||||||
<input type="checkbox" name="rating_enable" id="rating_enable" value="1" <!--#if int($rating_enable) > 0 then 'checked="checked"' else ""#--> />
|
<input type="checkbox" name="rating_enable" id="rating_enable" value="1" <!--#if int($rating_enable) > 0 then 'checked="checked"' else ""#--> />
|
||||||
<span class="desc">$T('explain-rating_enable')</span>
|
<span class="desc">$T('explain-rating_enable').replace('. ', '.<br/>')</span>
|
||||||
</div>
|
|
||||||
<div class="field-pair">
|
|
||||||
<label class="config" for="rating_feedback">$T('opt-rating_feedback')</label>
|
|
||||||
<input type="checkbox" name="rating_feedback" id="rating_feedback" value="1" <!--#if int($rating_feedback) > 0 then 'checked="checked"' else ""#--> />
|
|
||||||
<span class="desc">$T('explain-rating_feedback')</span>
|
|
||||||
</div>
|
|
||||||
<div class="field-pair">
|
|
||||||
<label class="config" for="rating_host">$T('opt-rating_host')</label>
|
|
||||||
<input type="text" name="rating_host" id="rating_host" value="$rating_host" />
|
|
||||||
</div>
|
</div>
|
||||||
<div class="field-pair">
|
<div class="field-pair">
|
||||||
<label class="config" for="rating_api_key">$T('opt-rating_api_key')</label>
|
<label class="config" for="rating_api_key">$T('opt-rating_api_key')</label>
|
||||||
@@ -385,7 +345,7 @@
|
|||||||
<p>
|
<p>
|
||||||
<label for="rating_filter_abort_video">$T('opt-rating_filter_video')</label>
|
<label for="rating_filter_abort_video">$T('opt-rating_filter_video')</label>
|
||||||
<select name="rating_filter_abort_video" id="rating_filter_abort_video">
|
<select name="rating_filter_abort_video" id="rating_filter_abort_video">
|
||||||
<option value="0" <!--#if $rating_filter_abort_video == 0 then 'selected="selected"' else ""#--> >$T('notUsed')</option>
|
<option value="0" <!--#if $rating_filter_abort_video == 0 then 'selected="selected"' else ""#--> >$T('notUsed')</option>
|
||||||
<!--#for $val in $range(1, 10)#--><option value="$val" <!--#if $rating_filter_abort_video == $val then 'selected="selected"' else ""#--> >$val $T('orLess')</option><!--#end for#-->
|
<!--#for $val in $range(1, 10)#--><option value="$val" <!--#if $rating_filter_abort_video == $val then 'selected="selected"' else ""#--> >$val $T('orLess')</option><!--#end for#-->
|
||||||
</select>
|
</select>
|
||||||
</p>
|
</p>
|
||||||
@@ -405,7 +365,7 @@
|
|||||||
<input type="checkbox" value="1" id="rating_filter_abort_encrypted_confirm" name="rating_filter_abort_encrypted_confirm" <!--#if int($rating_filter_abort_encrypted_confirm) > 0 then 'checked="checked"' else ""#--> />
|
<input type="checkbox" value="1" id="rating_filter_abort_encrypted_confirm" name="rating_filter_abort_encrypted_confirm" <!--#if int($rating_filter_abort_encrypted_confirm) > 0 then 'checked="checked"' else ""#--> />
|
||||||
<label for="rating_filter_abort_encrypted_confirm">$T('opt-rating_filter_confirmed')</label>
|
<label for="rating_filter_abort_encrypted_confirm">$T('opt-rating_filter_confirmed')</label>
|
||||||
</span>
|
</span>
|
||||||
</p>
|
</p>
|
||||||
<p>
|
<p>
|
||||||
<span>
|
<span>
|
||||||
<input type="checkbox" value="1" id="rating_filter_abort_spam" name="rating_filter_abort_spam" <!--#if int($rating_filter_abort_spam) > 0 then 'checked="checked"' else ""#--> />
|
<input type="checkbox" value="1" id="rating_filter_abort_spam" name="rating_filter_abort_spam" <!--#if int($rating_filter_abort_spam) > 0 then 'checked="checked"' else ""#--> />
|
||||||
@@ -415,7 +375,7 @@
|
|||||||
<input type="checkbox" value="1" id="rating_filter_abort_spam_confirm" name="rating_filter_abort_spam_confirm" <!--#if int($rating_filter_abort_spam_confirm) > 0 then 'checked="checked"' else ""#--> />
|
<input type="checkbox" value="1" id="rating_filter_abort_spam_confirm" name="rating_filter_abort_spam_confirm" <!--#if int($rating_filter_abort_spam_confirm) > 0 then 'checked="checked"' else ""#--> />
|
||||||
<label for="rating_filter_abort_spam_confirm">$T('opt-rating_filter_confirmed')</label>
|
<label for="rating_filter_abort_spam_confirm">$T('opt-rating_filter_confirmed')</label>
|
||||||
</span>
|
</span>
|
||||||
</p>
|
</p>
|
||||||
<p>
|
<p>
|
||||||
<input type="checkbox" value="1" id="rating_filter_abort_downvoted" name="rating_filter_abort_downvoted" <!--#if int($rating_filter_abort_downvoted) > 0 then 'checked="checked"' else ""#--> />
|
<input type="checkbox" value="1" id="rating_filter_abort_downvoted" name="rating_filter_abort_downvoted" <!--#if int($rating_filter_abort_downvoted) > 0 then 'checked="checked"' else ""#--> />
|
||||||
<label for="rating_filter_abort_downvoted">$T('opt-rating_filter_downvoted')</label>
|
<label for="rating_filter_abort_downvoted">$T('opt-rating_filter_downvoted')</label>
|
||||||
@@ -453,7 +413,7 @@
|
|||||||
<input type="checkbox" value="1" id="rating_filter_pause_encrypted_confirm" name="rating_filter_pause_encrypted_confirm" <!--#if int($rating_filter_pause_encrypted_confirm) > 0 then 'checked="checked"' else ""#--> />
|
<input type="checkbox" value="1" id="rating_filter_pause_encrypted_confirm" name="rating_filter_pause_encrypted_confirm" <!--#if int($rating_filter_pause_encrypted_confirm) > 0 then 'checked="checked"' else ""#--> />
|
||||||
<label for="rating_filter_pause_encrypted_confirm">$T('opt-rating_filter_confirmed')</label>
|
<label for="rating_filter_pause_encrypted_confirm">$T('opt-rating_filter_confirmed')</label>
|
||||||
</span>
|
</span>
|
||||||
</p>
|
</p>
|
||||||
<p>
|
<p>
|
||||||
<span>
|
<span>
|
||||||
<input type="checkbox" value="1" id="rating_filter_pause_spam" name="rating_filter_pause_spam" <!--#if int($rating_filter_pause_spam) > 0 then 'checked="checked"' else ""#--> />
|
<input type="checkbox" value="1" id="rating_filter_pause_spam" name="rating_filter_pause_spam" <!--#if int($rating_filter_pause_spam) > 0 then 'checked="checked"' else ""#--> />
|
||||||
@@ -463,7 +423,7 @@
|
|||||||
<input type="checkbox" value="1" id="rating_filter_pause_spam_confirm" name="rating_filter_pause_spam_confirm" <!--#if int($rating_filter_pause_spam_confirm) > 0 then 'checked="checked"' else ""#--> />
|
<input type="checkbox" value="1" id="rating_filter_pause_spam_confirm" name="rating_filter_pause_spam_confirm" <!--#if int($rating_filter_pause_spam_confirm) > 0 then 'checked="checked"' else ""#--> />
|
||||||
<label for="rating_filter_pause_spam_confirm">$T('opt-rating_filter_confirmed')</label>
|
<label for="rating_filter_pause_spam_confirm">$T('opt-rating_filter_confirmed')</label>
|
||||||
</span>
|
</span>
|
||||||
</p>
|
</p>
|
||||||
<p>
|
<p>
|
||||||
<input type="checkbox" value="1" id="rating_filter_pause_downvoted" name="rating_filter_pause_downvoted" <!--#if int($rating_filter_pause_downvoted) > 0 then 'checked="checked"' else ""#--> />
|
<input type="checkbox" value="1" id="rating_filter_pause_downvoted" name="rating_filter_pause_downvoted" <!--#if int($rating_filter_pause_downvoted) > 0 then 'checked="checked"' else ""#--> />
|
||||||
<label for="rating_filter_pause_downvoted">$T('opt-rating_filter_downvoted')</label>
|
<label for="rating_filter_pause_downvoted">$T('opt-rating_filter_downvoted')</label>
|
||||||
@@ -487,20 +447,67 @@
|
|||||||
<script type="text/javascript">
|
<script type="text/javascript">
|
||||||
\$(document).ready(function() {
|
\$(document).ready(function() {
|
||||||
if (!\$('#rating_filter_enable').is(":checked")) {
|
if (!\$('#rating_filter_enable').is(":checked")) {
|
||||||
\$("#rating_filter_abort").hide();
|
\$("#rating_filter_abort").hide();
|
||||||
\$("#rating_filter_pause").hide();
|
\$("#rating_filter_pause").hide();
|
||||||
}
|
}
|
||||||
\$('#rating_filter_enable').change(function () {
|
\$('#rating_filter_enable').change(function () {
|
||||||
if (\$(this).is(":checked")) {
|
if (\$(this).is(":checked")) {
|
||||||
\$("#rating_filter_abort").show();
|
\$("#rating_filter_abort").show();
|
||||||
\$("#rating_filter_pause").show();
|
\$("#rating_filter_pause").show();
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
\$("#rating_filter_abort").hide();
|
\$("#rating_filter_abort").hide();
|
||||||
\$("#rating_filter_pause").hide();
|
\$("#rating_filter_pause").hide();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
\$('#history_retention_select, #history_retention_number').on('change', updateHistoryRetention)
|
||||||
|
function updateHistoryRetention() {
|
||||||
|
var retention_setting = \$('#history_retention')
|
||||||
|
var retention_select = \$('#history_retention_select').val()
|
||||||
|
var retention_number = \$('#history_retention_number')
|
||||||
|
// Keep all or keep none
|
||||||
|
if(retention_select == "0" || retention_select == "-1") {
|
||||||
|
retention_number.hide()
|
||||||
|
retention_number.val('')
|
||||||
|
retention_number.attr('placeholder', '')
|
||||||
|
retention_setting.val(retention_select)
|
||||||
|
} else {
|
||||||
|
retention_number.show()
|
||||||
|
// Days or number?
|
||||||
|
if(retention_select.indexOf("d") !== -1) {
|
||||||
|
retention_number.attr('placeholder', '$T('days').capitalize()')
|
||||||
|
if(retention_number.val()) {
|
||||||
|
retention_setting.val(retention_number.val() + 'd')
|
||||||
|
} else if(parseInt(retention_setting.val()) > 0) {
|
||||||
|
retention_number.val(parseInt(retention_setting.val()))
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
retention_number.attr('placeholder', '$T('history_retention-limit')')
|
||||||
|
if(retention_number.val()) {
|
||||||
|
retention_setting.val(retention_number.val())
|
||||||
|
} else if(parseInt(retention_setting.val()) > 0) {
|
||||||
|
retention_number.val(parseInt(retention_setting.val()))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Set the history-retention settig
|
||||||
|
var retention_setting_value = \$('#history_retention').val()
|
||||||
|
if(parseInt(retention_setting_value) > 0) {
|
||||||
|
// Days or number?
|
||||||
|
if(retention_setting_value.indexOf("d") !== -1) {
|
||||||
|
\$('#history_retention_select').val("d")
|
||||||
|
} else {
|
||||||
|
\$('#history_retention_select').val("n")
|
||||||
|
}
|
||||||
|
\$('#history_retention_number').val(parseInt(retention_setting_value))
|
||||||
|
} else {
|
||||||
|
// Keep all or keep none
|
||||||
|
\$('#history_retention_select').val(retention_setting_value)
|
||||||
|
\$('#history_retention_number').hide()
|
||||||
|
}
|
||||||
|
|
||||||
\$('.restoreDefaults').click(function(e) {
|
\$('.restoreDefaults').click(function(e) {
|
||||||
// Get section name
|
// Get section name
|
||||||
var sectionName = \$(this).parents('.section').find('.col2 h3').text().trim()
|
var sectionName = \$(this).parents('.section').find('.col2 h3').text().trim()
|
||||||
|
|||||||
@@ -1,26 +1,26 @@
|
|||||||
<html lang="$active_lang">
|
<html lang="$active_lang">
|
||||||
<head>
|
<head>
|
||||||
<title>SABnzbd</title>
|
<title>SABnzbd - $T('login')</title>
|
||||||
|
|
||||||
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
|
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
<meta name="viewport" content="width=device-width, initial-scale=1, user-scalable=no, maximum-scale=1" />
|
||||||
<meta name="apple-mobile-web-app-title" content="SABnzbd" />
|
<meta name="apple-mobile-web-app-title" content="SABnzbd" />
|
||||||
|
|
||||||
<link rel="apple-touch-icon" sizes="76x76" href="../staticcfg/ico/apple-touch-icon-76x76-precomposed.png" />
|
<link rel="apple-touch-icon" sizes="76x76" href="../staticcfg/ico/apple-touch-icon-76x76-precomposed.png" />
|
||||||
<link rel="apple-touch-icon" sizes="120x120" href="../staticcfg/ico/apple-touch-icon-120x120-precomposed.png" />
|
<link rel="apple-touch-icon" sizes="120x120" href="../staticcfg/ico/apple-touch-icon-120x120-precomposed.png" />
|
||||||
<link rel="apple-touch-icon" sizes="152x152" href="../staticcfg/ico/apple-touch-icon-152x152-precomposed.png" />
|
<link rel="apple-touch-icon" sizes="152x152" href="../staticcfg/ico/apple-touch-icon-152x152-precomposed.png" />
|
||||||
<link rel="apple-touch-icon" sizes="180x180" href="../staticcfg/ico/apple-touch-icon-180x180-precomposed.png" />
|
<link rel="apple-touch-icon" sizes="180x180" href="../staticcfg/ico/apple-touch-icon-180x180-precomposed.png" />
|
||||||
<link rel="apple-touch-icon" sizes="192x192" href="../staticcfg/ico/android-192x192.png" />
|
<link rel="apple-touch-icon" sizes="192x192" href="../staticcfg/ico/android-192x192.png" />
|
||||||
<link rel="shortcut icon" href="../staticcfg/ico/favicon.ico?v=1.1.0" />
|
<link rel="shortcut icon" href="../staticcfg/ico/favicon.ico?v=$version" />
|
||||||
|
|
||||||
<link rel="stylesheet" type="text/css" href="../staticcfg/bootstrap/css/bootstrap.min.css" />
|
<link rel="stylesheet" type="text/css" href="../staticcfg/bootstrap/css/bootstrap.min.css?v=$version" />
|
||||||
<link rel="stylesheet" type="text/css" href="../staticcfg/css/login.css?p=$pid" />
|
<link rel="stylesheet" type="text/css" href="../staticcfg/css/login.css?v=$version" />
|
||||||
|
|
||||||
<script type="text/javascript" src="../staticcfg/js/jquery-1.11.2.min.js"></script>
|
<script type="text/javascript" src="../staticcfg/js/jquery-3.2.1.min.js?v=$version"></script>
|
||||||
<script type="text/javascript" src="../staticcfg/bootstrap/js/bootstrap.min.js"></script>
|
<script type="text/javascript" src="../staticcfg/bootstrap/js/bootstrap.min.js?v=$version"></script>
|
||||||
</head>
|
</head>
|
||||||
<html>
|
<html>
|
||||||
<body>
|
<body>
|
||||||
<div class="account-wall">
|
<div class="account-wall">
|
||||||
<div class="text-center logo-header">
|
<div class="text-center logo-header">
|
||||||
<!--#include $webdir + "/staticcfg/images/logo-full.svg"#-->
|
<!--#include $webdir + "/staticcfg/images/logo-full.svg"#-->
|
||||||
@@ -32,10 +32,10 @@
|
|||||||
<!--#if $error#-->
|
<!--#if $error#-->
|
||||||
<div class="alert alert-danger" role="alert">$error</div>
|
<div class="alert alert-danger" role="alert">$error</div>
|
||||||
<!--#end if#-->
|
<!--#end if#-->
|
||||||
|
|
||||||
<input type="text" class="form-control" name="username" placeholder="$T('srv-username')" required autofocus>
|
<input type="text" class="form-control" name="username" placeholder="$T('srv-username')" required autofocus>
|
||||||
<input type="password" class="form-control" name="password" placeholder="$T('srv-password')" required>
|
<input type="password" class="form-control" name="password" placeholder="$T('srv-password')" required>
|
||||||
|
|
||||||
<button class="btn btn-default"><span class="glyphicon glyphicon-circle-arrow-right"></span> $T('login') </button>
|
<button class="btn btn-default"><span class="glyphicon glyphicon-circle-arrow-right"></span> $T('login') </button>
|
||||||
|
|
||||||
<div class="checkbox text-center" data-toggle="tooltip" data-placement="bottom" title="$T('explain-sessionExpire')">
|
<div class="checkbox text-center" data-toggle="tooltip" data-placement="bottom" title="$T('explain-sessionExpire')">
|
||||||
|
|||||||
@@ -13,62 +13,62 @@ body {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.logo-header {
|
.logo-header {
|
||||||
padding-bottom: 10px;
|
padding-bottom: 10px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.logo-header svg {
|
.logo-header svg {
|
||||||
height: 55px;
|
height: 55px;
|
||||||
width: 198px;
|
width: 198px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.account-wall {
|
.account-wall {
|
||||||
width: 300px;
|
width: 300px;
|
||||||
position: absolute;
|
position: absolute;
|
||||||
top: 30%;
|
top: 30%;
|
||||||
left: 50%;
|
left: 50%;
|
||||||
transform: translate(-50%, -30%);
|
transform: translate(-50%, -30%);
|
||||||
}
|
}
|
||||||
|
|
||||||
.text-center a {
|
.text-center a {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
right: 13px;
|
right: 13px;
|
||||||
top: 12px;
|
top: 12px;
|
||||||
color: #636363;
|
color: #636363;
|
||||||
font-size: 1.2em
|
font-size: 1.2em
|
||||||
}
|
}
|
||||||
|
|
||||||
.text-center a:hover {
|
.text-center a:hover {
|
||||||
color: black !important;
|
color: black !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
.form-signin .alert {
|
.form-signin .alert {
|
||||||
margin: 5px 0px;
|
margin: 5px 0px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
.form-signin input {
|
.form-signin input {
|
||||||
margin-top: 5px
|
margin-top: 5px
|
||||||
}
|
}
|
||||||
|
|
||||||
.form-signin .btn {
|
.form-signin .btn {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
margin-top: 10px;
|
margin-top: 10px;
|
||||||
font-weight: bold;
|
font-weight: bold;
|
||||||
}
|
}
|
||||||
|
|
||||||
.btn .glyphicon {
|
.btn .glyphicon {
|
||||||
margin-left: 3px;
|
margin-left: 3px;
|
||||||
float: right;
|
float: right;
|
||||||
top: 1px;
|
top: 1px;
|
||||||
font-size: 1.2em;
|
font-size: 1.2em;
|
||||||
}
|
}
|
||||||
|
|
||||||
input[type="checkbox"] {
|
input[type="checkbox"] {
|
||||||
width: 16px;
|
width: 16px;
|
||||||
height: 16px;
|
height: 16px;
|
||||||
margin-top: 2px;
|
margin-top: 2px;
|
||||||
}
|
}
|
||||||
|
|
||||||
label {
|
label {
|
||||||
color: #636363;
|
color: #636363;
|
||||||
}
|
}
|
||||||
@@ -106,7 +106,7 @@ label.config,
|
|||||||
}
|
}
|
||||||
label.config-hover {
|
label.config-hover {
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
|
|
||||||
}
|
}
|
||||||
label.narrow {
|
label.narrow {
|
||||||
width: 200px!important;
|
width: 200px!important;
|
||||||
@@ -131,13 +131,14 @@ label.wide,
|
|||||||
}
|
}
|
||||||
input[type="checkbox"]+.desc {
|
input[type="checkbox"]+.desc {
|
||||||
padding-top: 6px;
|
padding-top: 6px;
|
||||||
}
|
}
|
||||||
.desc-check {
|
.desc-check {
|
||||||
font-size: 12px;
|
font-size: 12px;
|
||||||
font-style: italic;
|
font-style: italic;
|
||||||
padding: 0 1px;
|
padding: 0 1px;
|
||||||
}
|
}
|
||||||
.col2 p {
|
.col2 p,
|
||||||
|
.col2-cats {
|
||||||
font-size: 12px;
|
font-size: 12px;
|
||||||
color: #666;
|
color: #666;
|
||||||
margin: 1em 0;
|
margin: 1em 0;
|
||||||
@@ -174,8 +175,8 @@ input[type="checkbox"]+.desc {
|
|||||||
padding-left: 12px;
|
padding-left: 12px;
|
||||||
font-size: 13px;
|
font-size: 13px;
|
||||||
}
|
}
|
||||||
.typeahead.dropdown-menu>.active>a,
|
.typeahead.dropdown-menu>.active>a,
|
||||||
.typeahead.dropdown-menu>.active>a:focus,
|
.typeahead.dropdown-menu>.active>a:focus,
|
||||||
.typeahead.dropdown-menu>.active>a:hover {
|
.typeahead.dropdown-menu>.active>a:hover {
|
||||||
color: black;
|
color: black;
|
||||||
background-color: #E5E5E5;
|
background-color: #E5E5E5;
|
||||||
@@ -272,6 +273,9 @@ textarea:hover, input[type="date"]:hover, input[type="datetime"]:hover, input[ty
|
|||||||
overflow: auto;
|
overflow: auto;
|
||||||
clear: both;
|
clear: both;
|
||||||
}
|
}
|
||||||
|
.label {
|
||||||
|
font-style: normal;
|
||||||
|
}
|
||||||
.padTable h3 {
|
.padTable h3 {
|
||||||
margin-top: 0;
|
margin-top: 0;
|
||||||
}
|
}
|
||||||
@@ -284,6 +288,27 @@ tr.separator {
|
|||||||
.catTable th {
|
.catTable th {
|
||||||
text-align: center;
|
text-align: center;
|
||||||
}
|
}
|
||||||
|
.catTable .glyphicon-option-vertical {
|
||||||
|
color: #808080;
|
||||||
|
display: block;
|
||||||
|
margin-right: -5px;
|
||||||
|
cursor: move;
|
||||||
|
}
|
||||||
|
.Categories form:not(.sorting-row) .glyphicon-option-vertical {
|
||||||
|
visibility: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
.Categories form.sorting-row:nth-child(even) tr {
|
||||||
|
background-color: #F8F8F8;
|
||||||
|
}
|
||||||
|
|
||||||
|
.Categories form:first-of-type tr:last-of-type {
|
||||||
|
background-color: #FFFFE0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.Categories hr {
|
||||||
|
margin: 5px;
|
||||||
|
}
|
||||||
|
|
||||||
.RSS .rss-section input[type="text"] {
|
.RSS .rss-section input[type="text"] {
|
||||||
max-width: 180px;
|
max-width: 180px;
|
||||||
@@ -300,6 +325,37 @@ tr.separator {
|
|||||||
.RSS form[action="add_rss_feed"] tr:nth-child(even) {
|
.RSS form[action="add_rss_feed"] tr:nth-child(even) {
|
||||||
border: 1px solid #E5E5E5;
|
border: 1px solid #E5E5E5;
|
||||||
}
|
}
|
||||||
|
.RSS .tab-pane table th:not(.no-sort) {
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
.RSS .tab-pane table th:not(.sorted) {
|
||||||
|
padding-right: 12px;
|
||||||
|
}
|
||||||
|
.RSS .tab-pane table th.sorted {
|
||||||
|
padding-right: 0px;
|
||||||
|
}
|
||||||
|
.RSS .tab-pane table th.sorted.ascending:after {
|
||||||
|
content:'';
|
||||||
|
display: inline-block;
|
||||||
|
width: 0;
|
||||||
|
height: 0;
|
||||||
|
margin-left: 4px;
|
||||||
|
vertical-align: middle;
|
||||||
|
border-bottom: 4px dashed;
|
||||||
|
border-right: 4px solid transparent;
|
||||||
|
border-left: 4px solid transparent;
|
||||||
|
}
|
||||||
|
.RSS .tab-pane table th.sorted.descending:after {
|
||||||
|
content:'';
|
||||||
|
display: inline-block;
|
||||||
|
width: 0;
|
||||||
|
height: 0;
|
||||||
|
margin-left: 4px;
|
||||||
|
vertical-align: middle;
|
||||||
|
border-top: 4px dashed;
|
||||||
|
border-right: 4px solid transparent;
|
||||||
|
border-left: 4px solid transparent;
|
||||||
|
}
|
||||||
.Config .table {
|
.Config .table {
|
||||||
margin: 0;
|
margin: 0;
|
||||||
border: 1px solid #ddd
|
border: 1px solid #ddd
|
||||||
@@ -496,6 +552,7 @@ h2.activeRSS {
|
|||||||
}
|
}
|
||||||
.favicon {
|
.favicon {
|
||||||
background-position: center center!important;
|
background-position: center center!important;
|
||||||
|
background-size: 16px 16px;
|
||||||
opacity: 1;
|
opacity: 1;
|
||||||
top: -1px;
|
top: -1px;
|
||||||
height: 16px;
|
height: 16px;
|
||||||
@@ -504,6 +561,9 @@ h2.activeRSS {
|
|||||||
margin: 0 6px 0 2px;
|
margin: 0 6px 0 2px;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
}
|
}
|
||||||
|
.source-icon span {
|
||||||
|
top: -3px;
|
||||||
|
}
|
||||||
.feed {
|
.feed {
|
||||||
text-decoration: none;
|
text-decoration: none;
|
||||||
}
|
}
|
||||||
@@ -533,6 +593,9 @@ h2.activeRSS {
|
|||||||
padding-right: .3em;
|
padding-right: .3em;
|
||||||
width: auto;
|
width: auto;
|
||||||
}
|
}
|
||||||
|
#subscriptions .favicon {
|
||||||
|
margin-left: 8px;
|
||||||
|
}
|
||||||
.ie6 .subscription-title {
|
.ie6 .subscription-title {
|
||||||
width: 20em;
|
width: 20em;
|
||||||
}
|
}
|
||||||
@@ -556,7 +619,6 @@ h2.activeRSS {
|
|||||||
padding: 0 0 .5em;
|
padding: 0 0 .5em;
|
||||||
}
|
}
|
||||||
.feed-row div {
|
.feed-row div {
|
||||||
padding-right: 10px;
|
|
||||||
overflow:hidden;
|
overflow:hidden;
|
||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
text-overflow: ellipsis;
|
text-overflow: ellipsis;
|
||||||
@@ -564,12 +626,18 @@ h2.activeRSS {
|
|||||||
.tab-content .catTable {
|
.tab-content .catTable {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
}
|
}
|
||||||
|
.tab-content .catTable th {
|
||||||
|
text-align: left;
|
||||||
|
}
|
||||||
.tab-content .catTable tbody {
|
.tab-content .catTable tbody {
|
||||||
border: 1px solid #ddd;
|
border: 1px solid #ddd;
|
||||||
}
|
}
|
||||||
.tab-content .catTable td {
|
.tab-content .catTable td {
|
||||||
line-height: 2em;
|
line-height: 2em;
|
||||||
}
|
}
|
||||||
|
.tab-content .catTable td:nth-child(4) {
|
||||||
|
word-break: break-all;
|
||||||
|
}
|
||||||
.tab-content .catTable tr:hover td {
|
.tab-content .catTable tr:hover td {
|
||||||
background-color: #F2F2F2;
|
background-color: #F2F2F2;
|
||||||
}
|
}
|
||||||
@@ -681,7 +749,7 @@ ul.tabs li.active a {
|
|||||||
transition: none;
|
transition: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
input[type=checkbox],
|
input[type=checkbox],
|
||||||
input[type=radio] {
|
input[type=radio] {
|
||||||
margin-top: 2px;
|
margin-top: 2px;
|
||||||
}
|
}
|
||||||
@@ -725,10 +793,10 @@ select {
|
|||||||
box-shadow: inset 0 1px 1px rgba(0,0,0,.075);
|
box-shadow: inset 0 1px 1px rgba(0,0,0,.075);
|
||||||
display:inline-block;
|
display:inline-block;
|
||||||
vertical-align:middle;
|
vertical-align:middle;
|
||||||
max-width: 100%;
|
max-width: 100%;
|
||||||
min-height: 34px;
|
min-height: 34px;
|
||||||
font-size: 13px;
|
font-size: 13px;
|
||||||
background-color: white;
|
background-color: white;
|
||||||
}
|
}
|
||||||
|
|
||||||
input[type="text"]:not([size]),
|
input[type="text"]:not([size]),
|
||||||
@@ -793,15 +861,21 @@ input[type="checkbox"] {
|
|||||||
max-width: 150px;
|
max-width: 150px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.Categories input {
|
.Categories #content input {
|
||||||
max-width: 150px !important;
|
max-width: 150px !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
.Scheduling input[type="checkbox"] {
|
.Scheduling form[action="addSchedule"] input[type="checkbox"] {
|
||||||
margin-top: 0px;
|
margin-top: 0px;
|
||||||
margin-left: -20px;
|
margin-left: -20px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.Scheduling form[action="delSchedule"] input[type="checkbox"] {
|
||||||
|
position: initial;
|
||||||
|
float: left;
|
||||||
|
margin: 9px 10px 0px 5px;
|
||||||
|
}
|
||||||
|
|
||||||
.navbar .container {
|
.navbar .container {
|
||||||
padding-right: 0;
|
padding-right: 0;
|
||||||
}
|
}
|
||||||
@@ -829,7 +903,26 @@ input[type="checkbox"] {
|
|||||||
margin-bottom: -1px;
|
margin-bottom: -1px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#search-menu .glyphicon {
|
||||||
|
top: 3px;
|
||||||
|
}
|
||||||
|
|
||||||
|
#search-dropdown {
|
||||||
|
right: 0;
|
||||||
|
left: auto;
|
||||||
|
width: 300px;
|
||||||
|
padding: 5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
#search-dropdown .dropdown-header {
|
||||||
|
font-weight: bold;
|
||||||
|
color: black;
|
||||||
|
}
|
||||||
|
|
||||||
|
#search-dropdown a {
|
||||||
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
}
|
||||||
|
|
||||||
.navbar-logo-small svg {
|
.navbar-logo-small svg {
|
||||||
height: 40px;
|
height: 40px;
|
||||||
@@ -876,8 +969,8 @@ input[type="checkbox"] {
|
|||||||
width: 1em;
|
width: 1em;
|
||||||
height: 1em;
|
height: 1em;
|
||||||
}
|
}
|
||||||
.rss-symbol {
|
.rss-symbol {
|
||||||
stroke: none;
|
stroke: none;
|
||||||
fill: white;
|
fill: white;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -900,6 +993,63 @@ input[type="checkbox"] {
|
|||||||
opacity: 0.7;
|
opacity: 0.7;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.Servers .server-amounts-text {
|
||||||
|
width: 20%;
|
||||||
|
float: left;
|
||||||
|
}
|
||||||
|
|
||||||
|
.Servers .server-chart {
|
||||||
|
float: right;
|
||||||
|
width: calc(100% - 250px - 25%);
|
||||||
|
text-align: center;
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
|
||||||
|
.Servers .ct-series-a .ct-line {
|
||||||
|
stroke: #666666;
|
||||||
|
}
|
||||||
|
|
||||||
|
.Servers .ct-series-a .ct-point {
|
||||||
|
stroke: #666666;
|
||||||
|
stroke-width: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.Servers .ct-series-a .ct-area {
|
||||||
|
fill: #666666
|
||||||
|
}
|
||||||
|
|
||||||
|
.Servers .ct-label {
|
||||||
|
font-size: 1em;
|
||||||
|
color: black;
|
||||||
|
}
|
||||||
|
|
||||||
|
.Servers .chart-selector {
|
||||||
|
position: absolute;
|
||||||
|
display: block;
|
||||||
|
top: -7px;
|
||||||
|
left: 50%;
|
||||||
|
width: 120px;
|
||||||
|
margin-left: -40px;
|
||||||
|
min-width: initial;
|
||||||
|
opacity: 0.8;
|
||||||
|
}
|
||||||
|
|
||||||
|
.Servers .chart-selector:hover {
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.Servers .ct-grid.ct-vertical:first-of-type {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.advanced-settings {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.Servers .port-highlight {
|
||||||
|
animation: highlight-fade 1s ease-in 1;
|
||||||
|
}
|
||||||
|
|
||||||
.Servers .col2 label,
|
.Servers .col2 label,
|
||||||
.Email .col2 label {
|
.Email .col2 label {
|
||||||
margin: 0;
|
margin: 0;
|
||||||
@@ -917,7 +1067,6 @@ input[type="checkbox"] {
|
|||||||
display: none;
|
display: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
.alert-no-category,
|
|
||||||
.alert-translate {
|
.alert-translate {
|
||||||
display: none;
|
display: none;
|
||||||
margin: 5px 0px 0px;
|
margin: 5px 0px 0px;
|
||||||
@@ -969,6 +1118,12 @@ input[type="checkbox"] {
|
|||||||
.main-restarting small {
|
.main-restarting small {
|
||||||
font-size: 3rem !important;
|
font-size: 3rem !important;
|
||||||
}
|
}
|
||||||
|
.value-and-select input {
|
||||||
|
margin-right: -5px;
|
||||||
|
}
|
||||||
|
.value-and-select select {
|
||||||
|
min-width: 30px;
|
||||||
|
}
|
||||||
|
|
||||||
.dotOne, .dotTwo, .dotThree {
|
.dotOne, .dotTwo, .dotThree {
|
||||||
opacity: 0;
|
opacity: 0;
|
||||||
@@ -990,37 +1145,51 @@ input[type="checkbox"] {
|
|||||||
100% { opacity: 1; }
|
100% { opacity: 1; }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@keyframes highlight-fade {
|
||||||
|
0% { background: #FEFEDA; }
|
||||||
|
100% { background: none; }
|
||||||
|
}
|
||||||
|
|
||||||
.spin-glyphicon {
|
.spin-glyphicon {
|
||||||
animation: spin 2s infinite linear;
|
animation: spin 2s infinite linear;
|
||||||
}
|
}
|
||||||
|
|
||||||
@keyframes spin {
|
@keyframes spin {
|
||||||
0% { transform: rotate(0deg); }
|
0% { transform: rotate(0deg); }
|
||||||
100% { transform: rotate(359deg); }
|
100% { transform: rotate(359deg); }
|
||||||
}
|
}
|
||||||
|
|
||||||
@media screen and (min-width: 1200px) {
|
@media screen and (min-width: 1200px) {
|
||||||
.Categories input[name="dir"] {
|
.Categories input[name="dir"] {
|
||||||
max-width: 240px !important;
|
max-width: 240px !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.navbar-nav {
|
||||||
|
/* For extra wide languages like Polish */
|
||||||
|
margin-right: -150px;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@media screen and (min-width: 768px) and (max-width: 1200px) {
|
@media screen and (min-width: 768px) and (max-width: 1200px) {
|
||||||
.navbar-default .navbar-nav>li>a>span,
|
.navbar-default .navbar-nav>li>a>span,
|
||||||
.navbar-default .navbar-nav>li>a:hover>strong,
|
.navbar-default .navbar-nav>li>a:hover>strong,
|
||||||
.navbar-default .navbar-nav>li>a.active>strong {
|
.navbar-default .navbar-nav>li>a.active>strong {
|
||||||
display: inline;
|
display: inline;
|
||||||
}
|
}
|
||||||
|
|
||||||
.navbar-default .navbar-nav>li>a>svg {
|
.navbar-default .navbar-nav>li>a>svg {
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
}
|
}
|
||||||
|
|
||||||
.navbar-default .navbar-nav>li>a>strong,
|
.navbar-default .navbar-nav>li>a>strong,
|
||||||
.navbar-default .navbar-nav:hover>li>a.active>strong {
|
.navbar-default .navbar-nav:hover>li>a.active>strong {
|
||||||
display: none;
|
display: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#search-menu>a>strong {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
.RSS #content,
|
.RSS #content,
|
||||||
.Categories #content {
|
.Categories #content {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
@@ -1032,23 +1201,32 @@ input[type="checkbox"] {
|
|||||||
float: right;
|
float: right;
|
||||||
padding: 5px 0px;
|
padding: 5px 0px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.col1 h5 {
|
.col1 h5 {
|
||||||
margin-left: 20px;
|
margin-left: 20px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.col2 {
|
.col2 {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
padding: 10px;
|
padding: 10px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.col2 p {
|
.col2 p {
|
||||||
margin-left: 20px;
|
margin-left: 20px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.Servers .col2 button:first-of-type {
|
.Servers .col2 button:first-of-type {
|
||||||
margin-bottom: 0;
|
margin-bottom: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.Servers .server-chart {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.Servers .server-amounts-text {
|
||||||
|
padding: 0px 15px 10px;
|
||||||
|
width: inherit;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@media screen and (max-width: 768px) {
|
@media screen and (max-width: 768px) {
|
||||||
@@ -1063,33 +1241,41 @@ input[type="checkbox"] {
|
|||||||
}
|
}
|
||||||
.navbar-collapse.in .navbar-nav>li>a>span,
|
.navbar-collapse.in .navbar-nav>li>a>span,
|
||||||
.navbar-collapse.collapsing .navbar-nav>li>a>span {
|
.navbar-collapse.collapsing .navbar-nav>li>a>span {
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
width: 1.5em;
|
width: 1.5em;
|
||||||
}
|
}
|
||||||
|
|
||||||
.navbar-collapse.in .navbar-nav>li>a>svg,
|
.navbar-collapse.in .navbar-nav>li>a>svg,
|
||||||
.navbar-collapse.collapsing .navbar-nav>li>a>svg {
|
.navbar-collapse.collapsing .navbar-nav>li>a>svg {
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
margin-right: 0.5em;
|
margin-right: 0.5em;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.navbar .nav>li>a {
|
||||||
|
height: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
#search-menu {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
.section .col2 h3 {
|
.section .col2 h3 {
|
||||||
padding-right: 25px;
|
padding-right: 25px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.section .col2 h3 a {
|
.section .col2 h3 a {
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
}
|
}
|
||||||
|
|
||||||
.main-restarting.in strong {
|
.main-restarting.in strong {
|
||||||
font-size: 2em;
|
font-size: 2em;
|
||||||
font-weight: bold;
|
font-weight: bold;
|
||||||
}
|
}
|
||||||
|
|
||||||
.main-restarting.in span {
|
.main-restarting.in span {
|
||||||
top: 2px;
|
top: 2px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.main-restarting.in small {
|
.main-restarting.in small {
|
||||||
font-size: 2rem !important;
|
font-size: 2rem !important;
|
||||||
}
|
}
|
||||||
@@ -1099,55 +1285,55 @@ input[type="checkbox"] {
|
|||||||
padding: 0 !important;
|
padding: 0 !important;
|
||||||
padding-top: 55px !important;
|
padding-top: 55px !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
#content fieldset {
|
#content fieldset {
|
||||||
min-width: 0;
|
min-width: 0;
|
||||||
padding: 0 !important;
|
padding: 0 !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
.colmask {
|
.colmask {
|
||||||
border: none !important;
|
border: none !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
.col2 {
|
.col2 {
|
||||||
padding: 10px;
|
padding: 10px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.col2 h3 {
|
.col2 h3 {
|
||||||
margin: -10px -10px 0px;
|
margin: -10px -10px 0px;
|
||||||
padding-left: 10px;
|
padding-left: 10px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.col2 p {
|
.col2 p {
|
||||||
margin-left: 0px;
|
margin-left: 0px;
|
||||||
margin-bottom: 0;
|
margin-bottom: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.col1 h5 {
|
.col1 h5 {
|
||||||
margin-left: 0px;
|
margin-left: 0px;
|
||||||
}
|
}
|
||||||
|
|
||||||
label.config {
|
label.config {
|
||||||
margin-left: 0px;
|
margin-left: 0px;
|
||||||
margin-bottom: 2px;
|
margin-bottom: 2px;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
float: none;
|
float: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
.desc {
|
.desc {
|
||||||
margin: 0;
|
margin: 0;
|
||||||
margin-left: 3px;
|
margin-left: 3px;
|
||||||
margin-top: 2px;
|
margin-top: 2px;
|
||||||
padding: 0 !important;
|
padding: 0 !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
.col1 input[type="checkbox"] {
|
.col1 input[type="checkbox"] {
|
||||||
position: inherit;
|
position: inherit;
|
||||||
float: left;
|
float: left;
|
||||||
margin-right: 5px;
|
margin-right: 5px;
|
||||||
margin-top: 0px;
|
margin-top: 0px;
|
||||||
}
|
}
|
||||||
|
|
||||||
div.field-pair{
|
div.field-pair{
|
||||||
min-width: 0;
|
min-width: 0;
|
||||||
padding: 5px 10px;
|
padding: 5px 10px;
|
||||||
@@ -1155,8 +1341,8 @@ input[type="checkbox"] {
|
|||||||
|
|
||||||
.fileBrowserField {
|
.fileBrowserField {
|
||||||
max-width: 65% !important;
|
max-width: 65% !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
ul.tabs a {
|
ul.tabs a {
|
||||||
padding-left: 5px;
|
padding-left: 5px;
|
||||||
padding-right: 5px;
|
padding-right: 5px;
|
||||||
|
|||||||
|
Before Width: | Height: | Size: 4.2 KiB After Width: | Height: | Size: 1.7 KiB |
|
Before Width: | Height: | Size: 2.3 KiB After Width: | Height: | Size: 1.3 KiB |
|
Before Width: | Height: | Size: 3.2 KiB After Width: | Height: | Size: 1.5 KiB |
|
Before Width: | Height: | Size: 3.9 KiB After Width: | Height: | Size: 1.7 KiB |
|
Before Width: | Height: | Size: 1.6 KiB After Width: | Height: | Size: 982 B |